Control Flow and Recursion¶
These examples show the distinction between control flow that can be made static for QIR generation and control flow that still implies dynamic or cyclic structure in the lowered program. They also detail some Guppy features that are not currently supported by H-Series hardware.
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 result
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)
b = measure(q1)
c = measure(q2)
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)
result("a", a)
result("b", b)
result("c", c)
result("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, result # 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):
exit("Postselected: Criteria not met", 1)
x(q)
result("q", measure(q))
Source file: guppy_examples/guppy-features/unsupported/panic.py
from guppylang import guppy, qubit
from guppylang.std.platform import panic, result
from guppylang.std.quantum import h, measure, x
@guppy
def main() -> None:
q = qubit()
fake_ancilla = qubit()
h(fake_ancilla)
if measure(fake_ancilla):
panic("Criteria not met")
x(q)
result("q", measure(q))
Early exit using either exit or panic is not currently supported on H-Series hardware and will not pass the QIR validation step.
Expected error (for both examples):
QIR generation failed. This may be the result of a bug but can also happen when trying to convert a feature in HUGR/Guppylang which is not supported in QIR. The failure occurred in the validity check of the generated QIR. This check can be disabled by setting `--no-validate-qir` on the cli or passing `validate_qir=False` for library calls. Error details: Encountered Unexpected Instruction tail call void @abort()
Supported: unrollable loops¶
Source file: guppy_examples/guppy-features/supported/unrollable-loops.py
import sys
from guppylang import guppy
from guppylang.std.platform import result
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(30):
x(q1)
result("q", measure(q1))
if __name__ == "__main__":
sys.stdout.buffer.write(main.compile().to_bytes())
This pattern works because the loop structure can be serialized into static control flow during lowering. Note that loop unrolling within @guppy annotated functions relies on the capabilities of the chosen LLVM optimization level (default: Aggressive). 100% unrolling is not guaranteed and will not happen for loops of a certain size and complexity, leading to a conversion error.
This reliance on LLVM optimization can be mitigated by writing loops within @guppy.comptime when possible. This guarantees loop unrolling at compile time, since the loop is fully executed by the Python interpreter. This is, of course, not possible if the loop branches on runtime values, such as measurement results.
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):
break
if __name__ == "__main__":
sys.stdout.buffer.write(main.compile().to_bytes())
This loop depends on measurement results and therefore remains genuinely dynamic in the generated control-flow graph.
Expected error:
QIR generation failed. This may be the result of a bug but can also happen when trying to convert a feature in HUGR/Guppylang which is not supported in QIR. The failure occurred in the validity check of the generated QIR. This check can be disabled by setting `--no-validate-qir` on the cli or passing `validate_qir=False` for library calls. Error details: Found loop in CFG containing the block: cond_79_case_1
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 qubit, result
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)
result("q", measure(q))
if __name__ == "__main__":
sys.stdout.buffer.write(main.compile().to_bytes())
This recursive form works because it can be turned into static control flow for QIR generation.
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 qubit, result
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):
return recursive_func(q)
return None
@guppy
@no_type_check
def main() -> None:
q = qubit()
h(q)
recursive_func(q)
result("q", measure(q))
if __name__ == "__main__":
sys.stdout.buffer.write(main.compile().to_bytes())
Here the recursive path depends on a measurement result, so the generated CFG contains a loop that the current backend rejects during validation.
Expected error:
QIR generation failed. This may be the result of a bug but can also happen when trying to convert a feature in HUGR/Guppylang which is not supported in QIR. The failure occurred in the validity check of the generated QIR. This check can be disabled by setting `--no-validate-qir` on the cli or passing `validate_qir=False` for library calls. Error details: Found loop in CFG containing the block: tailrecurse.i
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 qubit, result
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)
result("0", measure(q))
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 qubit, result
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)
result("0", measure(q))
This example creates a cycle across multiple Guppy functions, which eventually lowers to an unsupported loop in the control-flow graph.
Expected error:
QIR generation failed. This may be the result of a bug but can also happen when trying to convert a feature in HUGR/Guppylang which is not supported in QIR. The failure occurred in the validity check of the generated QIR. This check can be disabled by setting `--no-validate-qir` on the cli or passing `validate_qir=False` for library calls. Error details: Found loop in CFG containing the block: tailrecurse.i