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.
| Surface | Kind | Imports on first use |
|---|---|---|
toric_code, gross_code, load_dem, load_bp_otf, simulate_toric + the Code / DetectorErrorModel / BpOtf classes | Rust core | — |
glean.dss | pure-Python estimator | numpy |
glean.splitting | pure-Python estimator | numpy |
glean.SinterDecoder | pure-Python sinter.Decoder adapter | sinter |
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).
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
B1Construct 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
B3Construct 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).
| Member | Signature | Description |
|---|---|---|
n | property → int | Number of physical qubits. |
k | property → int | Number 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) → bool | True if error ⊕ correction is a nontrivial logical operator
(not a stabilizer). |
glean.simulate_toric
Code-capacity Monte-Carlo on the toric code. Returns (failures, shots, logical_error_rate, stderr).
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
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)
| Property | Description |
|---|---|
num_detectors | Syndrome bits / check rows. |
num_observables | Logical observables. |
num_mechanisms | Error mechanisms (columns of the check matrix). |
priors | Per-mechanism prior error probabilities (one per column). The rare-event estimators read these. |
BP + OSD-CS
B3The validated baseline — B3: == Roffe's ldpc to 99.8% / <0.4σ on the gross
code.
| Method | Signature | Returns |
|---|---|---|
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. |
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
B4The OSD-free, real-time differentiator — B4: == the authors' relay_bp crate,
<0.95σ; real-time iteration budget met.
| Method | Signature | Returns |
|---|---|---|
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).
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)
B7The 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
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
| Member | Signature | Description |
|---|---|---|
num_detectors / num_observables | property | Shared DEM counts. |
num_mechanisms | property | Full-DEM columns. |
num_mechanisms_sdem | property | Sparsified-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
B8Heuß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⁻³.
| Function | Signature | Description |
|---|---|---|
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.ndarray | The 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.
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)— drawcountfault sets of exactly weightwfromPr(S) ∝ ∏_{i∈S} p_i/(1−p_i), then decode + compare forp_fail^(w).
glean.splitting — splitting + Bennett ratio
B9 · B10Bravyi & 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).
| Function | Signature (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 orNone.splitting_sample(odds, init, seeds, burn_in=2000, n_samples=2000, thin=10, …)— run one Metropolis chain perseedsentry (parallel, GIL released).
See benchmarks/splitting_gross.py for the full deep-point harness.
5 · sinter integration
B6class glean.SinterDecoder
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,alphaforbp_osd;num_sets,stop_nconv,gamma_dist,seedforrelay. Instances are picklable, sosintercan ship them to worker processes.
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.