Skip to content

Latest commit

 

History

History
191 lines (152 loc) · 7.19 KB

File metadata and controls

191 lines (152 loc) · 7.19 KB

End-to-end memory experiment

This tutorial runs the first complete qec-canvas path:

  1. construct a distance-three rotated planar code;
  2. define a twelve-round memory experiment;
  3. add one deliberate fault and a seeded phenomenological noise model;
  4. execute the Python reference backend;
  5. decode with three commit rounds and two lookahead rounds; and
  6. 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.png

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

Build the experiment

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.

Simulate once and retain provenance

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.

Decode with explicit latency

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.

Read the common timeline

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.

Twelve-round memory experiment with labelled data and measurement faults, syndromes, and decoder windows

Distinguish flip parity from a logical value

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

Scope of the result

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.