Skip to content

Releases

stabilizer-qec 2.2.0

Choose a tag to compare

@Jaspersands Jaspersands released this 30 Oct 05:16

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_weights is False), and the sampler
    now says so with a UserWarning, 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 is 24*(arena+largest) bytes before factors/overhead
    (arena=entries/2, at most 36*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 with max_contraction=0 too. 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, which exact_weights=False reports without a warning: its cross terms
    are overlaps of the exact engine's states, not products of factors, so max_contraction has
    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 from coherent.exact and
    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_blossom line 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's pymatching decoder), decodes
    on it: decode, decode_batch, decode_to_edges_array and decode_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_batch with
    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's compute_wrapped_radius returns int32_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 a replace or smallest-weight merge 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's 1 << id is 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 animate and pymatching.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_in is 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_dir it
    cannot make, a hit index past 64 bits), stabilizer-qec animate prints stabilizer-qec animate: <message> and exits 1, and pymatching.cli raises 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_exact in 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 of tools/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|help is Chromobius's program, with Stim's flag parsing
    and record for...
Read more

stabilizer-qec 2.1.0

Choose a tag to compare

@Jaspersands Jaspersands released this 20 Oct 06:57

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_errors now 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's diagram takes --filter_coords, and help <topic> prints Stim's pages (gates,
    formats, commands, indexes, markdown). matchgraph-svg draws a model's undecomposed
    hyperedges.
  • PyMatching. The CLI's predict reads b8 by default, as PyMatching's does.
    stabilizer_qec.pymatching.__version__ is "2.4.0".
  • sinter. mw_parity_factor and hypergraph_union_find run 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 own Circuit, DetectorErrorModel and DemInstruction, read
    exactly. Before, they raised errors, and sinter's Fusion Blossom and MWPF decoders silently
    dropped every error of a Stim model.
    MWPF's HeraldedDetectorErrorModel.of, RefCircuit.of and with_circuit take them too, and
    the Tesseract port gives Stim's models back where it was given Stim's.
  • stimbposd. stabilizer_qec.stimbposd is 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) and SinterDecoder_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. With logging=True the runner
    and sinter decoders write relay_logging.out. insert_uniform_academic_circuit_noise takes
    real Stim circuits with REPEAT.
  • ldpc. BpOsdDecoder and BpLsdDecoder take ldpc's schedule= and
    random_schedule_seed=. lsd_method and lsd_order can be set after construction,
    OSD_OFF included. 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_decoders and
      status codes.
    • Decoding: enqueue_syndromes(id, syndromes, tag) and get_corrections(id, return_size, reset), sliding windows and stim_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_vec and generate_timelike_sparse_detector_matrix.
    • KernelNotRunWarning for Python @code with registered kernels.
  • Tsim. tsim.Circuit is a real subclass of sq.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_stim and without_annotations;
    • CompiledStateProbs, and to_matrix by Tsim's rules;
    • diagrams that label tagged gates, with Tsim's height, width and zoomable HTML wrapper.
  • 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,
    and BlockGraph.compile takes k= and noise_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.tqec loads 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. The Executor and TargetLanguage; the Mimiq, CUDA-Q and Guppy
    run-outcome parsers; stim_polygons and 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-decoder or
      python -m stabilizer_qec.mqt_qecc.cc_decoder.
    • NoisyDFTStatePrepSimulator runs 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.
  • PanQEC and qecsim. stabilizer-qec panqec ... and stabilizer-qec qecsim ... run their
    command lines; PanQEC's generated cluster scripts call them, and stabilizer-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×13 run-ftp example takes 40 s instead of
    timing out.
  • PECOS. SparseSim.print_stabs(print_destabs=, print_y=), copy, refactor, find_stab,
    run_direct and '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, and decode_via_files reads short
    files, partial records and other record widths as Chromobius does.
  • Deltakit. deltakit.decode carries Deltakit's cloud decoders' names.
  • Resource estimates.
    • Azure: the report (status, the jobParams echo, physicalCountsFormatted, reportData),
      format_metric_prefix, format_duration and LogicalCounts.estimate.
      azure_estimate returns an EstimatorResult; a frontier's result["physicalCounts"] is
      its first entry's.
    • Azure: EstimatorError, a ValueError with the QDK's .code and .message, for every
      refusal, in the QDK's order. Unreadable input raises QSharpError with serde's reason and
      column. Both match the QDK on 286 recorded inputs.
    • Azure: LogicalCounts({...}) takes the QDK's dictionary. A qecScheme without a name is a
      custom scheme.
    • Qualtran: stabilizer_qec.estimate.qualtran is 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_complexity and TComplexity are also in.
  • The exact and stabilizer-rank samplers have a .device.
  • qsample. Drawing and plotting raise an ImportError that 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_generators counts 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, and kernel_complete is 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.
  • **ldpc'...
