Skip to main content
The @coherent decorator is b01t’s primary tool for writing quantum circuits that are certified safe by construction. When you call .build_exact() on a coherent function, b01t validates the entire program structure — gate types, ancilla discipline, and uncomputation — before producing an ExactProgram with certification == Certification.SAFE. No simulation required.

The full workflow

1

Import and define a @coherent function

Decorate a Python function with @coherent. Each parameter must be annotated as QReg — these become the quantum registers your function operates on.
from b01t import coherent, QReg, cx, z
from b01t import ancilla, compute, phase, uncompute

@coherent
def oracle(sys: QReg):
    with ancilla(1) as anc:
        compute(lambda: cx(sys[0], anc[0]))
        phase(lambda: z(anc[0]))
        uncompute()
You cannot call a @coherent function directly — it must be built via .build_exact().
2

Use ancilla for scratch qubits

Allocate scratch qubits with the ancilla(n) context manager. All scratch qubits allocated this way are automatically verified to return to |0⟩ at the end of the block.Inside the with ancilla(...) as anc: block, you must structure your operations as one or more complete compute / phase / uncompute cycles:
  • compute(fn) — apply permutation gates (X, CX, CCX, SWAP, MCX) to copy system information into the ancilla. Superposition gates like H are not allowed here.
  • phase(fn) — apply diagonal gates (Z, S, T, CZ, CCZ, MCZ) to accumulate phase. Only gates that are diagonal in the computational basis are allowed.
  • uncompute() — automatically generates and appends the inverse of the compute block.
You can also use apply(fn) instead of phase(fn) for the “CMA pattern”: any exact gates are allowed, but they must operate on wires disjoint from the compute block’s wires.
@coherent
def multi_phase(sys: QReg):
    with ancilla(1) as anc:
        # First cycle
        compute(lambda: cx(sys[0], anc[0]))
        phase(lambda: z(anc[0]))
        uncompute()
        # Second cycle in the same ancilla block
        compute(lambda: cx(sys[1], anc[0]))
        phase(lambda: z(anc[0]))
        uncompute()
3

Build with build_exact()

Call .build_exact() with register specifications — tuples of (name, size) matching the function’s parameters in order.
prog = oracle.build_exact(("sys", 1))
For functions with multiple registers, pass multiple specs:
prog = multi_oracle.build_exact(("inp", 3), ("tgt", 1))
build_exact() returns an ExactProgram. If any rule is violated, it raises DSLValidationError before returning.
4

Check certification

A program built with @coherent always has certification == Certification.SAFE. You can assert this to make the guarantee explicit in your code:
from b01t import Certification

assert prog.certification == Certification.SAFE
Programs built with @primitive have certification == Certification.PRIMITIVE — they use the same closed gate set but do not enforce ancilla cleanliness.

Complete example: phase oracle

This is the canonical example from the b01t README. It computes the phase oracle U_f|x⟩ = (-1)^{f(x)}|x⟩ for a single-qubit function.
from b01t import coherent, QReg, cx, z, Certification
from b01t import ancilla, compute, phase, uncompute

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

prog = oracle.build_exact(("sys", 1))
assert prog.certification == Certification.SAFE
print(prog.name, prog.regs)  # oracle (QReg(sys, 1), QReg(anc0, 1))
The ancilla qubit anc0 starts at |0⟩, the CX gate copies sys[0] into it, the Z gate applies a phase, and uncompute() generates the inverse CX to restore anc0 to |0⟩.

Parallel composition with par()

Use par() to compose two sub-programs that operate on disjoint registers simultaneously. b01t enforces wire-disjointness at build time.
from b01t import coherent, QReg, h, z
from b01t import par

@coherent
def hadamard_pair(a: QReg, b: QReg):
    par(
        lambda: h(a[0]),
        lambda: h(b[0]),
    )

prog = hadamard_pair.build_exact(("a", 1), ("b", 1))
assert prog.certification == Certification.SAFE
If the two lambdas touch the same wire, build_exact() raises DSLValidationError: par() requires disjoint wires.

Inverse operations with adjoint()

Use adjoint() to append the inverse of another @coherent or @primitive function into the current program body. This is useful for uncomputing a subroutine at the top level (outside an ancilla block).
from b01t import coherent, QReg, h, cx
from b01t import adjoint

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

@coherent
def entangle_then_undo(a: QReg, b: QReg):
    entangle(a, b)
    adjoint(entangle, a, b)

prog = entangle_then_undo.build_exact(("a", 1), ("b", 1))
assert prog.certification == Certification.SAFE
adjoint() cannot be used inside an ancilla block’s compute/phase/uncompute cycle. It is only valid at the top level of a @coherent function body.

Common errors

Wrong gate in a compute block

@coherent
def broken(sys: QReg):
    with ancilla(1) as anc:
        compute(lambda: h(anc[0]))  # H is not a permutation gate
        phase(lambda: z(anc[0]))
        uncompute()

# DSLValidationError: gate H is not allowed in exact compute blocks
Fix: Use only permutation gates in compute(): x, cx, ccx, swap, mcx.

Wrong gate in a phase block

@coherent
def broken(sys: QReg):
    with ancilla(1) as anc:
        compute(lambda: cx(sys[0], anc[0]))
        phase(lambda: h(anc[0]))  # H is not diagonal
        uncompute()

# DSLValidationError: gate H is not allowed in exact phase blocks
Fix: Use only diagonal gates in phase(): z, s, sdg, t, tdg, cz, ccz, mcz.

Missing uncompute

@coherent
def broken(sys: QReg):
    with ancilla(1) as anc:
        compute(lambda: cx(sys[0], anc[0]))
        phase(lambda: z(anc[0]))
        # forgot uncompute()

# DSLValidationError: ancilla block exited with incomplete cycle
Fix: Every compute / phase cycle must be closed with uncompute().

Parameterized rotation in @coherent

@coherent
def broken(sys: QReg):
    rx(0.5, sys[0])  # arbitrary rotation not in the exact gate set

# DSLValidationError: gate 'rx' is not allowed in @coherent
Fix: Use @parametric instead of @coherent when you need arbitrary rotation angles like rx, ry, rz, cry, or crz.

Calling @coherent outside build context

oracle(my_reg)  # called directly, not inside build_exact

# DSLValidationError: oracle is an exact DSL function;
# use oracle.build_exact(...) to produce an ExactProgram
Fix: Always call .build_exact(("name", size), ...) to produce a program.

Exact gate set

The following gates are available in @coherent programs:
GateSingle-qubitMulti-qubit
Pauli Xx
Hadamardh
Pauli Zz
S / S†s, sdg
T / T†t, tdg
Controlled-Xcx
Controlled-Zcz
SWAPswap
Toffoliccx
CCZccz
Multi-controlled Xmcx
Multi-controlled Zmcz
Parameterized rotations (rx, ry, rz, cry, crz) are not part of the exact gate set. Use @parametric for those.