Data Types and Structs

These examples show the data-shape patterns that are currently practical for H-Series targets.

Numeric types

Source file: guppy_examples/guppy-features/supported/num-types.py

from typing import no_type_check

from guppylang import guppy, qubit
from guppylang.std.angles import angle
from guppylang.std.lang import owned
from guppylang.std.num import nat
from guppylang.std.platform import result
from guppylang.std.quantum import h, measure, rz


@guppy
def plus_or_minus(res: bool) -> int:
    if res:
        return 1
    return -1


@guppy.comptime
@no_type_check
def int_from_reg(
    qs: tuple[qubit, qubit, qubit, qubit] @ owned,
) -> tuple[tuple[bool, bool, bool, bool], int]:
    res_integer_value = 0
    rs = measure(qs[0]), measure(qs[1]), measure(qs[2]), measure(qs[3])
    for i in range(4):
        res_integer_value = (res_integer_value << 1) | int(rs[i])
    return rs, res_integer_value


@guppy
@no_type_check
def main() -> None:
    qs = qubit(), qubit(), qubit(), qubit()
    my_float = 0.4  # constant float is ok, any arithmetic or round(),
    rz(qs[0], angle(my_float))
    h(qs[1])
    h(qs[2])
    h(qs[3])
    h(qs[3])
    h(qs[2])
    h(qs[1])
    rs, rs_int = int_from_reg(qs)
    result("q0", rs[0])
    result("q1", rs[1])
    result("q2", rs[2])
    result("q3", rs[3])
    result("big_endian_res", rs_int)

    random_sum = 0
    random_sum += plus_or_minus(rs[0])
    random_sum += plus_or_minus(rs[1])
    random_sum += plus_or_minus(rs[2])
    random_sum += plus_or_minus(rs[3])
    result("random_sum", random_sum)

    rsum2 = random_sum * random_sum
    six = nat(6)
    four = nat(4)
    example_result = six + four - rsum2
    result("int_res", example_result)

This page is especially useful for the current float caveat: constant float values are fine, but runtime float arithmetic is not supported on H-Series.

Tuples

Source file: guppy_examples/guppy-features/supported/guppy-tuple.py

import sys
from typing import no_type_check

from guppylang import guppy
from guppylang.std.builtins import qubit, result
from guppylang.std.lang import owned
from guppylang.std.quantum import cx, measure, x


@guppy
def create_steane() -> tuple[qubit, qubit, qubit, qubit, qubit, qubit, qubit]:
    return qubit(), qubit(), qubit(), qubit(), qubit(), qubit(), qubit()


@guppy.comptime
def steane_x(stq: tuple[qubit, qubit, qubit, qubit, qubit, qubit, qubit]) -> None:
    for i in range(7):
        x(stq[i])


@guppy.comptime
def steane_cx(
    stq1: tuple[qubit, qubit, qubit, qubit, qubit, qubit, qubit],
    stq2: tuple[qubit, qubit, qubit, qubit, qubit, qubit, qubit],
) -> None:
    for i in range(7):
        cx(stq1[i], stq2[i])


@no_type_check
def steane_measure(
    stq: list[qubit] @ owned,
) -> tuple[bool, bool, bool, bool, bool, bool, bool]:
    return tuple([measure(stq[i]) for i in range(7)])


@no_type_check
def steane_measure_result(
    stq1: tuple[str, tuple[qubit, qubit, qubit, qubit, qubit, qubit, qubit]] @ owned,
) -> None:
    name, qbs = stq1
    qblist = list(qbs)
    res = steane_measure(qblist)
    for i in range(7):
        result(f"{name}_{i}", res[i])


@guppy.comptime
@no_type_check
def main() -> None:
    steane_q1 = (
        "q1",
        create_steane(),
    )  # this is a python tuple of a python str and guppy tuple
    steane_q2 = "q2", create_steane()
    steane_x(steane_q1[1])
    steane_x(steane_q2[1])
    steane_cx(steane_q1[1], steane_q2[1])
    steane_measure_result(steane_q1)
    steane_measure_result(steane_q2)


if __name__ == "__main__":
    sys.stdout.buffer.write(main.compile().to_bytes())

This example stays within the supported subset by doing tuple-oriented bulk operations under @guppy.comptime.

Structs

Source file: guppy_examples/guppy-features/supported/guppy-struct.py

from __future__ import annotations

import sys
from typing import TYPE_CHECKING, no_type_check

from guppylang.decorator import guppy
from guppylang.std.builtins import qubit, result
from guppylang.std.quantum import cx, cz, measure, x

if TYPE_CHECKING:
    from guppylang.std.lang import owned


@guppy.struct
class SteaneQubit:
    pq0: qubit
    pq1: qubit
    pq2: qubit
    pq3: qubit
    pq4: qubit
    pq5: qubit
    pq6: qubit

    @guppy
    def x_all(self) -> None:
        """Single Steane Qubit op"""
        x(self.pq0)
        x(self.pq1)
        x(self.pq2)
        x(self.pq3)
        x(self.pq4)
        x(self.pq5)
        x(self.pq6)

    @guppy
    def cx_all(self, other: SteaneQubit) -> None:
        """Binary Steane Qubit op"""
        cx(self.pq0, other.pq0)
        cx(self.pq1, other.pq1)
        cx(self.pq2, other.pq2)
        cx(self.pq3, other.pq3)
        cx(self.pq4, other.pq4)
        cx(self.pq5, other.pq5)
        cx(self.pq6, other.pq6)


@no_type_check
def steane_measure_result(steane_qb: SteaneQubit @ owned, name: str) -> None:
    """
    Measure and record results for all qubits of a SteaneQubit

    The only way to get naming to work well is to implement this method as a
    pure python function and call it within @guppy.comptime. Otherwise, the
    name string passed into the function will be a runtime guppy string, which
    cannot be printed at compile time. Also, guppy runtime doesn't have string
    operations, so strings can't be modified at runtime, which we would need to
     do here.

    :param steane_qb: The Steane Qubit to measure
    :param name: Name of Steane Qubit for result identification
    """
    result(f"{name}_0", measure(steane_qb.pq0))
    result(f"{name}_1", measure(steane_qb.pq1))
    result(f"{name}_2", measure(steane_qb.pq2))
    result(f"{name}_3", measure(steane_qb.pq3))
    result(f"{name}_4", measure(steane_qb.pq4))
    result(f"{name}_5", measure(steane_qb.pq5))
    result(f"{name}_6", measure(steane_qb.pq6))


def steane_cz(q1: SteaneQubit, q2: SteaneQubit) -> None:
    """Alternative definition of a binary Steane Qubit op"""
    cz(q1.pq0, q2.pq0)
    cz(q1.pq1, q2.pq1)
    cz(q1.pq2, q2.pq2)
    cz(q1.pq3, q2.pq3)
    cz(q1.pq4, q2.pq4)
    cz(q1.pq5, q2.pq5)
    cz(q1.pq6, q2.pq6)


@guppy.comptime
@no_type_check
def main() -> None:
    steane = SteaneQubit(*[qubit() for _ in range(7)])
    other_steane = SteaneQubit(*[qubit() for _ in range(7)])
    x(steane.pq0)
    x(steane.pq2)
    steane.x_all()
    steane.cx_all(other_steane)
    steane_cz(steane, other_steane)
    steane_measure_result(steane, "q1")
    steane_measure_result(other_steane, "q2")


if __name__ == "__main__":
    sys.stdout.buffer.write(main.compile().to_bytes())

This example is a good pattern to prefer over runtime arrays when you want a fixed-size register-like object in supported H-Series code.