Skip to content

Quantum Error Correction Experiments

FlagQuantum edited this page Oct 20, 2026 · 2 revisions

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.

1. What Is Available?

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.

2. Install the Example Requirements

Create a supported environment using Installation and Compatibility, then install:

python -m pip install "flagquantum[stim]==0.3.1" numpy
python -m pip check

The 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.

3. Understand Syndrome Extraction and Detection Events

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

$$S_0=Z_0Z_1,\qquad S_1=Z_1Z_2.$$
Formula / mathematical operation Corresponding code excerpt Check / interpretation
$S_0=Z_0Z_1$ RepetitionCode(distance=3) The profile declares the adjacent data-qubit Z checks.
$s_0=b_0\oplus b_1,\quad s_1=b_1\oplus b_2$ shot.raw_final_data_bits For a single X fault, parity identifies which data bit differs; qec_repetition.py exercises extraction and correction routes.
$b_{\mathrm{decoded}}=(0,0,0)$ 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.

Example 1: Inspect Three Code Layouts

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.

4. Run Extraction, Decoding, and Correction End to End

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.

Example 2: Inject a Known X Error and Compare All Six Modes

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.

5. Read Measurements and Reconstruct Detector Parities

Example 3: Inspect a Surface-Code Measurement Record

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.

6. Build a Detector Error Model and Decode Samples

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

$$\text{residual logical flips} =\text{sampled logical flips}\oplus\text{predicted logical flips}.$$
Formula / mathematical operation Corresponding code excerpt Check / interpretation
$\ell_{\mathrm{residual}}=\ell_{\mathrm{sampled}}\oplus\ell_{\mathrm{predicted}}$ residual = samples.observables.bool() ^ predictions.bool() qec_sampling_decode.py works with logical-label arrays of shape (shots, num_observables).
$N_{\mathrm{fail}}=\sum_s\mathbf1[\exists j:\ell_{s,j}=1]$ 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.

Noise Records Have Different Placement Rules

  • PhenomenologicalNoise supplies 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.
  • RepetitionNoiseProfile is 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.

Example 4: Compare Circuit-Sampled and Model-Sampled Data

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.

7. Understand Temporal Decoding and Failure Cases

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.

Example 5: Check the Temporal Policy and an Uncorrectable Pattern

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.

8. Choose a Decoder Within Its Limits

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.

Example 6: Handle Hyperedges and Duplicate Mechanisms Explicitly

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.

9. Capacity, Interpretation, and Common Problems

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.

Sources and Related Guides

Technical sources below are pinned to v0.3.1:

Clone this wiki locally