Skip to main content
Decorators are the entry point for every b01t program. You apply a decorator to a Python function that accepts QReg arguments, and b01t wraps it in a typed object — either an ExactDSLFunction or a DSLFunction — that you call .build_exact() or .build() on to produce a compiled program. Your choice of decorator determines which gates are allowed, what certification the program receives, and whether classical branching is permitted.

@coherent

from b01t import coherent

@coherent
def my_program(sys: QReg) -> None:
    ...
@coherent wraps the decorated function in an ExactDSLFunction with Certification.SAFE. Use it for programs that must be fully ancilla-certified — every ancilla qubit is guaranteed to return to |0⟩ by construction. The compiler enforces the complete ancilla discipline (compute / phase / uncompute) and rejects any gate not in the exact gate set ({X, H, Z, S, S†, T, T†, CX, CZ, SWAP, CCX, CCZ, MCX, MCZ}). When to use: whenever you need a structurally certified unitary channel. The hero theorem applies: if .build_exact() succeeds, the program is provably safe. Key rules:
  • Only exact gates are allowed. rx, ry, rz, cry, crz raise DSLValidationError.
  • measure is forbidden.
  • if_then is forbidden.
  • Ancilla blocks must complete at least one full compute / phase (or apply) / uncompute cycle.
  • A @coherent function may not call a @primitive function outside of an ancilla compute or phase block.
from b01t import coherent, QReg, h, cx, z
from b01t import ancilla, compute, phase, uncompute

@coherent
def phase_kickback(ctrl: QReg, tgt: QReg) -> None:
    h(ctrl[0])
    cx(ctrl[0], tgt[0])
    z(tgt[0])
    cx(ctrl[0], tgt[0])
    h(ctrl[0])

prog = phase_kickback.build_exact(("ctrl", 1), ("tgt", 1))
print(prog.certification)  # Certification.SAFE

@primitive

from b01t import primitive

@primitive
def my_prim(anc: QReg, sys: QReg) -> None:
    ...
@primitive wraps the decorated function in an ExactDSLFunction with Certification.PRIMITIVE. The gate set and syntax rules are identical to @coherent, but scratch registers are caller-managed rather than certified by the compiler. Use @primitive for low-level building blocks where you manage ancilla cleanliness yourself, or where the ancilla is shared with the calling function. When to use: for subroutines that intentionally leave ancilla in a non-zero state for a caller to clean up, or for inner routines that are called from within a compute or phase block of a @coherent function. Key rules:
  • Same exact gate set as @coherent.
  • A @coherent function can call a @primitive function only from inside a compute(...) or phase(...) block.
  • Calling a @primitive function at the top level of a @coherent body raises DSLValidationError.
from b01t import primitive, coherent, QReg, cx
from b01t import ancilla, compute, phase, uncompute, z

@primitive
def carry_bit(a: QReg, b: QReg, anc: QReg) -> None:
    cx(a[0], anc[0])
    cx(b[0], anc[0])

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

prog = adder.build_exact(("a", 1), ("b", 1))
print(prog.certification)  # Certification.PRIMITIVE... wait, caller is @coherent
# => Certification.SAFE

@parametric

from b01t import parametric

@parametric
def my_program(sys: QReg) -> None:
    ...
@parametric wraps the decorated function in a DSLFunction with Effect.COHERENT. It unlocks the parametric rotation gates (rx, ry, rz, cry, crz) while keeping unitary semantics — no measurement, no classical branching. The resulting IRProgram has effect = Effect.COHERENT. When to use: variational algorithms (VQE, QAOA) and any circuit that needs continuous-angle rotations. Programs that pass the safety checker (is_safe_program) are marked is_safe = True in the returned IRProgram. Key rules:
  • All exact gates plus rx, ry, rz, cry, crz are allowed.
  • measure and if_then are forbidden.
  • A @parametric function cannot call an @adaptive function.
from b01t import parametric, QReg, h, rz, cx
import math

@parametric
def ansatz(q: QReg) -> None:
    h(q[0])
    rz(math.pi / 4, q[0])
    cx(q[0], q[1])

prog = ansatz.build(("q", 2))
print(prog.effect)    # Effect.COHERENT
print(prog.is_safe)   # False (rz is parametric, not exact-safe)

@adaptive

from b01t import adaptive

@adaptive
def my_program(sys: QReg) -> None:
    ...
@adaptive wraps the decorated function in a DSLFunction with Effect.ADAPTIVE. Adaptive programs can measure qubits and branch on classical results via if_then. The returned IRProgram has effect = Effect.ADAPTIVE and is_safe = False (adaptive programs are never safe by the safety model). When to use: measurement-based protocols, quantum error correction, and teleportation circuits that require mid-circuit measurement and feed-forward. Key rules:
  • All gates from both the exact and parametric sets are allowed.
  • measure, measure_all, and if_then are allowed.
  • An @adaptive function can call @parametric functions, but not vice versa.
from b01t import adaptive, QReg, h, measure
from b01t import if_then, x

@adaptive
def teleport_prep(q: QReg, out: QReg) -> None:
    h(q[0])
    m = measure(q[0])
    if_then(m, lambda: x(out[0]))

prog = teleport_prep.build(("q", 1), ("out", 1))
print(prog.effect)  # Effect.ADAPTIVE

ExactDSLFunction.build_exact()

Compiles a @coherent or @primitive function into an ExactProgram.
prog = my_fn.build_exact(*arg_specs)
arg_specs
tuple[str, int] | QReg
required
One argument per register in the decorated function. Each argument is either a ("name", size) tuple or an existing QReg object. Register names must be unique.
Returns: ExactProgram with fields name, regs, ops, and certification. Raises: DSLValidationError if any gate, ancilla, or structural rule is violated during the build.
from b01t import coherent, QReg, h, cx

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

# Using tuples
prog = bell.build_exact(("a", 1), ("b", 1))

# Using QReg objects
a = QReg("a", 1)
b = QReg("b", 1)
prog = bell.build_exact(a, b)

DSLFunction.build()

Compiles a @parametric or @adaptive function into an IRProgram.
prog = my_fn.build(*arg_specs)
arg_specs
tuple[str, int] | QReg
required
One argument per register in the decorated function. Each argument is either a ("name", size) tuple or an existing QReg object. Register names must be unique.
Returns: IRProgram with fields name, effect, regs, ops, and is_safe. Raises: DSLValidationError if any rule is violated during the build.
from b01t import parametric, QReg, h, rz, cx
import math

@parametric
def rotation_layer(q: QReg) -> None:
    for i in range(len(q)):
        rz(math.pi / 8, q[i])

prog = rotation_layer.build(("q", 3))
print(prog.name)    # "rotation_layer"
print(prog.effect)  # Effect.COHERENT