Quantum Error Correction#
- class iqm.qrisp_iqm.qec.DetectorExperiment(func: Callable[[...], Any])#
Decorator that turns a Jasp function into a detector-experiment handle.
What problem does this class solve?
A quantum error correction experiment involves many moving parts: you define a syndrome-extraction circuit with noise annotations via
stim_noise(), return detector and observable parity checks viaparity(), then you must trace the circuit, extract a detector error model (DEM), sample the circuit, feed detection events and the DEM into a decoder (PyMatching), compare predictions against observed logical values, and tally the logical error rate (LER). Doing this by hand for every experiment — especially when sweeping parameters or switching between Stim and hardware backends — is tedious and error-prone.DetectorExperimentautomates this entire pipeline. Decorate your Jasp-traceable experiment function with@DetectorExperiment, and the class gives you:One-shot LER:
compute_LER()traces the function, builds the DEM from yourstim_noiseannotations, samples the circuit (via Stim’s built-in sampler or any hardware backend), decodes with PyMatching, and returns the logical error rate — all in a single call.Batched sweeps:
batched_compute_LER()submits multiple parameter sets (e.g. a delay sweep) as a single hardware batch, eliminating per-job queue overhead.Circuit inspection:
to_stim()andto_iqm()let you extract and visualize the underlying Stim or IQM Pulse circuit before running on hardware, so you can verify detector / observable placement and gate decompositions at a glance.
How it works
When you call any of the public methods, the class internally:
Traces your function via Jasp to obtain a quantum circuit with explicit detector and observable annotations.
Extracts the Stim circuit (including the DEM built from your
stim_noisecalls) and, for hardware backends, a Qrisp circuit plus a post-processing function.Samples the circuit — either via Stim’s fast built-in sampler (when no backend is given) or via the provided hardware / simulator backend.
Decodes the detection events with PyMatching’s minimum-weight perfect matching, using the DEM that was automatically constructed from your noise annotations.
Compares the decoder’s predictions against the observed logical values (your
observable=Trueparities) and returns the fraction of shots where they disagree — the logical error rate.
Because the DEM is derived directly from the
stim_noisecalls in your function, you never need to manually construct or synchronise error models. The same function works with Stim (for fast, noise-model-based simulation) and with real hardware (where physical noise replaces the annotated model).- Parameters:
func (callable) – A Jasp-traceable function that implements the detector experiment. Must return
(list[Detector], list[Observable])— each detector and observable being the result of aparity()call.
Examples
A minimal repetition code memory experiment:
from qrisp import ( QuantumArray, QuantumBool, x, cx, measure, reset, parity, ) from qrisp.misc.stim_tools import stim_noise from iqm.qrisp_iqm.qec import DetectorExperiment p = 0.01 # physical error strength @DetectorExperiment def rep_code(delay_time=0.0): # ── Allocate qubits ──────────────────────────────────────────── qubits = QuantumArray(shape=(7,), qtype=QuantumBool()) data = qubits[::2] # 4 data qubits ancilla = qubits[1::2] # 3 ancilla qubits # ── Prepare logical |1_L⟩ ────────────────────────────────────── x(data) # ── One syndrome round ───────────────────────────────────────── # Reset ancillas (with noise on data that idle during reset) reset(ancilla) stim_noise("X_ERROR", p, ancilla) stim_noise("DEPOLARIZE1", p, data) # CNOT layer 1: data[i] → ancilla[i] for i in range(3): cx(data[i], ancilla[i]) stim_noise("DEPOLARIZE2", p, data[i], ancilla[i]) stim_noise("DEPOLARIZE1", p, data[3]) # untouched # CNOT layer 2: data[i+1] → ancilla[i] for i in range(3): cx(data[i+1], ancilla[i]) stim_noise("DEPOLARIZE2", p, data[i+1], ancilla[i]) stim_noise("DEPOLARIZE1", p, data[0]) # untouched # Pre-measurement noise stim_noise("X_ERROR", p, ancilla) stim_noise("X_ERROR", p, data) # Measure ancillas → syndrome detectors anc_meas = measure(ancilla) # First (and only) round: compare to expected initial |0⟩ synd_det = parity(anc_meas, expectation=0) # ── Final data measurement ───────────────────────────────────── data_meas = measure(data) # ── Measurement-round detectors ──────────────────────────────── # Tie each ancilla back to its two neighboring data qubits det_0 = parity(data_meas[0], anc_meas[0], data_meas[1], expectation=0) det_1 = parity(data_meas[1], anc_meas[1], data_meas[2], expectation=0) det_2 = parity(data_meas[2], anc_meas[2], data_meas[3], expectation=0) # ── Observable ───────────────────────────────────────────────── # Logical |1_L⟩ → every data qubit should read 1 obs = parity(data_meas[3], observable=True, expectation=1) return [synd_det, det_0, det_1, det_2], [obs]
Once decorated, use the provided methods to analyze the experiment:
# Compute logical error rate with Stim (fast simulation) ler = rep_code.compute_LER(0.0, shots=10_000) print(f"Logical error rate: {ler:.4f}") # Extract the Stim circuit for visualization stim_circuit = rep_code.to_stim(0.0) print(stim_circuit) # Run on hardware with an IQM backend from iqm.qrisp_iqm import IQMBackend, vf2pp_layout from qrisp import PassManager, convert_to_cz, convert_to_prx backend = IQMBackend( device_instance="emerald", api_token="YOUR_TOKEN", server_url="https://resonance.iqm.tech/", pass_manager=PassManager(), ) backend.pm += vf2pp_layout(backend.connectivity) backend.pm += convert_to_cz() backend.pm += convert_to_prx ler_hw = rep_code.compute_LER(0.0, shots=10_000, backend=backend) print(f"Hardware LER: {ler_hw:.4f}")