Overview#

This section describes the basic working principle of the cuStabilizer library. For a general introduction to quantum circuits, please refer to Introduction to quantum computing.

Introduction to Pauli frame simulation#

Trajectory-based noise simulation#

A common approach to model the effect of noise on quantum computation is a trajectory-based simulation. For any quantum channel that consists of \(k\) Kraus operators, this approach considers \(k\) different trajectories - each corresponding to a different circuit where the channel is replaced by a corresponding Kraus operator. Each additional noise channel multiplies the number of possible trajectories by \(k\), thus for a large number of channels, a simulation cannot run every trajectory. Instead, the approach is to create an ensemble of \(K\) trajectories where each trajectory is added with probability corresponding to the probability of the Kraus operator.

Each circuit in the ensemble is then simulated using conventional noise-free methods and the outputs of each simulation are aggregated to form the final output. This is equivalent to running a regular circuit simulation with a probabilistic gate application at the locations of the noise channels.

Pauli frame simulation#

Pauli frame simulation applies the same principle as the trajectory-based simulation, but instead of simulating a quantum state, it tracks the effect of the noisy gates.

For example, in a trajectory with a noisy “X” gate on qubit 0, a traditional trajectory simulation would apply the gate to a statevector and proceed with other gates in the circuit. In Pauli frame simulation, the simulator records X0 in a Pauli frame - a string of Pauli operators. Then, the subsequent gates \(G_i\) are applied by conjugating the frame: \(P_{t+1} = G_i(P_t) = G_i^\dagger P_t G_i\). This simulation requires \(G_i\) to produce a Pauli string under conjugation, which is why it only supports Clifford gates.

Pauli frame simulation only tracks the difference between noise-free and noisy simulations, making it particularly useful for simulating circuits with a known noise-free state, such as error correction circuits that aim to preserve the all-zero state.

In the Pauli frame simulation, each frame is independent of the others, and thus can be simulated in parallel, which is the approach used by cuStabilizer.

To watch a Pauli frame evolve under a specific circuit, see the interactive frame simulator.

Leakage simulation#

A leakage noise event cannot be represented using Pauli errors alone, because the affected qubit has left the computational space. cuStabilizer extends the frame with a leakage flag, approximating the leakage effect by randomizing the qubit’s frame via a scramble operation: a maximally-depolarizing channel conditioned on the leakage flag. Entangling operations between a leaked and non-leaked qubit can propagate decoherence and leakage to the partner. Leaked qubits can be detected by heralding the leakage events. This approach is similar to an approximation commonly referred to in the literature as erasure error. cuStabilizer provides leakage instructions for this kind of simulation.

The instruction set is relatively low-level as it allows to create circuits that do not represent a correct modelling of a physical process. For example, omitting the scramble after a leakage mark leaves that qubit’s frame at its pre-leak, deterministic value; cuStabilizer does not reject or flag such a circuit.

The frame simulation approach cannot be readily used, however, to simulate other models such as ones where an non-Pauli Clifford gate is skipped if applied to a leaked qubit; each Pauli frame represents a difference from a noise-free sample, and thus can only represent a Pauli-operator difference. Such models are needed to represent effects like atom loss. One can instead use approaches such as the Pauli envelope framework to approximate this process; see Liu et al., Achieving Optimal-Distance Atom-Loss Correction via Pauli Envelope.

See leakage simulation API for the Python LeakageFrameSimulator class.

Main data structures#

Bit tables#

A common encoding for Pauli operators is using two bits:

Pauli Operator

Encoding (x_bit, z_bit)

\(I\)

(\(0\), \(0\))

\(X\)

(\(1\), \(0\))

\(Z\)

(\(0\), \(1\))

\(Y\)

(\(1\), \(1\))

A single Pauli string on \(N\) qubits would be represented by \(2N\) bits.

To specify \(K\) Pauli frames on \(N\) qubits, we use two bit tables - matrixes of bits of size \(N \times K\): one for X bits and one for Z bits.

Overall the Pauli frame simulation acts on following data structures:

  1. Bit table for X bits - size \(N \times K\)

  2. Bit table for Z bits - size \(N \times K\)

  3. Bit table for measurement outcomes - size \(M \times K\) for \(M\) measurement instructions

  4. Bit table for leakage flags - size \(N \times K\) (leakage simulator only)

Please refer to custabilizerFrameSimulatorApplyCircuit() and custabilizerLeakageFrameSimulatorApplyCircuit() for more details on how to specify the tables.

Circuit#

cuStabilizer uses a circuit specification format similar to Stim.

  • A Circuit is a sequence of Instructions.

  • A circuit Instruction can be of different types. A simple gate instruction has a list of arguments and gate targets, while a REPEAT instruction points to another Circuit to repeat.

  • Instruction targets may refer to qubits or measurement outcomes, depending on the instruction type.

For example, the following circuit:

