Documentation

Python API reference

The complete public surface of import glean. Every decoder, code, and estimator traces to a peer-reviewed source — the Iron Rule — and names the benchmark that validates it.

Build from source: maturin develop --release

Overview

glean re-exports the compiled Rust core (glean.glean, a PyO3 extension) at the top level, and exposes three pure-Python add-ons lazily so that a plain import glean never pulls in sinter or numpy.

SurfaceKindImports on first use
toric_code, gross_code, load_dem, load_bp_otf, simulate_toric + the Code / DetectorErrorModel / BpOtf classesRust core
glean.dsspure-Python estimatornumpy
glean.splittingpure-Python estimatornumpy
glean.SinterDecoderpure-Python sinter.Decoder adaptersinter

glean.__version__ is the package version string. Optional extras: stim (build circuits / detector error models), sinter (the collection framework the adapter plugs into), numpy (returned-array ergonomics + the rare-event estimators).

quickstart.py
import glean

code = glean.toric_code(4)               # [[32, 2, 4]]
error = [0] * code.n
error[5] = 1
syndrome = code.hz_syndrome(error)
correction = code.decode(syndrome, p=0.05)          # BP + OSD-CS-10
assert code.hz_syndrome(correction) == syndrome
failed = code.is_logical_failure(error, correction)

1 · Code construction

glean.toric_code

B1
glean.toric_code(l) -> Code

Construct the distance-l toric code [[2l², 2, l]]. The plumbing warmup — needs no external dependencies.

B1 reproduces Roffe 2020's 9.9 ± 0.2% threshold.

glean.gross_code

B3
glean.gross_code() -> Code

Construct the [[144, 12, 12]] bivariate-bicycle "gross code" (Bravyi et al., Nature 2024; literature note 002). n=144, k=12, weight-6, CSS.

Validated: B3.

class Code

A CSS code with its decoder attached. Decoding targets the X-error sector: the syndrome comes from Hz, and the logical-failure test is against rowspace(Hx).

MemberSignatureDescription
nproperty → intNumber of physical qubits.
kproperty → intNumber of logical qubits.
hz_syndrome(error: list[int])list[int]Z-stabilizer syndrome of an X error (n bits in, check bits out).
decode(syndrome, p, max_iter=30, alpha=0.9, osd_order=10)BP + OSD decode of a Z-syndrome into an X correction (n bits). p is the per-qubit prior. See osd_order.
is_logical_failure(error, correction)boolTrue if error ⊕ correction is a nontrivial logical operator (not a stabilizer).

glean.simulate_toric

glean.simulate_toric(l, p, shots, seed=0, max_iter=30, alpha=0.9, osd_order=10)

Code-capacity Monte-Carlo on the toric code. Returns (failures, shots, logical_error_rate, stderr).

simulate.py
failures, shots, ler, stderr = glean.simulate_toric(l=6, p=0.05, shots=5000)

2 · Circuit-level decoding from a Stim DEM

The real workflow: ingest a flattened Stim detector error model (DEM) and decode batches of syndromes entirely in Rust.

glean.load_dem

glean.load_dem(text, num_detectors, num_observables) -> DetectorErrorModel

Ingest a flattened Stim DEM string — the text of circuit.detector_error_model(flatten_loops=True). num_detectors and num_observables are the authoritative counts from the DEM (literature note 001).

class DetectorErrorModel

Decodes detector syndromes into predicted logical-observable flips. The Tanner graph is built once and cached on the object, then reused across every decode.

Accessors (properties, no parentheses)

PropertyDescription
num_detectorsSyndrome bits / check rows.
num_observablesLogical observables.
num_mechanismsError mechanisms (columns of the check matrix).
priorsPer-mechanism prior error probabilities (one per column). The rare-event estimators read these.

BP + OSD-CS

B3

The validated baseline — B3: == Roffe's ldpc to 99.8% / <0.4σ on the gross code.

MethodSignatureReturns
decode(syndrome, max_iter=30, alpha=0.9, osd_order=10)One predicted observable-flip list (num_observables bits).
decode_batch(syndromes, max_iter=30, alpha=0.9, osd_order=10, parallel=False)One predicted-flip list per shot; the whole loop stays in Rust (one PyO3 crossing). parallel=True spreads shots across cores with rayon (GIL released) — output bit-identical to serial.
decode_dem.py
import glean, stim, numpy as np

circuit = stim.Circuit.generated(
    "surface_code:rotated_memory_z", distance=5, rounds=5,
    after_clifford_depolarization=1e-3)
dem = circuit.detector_error_model(flatten_loops=True)

g = glean.load_dem(str(dem), dem.num_detectors, dem.num_observables)
sampler = circuit.compile_detector_sampler()
det, obs = sampler.sample(shots=10_000, separate_observables=True)

pred = np.asarray(g.decode_batch(det.tolist(), osd_order=10, parallel=True))
ler = (pred != obs).any(axis=1).mean()

relay-BP

B4

The OSD-free, real-time differentiator — B4: == the authors' relay_bp crate, <0.95σ; real-time iteration budget met.

