Clifford / Stabilizer Simulator

quantum_debugger.stabilizer.StabilizerSimulator is a second simulation engine for Clifford circuits (H, S, X, Y, Z, CNOT, CZ + measurement). Instead of a 2**n state vector it tracks the stabilizer group as a binary tableau (Aaronson-Gottesman CHP), updating in O(n) per gate and O(n^2) per measurement. Circuits of hundreds of qubits run instantly – far beyond the dense state-vector simulator.

from quantum_debugger.stabilizer import StabilizerSimulator

# 200-qubit GHZ state -- impossible for a state vector (2**200 amplitudes).
sim = StabilizerSimulator(200, seed=0)
sim.h(0)
for q in range(199):
    sim.cnot(0, q + 1)

outs = sim.measure_all()
assert len(set(outs)) == 1     # every qubit measures the same bit (GHZ correlation)

Inspecting the stabilizer group

stabilizers() returns the n generators as (sign, pauli_string) pairs, with pauli_string[q] in I/X/Y/Z acting on qubit q.

sim = StabilizerSimulator(2)
sim.h(0)
sim.cnot(0, 1)
sim.stabilizers()      # [(1, 'XX'), (1, 'ZZ')]  -- the Bell state's stabilizers

Measurement

measure(q) collapses the tableau and returns 0/1. Indeterminate outcomes are random (seeded); once a qubit is fixed, re-measuring returns the same value, and correlated qubits agree.

sim = StabilizerSimulator(3)
sim.h(0); sim.cnot(0, 1); sim.cnot(1, 2)   # 3-qubit GHZ
b = sim.measure(0)
assert sim.measure(0) == b                 # deterministic on repeat
assert sim.measure(2) == b                 # perfectly correlated

Graph / cluster states at scale

StabilizerSimulator.graph(n, edges) prepares a graph (cluster) state – Hadamard on every qubit, then CZ on each edge – in O(n + |edges|). Graph states of thousands of qubits are instant, with the canonical stabilizers X_i * prod_{j~i} Z_j.

sim = StabilizerSimulator.graph(3, [(0, 1), (1, 2)])   # linear cluster
sim.stabilizers()        # [(1, 'XZI'), (1, 'ZXZ'), (1, 'IZX')]

# A 1000-qubit ring cluster state, instantly:
ring = StabilizerSimulator.graph(1000, [(i, (i + 1) % 1000) for i in range(1000)])

Pauli expectation values

expectation_value(pauli_string) returns <psi|P|psi> for the current stabilizer state – exactly +1/-1 if P (or -P) is in the stabilizer group, else 0 (when P anticommutes with a stabilizer). No state vector required.

sim = StabilizerSimulator(2)
sim.h(0); sim.cnot(0, 1)          # Bell state
sim.expectation_value("XX")        # 1
sim.expectation_value("YY")        # -1
sim.expectation_value("XZ")        # 0

s_dagger(q) is also available (the inverse phase gate).

Entanglement entropy without a state vector

The entanglement entropy across any bipartition is computable directly from the binary tableau in O(n^3) — so it scales to the hundreds of qubits the engine handles, where a 2^n state vector is hopeless:

sim = StabilizerSimulator(200, seed=1)
sim.h(0)
for q in range(199):
    sim.cnot(0, q + 1)             # 200-qubit GHZ

sim.entanglement_entropy(range(100))   # 1.0 bit -- instant, no state vector

The result is S_A = rank_GF2(G_B) - |B| (Fattal et al.), where G_B is the stabilizer check matrix restricted to the complementary region — always an integer number of bits, and verified against the dense density-matrix entropy on random Clifford states.

When to use it

Use the stabilizer simulator for stabilizer codes, GHZ/graph/cluster states, randomized benchmarking sequences, and any large Clifford circuit. For circuits with non-Clifford gates (T, arbitrary rotations, controlled-phase at generic angles) use the state-vector simulator (QuantumCircuit) instead.

Correctness is verified against the state-vector simulator: for random Clifford circuits, every reported stabilizer S satisfies <psi|S|psi> = +1 to machine precision.