H 0 1 15
X_ERROR(0.01) 1 2
REPEAT 2 {
    CNOT 2 3 4 5
    DEPOLARIZE1(0.001) 4
    MX(0.1) 5 6 7
}

Is a valid circuit with

  • 16 qubits

  • 6 measurements with outcome noise probability 0.1 (3 targets × 2 REPEAT iterations)

  • 4 CNOT gates

  • 3 H gates

  • 4 noisy gates

See this circuit in the interactive frame simulator.

While cuStabilizer generally will accept any practical Stim circuit, there are important restrictions that may not be respected by a valid Stim circuit.

  1. Instruction targets must not overlap. E.g. instruction CX 0 1 2 4 is valid, but CX 0 1 1 2 is not.

  2. The depth of nested REPEAT instructions must not exceed 3.

Please refer to the Stim gate reference for more details on the supported instructions.

Supported circuit instructions#

  1. Measurement and error detection instructions:

    1. M - Measure on Z basis

    2. MX - Measure on X basis

    3. MY - Measure on Y basis

    4. MR - Measure on Z basis and reset

    5. MRX - Measure on X basis and reset

    6. MRY - Measure on Y basis and reset

    7. RZ - Reset on Z basis

    8. RX - Reset on X basis

    9. RY - Reset on Y basis

    10. DETECTOR - Detector instruction

    11. HERALD_LEAKAGE_EVENT - Leakage-flag readout (leakage simulator only).

  2. Clifford gates:

    1. H / H_XZ - Hadamard

    2. ZCX / CNOT - Controlled-X gate. Must have an even number of targets.

    3. CZ - Controlled-Z gate. Must have an even number of targets.

  3. Noise channels:

    1. PAULI_CHANNEL_1(p_x, p_y, p_z) - Pauli channel 1 with probabilities p_x, p_y, p_z

    2. PAULI_CHANNEL_2(p_x, ..., p_zz) - Pauli channel 2 with probabilities p_x, …, p_zz. Must have 15 arguments and an even number of targets.

    3. DEPOLARIZE1(p) - Depolarize 1 with probability p

    4. DEPOLARIZE2(p) - Depolarize 2 with probability p. Must have an even number of targets.

    5. X_ERROR(p) - X error with probability p

    6. Y_ERROR(p) - Y error with probability p

    7. Z_ERROR(p) - Z error with probability p

  4. Control flow instructions:

    1. REPEAT n {...} - Repeat a circuit n times

    2. TICK - No operation.

  5. Shot masking instructions:

    1. MASK_SET(start, end) / SHOT_MASK_SET(start, end) - Set the active shot mask to the half-open interval [start, end).

    2. MASK_XOR(start, end) / SHOT_MASK_XOR(start, end) - Toggle the active shot mask for the half-open interval [start, end).

  1. Leakage instructions (require custabilizerLeakageFrameSimulatorApplyCircuit()):

    Leakage state is tracked in a per-qubit leakage bit table. Instructions fall into four families by their invariant:

    • Mark — change leakage flags only, X/Z frame untouched: LEAKAGE_MARK[1,2], LEAKAGE_PROPAGATE_MARK, LEAKAGE_RESET, LEAKAGE_RELAX.

    • Scramble — leakage-conditioned Pauli-frame noise, leakage flags untouched: LEAKAGE_SCRAMBLE, LEAKAGE_SCRAMBLE_PARTNER, LEAKAGE_PAULI[1,2].

    • Combined — mark and scramble newly leaked shots in one step: LEAKAGE[1,2], LEAKAGE_PROPAGATE.

    • Readout — HERALD_LEAKAGE_EVENT copies leakage flags into the measurement table.

    1. LEAKAGE_MARK1(p) - Set each target’s leakage flag with probability p.

    2. LEAKAGE_MARK2(p_ll, p_li, p_il) - Two-qubit leakage mark over target pairs (q0, q1). Mutually exclusive rates: p_ll leaks both, p_li leaks only q0, p_il leaks only q1. Must have an even number of targets.

    3. LEAKAGE1(p) - Same as LEAKAGE_MARK1(p), then apply a uniformly random Pauli from {I, X, Y, Z} on each newly leaked shot (XOR the qubit’s X and Z bits with independent Bernoulli(0.5) samples).

    4. LEAKAGE2(p_ll, p_li, p_il) - Same as LEAKAGE_MARK2, then apply a uniformly random Pauli from {I, X, Y, Z} on each newly leaked shot of the affected qubits. Must have an even number of targets.

    5. LEAKAGE_PROPAGATE(p_spread_01, p_spread_10, p_move_01, p_move_10) - For each target pair (q0, q1): first apply a uniformly random Pauli from {I, X, Y, Z} on the partner of every already leaked qubit, then try to propagate leakage. Eligible shots have the source leaked and the partner unleaked. Per direction, p_spread and p_move are mutually exclusive (p_spread + p_move <= 1): spread leaks the destination and keeps the source; move leaks the destination and clears the source. Indices 01 / 10 are q0→q1 / q1→q0. Must have an even number of targets.

    6. LEAKAGE_PROPAGATE_MARK(p_spread_01, p_spread_10, p_move_01, p_move_10) - Same propagation rates and eligibility as LEAKAGE_PROPAGATE, but without applying random Paulis to partners. Must have an even number of targets.

    7. LEAKAGE_SCRAMBLE - On each target, for shots where that target is leaked, apply a uniformly random Pauli from {I, X, Y, Z} (XOR the qubit’s X and Z bits with independent Bernoulli(0.5) samples).

    8. LEAKAGE_SCRAMBLE_PARTNER - For each target pair (q0, q1), apply a uniformly random Pauli on q1 for shots where q0 is leaked, and on q0 for shots where q1 is leaked. Must have an even number of targets.

    9. LEAKAGE_PAULI1(p_x0, p_y0, p_z0, p_x1, p_y1, p_z1) - One-qubit Pauli channel conditioned on the leakage bit: unleaked shots use (p_x0, p_y0, p_z0); leaked shots use (p_x1, p_y1, p_z1). Argument order matches PAULI_CHANNEL_1.

    10. LEAKAGE_PAULI2(...) - Two-qubit Pauli channel conditioned on the four leakage states of each target pair (q0, q1). Must have 60 arguments (four blocks of 15) and an even number of targets. Block order by (l0, l1) is (0,0), (0,1), (1,0), (1,1). Within each block, probabilities follow the same order as PAULI_CHANNEL_2: IX, IY, IZ, XI, XX, XY, XZ, YI, YX, YY, YZ, ZI, ZX, ZY, ZZ.

    11. LEAKAGE_RESET - Clear leakage flags on the targets.

    12. LEAKAGE_RELAX(p) - Clear each target’s leakage flag with probability p.

    13. HERALD_LEAKAGE_EVENT / HERALD_LEAKAGE_EVENT(p) / HERALD_LEAKAGE_EVENT(p_fp, p_fn) - Copy each target’s leakage flag into the next measurement-table row. With one argument, flip each herald bit with probability p. With two arguments, use p_fp on unleaked shots (false positive) and p_fn on leaked shots (false negative).