Read more

2.0.0 — parity with every other QEC tool, and Clifford+T under coherent noise

Choose a tag to compare

@Jaspersands Jaspersands released this 18 Oct 13:28

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 with decode_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

Choose a tag to compare

@Jaspersands Jaspersands released this 09 Oct 02:41

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
    or I_ERROR[R_PAULI(theta=…, pauli=XZY)] 0 1 2. Stim reads them as identities.
  • The coherent sampler (Circuit.compile_coherent_sampler; Rust Circuit::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=…)], SEEP and LEAK_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

Choose a tag to compare

@Jaspersands Jaspersands released this 08 Oct 09:32

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 (BpLsd on a model, BpLsdDecoder on a check matrix with ldpc's arguments;
    Rust BpLsd, BpLsdDecoder, LsdOptions): belief propagation, then localized statistics
    decoding (Hillmann et al., 2024), LSD-0, LSD-E and LSD-CS. Corrections equal to ldpc
    2.4.1's on every shot checked; the only differences possible are where ldpc's own order
    is not reproducible (equal keys in a long std::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 in ldpc's tsl::robin_set order, which is reproduced. 0.6 of ldpc's time
    on the gross code.
  • Relay-BP (RelayBp, RelayBpDecoder; Rust RelayBp, RelayBpDecoder,
    RelayOptions): min-sum BP with memory, run in legs of disordered memory strengths (Müller
    et al., IBM, 2025). Identical to IBM's relay_bp 0.2.2 on every shot (correction,
    convergence and iteration count), for explicit strengths and for a seed: its random
    strengths come from rand 0.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; Rust ColorMatching): 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; Rust SearchDecoder, 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 in stabilizer-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

Choose a tag to compare

@Jaspersands Jaspersands released this 08 Oct 03:43

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 without canonicalize_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.py adds 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.py and tests/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.rs runs 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

Choose a tag to compare

@Jaspersands Jaspersands released this 07 Oct 12:09

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 n with records, detectors and coordinates in terms of iter, 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") and diagram("detslice-with-ops-svg"), and tick as a range
    (with rows) for these and detslice-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, the TICKs before it and the loop passes around it. The results
    (ExplainedError, CircuitErrorLocation and their parts, Stim's names) print as Stim's
    character for character, each part on its own as well (Rust: Display for
    TargetWithCoords and CircuitErrorLocation, and CircuitErrorLocation::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_error on 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.py holds it to Stim's.
  • search_for_undetectable_logical_errors on 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 generic CssCode schedule loses the colour code's
    distance likewise, while the surface codes and the Hamming hypergraph product keep theirs.
    tools/distances.py measures every memory the package builds.
  • write_shot_data_file and read_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 (or python -m stabilizer_qec), after Stim's: gen,
    sample, detect, m2d, analyze_errors, explain_errors, diagram, convert,
    sample_dem and help, with Stim's flags and formats, and decode after PyMatching's
    predict (any of the package's decoders). Tested end to end against the stim command: the
    same bytes from gen (its header and qubit layout included), analyze_errors, m2d,
    convert, explain_errors, and from sample and detect on 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's CompiledDemSampler, 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.md says where to ask for help.

Changed

  • An explicit zero argument on M, MX, MR, MRX and MPAD (M(0) 0) is kept and
    written back, as Stim keeps it.

stabilizer-qec 1.5.0

Choose a tag to compare

@Jaspersands Jaspersands released this 04 Oct 13:13

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::generated with
    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. polynomials gives 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), and CssCode.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 with BpOsd.
  • 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 by tools/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.json and 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, not 0.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

Choose a tag to compare

@Jaspersands Jaspersands released this 04 Oct 11:05

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. WindowMatching takes 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=False for 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.streamed says 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 with TICK groups bracketed, loops drawn once with their count and
    measurements numbered; "detslice-text" and "detslice-svg", what each detector compares
    after tick TICKs, 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 a Diagram: it prints as its text, shows as
    a picture in a notebook, and saves to a file. The pictures follow the reader's light or
    dark theme.

stabilizer-qec 1.3.0

Choose a tag to compare

@Jaspersands Jaspersands released this 04 Oct 07:09

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, and num_errors equals 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_1 is 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 no approximate_disjoint_errors here
    either. Disjoint cases that cannot be told apart are merged in Stim's order.