Generalized Stabilizer Tableau
Overview
GeneralizedTableau simulates quantum
circuits in the Schrodinger picture using a generalized stabilizer decomposition.
Clifford operations are tracked efficiently in the stabilizer tableau. Non-Clifford gates (T gates, arbitrary rotations) expand the state into a superposition of stabilizer states, tracked via a sparse coefficient vector. The cost scales exponentially only in the number of non-Clifford gates, making it well-suited for circuits with few T gates and many Clifford operations.
Basic usage
from ppvm import GeneralizedTableau
tab = GeneralizedTableau(n_qubits=2)
tab.h(0)
tab.cnot(0, 1)
r0 = tab.measure(0)
r1 = tab.measure(1)
print(f"Qubit 0: {r0}, Qubit 1: {r1}") # always correlated (Bell state)
Forking for multiple shots
Measurement mutates the tableau, so running multiple shots requires creating
independent copies. Use fork() to clone the quantum state with a fresh RNG:
tab = GeneralizedTableau(n_qubits=2, seed=42)
tab.h(0)
tab.cnot(0, 1)
for shot in range(100):
t = tab.fork(seed=shot)
print(t.measure(0), t.measure(1))
To preserve the RNG state exactly (e.g. for checkpointing), use copy.copy() instead.
STIM circuit support
Circuits in STIM format can be executed
directly via run_stim_string or run_stim_file. Only the gate/measurement
subset of STIM is supported; control flow instructions are not.
from ppvm import GeneralizedTableau
tab = GeneralizedTableau(n_qubits=2)
stim_circuit = """
H 0
CX 0 1
M 0 1
"""
results = tab.run_stim_string(stim_circuit)
print(f"Bell state measurement: {results}")
For larger circuits stored as .stim files, use run_stim_file:
tab = GeneralizedTableau(n_qubits=85)
results = tab.run_stim_file("path/to/circuit.stim")
Noise and loss
GeneralizedTableau supports the same noise channels as PauliSum:
- Depolarizing:
depolarize(addr, p)anddepolarize2(addr0, addr1, p). - Pauli error:
pauli_error(addr, [p_x, p_y, p_z]). - Loss:
loss_channel(addr, p)andcorrelated_loss_channel(addr0, addr1, p).
When a qubit is lost, subsequent measurement returns
MeasurementResult.LOST
instead of ZERO or ONE. You can check and reset loss state:
tab = GeneralizedTableau(n_qubits=1, seed=0)
tab.loss_channel(0, 1.0) # deterministic loss
print(tab.is_lost(0)) # True
print(tab.measure(0)) # MeasurementResult.LOST
tab.reset_loss_channel(0)
print(tab.is_lost(0)) # False
Coefficient pruning
Each non-Clifford gate doubles the number of terms in the internal coefficient vector.
The min_abs_coeff parameter controls pruning of small coefficients:
tab = GeneralizedTableau(n_qubits=4, min_abs_coeff=1e-8)