Repository navigation
Releases
stabilizer-qec 2.2.0
The coherent sampler sums the selected completed kernel in floating-point arithmetic when
its contraction plan fits the entry budget, where 2.1 summed each shot's whole class only
up to twelve. PyMatching's own sparse blossom, transliterated line for line, is now the matcher
behind PyMatching's API and behind Chromobius. tqec's compile pipeline, PyMatching's animate,
Tsim's ZX graphs, CUDA-Q QEC's dem_chunks and trt_decoder, the QDK's parameter classes and
report, Qualtran's Dash app, Fusion Blossom's Bottle server, and Tesseract's and Chromobius's
command lines are ported, each checked against what its original records or its own tests. What is
still not ported, and why, is at the end. The Python package and the Rust crate both at 2.2.0.
Behaviour changes are under Changed.
Exact class sums, by contraction. A coherent-sampler shot's weight sums the amplitudes of
every fault set no record tells apart from the shot's: its class, a coset of the circuit's
spacetime kernel, 2^k sets for k generators. 2.1 summed the whole class up to twelve generators
and truncated it past that. 2.2 writes the sum over c ∈ GF(2)^k as a product of small factors
(one for each group of generators that share locations, a phase and a sign for each generator,
and (−1)^(c_i c_j) for each pair whose operators anticommute: a quadratic form) and contracts it
one generator at a time along a min-fill elimination order, in O(k·2^w) a shot, w the
contraction width (CoherentSampler.contraction_width).
- When it is exact. Whenever the contraction's tables fit
compile_coherent_sampler(max_contraction=65536), table entries in all. 0 turns the
contraction off, a budget past 2^31 is 2^31, and no table spans 32 or more generators at any
budget. Past the budget, a kernel of at most twelve generators is still listed whole, as in
2.1; a larger one is truncated as 2.1 truncated it (exact_weightsis False), and the sampler
now says so with aUserWarning, where 2.1 was silent. Its entry lower bound is not a
guaranteed sufficient budget. The warning describes the chosen basis/elimination heuristic: "the circuit's K interference generators contract past max_contraction=B table
entries (their tables would hold N or more): ...; a max_contraction below N cannot sum the
whole class, and N or more may"; or, when no budget would, "... interact too widely to
contract at any max_contraction: their tables would hold more than 2^31 = 2147483648 entries
in all, the most a budget allows; ..."; or "the contraction is off (max_contraction=0), ...".
Unexpected internal refusal warns and asks for the circuit. Width is not optimized over
equivalent circuit representations. Successful planning can dominate compilation: audited
20/40-round crosstalk memories took 0.3060–0.3078 / 2.1947–2.2014 CPU seconds, versus 2.1.0's
0.00648–0.00812 / 0.02049–0.02076 (three runs, shared load; different exact/truncated work).
Sampling scratch per active worker is24*(arena+largest)bytes before factors/overhead
(arena=entries/2, at most36*entries), so the entry budget is not a byte limit. - Checked against the whole kernel listed (a random circuit for each k from 13 to 20, every
one with couplings between its generators, to 1e-10), against a sum over every one of the 2^k
products (eight random circuits for each k from 13 to 16, to 1e-11), and against the state
vector.
These are finite controls, not a proof over all numerical ranges. Contraction can change
last bits even at ≤12 generators; corrected proposal/group arithmetic can change seeded
outcomes withmax_contraction=0too. Historical sample-time ratios require remeasurement. - The joint sampler keeps 2.1's rule, the whole class up to twelve generators and the
truncation past that, whichexact_weights=Falsereports without a warning: its cross terms
are overlaps of the exact engine's states, not products of factors, somax_contractionhas
no effect on it. - Measurement provenance. All 8 validation and 28 capacity rows in
data/coherent/, and
the 6 d = 3 sweep and 4 d = 3 crosstalk rows, were remeasured with the 2.2.0 wheel and carry
generation_revision="coherent-merged-proposal-v1", which the current table and chart
generators require. This marker records provenance, separately fromcoherent.exactand
validation; it is never inferred from a version or added to an old row. Only the 12
larger-distance sweep and 5 crosstalk rows, measured by 1.8.0 before the inherited
merged-proposal correction, remain, as archive data excluded from the current tables; their
ratios, ESS/error-bar claims and distance-scaling conclusions are not current validation.
Earlier development-2.2 measurements remain in Git history and the preserved raw evidence.
Added
- PyMatching's sparse blossom, transliterated.
src/pm_exact/is PyMatching 2.4.0's
sparse_blossomline for line: its radix heap and the order it breaks ties in, its arenas'
reuse of freed slots, and its graph's neighbour order.Matching, PyMatching's class here
(sq.Matching,stabilizer_qec.pymatching.Matching, sinter'spymatchingdecoder), decodes
on it:decode,decode_batch,decode_to_edges_arrayanddecode_to_matched_dets_array
agree in the recorded plain-decoding corpus, ties and orders included. Checked shot by shot on
950,000 shots of 19 models (surface codes d = 3 to 7 at p = 0.001 to 0.02, colour-code
Möbius graphs, repetition codes, unit and negative weights) and 600,000 of 3,000 hand-built
graphs: 0 differences. It takes 1.00 to 1.04 times PyMatching's time (decode_batchwith
weights, d = 5 memory over 5 rounds, 100,000 shots, one thread). The tie tolerances in the
PyMatching, sinter, PanQEC, beliefmatching (min-sum BP) and folding comparisons are gone:
those finite comparisons are exact. Correlated facade decoding uses the separate matcher and
requires an unedited DEM/circuit loaded with correlations, with actual observable IDs 0–63;
ensured wider output is zero-padded. Edited or hand-built graphs refuse correlated decoding,
even where upstream accepts them. Two further divergences are deliberate. When a bit-packed
batch fails on a padding bit, PyMatching's later decodes raise "!mwpm.flooder.queue.empty()"
if the failing shot had earlier detection events at non-boundary nodes; this decoder refuses
the shot before it makes any region, and keeps decoding. A failed batch still names its shot
("shot k: " and PyMatching's message). And a region's radius offset is 64-bit: PyMatching
2.4.0'scompute_wrapped_radiusreturnsint32_t, so where a region's scaled radius passes
what that holds (a uniform chain of 131 or more nodes at weight 1.5 or 16,777,214.5) its decoder never
finishes, and this one decodes the path (tests/test_matching_radius.py). A fault id at or
past the decoder's count, which only areplaceorsmallest-weightmerge can put on an
edge (merges do not extend the count), is ignored when the count is 64 or fewer, as it is
above 64: PyMatching's1 << idis undefined there, and its arm64 wheel drops such ids eight
at a time and wraps the rest modulo 64, so no rule matches it, and this one never reports a
counted fault id for an uncounted one. - PyMatching's
animate:stabilizer-qec animate,python -m stabilizer_qec.pymatching animateandpymatching.cli(command_line_args=["animate", ...]), with PyMatching's flags,
messages and exit codes. Its frames are those of PyMatching 2.4.0's macOS arm64 wheel: all
1,080 frames of 11 recorded cases equal as sorted lines, the arithmetic fused as that wheel
fuses it on aarch64 (nothing is fused on x86-64), and all 44 recorded misuses give the
wheel's messages. Of 600 random command lines, 590 behave identically; the other 10 name a
model that does not parse (see the Tesseract entry). These recordings do not establish
general byte parity for NaN/overflow geometry or locale-dependent high-byte parsing. All
coordinate axes contribute to projection. Write/close failures produce controlled exit 1,
deliberately differing from observed native silent truncated success.--dets_inis read only to the first
shot's end, as C's stdio reads it, and a model's bytes past ASCII in comments and tags are
passed over, as Stim passes over them. Where PyMatching's program aborts (an--out_dirit
cannot make, a hit index past 64 bits),stabilizer-qec animateprintsstabilizer-qec animate: <message>and exits 1, andpymatching.cliraises the RuntimeError PyMatching's
does. Out-directory messages follow local host/library policies; exact probes below
cover macOS arm64, not universal native wording. MSVC's wording is not reproduced. Fresh exact ENAMETOOLONG/EACCES probes were macOS
arm64 only; a trailing diagnostic punctuation difference is retained. The six-digit halfway
formatter correction was checked on 20,374 finite f32 values on Apple libc, not all runtimes. - Chromobius on PyMatching 2.2.2's matcher, and its command line. The Möbius matching is now
pm_exactin the neighbour order of PyMatching 2.2.2, the PyMatching Chromobius 1.1.1 links.
The lifting gets PyMatching's edges in PyMatching's order. Recorded macOS predictions agree
on every shot of a 65,000-shot sweep (colour memories d = 3, 5 and 7
over 3 to 7 rounds, p = 0.001 to 0.01, X and Z; 240,000 shots in an extended run) and on all
340,000 shots oftools/decoder_check.py's colour cases (0 ties, at 0.72 times Chromobius's
time), with the weights equal as float32. This finite evidence does not ensure equal
observable choices on other inputs: tied lifting can differ at equal matching weight on
any platform, including macOS. Facade cases permit 95% agreement; the sweep requires 99%.python -m stabilizer_qec.chromobius predict|benchmark|describe_decoder|helpis Chromobius's program, with Stim's flag parsing
and record for...
stabilizer-qec 2.1.0
An audit of every port against its original's own documented examples, and the gaps it found,
closed. Each port was run on the examples, tutorials and command lines its original documents,
on Stim's own objects as well as this package's, and on Python 3.9 to 3.14 and Windows; what
differed or was missing is fixed below, and what is left is listed in the README and the
inventory (docs/superpowers/notes/2026-10-09-parity-inventory.md). The Python package and the
Rust crate both at 2.1.0. Behaviour changes are under Changed.
Added
- Stim.
search_for_undetectable_logical_errorsnow takes Stim's own steps, and finds Stim's
faults exactly: the same terms on 3,804 random models, the colour, surface and bivariate
bicycle codes and BPC72, and Stim's messages where both fail. It is also faster than Stim:
0.07 s on BPC72 against Stim's 0.25 s (2.0 took 8.1 s), and 0.6 s against 4.0 s on the d = 5
surface code at limits (6, 6). Limits of (2, 2) go to the graph-like search, as in Stim. The
CLI'sdiagramtakes--filter_coords, andhelp <topic>prints Stim's pages (gates,
formats, commands, indexes, markdown).matchgraph-svgdraws a model's undecomposed
hyperedges. - PyMatching. The CLI's
predictreads b8 by default, as PyMatching's does.
stabilizer_qec.pymatching.__version__is "2.4.0". - sinter.
mw_parity_factorandhypergraph_union_findrun on this package's MWPF. - Stim's objects in every port. The qec-util, qLDPC, MQT QECC, MWPF, Deltakit, sinter and
Relay-BP ports take Stim's ownCircuit,DetectorErrorModelandDemInstruction, read
exactly. Before, they raised errors, and sinter's Fusion Blossom and MWPF decoders silently
dropped every error of a Stim model.
MWPF'sHeraldedDetectorErrorModel.of,RefCircuit.ofandwith_circuittake them too, and
the Tesseract port gives Stim's models back where it was given Stim's. - stimbposd.
stabilizer_qec.stimbposdis stimbposd 0.2.0 whole:BPOSD,BPLSD, the
sinter decoders,sinter_decoders()(eight variants) and
detector_error_model_to_check_matrices, bit-identical on ldpc 2.4.1. Use it with
--custom_decoders_module_function stabilizer_qec.stimbposd:sinter_decoders. - Relay-BP.
ObservableDecoderRunner(parallel=True)andSinterDecoder_RelayBP(parallel=True)
decode on threads without the GIL: 4.5× serial on the gross code, where 2.0's parallel run
was slower than serial. Results are bit-identical to relay_bp. Withlogging=Truethe runner
and sinter decoders writerelay_logging.out.insert_uniform_academic_circuit_noisetakes
real Stim circuits withREPEAT. - ldpc.
BpOsdDecoderandBpLsdDecodertake ldpc'sschedule=and
random_schedule_seed=.lsd_methodandlsd_ordercan be set after construction,
OSD_OFFincluded.input_vector_type="received_vector"on BP+OSD, BP+LSD and BeliefFind
matches ldpc's decodings and iteration counts. ldpc's exceptions match in type and message,
and the v1 classes enforce Cython's argument types. - CUDA-Q QEC real-time decoding.
- Configuration:
decoder_config,multi_decoder_config, CUDA-Q's YAML (read without a new
dependency),configure_decoders(and_from_file,_from_str),finalize_decodersand
status codes. - Decoding:
enqueue_syndromes(id, syndromes, tag)andget_corrections(id, return_size, reset), sliding windows andstim_dem_path. The ticket API is kept. - Schemas:
decoder_param_schema,registered_decoder_schemas,validate_custom_args,
JSON Schema export, and the deprecated typed configuration classes. - Decoders under CUDA-Q's names: nv-qldpc-decoder, chromobius and nv-fusion-decoder. A
setting that isn't supported is refused by name. - Helpers:
dem_sampling(bit for bit with CUDA-Q's CPU sampler),relay_solutions,patch,
pcm_from_sparse_vecandgenerate_timelike_sparse_detector_matrix. KernelNotRunWarningfor Python@codewith registered kernels.
- Configuration:
- Tsim.
tsim.Circuitis a real subclass ofsq.Circuit. It has:- Tsim's
append(in half-turns),append_from_stim_program_text,from_file,+with Stim's
circuits and this package's; from_stim_program,stim_circuit,cast_to_stimandwithout_annotations;CompiledStateProbs, andto_matrixby Tsim's rules;- diagrams that label tagged gates, with Tsim's height, width and zoomable HTML wrapper.
- Tsim's
- tqec. tqec's call shape:
compile_block_graph(g, convention=...).generate_stim_circuit(k=..., noise_model=NoiseModel.uniform_depolarizing(p))at distance 2k + 1.NoiseModel
(uniform_depolarizing,si1000) gives tqec's noisy circuits instruction for instruction,
andBlockGraph.compiletakesk=andnoise_model=. Block graphs that reuse a tile at
different times now compile (two CNOTs in a row), and so do pass-through cubes drawn as
spatial. New:stabilizer_qec.tqec.simulation(start_simulation_using_sinter,
binary_search_threshold, plots, CSV) on this package's sinter, and
stabilizer_qec.tqec.interop.pyzx(PyZX graphs in and out,PositionedZX,block_synthesis,
Pauli webs; PyZX optional).sq.tqecloads on first use. - Loom. Loom's operations: transversal Hadamard, state injection, auxiliary CNOT, move
block, move corners, the Y-wall phase gate and rotate block. Its error models
(CircuitErrorModel,HomogeneousTimeIndependentCEM,HomogeneousTimeDependentCEM,
AsymmetricDepolarizeCEM) and their noise in the Stim export; the Mimiq and Guppy exporters.
The repetition, Shor, Steane and five-qubit code factories, with the repetition chain's grow,
shrink, merge and split. TheExecutorandTargetLanguage; the Mimiq, CUDA-Q and Guppy
run-outcome parsers;stim_polygonsand the crumble printer; Loom's module paths. Each is
checked against fixtures recorded from Loom 0.4.0. - MQT QECC.
- The analog-information simulators take
decoding_method="bposd"again. - The colour-code decoder runs as
stabilizer-qec mqt.qecc.cc-decoderor
python -m stabilizer_qec.mqt_qecc.cc_decoder. NoisyDFTStatePrepSimulatorruns on this package's qsample at fault-tolerant rates. CNOTs
are read as (control, target), decoding measures each stabilizer, and a wrong verification
order is refused. Upstream's simulator never ran.
- The analog-information simulators take
- PanQEC and qecsim.
stabilizer-qec panqec ...andstabilizer-qec qecsim ...run their
command lines; PanQEC's generated cluster scripts call them, andstabilizer-qec qecsim run
picks up qecsim extension packages' entry points. qecsim's Blossom V stand-in stays exact past
512 nodes by matching with Fusion Blossom: the 13×13run-ftpexample takes 40 s instead of
timing out. - PECOS.
SparseSim.print_stabs(print_destabs=, print_y=),copy,refactor,find_stab,
run_directand'G2'. - Tesseract. Its binding's behaviour: constructor overloads (
TesseractConfig(7)),
pybind11's enums, the binding's exception types, and output where Tesseract's goes
(ostream_redirect,Visualizer.write). The decoder's config is read at every decode, as
Tesseract's is, except the sparsify settings. - Chromobius. Faults of probability above 1/2 decode as Chromobius's negative-weight
edges, with its predictions and weights; probability 1 is refused, as Chromobius's weights are
NaN there. The sinter decoder takes any integer data, anddecode_via_filesreads short
files, partial records and other record widths as Chromobius does. - Deltakit.
deltakit.decodecarries Deltakit's cloud decoders' names. - Resource estimates.
- Azure: the report (
status, thejobParamsecho,physicalCountsFormatted,reportData),
format_metric_prefix,format_durationandLogicalCounts.estimate.
azure_estimatereturns anEstimatorResult; a frontier'sresult["physicalCounts"]is
its first entry's. - Azure:
EstimatorError, aValueErrorwith the QDK's.codeand.message, for every
refusal, in the QDK's order. Unreadable input raisesQSharpErrorwith serde's reason and
column. Both match the QDK on 286 recorded inputs. - Azure:
LogicalCounts({...})takes the QDK's dictionary. AqecSchemewithout a name is a
custom scheme. - Qualtran:
stabilizer_qec.estimate.qualtranis a package with Qualtran's fifteen
submodules, so Qualtran's module paths work. Its UI helpers run without Dash, plotly or
pandas, with figures as plotly dictionaries. They equal Qualtran 0.7.0 on 448 recorded
inputs.AlgorithmSummary.from_bloq,NoisyPauliRotation,storage_error,
GateCounts.to_legacy_t_complexityandTComplexityare also in.
- Azure: the report (
- The exact and stabilizer-rank samplers have a
.device. - qsample. Drawing and plotting raise an
ImportErrorthat names the missing optional
dependency.
Changed
- The coherent and joint samplers complete the circuit's spacetime kernel. One elimination
over the rotations finds what the local search misses: a logical error at one time with the
same logical at another, whose amplitudes add.- A d = 3 repetition memory with one over-rotation per round was read at the twirl's rate of
undetected logical errors: 0.00103 against the exact 0.00203, −44σ. It now reads
0.00206 ± 0.00005. - With at most twelve generators the coherent sampler sums the whole class, so its weights
are exact (exact_weights). Past that, a shot whose faults reach a non-local generator
sums its elements;num_nonlocal_generatorscounts those generators. compile_coherent_sampler(max_complete=50_000)bounds the completion. Past it the sampler
warns that interference between distant faults is left out, andkernel_completeis False.- Circuits with an over-rotation after every tick (the README's sweeps, d = 3 to 11) and the
distillation circuits already had complete local kernels. Their results are unchanged.
- A d = 3 repetition memory with one over-rotation per round was read at the twirl's rate of
- **ldpc'...
2.0.0 — parity with every other QEC tool, and Clifford+T under coherent noise
The Python package and the Rust crate both at 2.0.0.
Parity with the other QEC tools, and one thing none of them has. The features of
twenty-five other QEC tools — Stim, PyMatching, sinter, ldpc, Tsim, tqec, Loom, Deltakit, PECOS,
PanQEC, QUITS, qLDPC, qecsim, MQT QECC, Chromobius, Tesseract, Relay-BP, beliefmatching,
Fusion Blossom, MWPF, CUDA-Q QEC, qsample, qec-util, Qualtran's and Azure's resource estimators
— under the names those tools use, each port checked against its original (the inventory in
docs/superpowers/notes/2026-10-09-parity-inventory.md says what is here and what is not); and
Clifford+T circuits with a coherent error on every gate, sampled at sizes no state vector
reaches.
- Stim's objects and text.
str(circuit)is Stim's text exactly, instructions fuse as Stim
fuses them,Circuit.generated, diagrams character for character, any number of observables,
Crumble URLs,gate_data, the reference sample as Stim's. - PyMatching's graph API on the matcher; sinter whole without Stim; qec-util's and
deltakit's analysis (Λ fits, p_ij, error budgets); subset sampling (qsample). - Decoders. ldpc whole (BP, BP+OSD, BP+LSD, union-find, belief-find, flip), bit for bit;
Relay-BP, Tesseract, Chromobius, qLDPC, CUDA-Q QEC's decoders, MQT QECC, qecsim, bposd,
Fusion Blossom and MWPF ports; the clustering decoders; a CNN pre-decoder; every decoder of a
detector error model withdecode_async. - Codes and code tools. PECOS, PanQEC and QUITS whole; Deltakit whole on Deltakit's Stim;
tqec's block graphs and Loom's lattice surgery, compiled through this package's surgery
engine; QuantumClifford's canonical forms and entropies. - Noise and hardware. Density matrices and process matrices, amplitude and phase damping,
Quantumsim's perturbative expansion, leakage with heralds, Deltakit's leakage model exactly. - Clifford+T. A stabilizer-rank sampler for T gates and rotations at any qubit count
(checked against the state vector and against Tsim), Tsim's circuit surface, and the joint
coherent sampler: the non-Clifford gates exact, the coherent errors around them twirled and
reweighted. Magic-state injection and distillation under over-rotation on every gate, where
the twirl under-reads the encoded state's infidelity two to three times and turns a working
distillation into a broken one. - Resource estimates. Qualtran's surface-code model and Azure's Resource Estimator ported,
bit for bit on 100 recorded QDK cases. - Real time and the GPU. Decoding in the background, a decoder queue with latency
statistics, a decoder server over TCP or pipes, CUDA-Q QEC's host API; frame sampling and
min-sum BP on any GPU wgpu drives (Metal, Vulkan, DX12, in every wheel), each bit for bit its
CPU reference's. - The site edits circuits: section 17, Crumble's job in the page, markers propagated to the
detectors they flip, checked against the package and the engine.
Breaking changes (see the CHANGELOG's Changed): str(circuit) and the decoders' defaults
are now Stim's and ldpc's; the reference sample takes every random measurement as 0; Matching
is PyMatching's Matching. Everything deprecated in 1.x is gone.
Two adversarial reviews before tagging (fifteen independent reviews in all, the
CHANGELOG's review paragraph lists every fix): among them the 1.9 coherent sampler's reference
run, which complemented detection events on nondeterministic measurements; a first-order bias
in the joint sampler's truncation; Clifford-angle tagged gates that the frame engines read as
nothing; a lattice-surgery observable that cancelled across two seams; the GPU pacer, the
dropped-dispatch sentinel and the per-word generator; and the estimate port's formula
evaluator, now fasteval's grammar.
Nine tutorials, the technical report's 2.0 section, the API docs with the 2.0 subpackages, and
benchmarks re-recorded with every reference installed.
1.9.0 — beyond Pauli noise
The Python package and the Rust crate both at 1.9.0. API additions only.
Coherent errors at circuit level, at sizes a state vector cannot reach. Stim and every
decoder treat noise as random Pauli errors. Hardware also over-rotates, the same way every
time, and the usual stand-in for that, the Pauli twirl, adds probabilities where the rotations
add amplitudes. This release samples and decodes any circuit with coherent errors on every
gate, at sizes a state vector cannot reach.
Correction (2026-10-09): these notes first said no other tool does this. Tsim (QuEra, arXiv
2604.01059) samples circuits with T gates and rotations at 80+ qubits, at a cost exponential in
the number of non-Clifford gates, so it suits circuits with a few of them. What is new here is
coherent noise on every gate, thousands of small rotations, beyond a state vector.
- Stim-readable rotations. Write
I_ERROR[R_Z(theta=0.01)] 0,II_ERROR[R_ZZ(theta=0.02)] 0 1
orI_ERROR[R_PAULI(theta=…, pauli=XZY)] 0 1 2. Stim reads them as identities. - The coherent sampler (
Circuit.compile_coherent_sampler; RustCircuit::coherent_sampler).
It draws shots of the twirled circuit and gives each a weight, its class's coherent over
twirled probability. Weighted, the shots are the coherent circuit's.- Exact on every branch of 150 random small circuits.
- Within error bars of the state vector's exact answers, where the twirl is 5–10× off.
- What it finds. At circuit level the twirl underestimates the surface code's logical error,
and by more at larger distance. With a Z over-rotation of θ = 0.01 per tick, the coherent rate
is 2.4× the twirl's at d = 3 and 6.8× at d = 9; at d = 5 and θ = 0.02 it is 12×. At code
capacity the twirl is accurate, as Bravyi et al. found. - A state vector (
compile_exact_sampler,exact_distribution; up to 24 qubits). It also
runs T, U3 and amplitude damping. Circuit.twirled(merge=True), the coherence-aware model for a decoder.- Leakage:
I_ERROR[LEAK(p=…)],SEEPandLEAK_TRANSPORT, in a frame sampler with heralds
and in the state vector, which agree.
See the README's "Beyond Pauli noise", site section 16, tutorial 05 and the CHANGELOG.
1.8.0 — more decoders
The Python package and the Rust crate both at 1.8.0, "more decoders": four decoders the field
compares against, each ported from its authors' code and held to it shot for shot
(tools/decoder_check.py, run in CI). API additions only.
Added
- BP+LSD (
BpLsdon a model,BpLsdDecoderon a check matrix withldpc's arguments;
RustBpLsd,BpLsdDecoder,LsdOptions): belief propagation, then localized statistics
decoding (Hillmann et al., 2024), LSD-0, LSD-E and LSD-CS. Corrections equal toldpc
2.4.1's on every shot checked; the only differences possible are whereldpc's own order
is not reproducible (equal keys in a longstd::sort, or several clusters merged at once,
which it orders by hashed pointers), and those shots are reported as ties. Each cluster's
faults iterate inldpc'stsl::robin_setorder, which is reproduced. 0.6 ofldpc's time
on the gross code. - Relay-BP (
RelayBp,RelayBpDecoder; RustRelayBp,RelayBpDecoder,
RelayOptions): min-sum BP with memory, run in legs of disordered memory strengths (Müller
et al., IBM, 2025). Identical to IBM'srelay_bp0.2.2 on every shot (correction,
convergence and iteration count), for explicit strengths and for a seed: its random
strengths come fromrand0.8's generator, reproduced without the dependency. A model's
shot k draws from the seed and k, so results do not depend on the thread count. - Colour-code matching (
ColorMatching; RustColorMatching): Chromobius's Möbius
construction (Gidney and Jones, 2023) on this package's matcher. The Möbius matching weighs
the same as Chromobius 1.1.1's on every shot checked, and predictions differ only between
equally light matchings. 0.74 of Chromobius's time at d = 7. - A search decoder (
SearchDecoder; RustSearchDecoder,SearchOptions,
DetectorOrder): Tesseract's A* for the most likely error (Beni, Higgott and Shutty,
Google, 2025), for any model. The faults it finds are Tesseract's, fault for fault, on the
same platform (its ties are broken as the platform's C++ library breaks them). With no
beam, no queue bound and revisits allowed it is exact. - Colour annotations:
CssCode.memory_circuit(..., annotate_colors=True)(Rust
memory_circuit_with_colors) gives each detector Chromobius's 4th coordinate;
Circuit.generated("color_code:memory_xyz", ..., annotate_colors=True)(Rust
generated_with_colors) flattens Stim's colour code and annotates it as Chromobius's
authors do. - The four in
stabilizer_qec.sinter("bplsd","relay_bp","color_matching",
"search") and instabilizer-qec decode --decoder; fuzzed with Hypothesis and
fingerprinted; benchmarked against their references; tutorial 04 compares every decoder on a
colour code and the gross code.
The Rust crate stabilizer_qec 1.8.0 (tag crate-v1.8.0) has the same additions.
stabilizer-qec 1.7.0
The Python package and the Rust crate both at 1.7.0, "faster": error models in 0.4 to 0.6 of
Stim's time, measurement conversion in 0.1 to 0.25, the graph-like distance in 0.1 to 0.2 and
now as Stim's own answer, and matching at or below PyMatching's time on Apple silicon (about 1.1
times on Linux arm64 and 1.2 on x86_64), with every result unchanged and fingerprinted on every
platform. Benchmarks run on Linux arm64 as well as x86_64, and at 4 threads and every core.
Changed
shortest_graphlike_error, on circuits and on models, now runs Stim's own search (one
breadth-first search over pairs of detection events from every edge that flips an
observable) and so returns Stim's own faults, in Stim's order: its model text, and its
explained errors with and withoutcanonicalize_circuit_errors, equal Stim's character for
character (checked on Stim's generated codes, 300 random models and 100 random circuits;
1.6 matched Stim's length only). It takes 0.11 to 0.12 of Stim's time from d = 11 to 19,
where 1.6 took 0.71 to 1.82.- Error models are built in 0.6 of Stim's time on Apple silicon (1.6: 2.0): the walk no longer
allocates for every combination of a noise channel's errors, and the pass that looks ahead
for a loop's period skips noise, which it never collects. The models are unchanged (their
text is fingerprinted on each platform). - Measurements are converted to detection events 64 shots at a time, as Stim converts them
(each detector the XOR of its records' words, rows moved in and out by 64×64 bit
transposes): 0.24 of Stim's time on Apple silicon (1.6: 1.26), the output unchanged. - Matching is 14% faster plain and 12% correlated, every decision unchanged (the matcher's
fingerprints): a reminder for a node with nothing ahead is passed over without a scan while
nothing that could change that has happened, no scan at all when no region grows, the scan
reads one array with no selects, and reminders compare as integers. Against PyMatching,
plain matching now takes 0.98 of its time on Apple silicon, 1.08 on Linux arm64 and about
1.2 on Linux x86_64 (whose spare registers the loop exhausts); correlated matching 0.91 to
0.98, 0.85 and about 1.2. tools/bench.pyadds sampling and decoding at 4 threads and on every core (no reference:
Stim's sampler and PyMatching's batch decoding are single-threaded), and records the core
count.
Added
tools/fingerprints.pyandtests/test_fingerprints.py: a hash of 152 results (error models,
shots for a seed at any thread count, raw measurements, model samples, m2d, every decoder,
the distance searches), checked on Linux, macOS and Windows, so work on speed cannot change
a result unnoticed.examples/hot.rsruns each benchmark's job natively for a profiler;
benchmarks run on Linux arm64 as well as x86_64 (.github/workflows/bench.yml).
stabilizer-qec 1.6.0
The Python package and the Rust crate both at 1.6.0, "know your circuit": where each fault of
an error model comes from and how few faults defeat a circuit, both as Stim reports them, and
the exact circuit distance by integer programming; Stim's result formats, samplers and command
line; Stim's text timeline and time-slice diagrams; and a paper for the Journal of Open Source
Software. The first release archived on Zenodo.
Added
- The text timeline (
diagram("timeline-text"), and the command line's) is now Stim's,
character for character: Stim's moments, labels and boxes, loops between|columns headed
/REP nwith records, detectors and coordinates in terms ofiter, detectors drawn at their
qubit's coordinates. Checked on hand-made circuits pinning each rule, Stim's generated
memories and 200 random circuits using every gate. diagram("timeslice-svg")anddiagram("detslice-with-ops-svg"), andtickas arange
(withrows) for these anddetslice-svg: a panel per tick, each the qubits at their
coordinates with that tick's operations and/or the detector slice after them (Rust:
DiagramKind::TimeSliceSvg,DetectorSliceWithOpsSvg,DetectorSlicesSvg).Circuit.explain_detector_error_model_errors(*, dem_filter=None, reduce_to_one_representative_error=False)(Rust:Circuit::explain_errors): where each
fault of a circuit's error model comes from: the instruction (numbered as Stim numbers
them, adjacent identical instructions joined), its targets, the Pauli product or the
measurement it flips, theTICKs before it and the loop passes around it. The results
(ExplainedError,CircuitErrorLocationand their parts, Stim's names) print as Stim's
character for character, each part on its own as well (Rust:Displayfor
TargetWithCoordsandCircuitErrorLocation, andCircuitErrorLocation::instruction_text),
for every location and for the representative Stim picks, on
Stim's generated memories and on 1,000 random circuits using every gate, noise channel and
tag.shortest_graphlike_erroron circuits and on detector error models (Rust:
Circuit::shortest_graphlike_error,DetectorErrorModel::shortest_graphlike_error): the
graph-like circuit distance, the fewest faults of at most two detectors that together flip
an observable and no detector, found as Stim finds it (decomposed faults left out unless
ignore_ungraphlike_errors=False). Its length equals Stim's on Stim's generated surface,
repetition and colour codes up to d = 15. On a model it takes Stim's time at d = 11 (3.5 ms)
and 2.5 times it at d = 15;tools/bench.pyholds it to Stim's.search_for_undetectable_logical_errorson circuits and models: Stim's breadth-first
search through hyperedges, with its three limits, giving the same number of faults as
Stim's on its colour codes, surface codes, the Steane code and the [[72, 12, 6]] code.DetectorErrorModel.distance(): the exact distance of a model by integer programming (scipy's
HiGHS), hyperedges included, proven optimal or refused;method="graphlike"or"search"
for the two searches. It finds that Stim's generated XYZ colour-code memory has circuit
distance 2 at d = 3 and 3 at d = 5 (two or three correlated two-qubit faults; Stim's own
search agrees given room), and that the genericCssCodeschedule loses the colour code's
distance likewise, while the surface codes and the Hamming hypergraph product keep theirs.
tools/distances.pymeasures every memory the package builds.write_shot_data_fileandread_shot_data_file, Stim's functions with Stim's arguments, in
all six of its result formats (01,b8,r8,ptb64,hits,dets): files byte for
byte as Stim writes them, and Stim's read back to the same shots.- The command line,
stabilizer-qec(orpython -m stabilizer_qec), after Stim's:gen,
sample,detect,m2d,analyze_errors,explain_errors,diagram,convert,
sample_demandhelp, with Stim's flags and formats, anddecodeafter PyMatching's
predict(any of the package's decoders). Tested end to end against thestimcommand: the
same bytes fromgen(its header and qubit layout included),analyze_errors,m2d,
convert,explain_errors, and fromsampleanddetecton deterministic circuits. Circuit.reference_sample(): the noiseless run's measurement record, as Stim's.Circuit.compile_sampler(*, skip_reference_sample=False, seed=None): raw measurement
records, as Stim's measurement sampler: a noiseless reference run with each shot's flips,
equal to Stim's on deterministic circuits and to its rates otherwise.DetectorErrorModel.compile_sampler(seed=...)(Rust:DetectorErrorModel::sampler): shots
drawn from a model's faults directly, as Stim'sCompiledDemSampler, with the faults that
fired (return_errors) and recorded faults replayed (recorded_errors_to_replay); rates
and pairwise correlations agree with Stim's sampler, and a seed's shots do not depend on the
number of threads.detector_error_model(..., ignore_decomposition_failures=True)(Rust:
DemOptions::ignore_decomposition_failures), Stim's option: a fault that cannot be split
into graph-like pieces is kept whole. The text equals Stim's on its colour codes.- Documentation: the guide's "Knowing a circuit" and "The command line"; tutorial 01's "How
far from failing"; the README's and the report's "Circuit distance", every memory the package
builds measured three ways (tools/distances.py,data/distances.json), where the
integer program's best solution is kept as an upper bound when it does not finish. It finds
the 2-round [[90, 8, 10]] memory has circuit distance at most 9 (checked: 9 faults, no
detector, five logical qubits flipped). - A paper for the Journal of Open Source Software (
paper/, built by the Paper workflow) and
its submission checklist (docs/joss.md);CONTRIBUTING.mdsays where to ask for help.
Changed
- An explicit zero argument on
M,MX,MR,MRXandMPAD(M(0) 0) is kept and
written back, as Stim keeps it.
stabilizer-qec 1.5.0
The Python package and the Rust crate both at 1.5.0: Stim's generated circuits character for
character, every bivariate bicycle code of Bravyi et al.'s table and any CSS code from its
checks, tutorials, and benchmarks against Stim and PyMatching tracked in CI.
Added
Circuit.generated(code_task, *, distance, rounds, ...)(Rust:Circuit::generatedwith
GeneratedNoise): Stim's generated memory experiments, the repetition code, the rotated
and unrotated surface codes in either basis and the colour code's XYZ memory, with Stim's
four noise parameters. The text is Stim's character for character (every task at distances
2 to 7 and 25, one to seven rounds and 50, with and without each kind of noise), and the
arguments Stim refuses are refused.- Every bivariate bicycle code of Bravyi et al.'s Table 3:
BivariateBicycleCode("90")
([[90, 8, 10]]),"108"([[108, 8, 10]]) and"288"([[288, 12, 18]]) beside"72"and
"144"/"gross"(Rust:bb90,bb108,bb288,named), and any other from its
polynomials,BivariateBicycleCode.from_polynomials(l, m, a, b)(Rust:from_polynomials),
checked for weight-six checks and at least one logical qubit. Each comes with the paper's
depth-8 syndrome cycle, its memories in both bases, its logicals and its automorphisms; a
noiseless memory of each is checked to fire no detector.polynomialsgives a code's torus
and monomials. CssCode(Python and Rust): any CSS code from its check matrices, checked to commute and
to encode a qubit, with paired logical operators and a memory experiment correct for any
such code (every X check measured, then every Z check, in layers no qubit is used twice in;
rounds as a loop). Two families come built:CssCode.hypergraph_product(h1, h2)of any two
classical codes (the Hamming code's with itself is [[58, 16, 3]], two repetition codes'
the unrotated surface code), andCssCode.color_code(d), the triangular 6.6.6 colour code
on Stim's layout. Their memories' error models equal Stim's character for character, and
decode withBpOsd.- Tutorials: three notebooks in
tutorials/(getting started, from a circuit to a threshold
plot; decoders and real-time decoding; codes beyond the surface code), each built from a
plain Python file bytools/notebooks.py, and run on every push. - Benchmarks against Stim and PyMatching (
tools/bench.py): error models, sampling,
measurement conversion, matching and correlated matching, each as a ratio to the
reference's time on the same machine and thread. Runs are recorded in
data/bench/runs.jsonand shown on the site's new benchmarks page; every push is checked
against the last run recorded on the same kind of machine. - Zenodo metadata (
.zenodo.json) and the steps to link the repository
(docs/zenodo.md), so each release can be archived with a DOI; a Citing section in the
README.
Changed
- Instruction arguments are written as Stim writes them (
1e-05, not0.00001) whenever
Stim's six significant digits hold the value exactly; a value they would round is still
written in full, so circuit text never loses precision.
The Rust crate
stabilizer_qec 1.5.0 on crates.io, a minor release (cargo-semver-checks: no breaking
change): Circuit::generated with GeneratedNoise, BivariateBicycleCode::{bb90, bb108, bb288, named, from_polynomials}, and CssCode (new, hypergraph_product, color_code,
memory_circuit). cargo add stabilizer_qec · docs.rs
· crates.io
stabilizer-qec 1.4.0
The Python package and the Rust crate both at 1.4.0: window decoding of models too long to
unroll, streaming any circuit with a loop, and circuit diagrams after Stim's.
Added
- Window decoding of models too long to unroll.
WindowMatchingtakes its windows' graphs
from a short template of the model's longest loop, whose middle windows serve every window
away from the model's ends, shifted by whole passes of the loop; shots are fed in layer by
layer. A d = 11 rotated memory of 10,000 rounds (27.6 million faults) builds its model in
0.2 s and its decoder in about a second, and decodes from the folded model. It is the
default for a model of more than a million faults with a loop;template=True(Rust:
WindowOptions::with_template) asks for it,template=Falsefor the whole model, and both
give the same predictions (tested on Stim's rotated, unrotated and repetition memories,
sliding and parallel, with and without correlations).WindowMatching.streamedsays which. stream_memory(circuit=...)(Rust:stream_circuit): any circuit with a loop, sampled
round by round and window-decoded as it streams, its windows from a template of its folded
model. The template's middle spans a whole number of window periods that is also a whole
number of the loop's passes, so loops Stim folds two rounds at a time are served.- Diagrams, after Stim's
diagram:Circuit.diagram(type, tick=...)(Rust:
Circuit::diagram(DiagramKind)) draws"timeline-text"and"timeline-svg", every
operation in its column withTICKgroups bracketed, loops drawn once with their count and
measurements numbered;"detslice-text"and"detslice-svg", what each detector compares
aftertickTICKs, its Paulis drawn as shapes over the qubits at their coordinates (the
same slices as Stim's at every tick of its generated surface, colour and repetition
codes); and"matchgraph-svg", a decomposed model's matching graph
(DetectorErrorModel.diagram). The result is aDiagram: it prints as its text, shows as
a picture in a notebook, andsaves to a file. The pictures follow the reader's light or
dark theme.
stabilizer-qec 1.3.0
The Python package and the Rust crate both at 1.3.0: error models whose text is Stim's,
character for character; a union-find decoder; and a measurement converter that never unrolls.
Added
-
UnionFind(Python and Rust), weighted union-find decoding (Delfosse and Nickerson, with
Huang, Newman and Brown's weighted growth) on the matching graph, and"union_find"in the
sinter adapter. Its corrections always explain the detection events, every single fault is
corrected, and its logical error rate is a little above matching's (d = 9 at p = 0.005:
0.70% against 0.65%). It is the standard baseline rather than the fast option here: this
engine's matcher, near-linear too, is about twice as fast at every noise strength measured. -
The measurement converter no longer unrolls a circuit: its reference is one tableau run
through the loops as written, determinism and the sweep bits' effects come from the error
model's backward walk (folding loops; seven extra random runs, and one per sweep bit, are
gone), and shots are read by a program of the circuit's measurements and detectors with its
loops kept. A memory of millions of rounds converts; the 2²⁴-instruction limit is gone.
Fixed
- Building an error model, or a converter, for a circuit whose walk never settles into a
period (a qubit read every round and never reset) is refused once its work passes the limit,
rather than running for hours: the limit now counts the sensitivity sets merged, not only
the instructions.
Changed
- Error models print as Stim prints them, character for character (515 of 515 random,
disjoint, tagged, Pauli-observable and looped circuits, and Stim's generated memories). Fault
classes are Stim's: a fault's pieces in the order they arose, so a fault Stim lists once per
order is listed so here too, andnum_errorsequals Stim's. Probabilities are accumulated in
Stim's order and rounded as Stim's build for the machine rounds them (its ARM64 build fuses a
multiply into an add), and printed to Stim's 16 significant digits. PAULI_CHANNEL_1is converted to independent errors as Stim converts it: with the cases
relabelled so that identity is the likeliest, and by Newton's method where no exact solution
exists, so channels Stim treats as independent need noapproximate_disjoint_errorshere
either. Disjoint cases that cannot be told apart are merged in Stim's order.