Repeat-Until-Success¶
These examples show two bounded repeat-until-success patterns written in Guppy.
They are useful because they demonstrate retry structures that stay within the subset that hugr-qir can lower: the retry budget is finite, the outer structure is known at compile time, and the resulting control flow is static from the backend’s point of view.
Bounded repeat-until-success¶
This version expands the retry structure at comptime and materializes fresh ancillas per attempt.
Source file: guppy_examples/repeat-until-success/supported/bounded-rus.py
from typing import no_type_check
from guppylang import guppy, qubit
from guppylang.std.builtins import result
from guppylang.std.quantum import cx, discard, measure, t, tdg, z
from guppylang.std.quantum.functional import h
N = 10
@guppy
@no_type_check
def rus_attempt(q: qubit) -> bool:
# Same single-shot RUS body as the original example.
a, b = h(qubit()), h(qubit())
tdg(a)
cx(b, a)
t(a)
if measure(h(a)):
discard(b)
return False
t(q)
z(q)
cx(q, b)
t(b)
if measure(h(b)):
z(q)
return False
return True
@guppy
@no_type_check
def rus_step(q: qubit, ok: bool, n: int) -> tuple[bool, int]:
# The dynamic stop-or-retry decision lives in a regular Guppy function
# because the outer retry structure below is expanded at comptime.
if ok:
return True, n
return rus_attempt(q), n + 1
@guppy.comptime
@no_type_check
def main() -> None:
# This version differs from an unbounded retry loop in three ways:
# 1. The retry budget is finite (N attempts) rather than unbounded.
# 2. The loop is unrolled at comptime so hugr-qir sees static control flow.
# 3. Results expose both whether we succeeded and how many tries were used.
# This two-stage retry body also materializes fresh ancillas per attempt in
# QIR, unlike rus-flat-bounded.py which reuses 3 qubits.
q = h(qubit())
n = 0
ok = False
for _ in range(N):
ok, n = rus_step(q, ok, n)
result("attempts", n)
result("success", ok)
result("q", measure(q))
Compared to an unbounded retry loop, this version makes the retry count explicit and exposes both the number of attempts and the success flag as results.
Flat bounded repeat-until-success¶
This version allocates its ancillas once and reuses them across attempts.
Source file: guppy_examples/repeat-until-success/supported/rus-flat-bounded.py
from typing import no_type_check
from guppylang import guppy
from guppylang.std.builtins import comptime, result
from guppylang.std.qsystem import measure_and_reset
from guppylang.std.quantum import discard, h, qubit, s, toffoli, z
N = 10
@no_type_check
def repeat_until_success(q: qubit, attempts: int @ comptime) -> tuple[bool, int]:
# This differs from rus-flat-unbounded.py in three ways:
# 1. The retry budget is finite (`attempts`) rather than unbounded.
# 2. The loop bound is comptime-known so hugr-qir sees static control flow.
# 3. Ancillas are allocated once and reused with measure_and_reset, so the
# lowered QIR only needs 3 qubits.
success = False
tries = 0
a = qubit()
b = qubit()
for _ in range(attempts):
success, tries = rus_step(q, a, b, success, tries)
discard(a)
discard(b)
return success, tries
@guppy
@no_type_check
def rus_attempt(q: qubit, a: qubit, b: qubit) -> bool:
h(a)
h(b)
toffoli(a, b, q)
s(q)
toffoli(a, b, q)
h(a)
h(b)
c0 = measure_and_reset(a)
c1 = measure_and_reset(b)
if not (c0 | c1):
return True
z(q)
return False
@guppy
@no_type_check
def rus_step(
q: qubit, a: qubit, b: qubit, success: bool, tries: int
) -> tuple[bool, int]:
# The dynamic stop-or-retry decision lives in regular Guppy code because
# the outer retry structure below is expanded at comptime. Returning early
# on success keeps later unrolled attempts out of the runtime path.
if success:
return True, tries
if rus_attempt(q, a, b):
return True, tries
return False, tries + 1
@guppy.comptime
@no_type_check
def main() -> None:
# The unbounded version retries with `while True`. Here the retry budget is
# fixed at compile time and the final outputs are emitted once after the
# loop. `attempts` is the number of failed attempts before success, or `N`
# if all attempts fail.
q = qubit()
success, attempts = repeat_until_success(q, comptime(N))
result("success", success)
result("attempts", attempts)
discard(q)
Compared to the first version above, this variant is useful for understanding a lower-qubit implementation of the same retry pattern.