This codebase is written entirely by me and entirely for me.
Use it if you like it.
The model · Prototyping a phase oracle · Circuit graphs · Single-qubit operators · Supporting Clifford algebra · Design and compilation · Running and verification · Project layout
A Wire names one qubit’s path through a circuit. A WireOp describes the
processing on one wire and can be reused on any wire. Apply(u, b) binds
the operator u to wire b. The constructors CZ(a, b) and CCZ(a, b, c)
name the wires that participate in each interaction.
A Python function builds a circuit by passing these operations to seq in
execution order. The result, a Seq, is an immutable directed graph with an
declared wire order. Each surviving CZ or CCZ gives one Intersection in
its vertices tuple. A WireSegment connects two ports on the same wire and
carries the combined WireOp for that stretch. A wire that passes through
the entire circuit without an intersection has one input-to-output segment.
Composing two circuits joins wires by name and combines the operators on the joined segments. Two equal interactions cancel when they connect directly on every participating wire and the intervening processing is exactly identity. The resulting graph can be inspected or rewritten without running the Python function again.
| Value | Responsibility |
|---|---|
Clif |
Supporting exact algebra for the 192 matrices generated by H and S, including discrete global phases |
WireOp |
A canonical exact Clifford+T linear operator on one qubit |
Wire |
A wire identified by its string name |
Apply |
An operator applied to a named wire |
CZ, CCZ |
Symmetric phase gates applied to two or three distinct named wires |
Seq |
An open graph with ordered boundary wires, interaction vertices, and output edges |
Intersection |
One CZ or CCZ occurrence with a port for each participating wire |
WireSegment |
A connection from a source port carrying a canonical single-qubit operator |
These values are frozen, slotted dataclasses. Equality and hashing use their
stored fields. The canonical form of a WireOp makes equality
an exact test of the one-wire operation, including global phase. For a
Seq, equality also depends on the stored boundary and traversal order.
Consider a phase oracle that negates the amplitude of |011⟩ and preserves
every other basis amplitude, with wires ordered (a, b, c). To see what it
costs to use only diagonal gates, compare two implementations. The first
temporarily flips wire a. The second uses CZ and CCZ alone.
from quantum_circuits import Apply, CCZ, CZ, Wire, X, seq
def mark_011_by_flipping(a, b, c):
flip = Apply(X, a)
return seq(flip, CCZ(a, b, c), flip, wires=(a, b, c))
def mark_011_with_phases(a, b, c):
return seq(CZ(b, c), CCZ(a, b, c), wires=(a, b, c))
wires = tuple(map(Wire, "abc"))
flips = mark_011_by_flipping(*wires)
phases = mark_011_with_phases(*wires)
assert [v.kind for v in flips.vertices] == ["CCZ"]
assert [v.kind for v in phases.vertices] == ["CZ", "CCZ"]The first implementation flips a so that CCZ marks the desired input, then
restores a. The second marks both 011 and 111 with CZ on b,c. Its CCZ
flips the sign on 111 a second time, leaving only 011 marked. Both
implementations therefore give the same phase on every basis state. The
diagonal implementation replaces the two X applications with one additional CZ.
Wire('q') identifies the wire named q. Equal names mean the same wire; callers
choose fresh names when necessary. Apply(op, targ) applies an operator to a
wire, while gate(value, targ) additionally accepts a word.
Binding the same H operator to q and to r demonstrates why the operator
and its application have separate types. The applications compare differently
because they name different wires.
from quantum_circuits import Apply, H, Wire, gate, op
q, r = Wire('q'), Wire('r')
assert gate('HTH', q) == Apply(op('HTH'), q)
assert Apply(H, q) != Apply(H, r)
assert Apply(H, q).qubits == frozenset((q,))seq(*parts, wires=None) -> Seq accepts Apply, CZ, CCZ, and
existing graphs in execution order. gate(value, q) constructs a one-wire
application from an operator or word. CZ and CCZ apply a minus sign exactly
when all their participating bits are one. Each requires distinct named wires.
A Seq stores three tuples.
| Field | Meaning |
|---|---|
wires |
Ordered boundary wires. Equal names identify the same wire. |
vertices |
CZ and CCZ occurrences in topological order. A vertex's index identifies the occurrence within this graph. |
outputs |
One incoming edge per output port, in boundary order. |
Intersection(kind, inputs) uses the string 'CZ' or 'CCZ' and two or three
input edges respectively. The constructor orders its symmetric ports by wire
name. Each port continues the same wire on the output side.
WireSegment(wire, source=None, op=I) identifies its source port by vertex index and
wire. None means the input boundary on that wire. The destination owns the
edge. Its label applies after the source and before the destination. Parallel
edges between the same vertices carry distinct wire names. Direct graph
construction validates every edge against the latest port on its wire and
checks the output boundary. This enforces forward, unbranched paths.
An explicit wires= sets the complete interface and its order. With the
argument omitted, seq uses first appearance, including idle wires in a
child graph's interface. A symmetric primitive introduces its wires in name
order. .qubits returns the set of declared wires. seq() gives the graph
with zero wires, and seq(wires=(q,)) gives the identity on one wire.
Pass body.wires to reuse an ordered interface.
Composition connects ports by name. Processing labelled U followed by
processing labelled V gives the canonical label V @ U, including phase.
Only an interaction touching a wire closes its current segment. A nested
Seq contributes its vertices in traversal order and joins its boundary
labels to the surrounding segments. Equality and hashing compare the stored
fields, including the chosen boundary and traversal orders.
The two Hadamards below appear on opposite sides of a CZ in the written
program. Inspecting the output edges shows whether that interaction separates
them on wire c.
from quantum_circuits import Apply, CZ, H, Wire, WireSegment, seq
a, b, c = map(Wire, 'abc')
wires = (a, b, c)
cz = CZ(a, b)
body = seq(Apply(H, c), cz, Apply(H, c), wires=wires)
assert len(body.vertices) == 1
assert body.outputs == (WireSegment(a, 0), WireSegment(b, 0), WireSegment(c))Both H gates act on the same uninterrupted segment of wire c, whose label
reduces to identity. On wire a, placing an H on each side of the CZ would
put the H gates on two separate edges.
seq erases two occurrences of the same interaction when all inputs of the
later occurrence lead directly to the earlier one and every connecting label
is exact identity. An operation on a disjoint wire may lie between them in
traversal order. A nontrivial scalar phase or a T on a connecting edge
blocks the match. Raw Seq(wires, vertices, outputs) construction validates
this invariant and raises ValueError on a directly cancellable pair.
During construction, wire factors stay in execution order. Removing a vertex
reopens its incoming segments. Their factors can accumulate until another
match needs to check identity or the builder freezes the result. This also
applies to pairs exposed by composing two independently compiled blocks.
cancel_pairs(body) returns its argument for compatibility with existing callers.
The next example demonstrates cancellation across a composition boundary. Each block contains a CZ and a CCZ. At the join the CCZ pair cancels first, exposing the CZ pair. The assertion checks that the result is the identity on the original three wires.
from quantum_circuits import CCZ, CZ, Wire, seq
a, b, c = map(Wire, 'abc')
left = seq(CZ(a, b), CCZ(a, b, c))
right = seq(CCZ(c, b, a), CZ(b, a))
assert seq(left, right) == seq(wires=left.wires)One basis-change recipe can serve several subroutines. Here the caller supplies the operation to perform between two Hadamards. Passing a T gives a T rotation about the X axis, while passing a Z gives X.
from quantum_circuits import Apply, H, T, Wire, X, Z, gate, seq
q = Wire("q")
def in_h_basis(make_body, target):
return seq(Apply(H, target), make_body(target), Apply(H, target))
assert in_h_basis(lambda t: Apply(T, t), q) == seq(gate("HTH", q))
assert in_h_basis(lambda t: Apply(Z, t), q) == seq(Apply(X, q))WireOp stores a linear map independently of any wire name. Its two fields specify the following canonical representation.
@dataclass(frozen=True, slots=True, repr=False)
class WireOp:
initial: Clif
axes: str = ''The value denotes T[axes[-1]] ... T[axes[0]] initial: apply the initial
Clifford first, then the axes in string order. For example, axes='xyz'
denotes T_z T_y T_x initial in matrix order. The exact matrices are
T_z = T = diag(1, exp(i*pi/4)), T_x = H T H, and
T_y = S H T H S^-1. These equations use matrix order. Each axis gate
includes the conventional T gate's scalar phase.
Axes contain only lowercase x, y, z, with unequal adjacent letters.
Empty axes represent a pure Clifford. These fields uniquely represent each
exact single-qubit Clifford+T operator, including global phase. The basis is
Giles and Selinger, Remarks on Matsumoto and Amano's normal form for
single-qubit Clifford+T operators, Proposition 8.2
(paper).
Its conjugated T definitions agree exactly with the ones above.
Global phase matters when an operator is later placed under coherent control.
For example, op(phase=1) is exp(i*pi/4) * I and compares unequal to I.
Runtime normalisation uses exact integers and finite tables. Tests also use
floating-point matrices as an independent check.
Raw construction validates canonical data. Incorrect field types raise
TypeError; invalid or repeated axes raise ValueError. Use op to
normalise words. There is no artificial length bound.
The constructor signature gives the accepted inputs and the optional phase.
def op(
*parts: str | Clif | WireOp,
phase: int = 0,
) -> WireOp: ...Parts execute left to right. Strings accept uppercase I, X, Y, Z,
H, S, T, ignoring whitespace. Unknown letters raise ValueError;
unsupported types raise TypeError. phase is an integer (not a Boolean),
reduced modulo eight; it multiplies the whole result by omega**phase,
where omega = exp(i*pi/4).
A circuit author can write a short gate word, reuse previously constructed operators, or promote a Clifford. These equalities show how the three input forms compose in execution order and how the phase argument scales the result.
from quantum_circuits import H, I, S, T, clifford, op
assert op() == I
assert op("HTH") == op(H, T, H)
assert op("HS", T, "SH") == op("HSTSH")
assert op(clifford("HS")) == S @ H
assert op("HT", phase=-1) == op(phase=-1) @ op("HT")All top-level constants I, X, Y, Z, H, S, T have type
WireOp. An operator is independent of qubit names.
| Interface | Contract |
|---|---|
u.initial, u.axes |
Immutable canonical fields |
u.t_count |
len(u.axes), minimal T-count for the isolated exact operator |
u.is_clifford |
Whether axes are empty |
u.is_identity |
Empty axes and an initial Clifford exactly equal to I |
a @ b |
Apply b, then a; returns a canonical operator |
u.inverse() |
Exact inverse/adjoint, in canonical form |
u ** n |
Integer power; zero gives I, negative powers use the inverse |
u.iter_gates() |
Lazy exact H/S/T decomposition in execution order |
repr(u) / str(u) |
Diagnostic phase/representative/Pauli/axes form |
u == v, hash(u) |
Generated dataclass equality and hashing of canonical fields |
@ accepts only operators; promote other inputs with op. Unsupported
operands follow Python's NotImplemented protocol. Powers require integers
excluding Booleans; modular three-argument pow is not supported.
iter_gates emits exactly t_count T letters. It realises scalar phases
with H/S gates, so there is no external phase record. Its Clifford count
need not be minimal; identity emits nothing.
Two short identities illustrate exact equality. Two T gates reduce to S. Three repetitions of H composed with S produce a scalar phase, which the representation distinguishes from identity.
from quantum_circuits import H, I, S, T, op
assert op("HS") == S @ H
assert op("TT") == S
assert T.inverse() @ T == I
assert T ** 8 == I
assert (H @ S) ** 3 == op(phase=1)
assert not op(phase=1).is_identityTo inspect the cost of a one-wire design, lower its canonical operator to an H/S/T word. Reconstructing the operator checks its action including phase. Counting the emitted T letters checks its reported T-count.
from quantum_circuits import I, op
u = op("HTSHT")
assert not u.is_clifford
assert u.inverse() @ u == I
word = "".join(u.iter_gates())
assert op(word) == u
assert word.count("T") == u.t_countrepr(op('HTH')) is
WireOp(phase=0, rep=0, pauli=0, axes='x').
The Clifford coordinates describe initial; this diagnostic representation
is not an evaluable constructor expression. The operator itself is a suitable
dictionary key. Equal operators hash equally; Python hashes may collide and
are not persistent identifiers.
A private accumulator stores an initial Clifford C and an axis stack in
matrix order. Read inputs backwards and right-multiply factors. A Clifford
factor changes only C. For T_a, look up the signed axis in C P_a C^-1.
The exact reductions are:
C T_a = T_(+/-b) C
T_-b = T_b (omega S_b^-1)
T_b T_b = S_b, where S_b = T_b**2
Absorb the negative-axis correction into C. Push b unless it matches the stack top; then pop and absorb S_b. Each factor needs bounded table work without rescanning. Freeze the string once. Word normalisation, composition of two operators, and inversion take time linear in input size.
Repeated immutable extensions still copy their strings: prefer op(*parts)
to a loop of @ calls for a large word. Graph construction batches the
operators by wire segment and normalises them when a match or the output needs their product.
Clif(frame, phase=0) denotes
omega**phase * P[pauli] * R[rep]
frame = 4*rep + pauli
P = (I, X, Y, Z)
R = (I, H, S, HS, SH, HSH)
Labels below use matrix order, with the rightmost factor acting first.
| rep | Representative | pauli=0 | pauli=1 | pauli=2 | pauli=3 |
|---|---|---|---|---|---|
| 0 | I | 0: I | 1: X | 2: Y | 3: Z |
| 1 | H | 4: H | 5: XH | 6: YH | 7: ZH |
| 2 | S | 8: S | 9: XS | 10: YS | 11: ZS |
| 3 | HS | 12: HS | 13: XHS | 14: YHS | 15: ZHS |
| 4 | SH | 16: SH | 17: XSH | 18: YSH | 19: ZSH |
| 5 | HSH | 20: HSH | 21: XHSH | 22: YHSH | 23: ZHSH |
Frame is an integer in range(24); phase is an integer reduced modulo eight.
Booleans are rejected. There are 192 exact values. rep, pauli,
is_identity, @, inverse, and from_word retain their existing meanings.
clifford(*parts) accepts strings and Clifford values in execution order,
using I/X/Y/Z/H/S. Submodule constants retain the Clifford type; top-level
constants use the operator type. op(c) promotes a Clifford.
Products, inverses, and constructors reuse the small Clifford pool. Generated tables include phase corrections, signed Pauli actions, and exact H/S words. The generator uses rational Q(sqrt(2), i) arithmetic; runtime only imports data.
I use ordinary Python functions to express algorithmic choices and a single graph representation to inspect, compose and rewrite their implementations. Each input edge identifies the preceding vertex on its wire, making the direct dependency available from the source index.
The wiring follows the string diagrams of symmetric monoidal categories. Serial composition joins boundaries, and disjoint operations can be drawn side by side. The coherence theorem accounts for equalities arising from wiring. Gate identities supply additional equations for compiler passes. See Selinger, Theorem 3.12.
| Area | Status |
|---|---|
| Exact Clifford and canonical single-qubit Clifford+T algebra | Implemented, including composition, inversion, integer powers, and exact lowering |
| Open port graphs and composition by wire | Implemented |
| CZ and CCZ | Direct pair cancellation during construction and composition |
Contracts, named fragments, compact traversal such as zipWith |
Planned |
| Preparation, measurement, classical control | Deferred |
| Arbitrary-rotation synthesis, resource analysis, Cirq export | Planned |
The intended downstream vocabulary is single-qubit Cliffords, T, CZ, and CCZ. This project is responsible for decomposition to that vocabulary; downstream work will handle routing and magic-state factory selection. Canonical local operators keep equivalent implementations comparable without forcing every consumer to inspect a raw gate word.
The broader goal is to build algorithms against mathematical contracts and choose implementations separately. Interchangeable coherent and measured cleanup of a temporary AND remains a future milestone, after preparation and measurement are addressed.
Arbitrary-rotation synthesis will require an explicit approximation tolerance and phase guarantee. Its output should include an exact operator value and the approximation error. The minimum T-count of an isolated exact operator applies to that operator alone.
Measurement results will need explicit classical slots and inspectable branches. A temporary-AND cleanup contract must specify the valid input subspace and preserve coherence and entanglement on that subspace. Checking basis-state outputs alone cannot establish this. Future adjoint and coherent control transformations will have to distinguish unitary descriptions from implementations that use measurement and classical feedback.
Python 3.10 or later is required. There are no runtime or test dependencies beyond the standard library. From the project root:
python3.14 -m examples.basic
python3.14 -m unittest discover -v
python3.14 tools/generate_clifford_tables.py --checkThese commands use the available Python 3.14 interpreter. Another Python 3.10+
interpreter is suitable; on this Mac, bare python3 in the execution environment
currently selects Python 3.9 and cannot run the project.
An editable installation is optional, for importing from another project:
python3.14 -m pip install -e .The test suite covers the following cases.
- All 36,864 Clifford products and all 24-cubed frame associativity cases.
- Exact matrix uniqueness for all 8,832 canonical values through four T gates.
- Exact comparison of every H/S/T word through length six.
- Inverses and H/S/T decompositions for all 4,224 canonical values through three T gates.
- Signed-axis phase corrections, seeded random words and composition checks.
- Long single-wire runs, graph composition and validation, and CNOT/Toffoli recipes.
- Complete three-qubit matrices before and after construction and pair cancellation, including phases, parallel port connections, and newly exposed pairs.
The exact oracle constructs matrices independently of runtime tables and normalisation, reusing only the generator's scalar arithmetic. A separate numerical oracle uses conventional complex matrices. Bounded tests check regressions; the general uniqueness and T-optimality guarantees come from the normal-form theorem.
| Location | Purpose |
|---|---|
quantum_circuits/operators.py |
Central operator value, normalisation, construction, and lowering |
quantum_circuits/clifford.py |
Supporting Clifford algebra |
quantum_circuits/model.py |
Wires, graph values, and composition |
quantum_circuits/passes.py |
Compatibility entry point for explicit pair cancellation |
examples/basic.py |
Runnable examples |
tests/ |
Interface tests and independent exact/numerical matrix checks |
tools/generate_clifford_tables.py |
Reproducible exact table generation |
In the original Clifford-only API, Clif(c, q) applied a Clifford to a
named qubit. The current Clif(frame, phase) is the Clifford value itself.
Given a Clifford c, write Apply(op(c), q) to act on wire q. Replace
the original TGate(q) with Apply(T, q). Use clifford(...) to build a
Clifford from words and op(...) to construct a WireOp.