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 output
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]).read(),
        measure(qs[1]).read(),
        measure(qs[2]).read(),
        measure(qs[3]).read(),
    )
    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)
    output("q0", rs[0])
    output("q1", rs[1])
    output("q2", rs[2])
    output("q3", rs[3])
    output("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])
    output("random_sum", random_sum)

    rsum2 = random_sum * random_sum
    six = nat(6)
    four = nat(4)
    example_result = six + four - rsum2
    output("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 output, qubit
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]).read() 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):
        output(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.

Tuple unpacking

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

from typing import no_type_check

from guppylang import guppy
from guppylang.std.builtins import output


@guppy
@no_type_check
def split_pair(pair: tuple[int, int]) -> int:
    left, right = pair
    return left * 10 + right


@guppy
@no_type_check
def main() -> None:
    output("split_pair", split_pair((4, 7)))

Fixed-shape tuple unpacking is supported. Starred unpacking is more constrained because the starred part is array-like; see Arrays.

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 output, qubit
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
    """
    output(f"{name}_0", measure(steane_qb.pq0).read())
    output(f"{name}_1", measure(steane_qb.pq1).read())
    output(f"{name}_2", measure(steane_qb.pq2).read())
    output(f"{name}_3", measure(steane_qb.pq3).read())
    output(f"{name}_4", measure(steane_qb.pq4).read())
    output(f"{name}_5", measure(steane_qb.pq5).read())
    output(f"{name}_6", measure(steane_qb.pq6).read())


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.

Enums

Source file: guppy_examples/guppy-features/supported/enum-method.py

from typing import no_type_check

from guppylang import guppy
from guppylang.std.builtins import output


@guppy.enum
class Flag:
    On = {"value": int}  # noqa: RUF012
    Off = {}  # noqa: RUF012

    @guppy
    @no_type_check
    def score(self: "Flag") -> int:
        return 1


@guppy
@no_type_check
def main() -> None:
    flag = Flag.On(4)
    output("enum_score", flag.score())

Type aliases

Source file: guppy_examples/guppy-features/supported/type-alias.py

from typing import no_type_check

from guppylang import guppy
from guppylang.std.builtins import output

Row = guppy.type_alias("Row", "tuple[int, int, int, int]")

T = guppy.type_var("T")
Pair = guppy.type_alias("Pair", "tuple[T, T]", params=[T])


@guppy
@no_type_check
def row_sum(row: Row) -> int:
    return row[0] + row[1] + row[2] + row[3]


@guppy
@no_type_check
def pair_first(pair: Pair[int]) -> int:
    return pair[0]


@guppy.comptime
@no_type_check
def main() -> None:
    output("row_sum", row_sum((1, 2, 3, 4)))
    output("pair_first", pair_first((8, 13)))

Type aliases are supported when the expanded type is supported. Aliases that expand to runtime arrays still inherit the runtime-array limitation.

Option, Result, and Either

Source file: guppy_examples/guppy-features/supported/option-result-either.py

from typing import no_type_check

from guppylang import guppy
from guppylang.std.builtins import output
from guppylang.std.either import Either, left, right
from guppylang.std.err import Result, ok
from guppylang.std.option import Option, some


@guppy
@no_type_check
def option_value(value: int) -> Option[int]:
    return some(value)


@guppy
@no_type_check
def result_value(value: int) -> Result[int, int]:
    return ok(value)


@guppy
@no_type_check
def choose(flag: bool) -> Either[int, bool]:
    if flag:
        return left(7)
    return right(False)


@guppy
@no_type_check
def main() -> None:
    opt = option_value(5)
    res = result_value(9)
    either = choose(True)

    output("option_is_some", opt.is_some())
    output("option_value", opt.unwrap())
    output("result_is_ok", res.is_ok())
    output("result_value", res.unwrap())
    output("either_is_left", either.is_left())

These sum types are supported when their payload types are supported.