Skip to main content
The routing combinators in b01t.kit.routing provide a single set of names that work correctly inside both exact (@coherent, @primitive) and broad (@parametric, @adaptive) build contexts. You write the same ancilla, compute, phase, uncompute, par, and adjoint calls regardless of decorator, and b01t routes to the appropriate implementation automatically. Import them from b01t directly.
from b01t import ancilla, compute, phase, apply, uncompute, par, adjoint

ancilla

ancilla(size: int)
Creates an ancilla block context manager that allocates size fresh qubits. Use with a with statement; the returned register is only valid inside the block.
size
int
required
Number of ancilla qubits to allocate.
In an exact context (@coherent), the block enforces the full compute / phase (or apply) / uncompute discipline and certifies ancilla cleanliness. In a broad context (@parametric), it records an AncillaBlockOp in the IR. Nested ancilla blocks are not supported and raise DSLValidationError.
from b01t import coherent, QReg, cx, z
from b01t import ancilla, compute, phase, uncompute

@coherent
def phase_kick(sys: QReg) -> None:
    with ancilla(1) as anc:
        compute(lambda: cx(sys[0], anc[0]))
        phase(lambda: z(anc[0]))
        uncompute()

prog = phase_kick.build_exact(("sys", 1))

compute

compute(fn: Callable[[], None]) -> None
Marks the compute section of an ancilla block. fn is called immediately; any gates emitted inside it are captured as compute ops.
fn
Callable[[], None]
required
A zero-argument callable that emits gates. In exact context, only permutation gates are allowed (x, cx, ccx, swap, mcx). In broad context, only gates from _ALLOWED_COMPUTE are accepted.
compute must be the first section inside an ancilla block. Calling it outside a block raises DSLValidationError.
from b01t import coherent, QReg, cx, ccx, z
from b01t import ancilla, compute, phase, uncompute

@coherent
def carry(a: QReg, b: QReg) -> None:
    with ancilla(1) as anc:
        compute(lambda: (cx(a[0], anc[0]), cx(b[0], anc[0])))
        phase(lambda: z(anc[0]))
        uncompute()

prog = carry.build_exact(("a", 1), ("b", 1))

phase

phase(fn: Callable[[], None]) -> None
Marks the phase (diagonal) section of an ancilla block. fn is called immediately; gates emitted inside it are captured as phase ops and must all be diagonal.
fn
Callable[[], None]
required
A zero-argument callable that emits diagonal gates. In exact context, only z, cz, ccz, s, sdg, t, tdg, mcz are allowed. In broad context, only gates from _ALLOWED_PHASE (which also includes rz) are accepted.
phase must follow compute. Calling it before compute or outside a block raises DSLValidationError.
from b01t import coherent, QReg, cx, t
from b01t import ancilla, compute, phase, uncompute

@coherent
def t_phase(sys: QReg) -> None:
    with ancilla(1) as anc:
        compute(lambda: cx(sys[0], anc[0]))
        phase(lambda: t(anc[0]))
        uncompute()

prog = t_phase.build_exact(("sys", 1))

apply

apply(fn: Callable[[], None]) -> None
Marks the apply (CMA pattern) section of an ancilla block. In an exact context, apply allows any exact gate, but all wires used must be disjoint from the wires used in the compute block. In a broad context, apply falls through to phase semantics.
fn
Callable[[], None]
required
A zero-argument callable that emits gates on wires disjoint from the compute block’s wires.
apply is an alternative to phase when you want to run gates that are not diagonal — for example, applying a gate to system qubits while ancilla holds a value. The wire-disjointness constraint (the PreservesFirst condition) is checked at block finalization.
from b01t import coherent, QReg, cx, x
from b01t import ancilla, compute, apply, uncompute

@coherent
def controlled_x(ctrl: QReg, tgt: QReg) -> None:
    with ancilla(1) as anc:
        compute(lambda: cx(ctrl[0], anc[0]))
        apply(lambda: x(tgt[0]))   # tgt is disjoint from ctrl/anc
        uncompute()

prog = controlled_x.build_exact(("ctrl", 1), ("tgt", 1))

uncompute

uncompute() -> None
Auto-generates the uncompute section by reversing the compute ops and applying their inverses. You do not write the inverse gates by hand — b01t derives them for you. uncompute must follow a phase or apply section. Calling it before either raises DSLValidationError. After uncompute, you can start a new compute / phase / uncompute cycle within the same ancilla block.
from b01t import coherent, QReg, cx, z
from b01t import ancilla, compute, phase, uncompute

@coherent
def double_cycle(sys: QReg) -> None:
    with ancilla(1) as anc:
        compute(lambda: cx(sys[0], anc[0]))
        phase(lambda: z(anc[0]))
        uncompute()
        # start a new cycle in the same block
        compute(lambda: cx(sys[1], anc[0]))
        phase(lambda: z(anc[0]))
        uncompute()

prog = double_cycle.build_exact(("sys", 2))

par

par(fn1: Callable[[], None], fn2: Callable[[], None]) -> None
Parallel (tensor) composition. fn1 and fn2 are executed in separate capture contexts, and b01t verifies that they operate on disjoint wire sets. If any wire is shared, DSLValidationError is raised.
fn1
Callable[[], None]
required
First sub-program. Emits gates on one subset of wires.
fn2
Callable[[], None]
required
Second sub-program. Emits gates on a disjoint subset of wires.
from b01t import coherent, QReg, h, x
from b01t import par

@coherent
def parallel_ops(a: QReg, b: QReg) -> None:
    par(
        lambda: h(a[0]),
        lambda: x(b[0]),
    )

prog = parallel_ops.build_exact(("a", 1), ("b", 1))

adjoint

adjoint(fn, *args) -> None
Emits the inverse (adjoint) of a coherent subroutine into the current program. fn is re-executed in a capture context, and all its ops are reversed and inverted. The result is appended to the current program’s op sequence.
fn
ExactDSLFunction | DSLFunction
required
The coherent function whose inverse to emit. Must be decorated with @coherent (exact context) or @parametric with Effect.COHERENT (broad context). Passing an @adaptive function raises DSLValidationError.
args
QReg
required
The register arguments to pass to fn when capturing its ops. These must match fn’s parameter signature.
adjoint cannot be called inside an ancilla compute / phase block.
from b01t import coherent, QReg, h, cx
from b01t import adjoint

@coherent
def entangle(a: QReg, b: QReg) -> None:
    h(a[0])
    cx(a[0], b[0])

@coherent
def entangle_then_undo(a: QReg, b: QReg) -> None:
    entangle(a, b)
    adjoint(entangle, a, b)  # emits cx(a[0], b[0]), h(a[0]) in reverse

prog = entangle_then_undo.build_exact(("a", 1), ("b", 1))