Introduction to DEM sampling#

Detector Error Model#

A Detector Error Model (DEM) is a compact description of how independent error mechanisms in a quantum error correction circuit affect detection events (syndromes). A DEM is typically derived from a noisy circuit (e.g. via stim.Circuit.detector_error_model()) and consists of:

  • A list of \(E\) error mechanisms, each with a probability \(p_e\).

  • For each error mechanism, the set of detectors it flips.

This information can be represented as a probability vector \(\mathbf{p} \in [0,1]^{E}\) and a binary matrix \(B \in \mathrm{GF}(2)^{E \times D}\) where \(D\) is the number of detectors and \(B_{e,d} = 1\) iff error \(e\) flips detector \(d\).

DEM sampling algorithm#

To generate detection events for \(K\) shots:

  1. Sample errors – for each error mechanism \(e\) and each shot \(k\), draw an independent Bernoulli(\(p_e\)) random variable. This produces a binary matrix \(S \in \mathrm{GF}(2)^{K \times E}\).

  2. Compute detector outcomes – multiply over GF(2): \(C = S \otimes B\), where \(\otimes\) denotes XOR-based matrix multiplication. The result \(C \in \mathrm{GF}(2)^{K \times D}\) contains the detection events.

cuStabilizer executes this pipeline on the GPU using sparse sampling and sparse GF(2) matrix multiplication, offering significant speedups for large shot counts.

When error probabilities are low, the error matrix \(S\) is sparse. cuStabilizer exploits this by sampling into a CSR representation and using sparse GF(2) matrix multiplication, which is more efficient than a dense approach.

Useful tips#

Environment variables for logging#

cuStabilizer uses the following environment variables to control C library logging:

  • CUSTABILIZER_LOG_LEVEL: Set the logging level verbosity. Supports the following values:

    • 0 - Off (no logging)

    • 1 - Error

    • 2 - Trace

    • 3 - Hint

    • 4 - Info

    • 5 - Debug

  • CUSTABILIZER_LOG_MASK: Control which categories of messages are logged.

  • CUSTABILIZER_LOG_FILE: Redirect log output to a file at the specified path instead of standard output.

Example usage:

export CUSTABILIZER_LOG_LEVEL=5
export CUSTABILIZER_LOG_FILE=custabilizer_debug.log

./my_application

These environment variables control logging from the cuStabilizer C library. Python users should also see Stabilizer APIs for Python-level logging options.

Citing cuQuantum#

  • H. Bayraktar et al., “cuQuantum SDK: A High-Performance Library for Accelerating Quantum Science,” 2023 IEEE International Conference on Quantum Computing and Engineering (QCE), Bellevue, WA, USA, 2023, pp. 1050-1061, doi: 10.1109/QCE57702.2023.00119.