Repository navigation
Quantum Error Correction Experiments
Build bounded memory experiments, inspect syndrome extraction, decode detection events, and distinguish physical corrections from Pauli-frame interpretation.
Scope: FlagQuantum 0.3.1. All six complete scripts below were verified on October 20, 2026 with Python 3.12.14, PyTorch 2.13.0, Stim 1.16.0, and macOS ARM64 using local CPU execution. The QEC namespace is experimental; individual code/model/decoder capabilities have separate maturity and limits in the release catalog. These examples do not validate quantum hardware, real-time feedback, GPU execution, or fault-tolerant computation.
Read Core Concepts, Building and Simulating Circuits, and Noise and Open-System Simulation first if needed.
Formula/code review — October 20, 2026: The equations are paired with code excerpts, and the local CPU examples were re-run with Python 3.12.14 and PyTorch 2.13.0. See the review record for executed commands, distributed checks, and untested hardware/provider paths. Earlier environment and output records are retained.
| Capability | Interface / records | Scope |
|---|---|---|
| Three-data-qubit bit-flip memory reference |
run_repetition_memory_experiment, ErrorSchedule
|
Fixed logical-zero repetition experiment with deterministic X injection, repeated checks, six feedback/analysis modes, and shot-resolved results. |
| Code-driven memory layouts |
RepetitionCode, SteaneCode, RotatedSurfaceCode, build_memory_circuit
|
Code checks, ancillas, supported preparation/readout basis, detector parities, and logical-observable layouts. |
| Matrix-defined CSS codes/models |
CssCode, css_code_matrices, DetectorErrorModel.from_code / .from_code_matrices
|
Construct from supported parity-check/logical matrices; this is a separate model route with its own assumptions. |
| Raw measurements and detection-event sampling |
sample_memory_measurements, sample_memory_circuit
|
Sample supported memory circuits on the optional stabilizer engine. |
| Detector error models (DEMs) | DetectorErrorModel |
Mechanisms mapping noise locations to detector and logical-observable flips; model sampling and supported text interchange. |
| Small graphlike-model decoding | MinimumWeightMatchingDecoder |
Exact minimum-weight pairing within a bounded defect budget. |
| Hyperedge-aware decoding | BeliefPropagationDecoder |
Belief propagation with an ordered-statistics fallback; general optimality is not guaranteed. |
| Repetition lookup/temporal policies |
RepetitionLookupDecoder, streaming and temporal variants |
Bounded repetition-code decisions, rather than a general decoder for arbitrary codes. |
| Optional external cross-check | PyMatchingDecoder |
A separately installed comparison adapter with representability and numerical limits. |
A larger code record or detector model does not automatically enable feedback for that code. The integrated correction reference remains the fixed three-data-qubit repetition experiment. General memory sampling and DEM decoding below perform offline logical-label inference; they do not apply arbitrary physical corrections to a live device.
Create a supported environment using Installation and Compatibility, then install:
python -m pip install "flagquantum[stim]==0.3.1" numpy
python -m pip checkThe core release requires Python 3.10–3.12 and PyTorch >=2.10,<2.15; the stim extra declares stim>=1.16,<2. The stabilizer sampler is optional and reports a dependency error if it is absent. Core repetition-reference execution and native decoder arithmetic do not require the PyMatching extra.
Import from flagquantum.qec, as shown below. Treat these as version-scoped domain APIs rather than stable package-root additions. For the optional external matcher, install "flagquantum[pymatching]==0.3.1"; its declared dependency is pymatching>=2.3,<3. PyMatching was not executed for this page.
A stabilizer measurement produces a check bit: 0 corresponds to eigenvalue +1 and 1 to eigenvalue −1. The three-data-qubit bit-flip repetition profile measures
| Formula / mathematical operation | Corresponding code excerpt | Check / interpretation |
|---|---|---|
RepetitionCode(distance=3) |
The profile declares the adjacent data-qubit Z checks. | |
shot.raw_final_data_bits |
For a single X fault, parity identifies which data bit differs; qec_repetition.py exercises extraction and correction routes. | |
assert all(s.decoded_data_bits == (0, 0, 0) for s in result.shot_records) |
The script checks corrected readout bits for every shot and each correction route. |
For Z-parity extraction, an ancilla starts in |0>, the participating data qubits control CNOTs into it, and the ancilla is measured and reset. The reference uses data qubits 0, 1, 2 and ancillas 3, 4. Reset prevents the previous check result from becoming the next round's initial ancilla state. X-type checks use the corresponding ancilla preparation and basis-change gadget declared by the code.
Under the single-X-error assumption, the repetition lookup is:
Syndrome (S0 bit, S1 bit)
|
Inferred action |
|---|---|
(0, 0) |
No correction |
(1, 0) |
X on data qubit 0 |
(1, 1) |
X on data qubit 1 |
(0, 1) |
X on data qubit 2 |
A syndrome bit is a check's recorded result. A detection event is a specified parity of measurements, often the XOR of a check in adjacent rounds. A change can result from data noise, a readout fault, or a correction; it does not uniquely identify the cause by itself.
Initial and terminal detector definitions depend on preparation and readout basis. Under Z readout, a Z-type check is deterministic from the first round and can be compared with final data readout; an X-type check is compared only between successive rounds. The roles swap for X readout. Some raw measurements can therefore be random in a noiseless experiment while every declared detector is zero.
Save as qec_layouts.py:
from flagquantum.qec import RepetitionCode, SteaneCode, RotatedSurfaceCode, build_memory_circuit, code_names
print('Registered code names:', code_names())
for code, expected in [
(RepetitionCode(distance=3), (3, 2, 8)),
(SteaneCode(), (7, 6, 18)),
(RotatedSurfaceCode(distance=3), (9, 8, 24)),
]:
memory = build_memory_circuit(code, rounds=3, readout_basis='z')
data = len(code.data_qubits)
ancillas = code.num_ancilla_qubits
assert (data, ancillas, len(memory.detectors)) == expected
assert len(memory.observables) == 1
print(f'{type(code).__name__}: data={data}, ancillas={ancillas}, detectors={len(memory.detectors)}, observables={len(memory.observables)}')
print(f' X/Z checks: {code.num_x_stabilizers}/{code.num_z_stabilizers}')
print('Code layout checks passed.')Run python qec_layouts.py. Expected output:
Registered code names: ('repetition', 'rotated_surface', 'steane')
RepetitionCode: data=3, ancillas=2, detectors=8, observables=1
X/Z checks: 0/2
SteaneCode: data=7, ancillas=6, detectors=18, observables=1
X/Z checks: 3/3
RotatedSurfaceCode: data=9, ancillas=8, detectors=24, observables=1
X/Z checks: 4/4
Code layout checks passed.
These are three-round Z-readout layouts. Detector counts follow the code's check types and boundary conventions, not simply rounds * checks. Building a layout does not run a decoder or prove protection against an arbitrary error model.
The repetition code protects against the modeled bit-flip family; it is not a complete correction scheme for arbitrary X, Y, and Z errors on an unknown quantum state. The Steane and rotated-surface records describe broader stabilizer structures, but their existence does not widen the integrated repetition feedback profile.
The memory workflow is: prepare the reference state, inject the configured faults, extract checks repeatedly, infer a correction, apply a physical action or track a frame, and analyze final logical readout.
feedback_mode |
When and how correction is handled |
|---|---|
compiled_lookup |
A fixed lookup is compiled into immediate conditional X actions. |
offline_pauli_frame |
No in-circuit action; apply the terminal decoder's X frame to recorded data afterward. |
runtime_decoder |
Call a streaming decoder after each round and apply its physical X action. |
runtime_pauli_frame |
Call a streaming decoder and track an X frame, adjusting later interpretation and final readout. |
runtime_temporal_decoder |
Require a persistent syndrome before physical X feedback. |
runtime_temporal_pauli_frame |
Use the temporal policy with frame tracking. |
Save as qec_repetition.py:
from flagquantum.qec import ErrorEvent, ErrorSchedule, run_repetition_memory_experiment
schedule = ErrorSchedule((ErrorEvent(round_index=0, qubit=1),))
modes = (
'compiled_lookup', 'offline_pauli_frame', 'runtime_decoder',
'runtime_pauli_frame', 'runtime_temporal_decoder', 'runtime_temporal_pauli_frame',
)
for mode in modes:
result = run_repetition_memory_experiment(
error_schedule=schedule, rounds=3, shots=8, seed=0, feedback_mode=mode,
)
shot = result.shot_records[0]
assert result.shot_count == 8 and result.logical_failures == 0
assert all(s.decoded_data_bits == (0, 0, 0) for s in result.shot_records)
print('Mode:', mode)
print(' Syndrome history:', [r.bits for r in shot.syndrome_rounds])
print(' Detection events:', [(e.round_index, e.check_index) for r in shot.syndrome_rounds for e in r.detection_events])
print(' Feedback qubits:', [c.qubit for c in shot.executed_feedback])
print(' Raw / decoded bits:', shot.raw_final_data_bits, '/', shot.decoded_data_bits)
print(f' Logical failures: {result.logical_failures}/{result.shot_count}')
print('Injected-X correction checks passed.')Run python qec_repetition.py. Expected output:
Mode: compiled_lookup
Syndrome history: [(1, 1), (0, 0), (0, 0)]
Detection events: [(0, 0), (0, 1), (1, 0), (1, 1)]
Feedback qubits: [1, None, None]
Raw / decoded bits: (0, 0, 0) / (0, 0, 0)
Logical failures: 0/8
Mode: offline_pauli_frame
Syndrome history: [(1, 1), (1, 1), (1, 1)]
Detection events: [(0, 0), (0, 1)]
Feedback qubits: [None, None, None]
Raw / decoded bits: (0, 1, 0) / (0, 0, 0)
Logical failures: 0/8
Mode: runtime_decoder
Syndrome history: [(1, 1), (0, 0), (0, 0)]
Detection events: [(0, 0), (0, 1), (1, 0), (1, 1)]
Feedback qubits: [1, None, None]
Raw / decoded bits: (0, 0, 0) / (0, 0, 0)
Logical failures: 0/8
Mode: runtime_pauli_frame
Syndrome history: [(1, 1), (1, 1), (1, 1)]
Detection events: [(0, 0), (0, 1)]
Feedback qubits: [1, None, None]
Raw / decoded bits: (0, 1, 0) / (0, 0, 0)
Logical failures: 0/8
Mode: runtime_temporal_decoder
Syndrome history: [(1, 1), (1, 1), (0, 0)]
Detection events: [(0, 0), (0, 1), (2, 0), (2, 1)]
Feedback qubits: [None, 1, None]
Raw / decoded bits: (0, 0, 0) / (0, 0, 0)
Logical failures: 0/8
Mode: runtime_temporal_pauli_frame
Syndrome history: [(1, 1), (1, 1), (1, 1)]
Detection events: [(0, 0), (0, 1)]
Feedback qubits: [None, 1, None]
Raw / decoded bits: (0, 1, 0) / (0, 0, 0)
Logical failures: 0/8
Injected-X correction checks passed.
The error on qubit 1 produces syndrome (1, 1). Physical feedback changes the quantum data and clears later raw check outcomes. A Pauli frame changes interpretation without physically removing the error, so raw syndromes can remain nonzero while decoded readout is correct. Temporal feedback waits one additional round to confirm persistence.
executed_feedback records each round's action; None means no correction. In a frame mode, a non-None qubit denotes a frame update rather than a physical gate. decode_result is the terminal decoder recommendation and is distinct from the actions already executed. Do not apply a terminal frame a second time to data already decoded by the selected mode.
This reference starts in logical zero and defines the reported logical bit as the majority of the three decoded data bits. Zero failures for this known injected error checks the mechanism; it does not establish a finite-noise logical error rate or arbitrary logical-state preservation.
Runtime decoder feedback is a synchronous local trajectory workflow. Explicit batched feedback is refused; a replaceable Python decoder does not imply a hard-real-time latency guarantee or provider feedback support.
Save as qec_measurements.py:
import torch
from flagquantum.qec import (
MeasurementRef, PhenomenologicalNoise, RotatedSurfaceCode,
build_memory_circuit, sample_memory_circuit, sample_memory_measurements,
)
code = RotatedSurfaceCode(distance=3)
memory = build_memory_circuit(code, rounds=2, readout_basis='z')
noise = PhenomenologicalNoise()
records = sample_memory_measurements(memory, noise=noise, shots=32, seed=0)
samples = sample_memory_circuit(memory, noise=noise, shots=32, seed=0)
columns = []
for detector in memory.detectors.detectors:
parity = records.vector(detector.parity).to(torch.int64).sum(dim=1) % 2
columns.append(parity.bool())
reconstructed = torch.stack(columns, dim=1)
assert torch.equal(reconstructed, samples.detectors.bool())
assert not reconstructed.any()
x_ancilla = next(check.ancilla_qubit for check in code.checks if check.stabilizer.x_qubits)
refs = [MeasurementRef(0, x_ancilla), MeasurementRef(1, x_ancilla)]
bits = records.vector(refs).to(torch.int64)
assert torch.equal(records.integer(refs), bits[:, 0] + 2 * bits[:, 1])
print('Recorded measurement shape:', tuple(records.outcomes.shape))
print('Detector shape:', tuple(reconstructed.shape))
print('Chosen X-check measurement vector shape:', tuple(bits.shape))
print('Every detector reconstructed from recorded-bit parities: True')
print('Noiseless detectors all zero: True')
print('Measurement-handle integer packing is first-handle least-significant: True')Run python qec_measurements.py. Expected output:
Recorded measurement shape: (32, 25)
Detector shape: (32, 16)
Chosen X-check measurement vector shape: (32, 2)
Every detector reconstructed from recorded-bit parities: True
Noiseless detectors all zero: True
Measurement-handle integer packing is first-handle least-significant: True
MeasurementRef(round_index, qubit) addresses a recorded bit; round_index=None refers to terminal data readout. outcome reads one handle and vector reads handles in the requested order. The script verifies that every detector equals the XOR of the recorded bits named by its layout.
MeasurementSamples.integer(refs) packs the first handle into the least-significant bit. This is a measurement-handle utility, separate from FlagQuantum's qubit-0-most-significant statevector convention. Preserve the relevant convention when passing records to a decoder.
This noiseless test checks the extraction record and detector definitions. It does not physically correct the surface-code data, nor does it assert that individual X-check bits or terminal data bits are all zero.
A DEM mechanism lists a probability, the detectors it flips, and the logical observables it flips. A decoder receives the observed defective-detector indices and predicts which logical labels need to be toggled. The comparison is
| Formula / mathematical operation | Corresponding code excerpt | Check / interpretation |
|---|---|---|
residual = samples.observables.bool() ^ predictions.bool() |
qec_sampling_decode.py works with logical-label arrays of shape (shots, num_observables). | |
failures = int(residual.any(dim=1).sum()) |
A shot fails when any residual logical label is nonzero; syndrome detector indices are decoded separately. |
This is an offline logical correction, distinct from selecting and executing physical gates.
-
PhenomenologicalNoisesupplies X (data_flip), Z (phase_flip), Y (both_flip), and check-readout (measurement_flip) probabilities. Data faults occur at round boundaries; measurement faults occur at the relevant check readout. Optional per-qubit and per-check vectors replace the corresponding scalar rates for all locations and must have the required lengths. - The code-driven circuit/DEM route also accepts a restricted gate-bound
NoiseModel: supported single-qubit Pauli faults follow matching gates. It does not inherit every Kraus channel from the general noisy simulator. Depolarizing/damping channels, rules on measurement/reset, unsupported multi-wire bindings, and unsupported terminal placement are refused by that route. -
RepetitionNoiseProfileis a separate circuit-location profile for the fixed feedback reference, using bit flips after check CNOTs and independent readout confusion. The middle data qubit participates in two such CNOTs per round, so its fault opportunities differ from the edge qubits. Its probabilities are not interchangeable with per-round phenomenological rates.
Save as qec_sampling_decode.py:
import torch
from flagquantum.qec import (
DetectorErrorModel, MinimumWeightMatchingDecoder, PhenomenologicalNoise,
RepetitionCode, build_memory_circuit, sample_memory_circuit,
)
memory = build_memory_circuit(RepetitionCode(distance=3), rounds=3)
clean = sample_memory_circuit(memory, noise=PhenomenologicalNoise(), shots=16, seed=0)
assert not clean.detectors.any() and not clean.observables.any()
noise = PhenomenologicalNoise(data_flip=0.02, measurement_flip=0.02)
model = DetectorErrorModel.from_memory_circuit(memory, noise=noise).merge_duplicate_mechanisms()
decoder = MinimumWeightMatchingDecoder.from_detector_error_model(model)
shots = 256
circuit_samples = sample_memory_circuit(memory, noise=noise, shots=shots, seed=0)
model_samples = model.dem_sampling(shots=shots, seed=0)
print('Clean circuit: no detection events or logical flips.')
print('Detectors / observables:', model.num_detectors, '/', model.num_observables)
for name, samples in [('Circuit-sampled', circuit_samples), ('Model-sampled', model_samples)]:
assert tuple(samples.detectors.shape) == (shots, model.num_detectors)
assert tuple(samples.observables.shape) == (shots, model.num_observables)
predictions = torch.zeros_like(samples.observables)
for row in range(shots):
# decode() expects detector indices, not the dense 0/1 bit vector.
syndrome = tuple(int(i) for i in samples.detectors[row].nonzero().flatten())
decoded = decoder.decode(syndrome)
for observable in decoded.observables:
predictions[row, observable] = True
residual = samples.observables.bool() ^ predictions.bool()
failures = int(residual.any(dim=1).sum())
raw_flips = int(samples.observables.any(dim=1).sum())
assert 0 <= failures <= shots
print(f'{name}: detector_shape={tuple(samples.detectors.shape)}, raw_flips={raw_flips}, residual_failures={failures}/{shots}')
print('Both sampling routes decoded; residuals are logical-label XORs.')Run python qec_sampling_decode.py. Output observed in the verified environment:
Clean circuit: no detection events or logical flips.
Detectors / observables: 8 / 1
Circuit-sampled: detector_shape=(256, 8), raw_flips=50, residual_failures=3/256
Model-sampled: detector_shape=(256, 8), raw_flips=48, residual_failures=4/256
Both sampling routes decoded; residuals are logical-label XORs.
sample_memory_circuit executes the supported memory circuit on the stabilizer sampler. model.dem_sampling samples the DEM mechanisms. They are distinct routes sharing a configured experiment/model; equal seeds do not require equal samples. Circuit-based construction and sampling also share some source assumptions, so this comparison alone is not a fully independent proof of model correctness.
The decoder expects a set/tuple of defective detector indices, not a dense 0/1 syndrome vector. Its observables field is the tuple of logical-observable indices predicted to flip; it is not a probability or a corrected quantum state.
The script merges duplicate mechanisms before matching, checks sample dimensions, and calculates residual failures. Its random counts may vary with dependency/runtime versions. The 3/256 and 4/256 observations are small finite-shot measurements, not a threshold, a guaranteed improvement, or evidence of scaling. A failure rate needs its shot count and uncertainty; zero observed failures would not prove zero underlying error probability.
The bounded temporal policy waits for the same nonzero syndrome in two consecutive rounds. It can ignore an isolated measurement excursion, but repeated readout faults can mimic a persistent data error. A data error first seen in the final round remains unconfirmed.
Save as qec_temporal_limits.py:
from flagquantum.qec import (
DetectionEvent, ErrorEvent, ErrorSchedule, RepetitionTemporalDecoder,
SyndromeRound, run_repetition_memory_experiment,
)
def history(rows):
previous = (0, 0)
result = []
for r, bits in enumerate(rows):
events = tuple(DetectionEvent(r, c) for c in range(2) if bits[c] != previous[c])
result.append(SyndromeRound(r, bits, events))
previous = bits
return tuple(result)
decoder = RepetitionTemporalDecoder()
for name, rows, expected in [
('Isolated readout excursion', [(0, 0), (1, 0), (0, 0)], None),
('Persistent middle-qubit syndrome', [(0, 0), (1, 1), (1, 1)], 1),
('First seen in final round', [(0, 0), (0, 0), (1, 1)], None),
]:
correction = decoder.decode_round(history(rows))
assert correction.qubit == expected
print(f'{name}: correction={correction.qubit}')
# Two X faults before the same extraction exceed the single-error assumption.
result = run_repetition_memory_experiment(
error_schedule=ErrorSchedule((ErrorEvent(0, 0), ErrorEvent(0, 1))),
rounds=3, shots=8, seed=0, feedback_mode='compiled_lookup',
)
shot = result.shot_records[0]
assert shot.syndrome_rounds[0].bits == (0, 1)
assert shot.executed_feedback[0].qubit == 2
assert result.logical_failures == result.shot_count
print('Two-fault first syndrome:', shot.syndrome_rounds[0].bits)
print('Lookup correction qubit:', shot.executed_feedback[0].qubit)
print('Final data bits:', shot.decoded_data_bits)
print(f'Two-fault logical failures: {result.logical_failures}/{result.shot_count}')
print('Temporal-policy and failure-boundary checks passed.')Run python qec_temporal_limits.py. Expected output:
Isolated readout excursion: correction=None
Persistent middle-qubit syndrome: correction=1
First seen in final round: correction=None
Two-fault first syndrome: (0, 1)
Lookup correction qubit: 2
Final data bits: (1, 1, 1)
Two-fault logical failures: 8/8
Temporal-policy and failure-boundary checks passed.
The first three cases exercise decoder decisions on explicitly constructed histories; they are not samples of a physical readout-noise process. The last case runs the circuit with X errors on qubits 0 and 1 before the same check round. Their syndrome matches a single X on qubit 2, so lookup feedback produces 111 and a logical failure. This demonstrates the ambiguity beyond the single-error assumption.
ErrorEvent in this reference accepts X faults on data qubits 0–2 only. The schedule is bounded to 64 events, cannot repeat a round/qubit pair, and must fit the chosen round count. Other code records and noise families elsewhere in QEC do not change that deterministic injection contract.
| Decoder | Suitable input | Boundary |
|---|---|---|
| Repetition lookup | Two-bit repetition syndrome / complete history | Uses the latest syndrome under the single-X-error assumption; not a general measurement-error decoder. |
| Repetition temporal | Consistent round histories with detection events | Two-round persistence heuristic; final-round onset and repeated readout errors remain limitations. |
| Native minimum-weight matching | Unique, graphlike DEM mechanisms | Refuses three-or-more-detector hyperedges; exact pairing enumerates a bounded defect set, default maximum 20 defects. Raising that budget can be expensive. |
| Belief propagation | Supported independent DEM mechanisms, including hyperedges | Exactness on tree factor graphs does not imply optimality on loopy graphs; inspect convergence and fallback semantics. |
| PyMatching adapter | A representable decoding graph and installed optional dependency | External comparison; translation refusals, ties, and floating-point tolerance must be respected. |
A matching edge has log-odds weight log((1-p)/p) under the supported independent mechanism model. A one-detector mechanism reaches a boundary; a hyperedge cannot simply be replaced by pair edges without changing the problem. Minimum-weight explanation is also different from summing the probabilities of all explanations in a logical class.
Save as qec_decoder_boundaries.py:
from flagquantum.errors import CapabilityError
from flagquantum.qec import BeliefPropagationDecoder, DetectorErrorModel, MinimumWeightMatchingDecoder
model = DetectorErrorModel.from_stim_text(
'error(0.01) D0 D1 D2 L0\n'
'detector D0\ndetector D1\ndetector D2\nlogical_observable L0\n'
)
try:
MinimumWeightMatchingDecoder.from_detector_error_model(model)
except CapabilityError:
print('Matcher rejected a three-detector hyperedge: True')
else:
raise AssertionError('Expected graphlike-model restriction')
result = BeliefPropagationDecoder.from_detector_error_model(model).decode((0, 1, 2))
assert result.observables == (0,)
assert result.converged
print('Belief propagation converged:', result.converged)
print('Predicted logical observable indices:', result.observables)
split = DetectorErrorModel.from_stim_text(
'error(0.01) D0 L0\nerror(0.01) D0 L0\ndetector D0\nlogical_observable L0\n'
)
assert not split.mechanisms_are_unique()
merged = split.merge_duplicate_mechanisms()
assert merged.num_errors == 1
assert abs(merged.errors[0].probability - 0.0198) < 1e-12
print('Duplicate mechanisms merged:', split.num_errors, '->', merged.num_errors)
print(f'Independent-parity merged probability: {merged.errors[0].probability:.6f}')
print('Decoder boundary checks passed.')Run python qec_decoder_boundaries.py. Expected output:
Matcher rejected a three-detector hyperedge: True
Belief propagation converged: True
Predicted logical observable indices: (0,)
Duplicate mechanisms merged: 2 -> 1
Independent-parity merged probability: 0.019800
Decoder boundary checks passed.
The text reader handles the supported Stim DEM text format; using it does not turn an arbitrary Stim program into a FlagQuantum memory circuit. This tiny hyperedge has one explanation, making the predicted logical label easy to check. For general belief-propagation results, converged=False means the iterative messages did not settle and the configured ordered-statistics fallback answered instead; the flag is not a universal quality certificate.
Duplicate independent mechanisms with the same detector and observable signature can be merged using the probability that an odd number fires. Two independent 0.01 mechanisms therefore become 2*0.01*(1-0.01)=0.0198, rather than 0.02. The matcher refuses unmerged duplicate signatures. Other merge rules or mutually exclusive mechanism groups carry different assumptions and must not be treated as independent automatically.
Advanced records also support decoder contexts, basis components, chunking, and sliding-window workflows. Those are separately scoped model-processing interfaces, not evidence of hardware streaming feedback. Use their release contracts before extending the examples here.
| Issue | Check or limitation |
|---|---|
| Unexpected check or detector count | Check the preparation/readout basis, X/Z check types, and initial/terminal detector layout. |
| Nonzero raw syndrome after successful frame correction | A frame adjusts interpretation while the physical error can remain; inspect decoded bits and executed frame actions. |
| Decoder receives the wrong syndrome | Convert a detector bit row into active indices, as in example 4. |
| DEM-derived logical result differs from reference majority voting | These result contracts differ: use each experiment's declared logical observable or the fixed reference's majority definition consistently. |
| A general noise channel is rejected | QEC sampler/model grammars are narrower than the density-matrix noisy simulator; inspect the selected noise record and placement. |
| Decoder rejects the DEM | Check graphlike arity, uniqueness, supported priors/correlations, and the defect budget. |
| A code record builds but model construction is too large | Circuit-derived DEM construction can force statevector simulations; a larger record is not evidence that this route scales. |
| Missing sampler or external matcher | Install the matching stim or pymatching extra in the active interpreter. |
The documented circuit-derived rotated-surface DEM route has local evidence through distance 3 and encounters the statevector cost of data plus ancillas at larger distances. Matrix-derived CSS/code-capacity models avoid that forced-circuit route, but have their own boundary and preparation assumptions. They are not a way to silently substitute a different physical experiment for a circuit-based memory model.
All examples here are local CPU experiments with classical decoding. No claim follows for arbitrary logical gates, arbitrary encoded-state fidelity, universal fault tolerance, hard-real-time or remote-provider feedback, gradients, distributed QEC execution, capacity, or performance. Threshold and logical-suppression studies need suitable noise definitions, multiple code distances, sufficient shots with uncertainty, and separate validation of the modeled/sampled workflow.
When reporting an experiment, record package/optional dependency versions, code and distance, readout basis, rounds, noise placement and rates, sampling route, seed, decoder settings, correction mode, shot count, failure definition, and uncertainties. Keep known injected-error checks alongside statistical runs.
Technical sources below are pinned to v0.3.1:
- QEC guide and examples
- QEC implementation: feedback, models, sampling, and capacity boundaries
- Repetition memory workflow · Repetition decoder policies
- Code records · Memory circuits and detector layouts
- Measurement and circuit sampling · Detector error models
- Minimum-weight matching · Belief propagation
- Release dependency declarations · Capability catalog
- Noise and Open-System Simulation · Qubit Ordering and Result Conventions · Troubleshooting and FAQ
- Installation and Compatibility
- Quick Start
- Install and Run
- Qubit Ordering and Result Conventions
- Runnable Examples
- Building and Simulating Circuits
- Training Parameterized Circuits
- Hybrid Classical-Quantum Models
- Core Concepts
- From Papers and Equations to FlagQuantum Code
- Reproducing Molecular Hydrogen VQE
- Fundamentals
- Tutorials
- Choosing a Simulation Method
- Accelerator Setup
- Distributed Simulation and Training
- Execution
- Circuit Compilation and Interoperability
- Noise and Open-System Simulation
- Quantum Error Correction Experiments
- Cloud and Quantum Hardware Execution
- QPU Digital Twins
- Advanced Topics
- Support and Contribution
All 21 guides are linked in the navigation sections. The Wiki Directory lists published guides and current repository resources.
- Troubleshooting and FAQ
- Reproducibility and Benchmarks
- Development and Contribution Guide
- Documentation Guide
Documentation, capability, and contribution links in this section follow current main; the guides above target 0.3.1.