Skip to main content
b01t’s gates are ordinary Python functions. You call them inside a decorated function body, and the build system intercepts the calls to construct a typed AST rather than executing them directly. The gate set is split in two: the exact gate set for @coherent and @primitive programs, which contains only gates synthesisable from , and parametric rotations (rx, ry, rz, cry, crz), which are only available in @parametric and @adaptive programs.

The exact gate set

The exact gate set is the universe of gates available in a @coherent or @primitive program. It is a closed, finite set: no free-angle parameters, no symbolic expressions. Every gate in this set can be synthesised exactly from the universal basis , which is the basis of the Hero Theorem proof.
Attempting to use rx, ry, rz, cry, or crz inside a @coherent or @primitive function raises DSLValidationError immediately — parameterized rotations are not in the exact gate set.

Single-qubit gates

All single-qubit gates take a single Wire argument. You get a Wire by indexing a QReg: sys[0], anc[1], etc.
from b01t import coherent, QReg, x, h, z, s, sdg, t, tdg

@coherent
def single_qubit_demo(q: QReg):
    x(q[0])    # Pauli X (NOT gate)
    h(q[0])    # Hadamard
    z(q[0])    # Pauli Z (phase flip)
    s(q[0])    # S gate (√Z)
    sdg(q[0])  # S-dagger (S†, inverse of S)
    t(q[0])    # T gate (⁴√Z)
    tdg(q[0])  # T-dagger (T†, inverse of T)

Two-qubit gates

Two-qubit gates take two Wire arguments. All wires must be distinct — passing the same wire twice raises DSLValidationError.
from b01t import coherent, QReg, cx, cz, swap

@coherent
def two_qubit_demo(a: QReg, b: QReg):
    cx(a[0], b[0])   # CNOT: control=a[0], target=b[0]
    cz(a[0], b[0])   # Controlled-Z
    swap(a[0], b[0]) # SWAP

Three-qubit gates

The Toffoli gate (ccx) and doubly-controlled-Z (ccz) take three wire arguments — two controls and one target.
from b01t import coherent, QReg, ccx, ccz

@coherent
def three_qubit_demo(a: QReg, b: QReg, c: QReg):
    ccx(a[0], b[0], c[0])   # Toffoli: controls=a[0],b[0], target=c[0]
    ccz(a[0], b[0], c[0])   # CCZ: controls=a[0],b[0], target=c[0]

Multi-controlled gates

mcx and mcz accept an arbitrary number of controls as a list or sequence of wires, plus a single target wire. They require at least one control.
from b01t import coherent, QReg, mcx, mcz

@coherent
def multi_controlled_demo(ctrl: QReg, target: QReg):
    mcx([ctrl[0], ctrl[1], ctrl[2]], target[0])  # 3-controlled X
    mcz([ctrl[0], ctrl[1]], target[0])           # 2-controlled Z

Parametric rotation gates

Parametric gates take an angle (in radians) as their first argument, followed by the wire. They are only available inside @parametric and @adaptive functions.
from b01t import parametric, QReg, rx, ry, rz, cry, crz
import math

@parametric
def rotation_demo(q: QReg, r: QReg):
    rx(math.pi / 4, q[0])          # X-axis rotation by π/4
    ry(math.pi / 3, q[0])          # Y-axis rotation by π/3
    rz(math.pi / 2, q[0])          # Z-axis rotation by π/2
    cry(math.pi / 4, r[0], q[0])   # Controlled-RY
    crz(math.pi / 2, r[0], q[0])   # Controlled-RZ

Gate classification for ancilla blocks

Inside @coherent ancilla blocks, not every gate is available everywhere. The compute section allows only permutation gates and the phase section allows only diagonal gates. See Ancilla Discipline for the full explanation of why these restrictions exist.
GateExact setComputePhaseParametric
xYesYesNoNo
hYesNoNoNo
zYesNoYesNo
sYesNoYesNo
sdgYesNoYesNo
tYesNoYesNo
tdgYesNoYesNo
cxYesYesNoNo
czYesNoYesNo
swapYesYesNoNo
ccxYesYesNoNo
cczYesNoYesNo
mcxYesYesNoNo
mczYesNoYesNo
rxNoNoNoYes
ryNoNoNoYes
rzNoNoNoYes
cryNoNoNoYes
crzNoNoNoYes
“Parametric” here means the gate is available at the top level of @parametric and @adaptive functions.
h is in the exact gate set and is available at the top level of @coherent functions, but it is not a permutation gate and not a diagonal gate — so it is not allowed inside compute or phase blocks.

Measurement

measure and measure_all are only available in @adaptive functions. Attempting to use them in @coherent, @primitive, or @parametric raises DSLValidationError.
from b01t import adaptive, QReg, h, cx, measure

@adaptive
def measure_demo(q: QReg):
    h(q[0])
    result = measure(q[0])  # returns a classical bit name as a string
from b01t import adaptive, QReg, h, measure_all

@adaptive
def measure_all_demo(q: QReg):
    h(q[0])
    h(q[1])
    bits = measure_all(q)   # returns a list of classical bit names

Importing gates

All gates are available from the top-level b01t package:
from b01t import (
    # Single-qubit
    x, h, z, s, sdg, t, tdg,
    # Parametric single-qubit
    rx, ry, rz,
    # Two-qubit
    cx, cz, swap, cry, crz,
    # Three-qubit
    ccx, ccz,
    # Multi-controlled
    mcx, mcz,
    # Measurement
    measure, measure_all,
)