MethodSignatureReturns
decode_relay(syndrome, gamma0=0.1, pre_iter=80, num_sets=60, set_max_iter=60, gamma_dist=(-0.24, 0.66), stop_nconv=1, alpha=1.0, seed=0)(observable_flips, total_bp_iterations).
decode_batch_relay(syndromes, …same…, parallel=False)(flips_per_shot, total_bp_iterations_per_shot). Each shot re-seeds from seed, so parallel=True is bit-identical to serial.

gamma_dist is the (low, high) uniform memory-disorder interval. stop_nconv is the "-S" in "Relay-BP-S" (the number of converged relay legs to ensemble). The returned per-shot iteration count is the real-time budget metric (B4: median 16 / p90 212 ≪ 600).

decode_relay.py
pred, iters = g.decode_batch_relay(
    det.tolist(), gamma0=0.1, pre_iter=80, num_sets=60, set_max_iter=60,
    gamma_dist=(-0.24, 0.66), stop_nconv=5, alpha=1.0, seed=0, parallel=True)
# iters[i] = total BP iterations the decoder spent on shot i.

3 · BP + OTF (Ordered Tanner Forest)

B7

The OSD-free, inversion-free, almost-linear-time third decoder paradigm (deMarti iOlius et al. 2024; literature note 006). BP+OTF needs an offline-built sparsified DEM plus a transfer-matrix incidence, produced by benchmarks/otf_sparsify.py; see benchmarks/bp_otf_gross.py for the end-to-end build.

B7 — ensemble BP+OTF == Glean's BP+OSD-0, 0.33σ.

glean.load_bp_otf

glean.load_bp_otf(full_dem_text, sdem_dem_text, num_detectors, num_observables, merge_entries) -> BpOtf

Build a BP+OTF decoder from the full + sparsified flattened DEM texts and the transfer-matrix incidence merge_entries (each (k, j): full-DEM fault k piles into sparsified fault j).

class BpOtf

MemberSignatureDescription
num_detectors / num_observablespropertyShared DEM counts.
num_mechanismspropertyFull-DEM columns.
num_mechanisms_sdempropertySparsified-DEM columns.
decode(syndrome, s1_iter=391, s2_iter=113, otf_iter=113, alpha=1.0)(observable_flips, stage)stage is the pipeline stage (1/2/3) that solved the shot, or 0 on total failure.
decode_batch(syndromes, …same…, parallel=False)(flips_per_shot, stage_per_shot). parallel=True is bit-identical to serial.

A single BP+OTF decoder undershoots BP+OSD-0 (note 006 §IV expected); the published accuracy is recovered by a 23-member ensemble of decoders over different sparsified DEMs — see bp_otf_gross.py.

4 · Rare-event estimators

The deepest published logical-error rates (~10⁻⁷) are infeasible by direct Monte-Carlo (~10⁹ shots). Glean ships two decoder-agnostic importance-sampling estimators that reuse the same DetectorErrorModel (H / observables / priors) and batched decode. Both live in lazily-imported pure-Python modules and depend on numpy.

glean.dss — Dynamical Subset Sampling

B8

Heußen, Winter, Rispler, Müller, Phys. Rev. Research 6, 013177 (2024); literature note 007. Estimates p_L(p) = Σ_w A_w · p_fail^(w) by stratifying on the fault weight w: the weight probabilities A_w are exact and decoder-independent (Poisson–binomial of the priors), so only the few contributing low-w classes (w ≳ d/2) need sampling.

B8 — unbiased, 0.0σ vs direct MC at p=3×10⁻³.

FunctionSignatureDescription
estimate(dem, decode_fn, weights, shots=0, *, seed=0, z=1.0, w_max=None, chunk=50000, progress=None)End-to-end: returns a DssEstimate. weights is an iterable (each gets shots) or a {w: shots_w} map.
sample_pfail(dem, decode_fn, weights, shots=0, *, seed=0, chunk=50000, progress=None)The only part that decodes. Returns {w: (shots, fails)}. p-independent — sample once, recombine per target p.
combine(measured, a, *, z=1.0, w_max=None)Combine a sample_pfail table with A_w(p) at any target rate. Reuse one table to trace the whole p_L(p) curve.
weight_probs(dem, w_max)np.ndarrayThe exact A_w for w = 0..w_max. Decoder-independent.
wilson_interval(fails, shots, z=1.0)(lo, hi)Wilson score CI; a positive upper bound even when fails == 0 (what bounds an unsampled-failure low-w class).

decode_fn(syndromes) is any decoder: syndromes is a list[list[int]], the return is array-like (len(syndromes), num_observables). For relay-BP, wrap to drop the iteration counts. DssEstimate fields: p_L, err, p_L_lo, p_L_hi (rigorous truncation bracket), w_max, tail_mass, classes, total_shots.

dss.py
import glean, numpy as np

g = glean.load_dem(dem_text, n_det, n_obs)
decode_fn = lambda s: np.asarray(g.decode_batch(s, osd_order=10, parallel=True))

est = glean.dss.estimate(g, decode_fn, weights=range(1, 13), shots=50_000)
print(est.p_L, "+/-", est.err, "bracket", (est.p_L_lo, est.p_L_hi))

