Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Noise and QEC

PRISM-Q models noise and quantum error correction without falling back to a dense statevector per shot. The machinery is the compiled samplers and the native QEC program IR; this guide shows how they fit together.

Noisy shot sampling

Attach a NoiseModel and sample:

#![allow(unused)]
fn main() {
use prism_q::{simulate, BackendKind, NoiseModel};

let noise = NoiseModel::uniform_depolarizing(&circuit, 0.001);
let result = simulate(&circuit)
    .backend(BackendKind::Statevector)
    .noise(noise)
    .seed(42)
    .shots(1024)
    .unwrap();
}

NoiseModel carries per-instruction depolarizing channels (NoiseOp { qubit, px, py, pz }) and supports readout error and amplitude damping. For Clifford circuits, the noisy compiled sampler propagates noise sensitivity rows and XORs fired channels into each sample, avoiding per-shot state evolution entirely.

Detector sampling

For repeated syndrome extraction, compile_detector_sampler compiles a Clifford circuit with measurement and reset reuse into a packed sampler, then derives detector and observable records as parity rows over the measurement record. Reset reuse becomes fresh qubit aliases, so there is no per-shot tableau replay.

Native QEC programs

When you need detectors, logical observables, postselection, and Pauli-noise annotations as first-class constructs, use the native QEC program IR rather than a Circuit:

#![allow(unused)]
fn main() {
use prism_q::{parse_qec_program, run_qec_program};

let program = parse_qec_program(qec_text).unwrap();
let result = run_qec_program(&program).unwrap();
}

run_qec_program lowers Clifford-compatible programs into the packed compiled sampler. run_qec_program_reference is the per-shot statevector oracle for validating small programs.

What QEC programs support

Clifford gates, basis resets and measurements, MPP Pauli-product measurements, detectors, observables, postselection, X_ERROR / Z_ERROR / DEPOLARIZE1 / DEPOLARIZE2 noise, and terminal EXP_VAL final-state expectation estimates (noiseless programs use the analytical T strategies, with any detector records still sampled by the packed runner; noisy programs use the per-shot reference runner). Non-Clifford gates are rejected on the packed sampling path. See the QEC IR reference for the full grammar, and QEC program execution for the runner routing, the V1 reset requirement, and the EXP_VAL placement rules.

Detector error model export

Matching and belief-propagation decoders consume an error model, not raw detector samples. QecProgram::detector_error_model derives one from the program's noise annotations, detectors, and observables, and to_text renders it in the common detector error model text format that external decoders read:

#![allow(unused)]
fn main() {
let model = program.detector_error_model().unwrap();
std::fs::write("memory_d3.dem", model.to_text()).unwrap();
}

Each mechanism carries a probability and the detector and observable indices it flips; detector coordinates pass through from the program. In Python the model also exposes probabilities(), detector_matrix(), and observable_matrix(), the check-matrix triple that in-process decoder libraries accept directly. Matching decoders need at most two detectors per mechanism: decompose_graphlike returns that form, splitting each hypergraph mechanism across existing graphlike ones and erroring loudly when no split exists. See QEC program execution for the derivation semantics and the emitted grammar.

Decoding

UnionFindDecoder decodes sampled detectors against a graphlike model in-process, so the logical error rate of a memory experiment never leaves the tool:

#![allow(unused)]
fn main() {
use prism_q::{UnionFindDecoder, run_qec_program};

let model = program.detector_error_model()?.decompose_graphlike()?;
let decoder = UnionFindDecoder::from_model(&model)?;
let result = run_qec_program(&program)?;
let predicted = decoder.decode_packed(&result.detectors)?;
let failures = (0..result.total_shots)
    .filter(|&shot| predicted.get_bit(shot, 0) != result.observables.get_bit(shot, 0))
    .count();
}

The decoder is weighted union-find with peeling: edges weigh ln((1-p)/p), one-detector mechanisms are boundary edges, and mechanisms flipping no detector bound the achievable logical error rate from below. Construction rejects hypergraph models with a pointer to decompose_graphlike. Decoding is deterministic and allocation-free per shot; large batches decode in parallel. See the decoding section of QEC program execution for the growth and peeling semantics and the validation against the exact ML rate.

Homological sampling

run_shots_homological and ErrorChainComplex model the GF(2) chain complex over noise locations, identifying undetectable error cycles. noisy_marginals_analytical computes marginals in closed form from the parity matrix and noise rates, with no Monte Carlo.