This tutorial runs the first complete qec-canvas path:
- construct a distance-three rotated planar code;
- define a twelve-round memory experiment;
- add one deliberate fault and a seeded phenomenological noise model;
- execute the Python reference backend;
- decode with three commit rounds and two lookahead rounds; and
- render the faults, syndromes, detector events, window decisions, and software Pauli frame on one timeline.
Run the checked-in example from the repository root:
python examples/memory_experiment.py --output memory-experiment.pngThe example uses seed 234. With the declared probabilities, that seed samples
one Y data fault on q6 in round 3 and one classical measurement flip for
check X0 in round 4. The experiment also injects a deliberate X fault on
q4 before extraction in round 4.
from qec_canvas import (
MemoryExperiment,
PhenomenologicalNoise,
RotatedPlanarCode,
)
code = RotatedPlanarCode(distance=3)
experiment = (
MemoryExperiment(code, rounds=12)
.inject_pauli(
round=4,
qubit="q4",
pauli="X",
location="before_syndrome_extraction",
)
.with_noise(
PhenomenologicalNoise(
data_error_probability=0.003,
measurement_error_probability=0.006,
)
)
)The plan is immutable: inject_pauli() and with_noise() return new
experiments. The location is part of the deliberate fault because a Pauli
after extraction would not affect that round's measurement.
from qec_canvas import FaultType, PythonReferenceBackend
trace = PythonReferenceBackend().run(experiment, seed=234)
assert trace.seed == 234
assert trace.backend_name == "python-reference"
assert len(trace.sampled_faults) == 2
measurement_fault = trace.sampled_faults[1]
assert measurement_fault.fault_type is FaultType.MEASUREMENT_FLIP
assert measurement_fault.location.round == 4
assert measurement_fault.location.check_id == "X0"The sampled event tuple—not the seed alone—is the authoritative replay record. The backend computes exact stabilizer syndromes within its declared phase-free, phenomenological Pauli model.
from qec_canvas import ReferenceDecoder, SlidingWindow
result = SlidingWindow(
decoder=ReferenceDecoder(max_faults=3),
commit_rounds=3,
lookahead_rounds=2,
).decode(trace)
assert result.is_success
assert [update.round for update in result.frame_updates] == [3, 6]
assert [decision.commit_through_round for decision in result.decisions] == [
3,
6,
9,
12,
]The first window spans rounds 1–5 and commits through round 3, identifying the
sampled Y fault. The second spans rounds 4–8 and commits through round 6,
identifying both the injected X fault and the one-round X0 measurement flip.
The third spans rounds 7–11 and commits through round 9. The final flush covers
rounds 10–12.
Only data faults change the software Pauli frame, so the measurement flip is
committed as part of the decoder explanation without producing another frame
update. This is the central reason for retaining lookahead: a persistent data
syndrome and a transient measurement fault can be ambiguous at a window edge.
Each decision's frame_after value records the full running software frame
after that commit.
The cumulative correction is a software frame:
from qec_canvas import PauliString
assert result.final_frame == PauliString({"q6": "Y", "q4": "X"})For this controlled fixture, composing the known physical error with the frame gives identity. The test suite also checks the more general criterion that single-fault residuals commute with all stabilizers and logical representatives.
figure = trace.visualise(result=result)
figure.savefig("memory-experiment.png", dpi=160)The top lane distinguishes deliberate and sampled faults by text and marker
shape; the sampled measurement error is explicitly labelled
measurement flip X0. The syndrome grid uses 0 = +1 and 1 = -1; diamonds
mark changes between consecutive measured rounds. In the decoder lane, solid
regions are committed, hatched regions are lookahead, and plus markers are
software Pauli-frame updates. Text inside each committed region reports the
inferred logical-frame parity. A value such as flip(X)=1 means the current
frame anticommutes with the declared logical-X representative; it is not a
direct logical measurement.
The decoder-lane labels describe the inferred frame, not the state preparation.
In particular, flip(Z)=0 means that the current inferred frame commutes with
the declared logical-Z representative and therefore predicts no flip of a
known logical-Z eigenvalue. It does not assert that the memory was prepared
with logical Z value 0.
If a caller separately knows that the initial logical-Z bit was 0, the
decoder's predicted corrupted value is
predicted value = initial value XOR inferred Z-flip parity
The first memory fixture has inferred Z-flip parity 0. Its known physical
error equals the inferred frame, so their residual is identity: the decoder
predicts logical Z value 0, and the controlled ground truth is also 0.
The logical-failure fixture also displays flip(Z)=0,
but for a different reason. Its decoder infers Xq6, which commutes with
logical Z, while the actual error is Xq0 Xq3. Their residual is
Xq0 Xq3 Xq6, the code's logical X operator, which flips logical Z. Relative
to an assumed initial logical-Z bit of 0, the decoder therefore predicts 0
while the controlled ground truth is 1. The preparation and final logical
measurement are not simulated by MemoryExperiment; these values are an
analytic interpretation of the declared initial bit and known injected errors.
Plus markers have a separate meaning: they appear only when a commit produces
a non-identity PauliFrameUpdate, not at every commit boundary. In the
logical-failure figure:
| Commit boundary | Decoder action | Plus marker |
|---|---|---|
| Round 3 | Xq6 refers to a round-4 fault still in lookahead, so nothing is committed |
No |
| Round 6 | the round-4 Xq6 hypothesis becomes safe and changes the frame from I to Xq6 |
Yes |
| Round 9 | no new Pauli event is committed; the existing Xq6 frame is carried forward |
No |
| Round 12 | the final flush adds no Pauli event | No |
This example demonstrates API composition, deterministic provenance, phenomenological Pauli simulation, reference decoding, bounded-lookahead orchestration, and semantic visualisation. It is not evidence of threshold-scale decoder performance or circuit-level hardware accuracy. The README and backend capability record list the release boundaries explicitly.
The separate logical-failure example deliberately exceeds the distance-three correction guarantee and compares the decoder's inference with known simulation ground truth.
