Simulate and Decode

SkillDev tools

Run a LightStim circuit through the simulation pipeline and extract logical error rates. Use this skill whenever the user asks to simulate a circuit, get LER vs physical error rate, run PyMatching or BPOSD or MWPF decoding, sweep noise parameters, benchmark a code's threshold, plot logical error rates, configure post-selection (for state injection or distillation), or target specific logical observables in a multi-qubit circuit.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Simulate and Decode skill

What this skill tells your AI

The instructions your AI receives, as published by qutone/lightstim in skills/simulate-decode/SKILL.md and read by ahel’s review.

Takes a noisy stim.Circuit and runs it through SimulationPipeline.

Step 0: Get a noisy circuit

# From CircuitBuilder (custom protocol):
noisy = builder.build_noisy_circuit(NoiseConfig(p_2q=1e-3, p_meas=1e-3), "circuit_level")

# From any experiment class:
noisy = experiment.build()  # already returns noisy circuit

SimulationPipeline API

from lightstim.simulation.decoder_backend import SimulationPipeline, DecoderConfig

pipeline = SimulationPipeline(
    decoder_config=DecoderConfig("pymatching"),  # see decoder table below
    max_errors=200,      # primary stopping criterion: stop after N logical errors
    max_shots=1_000_000, # hard cap on total shots
    batch_size=10_000,   # shots per batch
    num_workers=4,       # parallel CPU processes
    print_progress=False,
)
stats = pipeline.run(noisy_circuit)

print(stats.logical_error_rate)    # errors / post_selected_shots
print(stats.ler_error_bar())       # 95% Wilson CI half-width (z=1.96)
print(stats.ler_error_bar(z=1.0))  # 1-sigma half-width
print(stats.shots)                 # total shots attempted
print(stats.post_selected_shots)   # shots surviving post-selection (= shots if no PS)

Sampling rule of thumb

For reported LER experiments, prefer error-targeted collection: choose a target number of logical errors and run until that count is reached, with a large max_shots cap. Fixed-shot simulations are useful for smoke tests, but they often produce long error bars when the LER is small.

Typical settings:

pipeline = SimulationPipeline(
    decoder_config=DecoderConfig("pymatching"),
    max_errors=100,          # collect this many logical errors per point
    max_shots=100_000_000,   # hard safety cap
    batch_size=10_000,
    num_workers=4,
    print_progress=False,
)

Use a smaller max_errors only for quick local demos; use 100-200+ errors for figures or comparisons.

Decoder options

NameBackendWhen to use
"pymatching"cpuDefault. MWPM. Always available. Fast for surface codes.
"bposd"cpuBP+OSD via stimbposd. Better for LDPC (BB, PQRM).
"mwpf"cpuMinimum-weight parity factor. Required for PQRM/CrossLS (hyperedges).
"nv-qldpc-decoder"gpuGPU BP+OSD via cudaq_qec. Use batch_size ≥ 50_000, num_workers=1.

Decoder selection rule:

  • Prefer pymatching whenever the detector error model is graphlike / has no hyperedges. This includes ordinary surface-code memory and surface-code lattice-surgery circuits.
  • Use mwpf or bposd when the circuit has hyperedges or important correlated fault mechanisms that should not be decomposed away.
  • Use bposd for LDPC codes such as BB/PQRM when BP+OSD is the intended decoder.
  • Use mwpf for CrossLS/PQRM-style hypergraph decoding, especially when high-weight X stabilizers or cross-code surgery create hyperedges.

Post-selection

Use post-selection for state injection (discard shots with injection errors) or distillation (post-select on magic state purity).

# Option A: Auto-detect from circuit tags (detectors tagged 'post-select')
# The SyndromeTracker inserts this tag when post_select_detector_coords is set.
pipeline = SimulationPipeline(
    decoder_config=DecoderConfig("pymatching"),
    max_errors=200,
    # post_select_detector_indices auto-discovered from circuit tags
)

# Option B: Post-select on decoder-corrected observable (distillation output)
# First, identify which observable index corresponds to the output qubit:
from lightstim.simulation.observable_analysis import (
    build_obs_patch_matrix, identify_distillation_observables
)
matrix, patch_names = build_obs_patch_matrix(circuit, system)
_, target_obs, ps_obs = identify_distillation_observables(
    matrix, patch_names, output_patches=["W4"]
)

pipeline = SimulationPipeline(
    decoder_config=DecoderConfig("bposd"),
    post_select_corrected_observable_indices=ps_obs,  # discard failed distillations
    target_observable_indices=target_obs,             # measure LER on output qubit only
    max_errors=50,
)
stats = pipeline.run(circuit)
print(f"Post-selection rate: {stats.post_selection_rate:.3f}")
print(f"LER on output qubit: {stats.logical_error_rate:.4e}")

Multi-observable circuits

For circuits with k > 1 logical observables (transversal CNOT, Bell pairs, distillation), specify which ones count as errors:

pipeline = SimulationPipeline(
    decoder_config=DecoderConfig("pymatching"),
    target_observable_indices=[0],   # only check logical 0 (ZZ observable of CNOT)
    max_errors=200,
)
# Without target_observable_indices, any observable flip counts as an error.

Threshold sweep pattern

import numpy as np
from lightstim.noise.config import NoiseConfig

pipeline = SimulationPipeline(
    decoder_config=DecoderConfig("pymatching"),
    max_errors=100, max_shots=100_000_000, print_progress=False,
)

results = []
for d in [3, 5, 7]:
    for p in np.logspace(-3, -1, 8):
        circuit = build_circuit(d=d, p=p)   # your build function
        stats = pipeline.run(circuit)
        results.append({
            "d": d, "p": p,
            "ler": stats.logical_error_rate,
            "eb":  stats.ler_error_bar(),
        })

Working examples

  • benchmarks/memory/run_memory.py — full threshold sweep with checkpointing, argparse, CSV output
  • benchmarks/logical_circuits/run_logical_circuits.py — multi-experiment runner pattern

Signals

GitHub stars
46
Forks
6
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
simulate-decode
Source
github.com/qutone/lightstim