# Trace a curve from ONE decode campaign (p_fail^(w) is p-independent):
table = glean.dss.sample_pfail(g, decode_fn, weights=range(1, 13), shots=50_000)
for p in (1e-3, 2e-3, 3e-3):
    g_p = glean.load_dem(build_dem_at(p), n_det, n_obs)   # your DEM rebuilt at rate p
    A = glean.dss.weight_probs(g_p, w_max=12)             # A_w depends on p; p_fail^(w) does not
    print(p, glean.dss.combine(table, A).p_L)

DEM-level primitives (on DetectorErrorModel)

  • dss_weight_probs(w_max)A_w[0..w_max] — decoder-independent Poisson–binomial pmf.
  • dss_sample(w, count, seed=0)(syndromes, true_observable_flips) — draw count fault sets of exactly weight w from Pr(S) ∝ ∏_{i∈S} p_i/(1−p_i), then decode + compare for p_fail^(w).

glean.splitting — splitting + Bennett ratio

B9 · B10

Bravyi & Vargo, Phys. Rev. A 88, 062308 (2013); circuit-level extension Mayer et al. 2025; literature note 008. The deep-number pin: a telescoping product P_L(p_target) = P_L(p_1) · ∏_j R_j where each ratio is estimated by Bennett's acceptance-ratio method over a Metropolis-in-F chain — no resolution wall at depth.

B9 — unbiased vs direct MC at p=3×10⁻³ (0.07σ) and p=2×10⁻³ (0.30σ); pins the Bravyi Nature deep point (B10).

FunctionSignature (abridged)Description
estimate(dem, p_seq, priors_per_rate, seed_log_pL, seed_log_pL_err, *, n_chains=8, burn_in=50000, n_samples=4000, thin=2000, …, chain_fn=None)Telescope P_L(p_seq[-1]) down p_seq. The decoder is fixed at the target rate, so the estimate is the standard matched LER. Returns a SplittingEstimate.
bennett_log_ratio(s_j, s_jp1, a_const, *, bracket=(-400,400), tol=1e-10, max_iter=200)Solve BV Eq. 10 for M = ln R_j by bisection (monotone residual).

SplittingEstimate fields include pL, pL_lo, pL_hi, log_pL, log_pL_err, rhat (Gelman–Rubin mixing diagnostic), p_seq, and ratios (the per-rate breakdown). The chained confidence bound is a heuristic (Mayer §V.1); honesty is reported via rhat < 1.1.

DEM-level primitives (on DetectorErrorModel)

  • splitting_find_failing(seed_priors, max_tries=200000, seed=0, …) — seed a chain by finding one BP+OSD-failing config. Returns fired-mechanism indices or None.
  • splitting_sample(odds, init, seeds, burn_in=2000, n_samples=2000, thin=10, …) — run one Metropolis chain per seeds entry (parallel, GIL released).

See benchmarks/splitting_gross.py for the full deep-point harness.

5 · sinter integration

B6

class glean.SinterDecoder

SinterDecoder(method="bp_osd", *, parallel=False, **params)

A drop-in decoder for Stim's sinter.collect(...) pipeline, conforming to the de-facto sinter.Decoder interface (compile_decoder_for_dem → decode_shots_bit_packed).

B6 — bit-exact vs the direct decode_batch / decode_batch_relay path; reproduces B3/B4 LER through sinter.collect.

  • method: "bp_osd" → BP+OSD-CS (B3 baseline); "relay" → relay-BP (B4).
  • parallel: spread shots across cores with rayon inside each decode (output identical to serial).
  • **params: forwarded to the batch decode — osd_order, max_iter, alpha for bp_osd; num_sets, stop_nconv, gamma_dist, seed for relay. Instances are picklable, so sinter can ship them to worker processes.
collect.py
import sinter, glean

results = sinter.collect(
    num_workers=8, tasks=tasks,
    decoders=["glean_bp_osd"],
    custom_decoders={"glean_bp_osd": glean.SinterDecoder(method="bp_osd")},
)

Requires quantumlib's Stim sinter (pip install sinter). import glean itself stays sinter-free (the adapter is imported lazily on first access of glean.SinterDecoder).

6 · Conventions

osd_order — the post-processor

Code.decode / DetectorErrorModel.decode* take osd_order to select the OSD post-processor: a negative value → OSD-0 (zeroth order); λ ≥ 0 → OSD-CS-λ (combination-sweep of order λ). The validated default across the library is osd_order=10 (BP+OSD-CS-10).

parallel — rayon batch parallelism

Every *_batch* method accepts parallel=False (default). parallel=True releases the GIL and spreads shots across cores with rayon, allocating one workspace per worker. The output is bit-identical to serial (same observables, same iteration counts) — purely a throughput knob (B5: 4–5× on a 10-core M4). The serial path stays rayon-free at runtime.

Real-time budget

relay-BP's headline real-time claim is the per-shot BP iteration count returned alongside the predictions (not wall-clock µs, which are offline/relative). B4 reports median 16 / p90 212 iterations against a 600-iteration budget.

See also