Control Flow and Recursion

H-Series programs cannot contain loops. Loops and recursion are supported when hugr-qir can fully expand them during compilation.

Supported: if / elif / else

Source file: guppy_examples/guppy-features/supported/guppy-if-elif-else.py

import sys
from typing import no_type_check

from guppylang import guppy, qubit
from guppylang.std.builtins import output
from guppylang.std.quantum import h, measure, x, y, z


@guppy
@no_type_check
def main() -> None:
    q0, q1, q2, q3 = qubit(), qubit(), qubit(), qubit()
    h(q0)
    h(q1)
    h(q2)
    h(q3)
    h(q3)
    h(q2)
    h(q1)
    h(q0)
    my_int = 0
    a = measure(q0).read()
    b = measure(q1).read()
    c = measure(q2).read()
    if a:
        my_int += 1
    if b:
        my_int += 1
    if c:
        my_int += 1

    if my_int == 0:
        x(q3)
    elif my_int == 1:
        h(q3)
    elif my_int == 2:
        y(q3)
    else:
        z(q3)

    d = measure(q3).read()
    output("a", a)
    output("b", b)
    output("c", c)
    output("d", d)


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

Unsupported: exit / panic

Source file: guppy_examples/guppy-features/unsupported/early-exit.py

from guppylang import guppy, qubit
from guppylang.std.builtins import exit, output  # noqa: A004
from guppylang.std.quantum import h, measure, x


@guppy
def main() -> None:
    q = qubit()
    fake_ancilla = qubit()
    h(fake_ancilla)
    if measure(fake_ancilla).read():
        exit("Postselected: Criteria not met", 1)
    x(q)
    output("q", measure(q).read())

Source file: guppy_examples/guppy-features/unsupported/panic.py

from guppylang import guppy, qubit
from guppylang.std.platform import output, panic
from guppylang.std.quantum import h, measure, x


@guppy
def main() -> None:
    q = qubit()
    fake_ancilla = qubit()
    h(fake_ancilla)
    if measure(fake_ancilla).read():
        panic("Criteria not met")
    x(q)
    output("q", measure(q).read())

Early exit using either exit or panic is unsupported on H-Series.

Expected error (for both examples):

Compilation failed: Program may panic: Postselected: Criteria not met. Runtime panics are unsupported on H-Series.

Supported: unrollable loops

Source file: guppy_examples/guppy-features/supported/unrollable-loops.py

import sys

from guppylang import guppy
from guppylang.std.platform import output
from guppylang.std.quantum import h, measure, qubit, x


@guppy
def main() -> None:
    i = 10
    q1 = qubit()
    while True:
        if i % 2 == 0:
            h(q1)
        else:
            x(q1)
        if i == 0:
            break
        i -= 1

    for _ in range(90):
        x(q1)

    output("q", measure(q1).read())


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

This loop has a fixed number of iterations, so hugr-qir can fully expand it. The default maximum is 800 iterations and can be configured using max_loop_unroll in Python or --max-loop-unroll on the command line.

For larger static loops, consider using @guppy.comptime so Guppy expands the loop during compilation. This cannot be used when the loop itself depends on a runtime value such as a measurement result.

Unsupported: non-unrollable loops

Source file: guppy_examples/guppy-features/unsupported/non-unrollable-loops.py

import sys

from guppylang import guppy
from guppylang.std.quantum import h, measure, qubit


@guppy
def main() -> None:
    while True:
        q1 = qubit()
        h(q1)
        if not measure(q1).read():
            break


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

The number of iterations depends on measurement results, so the loop cannot be fully expanded during compilation.

Expected error:

Compilation failed: Loop cannot be unrolled: its iteration count is not known at compile time or exceeds the limit of 800.

Supported: simple recursion

Source file: guppy_examples/guppy-features/supported/simple-recursion.py

import sys
from typing import no_type_check

from guppylang import guppy
from guppylang.std.builtins import output, qubit
from guppylang.std.quantum import measure, x


@guppy
@no_type_check
def recursive_func(q: qubit, n: int) -> None:
    x(q)
    if n < 10:
        return recursive_func(q, n + 1)
    return None


@guppy
@no_type_check
def main() -> None:
    q = qubit()
    x(q)
    recursive_func(q, 0)
    output("q", measure(q).read())


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

This recursive form has a fixed depth and can be fully expanded.

Unsupported: complex recursion

Source file: guppy_examples/guppy-features/unsupported/complex-recursion.py

import sys
from typing import no_type_check

from guppylang import guppy
from guppylang.std.builtins import output, qubit
from guppylang.std.quantum import h, measure, x


@guppy
@no_type_check
def recursive_func(q: qubit) -> None:
    x(q)
    q_temp = qubit()
    if measure(q_temp).read():
        return recursive_func(q)
    return None


@guppy
@no_type_check
def main() -> None:
    q = qubit()
    h(q)
    recursive_func(q)
    output("q", measure(q).read())


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

Here the recursive path depends on a measurement result, so it cannot be fully expanded.

Expected error:

Compilation failed: Loop cannot be unrolled: its iteration count is not known at compile time or exceeds the limit of 800.

Supported: non-cyclic call graphs

Source file: guppy_examples/guppy-features/supported/inline-noncyclic-call-graph.py

from typing import no_type_check

from guppylang import guppy
from guppylang.std.builtins import output, qubit
from guppylang.std.quantum import measure, x


@guppy
@no_type_check
def a(q: qubit) -> None:
    x(q)


@guppy
@no_type_check
def b(q: qubit) -> None:
    x(q)
    a(q)


@guppy
@no_type_check
def c(q: qubit) -> None:
    x(q)
    b(q)


@guppy
@no_type_check
def d(q: qubit) -> None:
    x(q)
    b(q)


@guppy
@no_type_check
def e(q: qubit) -> None:
    x(q)
    d(q)
    c(q)


@guppy
@no_type_check
def f(q: qubit) -> None:
    x(q)
    d(q)
    e(q)


@guppy
@no_type_check
def main() -> None:
    q = qubit()
    d(q)
    f(q)

    output("0", measure(q).read())

Unsupported: cyclic call graphs

Source file: guppy_examples/guppy-features/unsupported/cyclic-call-graph.py

from typing import no_type_check

from guppylang import guppy
from guppylang.std.builtins import output, qubit
from guppylang.std.quantum import measure, x


@guppy
@no_type_check
def a(q: qubit) -> None:
    b(q)


@guppy
@no_type_check
def b(q: qubit) -> None:
    x(q)
    d(q)


@guppy
@no_type_check
def c(q: qubit) -> None:
    x(q)
    a(q)


@guppy
@no_type_check
def d(q: qubit) -> None:
    x(q)
    c(q)


@guppy
@no_type_check
def main() -> None:
    q = qubit()
    d(q)
    output("0", measure(q).read())

This example creates recursion across multiple Guppy functions that cannot be fully expanded.

Expected error:

Compilation failed: Loop cannot be unrolled: its iteration count is not known at compile time or exceeds the limit of 800.