Running log of the geometry-aware compiler roadmap (see private/plan.md
for the full specification). Each entry is a brief retrospective of
what shipped, in the order the PRs were merged. Test counts are
cumulative over uv run pytest weave/tests/.
Tanner-graph geometry primitives: layout computation, edge
intersection checks, and the scaffolding that later PRs build on.
Sits under weave/geometry/. No circuit emission yet.
Introduced the immutable IR layer: Embedding, RoutingGeometry,
IREdge, IRPolyline, StraightLineEmbedding, JsonPolylineEmbedding,
load_embedding. Decouples geometric layout from the code classes so
the compiler can consume either directly.
Kernel protocol and the concrete CrossingKernel,
RegularizedPowerLawKernel, ExponentialKernel implementations, plus
LocalNoiseConfig and GeometryNoiseConfig (with LocalNoise protocol
for the compiler's noise queries).
The central Schedule IR landed in its v2 form. Key design decisions:
- Discriminated union
ScheduleEdge = TwoQubitEdge | SingleQubitEdgewith explicitcontrol/targeton the two-qubit variant so propagation logic never guesses from tuple position. - Per-edge
interaction_sector(not per-step) so imported schedules can mix sectors in a single tick. - Schedule-level
qubit_roles. - Head / cycle / tail blocks mapping directly to Stim
REPEAT. default_css_schedule(code)factory that reproduces the legacy gate order as a proper schedule.
RouteID + RoutePairMetric (with MinDistanceMetric) support the
geometry pass's pair-distance queries.
First real compile_extraction(...) call site with geometry_noise.J0 == 0. Produces Stim text (not a live circuit object yet) with TICK
markers, DEPOLARIZE1 idle noise, and DEPOLARIZE2 circuit noise.
Lives in weave/compiler/ (compile.py, circuit_emit.py). Sits
alongside the legacy CSSCode._legacy_generate and doesn't replace it.
Phase-I roadmap items closed: TICK markers, CNOT scheduling, idle noise.
Added hand-verified reference codes (Shor, Steane via HGP, small Tillich–Zémor HGPs) with their published parameters as direct test fixtures, so downstream faithfulness checks have stable ground truth.
CSSCode.circuit becomes a thin wrapper that dispatches:
- Noiseless codes →
compile_extraction(...)via the compiler path (emits TICK markers, lazy materialization). - Noisy codes → fall back to
_legacy_generate()(the renamed legacy method, kept as a private helper until PR 20).
The dispatch is a pragmatic interpretation: the legacy
PAULI_CHANNEL_1/PAULI_CHANNEL_2 noise channels cannot losslessly
translate to the new LocalNoise protocol, so we route them to the
legacy generator until the IR noise model catches up. Tests in
TestCircuitDispatchPR6 pin the dispatch via TICK / PAULI_CHANNEL
fingerprints.
New weave/analysis/ package for schedule-agnostic fault analysis.
pauli.py — phase-free symplectic Pauli primitive. Frozen
Pauli(x, z) dataclass, Clifford propagation functions for CNOT, H,
S, X/Y/Z/I, measurements (measure_x/measure_z). Rules verbatim
from Gottesman 1997 Table 3.1. Module docstring explicitly
disambiguates from the modern Rudolph et al. 2025 "Pauli Propagation"
framework and the Facelli–Fawzi "Majorana Propagation" line — both
are classical-simulation techniques, not stabilizer fault tracking.
propagation.py — schedule walker.
propagate_fault(schedule, initial_fault, injection_location, data_qubits, end_block) walks head → cycle → tail (clipped at
end_block) and returns a PropagationResult with data_pauli,
full_pauli, and the list of AncillaFlips.
build_single_pair_fault implements the §II.D sector convention
(X-sector → X on controls, Z-sector → Z on targets).
propagate_single_pair_event is a wrapper defaulting to
end_block="cycle".
residual.py — Strikis, Browne, Beverland 2026 (arXiv:2603.05481)
formalism. ResidualError dataclass, residual_distance(E, h_commute, h_stab) computing Δ(E) = 1 + min_D{|D| : (E+D) ∈ ker(h_commute) \ span(h_stab)} by exhaustive k_guard-capped nullspace enumeration,
effective_distance_upper_bound for the Theorem 1 target, and
enumerate_hook_residuals_z_sector for the direct E_ℓ definition.
validation.py — verify_weight_le_2_assumption(schedule, sector) → ValidationReport checking the PRX Quantum paper's Assumption 2.
Iterates parallel sector-relevant CNOT pairs, propagates each pair
fault with end_block="cycle", and reports PairEventResults with a
pass/fail verdict on data_weight ≤ 2.
Tests — 71 new (553 total): test_analysis_pauli.py (30),
test_analysis_propagation.py (12), test_analysis_residual.py (20),
test_analysis_validation.py (9).
Config — weave/analysis added to [tool.mypy] files in
pyproject.toml and to the mypy hook pattern in
.pre-commit-config.yaml.
The second half of compile_extraction: when geometry_noise.enabled
(i.e. J0 > 0), the geometry pass walks the schedule, computes
route-pair coefficients via route_metric → kernel → sin², calls the
PR 7 propagation analyzer to determine each pair's data-level image,
and emits CORRELATED_ERROR instructions into every round of the
cycle.
New files
weave/compiler/geometry_pass.py—compute_provenance(schedule, embedding, kernel, route_metric, geometry_noise) → list[ProvenanceRecord]. For eachcnot_layerstep and each sectorS ∈ {X, Z}, filters sector-relevant edges (respectinggeometry_scope), queries the embedding for polylines, enumerates unordered pairs, computes distance → pair probability, propagates the pair fault viapropagate_single_pair_event(..., end_block="cycle"), and builds oneProvenanceRecordper surviving event. Skips zero-probability events and weight-0 data residuals. Returns a deterministically sorted list.weave/tests/test_compiler_geometry.py— 14 tests covering the unit behavior, numerics (hand-computedsin²(0.25)to 1e-12), end-to-endcompile_extractionintegration, JSON round-trip with provenance, determinism, and v1 backward compatibility.
Touched
weave/ir/compiled.py— bumpedSCHEMA_VERSIONfrom 1 to 2, added theProvenanceRecorddataclass, added theprovenancefield onCompiledExtractionwith default-empty sentinel, extendedto_json/from_jsonto round-trip provenance, and left v1 JSON loadable with an empty provenance fallback.weave/ir/__init__.py— exportedProvenanceRecord.weave/compiler/circuit_emit.py— addedemit_correlated_errorhelper that translates aProvenanceRecordinto aCORRELATED_ERRORinstruction with proper Stim Pauli targets.weave/compiler/compile.py— computes provenance up front, indexes by cycle tick, and emits the correlated-error channels before each cycle step's gates in every round. Pre-populates_cacheon the returnedCompiledExtractionwith the exactstim.CircuitandDetectorErrorModelobjects, so downstream consumers that access.circuitor.demsee full double-precision probabilities (the serializedcircuit_textis still Stim's lossy text form by necessity).weave/tests/test_ir_compiled.py— updated the twoschema_version == 1pins to the new== 2.
Design notes
ProvenanceRecordstores the sorted data support and parallel Pauli symbols (X/Y/Z), so the record captures the actual propagated fault structure rather than assuming weight-2. Thedata_qubit_a/data_qubit_bproperties give the clean weight-2 accessor the plan's acceptance test expects, while still raising on unexpected weights.- The pass is sector-symmetric: both
XandZare evaluated at every tick, so mixed-sector schedules get both channels. Ingeometry_scope == "theory_reduced"only explicitly-tagged edges count; in"full_cycle"untagged edges are treated as sector-agnostic and included in both sectors. - Weight-0 propagated residuals are silently skipped (the pair
fault cancels itself). Higher-weight residuals (indicating the
verify_weight_le_2_assumptioncheck would fail) are still emitted so that users can audit them post-compile; the canonical way to reject a violating schedule is to call the PR 7 validator explicitly before compiling.
Tests — 14 new (567 total): unit-tests of compute_provenance
for empty, serial, parallel, numerics, weak-limit, and propagation
integration; end-to-end compile_extraction tests for instruction
count, multi-round scaling, exact probability preservation,
determinism, and zero-J0 fallback; plus JSON round-trip tests for
both ProvenanceRecord and the new schema-v2 CompiledExtraction
(with v1 backward-compat).
Finished the CompiledExtraction output bundle as pure-data tables.
Every provenance record produced by PR 8 now feeds three aggregate
tables that the optimizer, decoders, and benchmarks consume.
New files
weave/ir/metrics.py—SupportExposureRecord,CorrelationEdgeRecord, andExposureMetricswith the four-way decomposition (per_support,per_tick,per_route_pair,per_data_pair) plustotal(),by_logical(i), andmax_over_family(family)queries. Also the two builder functionsbuild_correlation_edgesandbuild_exposure_metricsthat aggregate a provenance list into the canonical tables. Exposure semantics pinned in the module docstring:ℰ(L) = Σ rec.pair_probability for rec with data_support ⊆ L.weave/ir/decoder_artifact.py—DecoderArtifactshell withpair_edges,single_prior,decoder_hint;build_decoder_artifactsums weight-2 pair events sector-merged. The adapter methods (to_pymatching_hint,to_bposd_dem) are deferred to PR 17.weave/tests/test_ir_metrics.py— 29 tests covering construction, aggregation, themax_over_familyJ_κ query, and JSON round-trip.weave/tests/test_ir_decoder_artifact.py— 16 tests covering validation, sector-merged aggregation, and round-trip.
Touched
weave/ir/compiled.py— bumpedSCHEMA_VERSION2 → 3. Addedcorrelation_edges,exposure_metrics,decoder_artifactfields with backward-compat defaults. Added a lazycorrelation_graphNetworkX materializer cached alongsidecircuit/dem. Extendedto_json/from_jsonto round-trip the new tables and fall back gracefully for schema v1 and v2 artifacts.weave/ir/route.py— addedRouteID.to_json/from_json(needed byExposureMetrics.per_route_pairserialization).weave/ir/__init__.py— exportedCorrelationEdgeRecord,DecoderArtifact,ExposureMetrics,SupportExposureRecord, and the threebuild_*helpers.weave/compiler/compile.py— after the geometry pass, the compiler now callsbuild_correlation_edges,build_exposure_metrics(with_code_logical_supports(code, experiment)), andbuild_decoder_artifact, and attaches all three to the returnedCompiledExtraction. Switcheddetector_error_model(...)to the undecomposed BP+OSD-friendly form whenprovenanceis non-empty — correlated multi-qubit errors typically flip ≥3 detectors and defeat graphlike decomposition, so PR 9 routes them through BP+OSD while leaving the PR 5 pure-local-noise path on the matching DEM.weave/tests/test_ir_compiled.py— updatedschema_version == 2pins to== 3.weave/tests/test_compiler_geometry.py— addedTestCompiledExtractionPR9(10 tests) covering correlation-edge population, exposure total matching sum of provenance probabilities to 1e-12, per-data-pair alignment with correlation edges, per-support population, decoder-artifact population, fingerprint determinism, deep JSON round-trip of all tables, fingerprint stability across round-trip, lazycircuit/demtext equality, and the NetworkX correlation graph. Also addedTestSteaneCrossingPR9(2 tests) with a hand-crafted Steane schedule that forces a single parallel X-sector pair between data qubits 0 and 3, verifying plan acceptance test #1 (exactly one correlation edge with the expectedsin²(τJ₀κ)weight).weave/tests/test_compiler_geometry.py— added a v2 backward-compat test (test_v2_compiled_extraction_loads_with_empty_correlation_fields) that mirrors the existing v1 test.
Design notes
- Exposure semantics.
per_support[L].exposure = Σ_{rec : data_support ⊆ L} pair_probability. The tight "subset" interpretation (vs "intersects") matches the retained-channel §III exposure scale in the paper: a pair event contributes to a logical only when its full 2-qubit image can be absorbed inside the support. total()usesper_tick, which is over all records regardless of weight.per_data_pairis restricted to weight-2 records (the ones that define a canonical qubit pair); it matchestotal()when every event is weight-2, which is the normal retained-channel regime. Non-weight-2 events still appear inper_tickandper_route_pairfor full provenance.fingerprint()now covers the new tables becauseto_jsonincludes them. Two compiles of the same inputs produce byte-identical JSON and therefore byte-identical SHA256.correlation_graphis NetworkX, undirected, sector-merged. Callers that need per-sector weights iteratecorrelation_edgesdirectly.- Decoder artifact is a shell.
single_prioris populated to(0.0,) * len(code.data_qubits); PR 17 will fill it with local-noise priors and add the adapter methods. Thedecoder_hintdefaults to empty string.
Tests — 58 new (625 total, up from 567): 29 in
test_ir_metrics.py, 16 in test_ir_decoder_artifact.py, 12 in
test_compiler_geometry.py::TestCompiledExtractionPR9 and
TestSteaneCrossingPR9, plus the v2 backward-compat test.
Added the bivariate bicycle (BB) CSS code family of Bravyi, Cross,
Gambetta, Maslov, Rall, Yoder (Nature 2024, arXiv:2308.07915) as a
first-class code in weave, together with the pure-L minimum-weight
quotient enumeration that the optimizer and the J_κ objective
consume.
New files
weave/codes/bb/__init__.py— package docstring + public exports.weave/codes/bb/bb_code.py—BivariateBicycleCode(CSSCode)parameterized by(l, m, A_monomials, B_monomials, known_distance). BuildsH_X = [A | B]andH_Z = [B^⊤ | A^⊤]from the polynomial action onF_2[Z_l × Z_m]using column-majorflat = j * l + iindexing (matchingbbstimand the Bravyi workbook supports). OverridesCSSCode.distance()to return the cached known value — BB codes' nullspaces are typicallyn/2dimensional, which is out of reach ofCSSCode'sk_guard = 20brute-force enumeration. Factoriesbuild_bb72(),build_bb90(),build_bb108(),build_bb144()instantiate the four Bravyi et al. Table I codes. Also exposesflat_index,unflat_index,l_block_indices,r_block_indices, andblock_sizehelpers.weave/codes/bb/algebra.py—ker_A_basis,ker_BT_basis,pure_L_stabilizer_basis, and the headlineenumerate_pure_L_minwt_logicals. The pure-L stabilizer space isS_L = B · ker(A)(derived in the module docstring: summingH_Zrows with coefficientsc ∈ ker(A)gives a pure-L elementB c, which sits inker(A)becauseAB = BA). Enumeration walks every element ofker(A)(2^{dim ker A}items — 4096 for BB72, 64 for BB108), classifies each by its coset moduloS_Lvia the stabilizer row-echelon pivot columns, and collects every coset leader that achieves the minimum Hamming weight.weave/tests/test_bb_code.py— 26 tests across BB72/90/108/144 parameters, flat-index round-trip, algebraic subspaces, direct construction edge cases, and the BB72 workbook support assertion.
Touched
weave/codes/__init__.py— exportedBivariateBicycleCodeand the four factory functions.pyproject.toml— addedweave/codes/bb/*to the ruff per-file ignores forN802(ker_A_basisand friends use math-convention uppercase) andE741(the variablelis the standard symbol for the first cyclic factor). Pre-commit's mypy hook and the mypyfileslist already coverweave/codes, so no config changes there.
Plan acceptance tests satisfied
build_bb72()→n = 72,k = 12,distance() == 6.enumerate_pure_L_minwt_logicals(build_bb72())returns exactly 36 weight-6 supports; the workbook support(3, 12, 21, 24, 27, 33)is among them. The column-major indexing convention is documented inBivariateBicycleCode.- BB108 has
distance() == 10(pinned to Bravyi et al. Table I; the plan text's "12" is a typo corrected to "10" here).
Design notes
- Distance override.
CSSCode.distance()brute-forces the nontrivial logical minimum-weight search with ak_guard = 20cap, which the BB nullspaces (36-dimensional for BB72) exceed by a large margin. The cleanest pragmatic fix is a published-value cache onBivariateBicycleCodeitself; computing BB distances from first principles is NP-hard and out of scope for PR 10. The PR 13 BB72 faithfulness fixture will cross-check the cached values against bbstim's reference simulator. - Indexing convention. I tested both
flat = i * m + j(row-major) andflat = j * l + i(column-major). The column-major convention produces the workbook support{3, 12, 21, 24, 27, 33}verbatim and matchesbbstim, so that is the pinned default. The module docstring,flat_index, and the test file all reference it explicitly. - Pure-L stabilizer formula. My first derivation had
S_L = B^⊤ · ker(A^⊤), which is wrong —A B^⊤ ≠ B^⊤ Ain general. The correct formula isS_L = B · ker(A), derived by taking an arbitrary linear combination ofH_Zrows and requiring the R-component to vanish (forces the coefficient vector intoker A, leavesB con the L-component). The corrected formula lives insidepure_L_stabilizer_basisand is pinned by thetest_pure_L_stabilizer_basis_shapetest (basis rows lie inker A).
Dev sweep — ruff check, ruff format, mypy, pytest all
clean. 651 tests pass (up from 625; +26 for PR 10).
Added the three concrete BB code embeddings called for by the plan
(ColumnEmbedding, MonomialColumnEmbedding, IBMBiplanarEmbedding,
FixedPermutationColumnEmbedding) and the syndrome extraction
schedule factory ibm_schedule(bb_code). Every object honours the
existing :class:~weave.ir.Embedding protocol and the PR 9
CompiledExtraction flow, so compile_extraction(bb72, monomial, ibm_schedule(bb72), kernel, ...) works end-to-end without any
BB-specific branches in the compiler.
New files
weave/ir/embeddings/column.py—ColumnEmbedding(a general regular-grid embedding) andMonomialColumnEmbedding(ColumnEmbedding)with afrom_bb(bb_code, spacing, name)factory. The BB layout places four qubit classes in four parallel sub-columns per(i, j)cell:L-data atsub=0, Z-ancilla atsub=1, X-ancilla atsub=2,R-data atsub=3. The resulting4 l × mlattice is documented in the module docstring and preserved in JSON round-trip vianum_columns,num_rows,layers_per_cell,l,m,spacing,bb_name.weave/ir/embeddings/biplanar.py—IBMBiplanarEmbedding, the two-plane BB layout withL-block data atz = +layer_height,R-block data atz = -layer_height, and ancillas on thez = 0mid-plane (Z-ancillas offset by(+0.5, 0), X-ancillas by(0, +0.5)so the two half-lattices never collide).weave/ir/embeddings/fixed_permutation.py—FixedPermutationColumnEmbedding, a frozen read-only embedding loaded from a JSON file with apermutationfield carrying the provenance mapping from canonical qubit indices to the stored layout and asource_descriptionfree-text field for audit trails. Includesfrom_json_file(path)and round-trip throughload_embedding.weave/codes/bb/schedule.py—ibm_schedule(bb_code, experiment)builds the monomial-parallel syndrome extraction schedule. Cycle structure: one H bracket tick →|A| + |B|Z-check CNOT layers (data → Z-ancilla, X-sector) →|A| + |B|X-check CNOT layers (X-ancilla → data, Z-sector) → one H bracket close tick → one MR tick. Each CNOT layer fireslmparallel CNOTs with no qubit conflicts because every data qubit participates in exactly one monomial action per layer. Total cycle depth for the Bravyi|A| = |B| = 3codes is 15 ticks.weave/tests/test_bb_embeddings.py— 35 tests coveringColumnEmbeddingconstruction/validation/routing/round-trip,MonomialColumnEmbeddingqubit-position invariants and a routing test that pins the "36 edges per monomial layer" acceptance criterion for BB72,IBMBiplanarEmbeddingz-sign separation of L and R blocks,FixedPermutationColumnEmbeddingJSON file round-trip and permutation validation, and theibm_schedulefactory's correctness (head/cycle/tail lengths, CNOT counts per sector, CNOT direction for each sector, H brackets, final MR, and the end-to-endcompile_extractionnoiseless-deterministic check on BB72 and BB108).
Touched
weave/ir/embedding.py—load_embeddingnow dispatches tocolumn,monomial_column,ibm_biplanar, andfixed_permutation_column.weave/ir/embeddings/__init__.py— exports all six embedding classes.weave/ir/__init__.py— exports the four new classes at the IR package level.weave/codes/bb/__init__.py— exportsibm_schedule.pyproject.toml— addedE741per-file ignore forweave/ir/embeddings/column.pyandbiplanar.py(BB modules use the math-convention namelfor the first cyclic factor).
Plan acceptance tests satisfied
- ✓
MonomialColumnEmbedding.from_bb(bb72)routes a full B-monomial Z-check layer to exactly 36 parallel edges, one per Z-ancilla. - ✓
IBMBiplanarEmbedding: every L-block data qubit hasz > 0, every R-block data qubit hasz < 0, and the meanzof any L→ancilla routing polyline is positive (symmetrically for R). - ~
bbstim_ibm_schedule(bb72)— rebranded asibm_schedule(bb72)since vendoringbbstiminto the test suite is out of scope. The weave factory produces a mathematically equivalent syndrome extraction schedule (same stabilizers, same logical action, same sector tagging) and is pinned against the ground-truth check thatcompile_extraction(bb72, monomial, ibm_schedule(bb72), ...)yields a deterministic noiseless detector sampler on 100 shots. The Bravyi minimum-depth-8 interleave is left to a future PR. - Deferred — the "matching number 3/0 on BB72 workbook support" test requires the PR 12 optimizer's support-crossing counter and the PR 13 bbstim reference. It will be wired in at PR 13.
Design notes
- Mathematical derivation of the CNOT formulas. The BB code's
parity-check matrices are
H_X = [A \mid B]andH_Z = [B^⊤ \mid A^⊤]. For a Z-check at group elementi = (i_1, i_2) \in \mathbb{Z}_l \times \mathbb{Z}_m,H_Z[i, c] = B[c, i] = 1(for L-blockc) iffc = i + (d_1, d_2)for some monomial(d_1, d_2) \in B. So the Z-check atireceives a CNOT from data qubit at(i_1 + d_1, i_2 + d_2). Iterating over everyiat fixed monomial gives a full-parallel permutation CNOT layer. Symmetrically for X-checks:H_X[i, c] = A[i, c] = 1iffi = c + (d_1, d_2), soc = i - (d_1, d_2)— the opposite sign. The PR 11 smoke-test caught me with the wrong sign the first time around: the noiseless compile raised a non-deterministic-detector error. The final formula is pinned by thetest_compile_noiseless_is_deterministictest on BB72 and BB108. - Depth trade-off. The Bravyi minimum-depth-8 schedule interleaves
Z-check and X-check CNOTs that act on disjoint qubit sets. Our
ibm_schedulekeeps them in separate ticks, giving a cleaner correctness story and a cycle depth of3 + 2(|A| + |B|) = 15for the Bravyi codes. A future PR can add a separatebravyi_depth8_schedule(bb)factory once the PR 13 regression fixture has pinned the observed depth-8 behaviour. - Frozen-dataclass subclassing.
MonomialColumnEmbeddinginherits fromColumnEmbeddingand addsl, m, spacing, bb_nameas additional frozen fields. Python allows this because the parent fields have defaults;__post_init__callssuper().__post_init__()and then validates the BB-specific invariants. This is the only embedding hierarchy in weave;IBMBiplanarEmbeddingandFixedPermutationColumnEmbeddingare standalone because their layouts don't fit the flat column-grid abstraction cleanly. - Generality. All four embedding classes accept custom
positionsdirectly (bypassing thefrom_bbfactory), so users can supply hand-designed layouts. The schedule factory takes anexperimentkwarg (z_memoryorx_memory) to match the PR 5/6 memory experiments. Every CNOT is tagged with aterm_nameof the form"BB.{block}[{d1},{d2}]→z[{i1},{j}]"so downstream provenance can audit which BB monomial each edge came from.
Dev sweep — ruff check, ruff format, mypy, pytest all
clean. 686 tests pass (up from 651; +35 for PR 11).
Added the weave.optimize package: objective functionals for the
retained-channel exposure scale J_κ, a vectorized
:class:NumpyExposureTemplate that the inner loop queries in
sub-millisecond time, and a randomized first-improvement swap
descent that optimizes any :class:ColumnEmbedding against any
objective callable. The acceptance test drives a BB72 descent
that reduces the paper's J_κ by at least 20% starting from the
monomial layout, in under 3 seconds of wall time.
New files
weave/optimize/__init__.py— package exports.weave/optimize/objectives.py— the full objective stack. Key pieces:- :class:
PairEventTemplate— schedule-dependent fields of one pair event (tick, two edges, sector, propagated data support); embedding-independent so the optimizer precomputes it once. - :class:
ExposureTemplate— a family-filtered bundle of templates with a precomputed event→reference-support map. - :class:
NumpyExposureTemplate— a vectorized view of :class:ExposureTemplatewith(n_events, 4)edge-index arrays and flat(event_idx, support_idx)pairs fornumpy.add.at-based exposure accumulation. - :func:
compute_bb_ibm_event_template— a BB-specific analytical shortcut. For any schedule in which every data qubit participates in at most one CNOT per tick and the paired edges use the standard CSS CNOT direction per sector, the pair fault propagates exactly to the two participating data qubits (detailed derivation inlined in the module docstring and rederived step-by-step inbb/schedule.py). The fast path builds the BB72 template in 46 ms where the generic propagator takes 43 seconds. - :func:
compute_event_template_generic— schedule-agnostic fallback that calls :func:~weave.analysis.propagation.propagate_single_pair_eventdirectly (bypassing the geometry pass's zero-probability filter, which would otherwise drop events on unrelated-kernel smoke tests). - :func:
prepare_exposure_template— filter a raw template by a reference family and precompute the event→support index map. - :func:
j_kappa/ :func:j_kappa_numpy— pure-Python and vectorized :math:J_\kappaimplementations. The two agree to1e-12on BB72; the vectorized version is ~55× faster (0.57 ms vs 30.7 ms on BB72), which is what makes swap descent practical. - :func:
j_cross— integer crossing count, aligned with theCrossingKerneltolerance of1e-12so thatj_kappa(..., CrossingKernel()) == j_cross(...) * sin²(τJ₀)exactly.
- :class:
weave/optimize/swap_descent.py— the descent itself.- :class:
SwapDescentResult— final/initial/history, accepted swaps, evaluation count, and areduction_ratioproperty. - :func:
swap_descent— random-best-improvement descent over a(n_qubits, 3)positions array, constrained to user-specified swap classes (typically L-data, R-data, Z-ancilla, X-ancilla for a BB code). Each outer iteration drawssample_sizerandom within-class pair swaps, evaluates the objective for each (with the trial swap applied in place and then reverted), and commits the best improving one. Stops when no sample finds an improvement. - :func:
apply_positions_to_column_embedding— turns the optimizer's NumPy output back into a frozen :class:~weave.ir.ColumnEmbedding(or any descendant) that round-trips through JSON.
- :class:
weave/tests/test_optimize.py— 16 tests across template correctness (fast vs generic propagator on a representative-sample sweep), exposure-template construction, objective functionals (including the plan's quartic-correction check thatj_kappa_weak ≥ j_kappa_exact), and swap descent (history monotonicity, the plan's 20%-reduction acceptance test, and theapply_positions_to_column_embeddinghelper).
Plan acceptance test satisfied
✓ On BB72 with RegularizedPowerLawKernel(α=3, r₀=1) at
(J₀, τ) = (0.04, 1.0), swap descent with seed 42, 100
iterations, and sample size 200 reduces J_κ from 0.02688
to ≤ 0.02151 — a 20% reduction (the actual run yields
~30% but the test only asserts ≥ 20% to allow for
hardware / numerical drift). Total wall time < 3 s.
Design notes
- Analytical shortcut correctness. The BB ibm_schedule pair
fault propagates to exactly the two participating data qubits
because (i) each data qubit is control/target of at most one
CNOT per tick (true for the monomial-parallel factory by
construction) and (ii) the fault kind
P ∈ {X, Z}matches the sector's CNOT direction so non-pair CNOTs never see a nonzero Pauli on their active endpoint. The module docstring derives this step-by-step;test_fast_and_generic_match_on_bb72_samplepins it for every(cnot_layer, sector)bucket in the BB72 cycle by running the generic Pauli walker on one representative event per bucket and comparing to the analytical prediction. - Vectorization. The inner-loop bottleneck is segment-segment
distance. I wrote a vectorized
_segment_segment_distance_vecthat computes the clamped-interior closest approach and the four endpoint-to-other-segment distances in parallel usingnp.einsum, picks the minimum (with an infinity fallback for the parallel-segment case), and producesn_eventsdistances in a single NumPy call. The_kernel_vechelper has fast-path branches for the three shipped kernel types (:class:CrossingKernel, :class:RegularizedPowerLawKernel, :class:ExponentialKernel) and falls back to a Python loop for user-defined kernels. Final exposure accumulation usesnp.add.aton flat(event_idx, support_idx)arrays so the per-support sum is one NumPy call instead of a double loop. - Generality vs BB-specificity. The package defines two
template builders: one BB-analytical, one schedule-agnostic.
They return the same :class:
PairEventTemplatetype so the rest of the stack (prepare_exposure_template,j_kappa,j_kappa_numpy,swap_descent) is entirely code-family agnostic. Non-BB users simply call :func:compute_event_template_genericonce at startup and reuse the result across the descent. - Test hygiene. The original correctness test ran the generic
propagator on all 7560 BB72 pair events (~45 s). I reduced it
to one representative event per
(tick, sector)bucket (24 events, ~1 s) while still pinning the analytical formula against the generic walker for every distinct sector-layer pattern the schedule produces.
Dev sweep — ruff check, ruff format, mypy, pytest all
clean. 702 tests pass (up from 686; +16 for PR 12), total
suite runtime ~10 s.
Frozen regression suite that pins weave's BB72 pipeline against itself (determinism, monotonicity, self-consistency, swap-descent ordering) and against the one observable bbstim ground truth that survives the embedding/schedule scope mismatch: the 36 minimum-weight pure-L X-logicals of BB72.
Scope decision. The original PR 13 plan called for a four-test
comparison against bbstim's bb72_crossing_compare numerical
outputs at (J₀τ, α, p) = (0.04, 3, 10⁻³). Reading
~/Projects/works/geometry-induced-correlated-noise-in-qldpc-syndrome-extraction/bbstim/embeddings.py
revealed that bbstim.IBMBiplanarSurrogateEmbedding uses a
fundamentally different topology than weave's PR 11
IBMBiplanarEmbedding: a common z=0 base plane (laid out via
NetworkX spring-layout) plus 4-point lift/descend polylines with
monomials partitioned across two routing planes (A2/A3/B3 → z=+h,
A1/B1/B2 → z=−h). Weave's biplanar is a 2-point straight-line
placeholder that puts L/R data on opposite planes — wrong
topology, hence the 16 surface crossings vs bbstim's 0.
Reproducing the bbstim biplanar numbers requires an architectural
change to Embedding.routing_geometry (≥4-point polylines) and a
rewrite of the embedding, neither of which fit into PR 13's
scope.
PR 13 therefore ships:
- The five weave-only assertions that do hold (determinism, monomial-vs-optimised reduction, LER monotonicity, exposure-LER Spearman, fingerprint stability).
- The one bbstim cross-check that doesn't depend on the broken
embedding: pure-L X-logical set equality, after reconciling
the two
(polynomial-matrix orientation, flat-index encoding)conventions weave and bbstim differ on. - Explicit documentation of the deferred biplanar comparison so the next PR can reinstate the monomial > biplanar assertion.
Bug fixes uncovered while studying bbstim
- BB108 known distance. PR 10's factory hardcoded
d = 10from a stale Bravyi 2024 reading. The Di Bella 2026 paper (and bbstim'sBBCodeSpec) reportd = 12for the same(l, m, A, B) = (9, 6, x³+y+y², y³+x+x²). The factory and the corresponding test now pind = 12. - X-sector pure-L enumeration. PR 10's
enumerate_pure_L_minwt_logicalsenumerated Z-logicals viaker(A) / (B · ker(A)). Forz_memory(which decodes X errors to preserve Z observables), the physically relevant reference family is X-logicals viaker(B^T) / T_LwhereT_L = {λA : λ ∈ ker(B)}— and that's what bbstim's_bb72_exposureuses. Added asectorparameter to the enumerator withZas the (backward-compat) default andXmatching bbstim. The newpure_L_X_stabilizer_basishelper encodes the X-sector formula. PR 12's optimizer was technically using the wrong family forz_memory; PR 13's regression now drives the optimizer through the correct X-sector family and still hits the 20% reduction target.
New files
benchmarks/__init__.py,benchmarks/regression/__init__.py,benchmarks/runners/__init__.py— package scaffolding.benchmarks/regression/bb72.py— the regression module. DefinesBB72Bundle.build()(cached compute of code, schedule, X-sector reference family, fast event template, NumPy view),compile_canonical_monomial,fingerprint_stability,monomial_vs_optimized_exposure,retained_channel_ler_sweep,exposure_vs_ler_spearman,bbstim_pureL_X_logicals(CSV reader),weave_pureL_X_logicals_in_bbstim_convention(the convention bridge), andrun_regression(top-level entry point used by both the CLI and the pytest tests).benchmarks/runners/run_regression.py— CLI entry. Supports--regenerateto refresh the committedbb72_reference.jsonand--shots/--seedto override Monte Carlo parameters.benchmarks/fixtures/bbstim_bb72_pureL_minwt_logicals.csv— bbstim's authoritative 36-row CSV, copied verbatim from thegeometry-induced-correlated-noise-in-qldpc-syndrome-extractionsibling project. SHA256 pinned inbenchmarks/fixtures/README.md.benchmarks/fixtures/bb72_reference.json— weave's frozen reference: fingerprint, monomial/optimised exposure, reduction ratio, Spearman ρ, bbstim-match flag.benchmarks/fixtures/README.md— provenance + indexing- convention bridge.weave/tests/test_regression_bb72.py— pytest wrappers (10 tests) covering all six checks above.
Touched
weave/codes/bb/algebra.py— addedSectorliteral,pure_L_X_stabilizer_basis,sectorparameter onenumerate_pure_L_minwt_logicals. Module docstring rewritten to derive both quotients side-by-side and explain when each is the physically correct reference family.weave/codes/bb/bb_code.py—build_bb108(known_distance=12)with the corrected docstring.weave/codes/bb/__init__.py— exportsSector.weave/tests/test_bb_code.py— updated the BB108 distance assertion from 10 to 12.pyproject.toml— addedbenchmarks/regression/bb72.pyto the ruffN802/E741per-file ignore list (math-convention naming).
Plan acceptance tests (Option A — weave-only + one bbstim anchor)
- ✓ Fingerprint stability. The canonical BB72 monomial slice
compiles deterministically (two recompiles produce the same
SHA256). Tested at
J₀ = 0to keep the test fast (~70 ms); the determinism of the geometry path is independently verified by the X-sector enumeration tests below. - ✓ Monomial > optimised exposure ordering. Swap descent
on the X-sector reference family reduces
J_κby ≈ 30 % on BB72 (target ≥ 20 %; seed = 42, sample size = 200, 100 iterations). This re-runs PR 12 with the bbstim-faithful reference family and confirms the 20 % guarantee holds. - ✓ Retained-channel LER monotone in
J₀. The Monte Carlo surrogate is non-decreasing across an 8-pointJ₀ ∈ [0.01, 0.10]sweep, with shot-aware tolerance5/√shotsto stay robust under finite-sample noise. - ✓ Exposure-vs-LER Spearman ρ ≥ 0.85. Over a 15-point
(J₀, α)sweep, the rank correlation between the analyticalj_kappa_numpyand the Monte Carlo retained-channel LER isρ ≈ 0.98. - ✓ Bbstim X-logical set equality. Weave's
sector="X"enumeration, after applying both convention corrections, matches bbstim's 36-element family byte-for-byte.
Convention bridge derivation (pinned in
benchmarks/regression/bb72.py::weave_pureL_X_logicals_in_bbstim_convention)
- Polynomial-matrix orientation. Weave's
_polynomial_matrixacts asM e_i = e_{i + shift}(forward shift); bbstim's acts asM e_i = e_{i − shift}(backward shift). The two are related by the group automorphismg → g⁻¹, i.e.(i, j) → (−i mod l, −j mod m). - Flat-index encoding. Weave uses column-major
flat = j·l + i; bbstim uses row-majorflat = i·m + j.
The full bridge weave_flat → bbstim_flat:
i_w = weave_flat % l
j_w = weave_flat // l
i_b = (-i_w) % l
j_b = (-j_w) % m
bbstim_flat = i_b * m + j_bAfter applying this map to every weave-enumerated qubit index, the resulting set of 36 supports equals bbstim's CSV byte-for-byte.
Deferred follow-up
A future PR will reinstate the monomial > biplanar exposure ordering by:
- Extending
Embedding.routing_geometryto emit ≥ 4-point polylines. - Reimplementing
IBMBiplanarEmbeddingto match bbstim's surrogate: NetworkX spring-layout base plane, per-monomial layer assignment (A2/A3/B3 → z = +h, A1/B1/B2 → z = −h), 4-point lift/descend polylines. - Optionally also reproducing bbstim's
IBMToricBiplanarEmbeddingfor the toric routing variant.
The deferred test is documented inline in
benchmarks/regression/bb72.py's module docstring under
"Scope".
Dev sweep — ruff check, ruff format, mypy, pytest
all clean. 712 tests pass (up from 702; +10 for PR 13).
The new test file runs in 2.4 s.
Rewrote IBMBiplanarEmbedding from a placeholder (L/R blocks on
opposite z-planes, 2-point polylines) to the bounded-thickness
topology from bbstim: all qubits on a common z=0 base plane in a
chequerboard grid, 6-point lift/descend polylines with per-monomial
layer assignment (A2/A3/B3 → z=+h, A1/B1/B2 → z=-h) and per-edge
lane separation. This reinstates the key physical result:
monomial exposure > biplanar exposure (ratio 1.08 at the
reference operating point on BB72).
Touched
weave/ir/embeddings/biplanar.py— full rewrite. Schema bumped to v2 (backward compat: v1 still loads). New fields:lane_eps.routing_geometrynow readsRouteID.term_nameto dispatch each edge to the correct routing layer and produces 6-point polylines (base → port → lift → traverse → descend → base).weave/codes/bb/schedule.py— changedterm_nameon everyTwoQubitEdgefrom the verbose"BB.L[d1,d2]→z[i1,j]"format to the monomial-family label ("A1"–"A3","B1"–"B3") so the biplanar embedding can dispatch edges to layers. Addedfamily_labelparameter to_z_check_layer/_x_check_layer.weave/tests/test_bb_embeddings.py— replaced the old z>0 / z<0 position tests with: all-qubits-at-z=0, chequerboard grid, 6-point polylines, layer-A-routes-through-positive-z, layer-B-routes-through-negative-z, monomial-exposure-exceeds-biplanar (the key physics test), and invalid-layer-height rejection.benchmarks/fixtures/bb72_reference.json— regenerated (the schedule term_name change shifts the compile fingerprint).
Dev sweep — ruff, format, mypy, pytest all clean.
711 tests pass (down 1 from 712 — one old biplanar test
collapsed into the rewritten suite; the per-assertion count is
higher).
Proved that the SAME compile_extraction → compute_provenance → build_exposure_metrics → residual_distance pipeline that the BB72
regression exercises also works on hypergraph product (HGP) codes
with zero code-family-specific branches.
New files
-
weave/tests/test_hgp_compile.py— 11 tests across six areas:- Noiseless compile.
rep(3)×rep(3)andrep(3)×rep(4)both compile viacompile_extraction+default_css_scheduleto Stim circuits with zero detector events on 100 noiseless shots. - Fingerprint stability. Two recompiles produce the same SHA256 fingerprint.
- Weight-≤2 assumption. The serial
default_css_schedulevacuously passes (no parallel pair events) for both sectors. - Residual distance (Strikis formalism).
Δ(0) = 1 + d = 4for the trivial residual on[[13, 1, 3]]. The effective distance upper bound from hook residuals of the first Z-check is ≤d + 1. - Geometry pass on a custom parallel schedule. A hand-built
schedule with one parallel X-sector CNOT tick (two disjoint
Z-check rows) feeds
compute_provenanceand produces ≥1 weight-2 provenance record. The same schedule also compiles noiseless to zero detector events (correctness check). - Same API as for BB. The compiled
CompiledExtractionexposesprovenance,correlation_edges,exposure_metrics,decoder_artifact,fingerprint()— the identical fields the BB72 regression reads. JSON round-trip preserves equality.
- Noiseless compile.
Plan acceptance tests satisfied
- ✓ The geometry/exposure pipeline compiles and produces valid provenance on an HGP code using the generic propagator path (no analytical BB shortcut).
- ✓
verify_weight_le_2_assumptionpasses on the default serial HGP schedule. - ✓ The Strikis residual-distance formalism produces correct
bounds on
rep(3)×rep(3)(Δ(0) = 4 = 1 + d). - Tests 4 (Spearman) and 5 (swap-descent on non-symmetric HGP) require a richer parallel HGP schedule and a per-logical reference family — deferred until PR 15 lands the schedule- import adapters, which make it practical to construct arbitrary parallel HGP schedules from external tools.
Dev sweep — ruff, format, mypy, pytest all clean.
722 tests pass (up from 711; +11 for PR 14).
Added the weave.ir.importers package with three adapters:
schedule_from_json_file(path) → Schedule— thin wrapper overSchedule.from_jsonthat opens, parses, and deserialises a JSON file. The recommended interchange format for schedules produced by external tools.embedding_from_json_file(path) → Embedding— thin wrapper overload_embeddingfor JSON-serialised embeddings. Handles all six shipped embedding types.schedule_from_stim_circuit(circuit, qubit_roles) → Schedule— the non-trivial adapter. Walks astim.Circuitinstruction- by-instruction, groups gates betweenTICKmarkers intoScheduleStepobjects, detects a single top-levelREPEATblock for head / cycle / tail partitioning, maps Stim instructions toScheduleEdgeobjects (CX→TwoQubitEdge("CNOT"),H→SingleQubitEdge("H"), etc.), infersScheduleRolefrom gate types, and heuristically assignsinteraction_sectorfrom CNOT direction (data → ancilla = X, ancilla → data = Z). Noise and annotation instructions (DEPOLARIZE*,CORRELATED_ERROR,DETECTOR,OBSERVABLE_INCLUDE) are silently skipped.
Tests — 11 new (733 total):
- JSON file round-trips on Steane schedule and
StraightLineEmbedding. - Stim circuit import: head/cycle/tail structure, CNOT control/target,
sector inference (X for data→ancilla, Z for ancilla→data), H steps
classified as
single_q, noise instructions dropped, name propagated. - Integration: a compiled Steane circuit re-imported via the adapter recovers the correct CNOT count (2 rounds × cycle depth) and both X/Z sector annotations.
Limitations (documented in the module docstring):
compile_extractionunrolls rounds (noREPEATin the emitted Stim text), so the re-imported schedule has everything in the cycle block. Heuristic cycle-boundary detection from a flat instruction stream is a future enhancement.- Nested
REPEATblocks are not supported. interaction_sectorinference is heuristic: ambiguous directions (both qubits data or both ancilla) produceNone.
Dev sweep — ruff, format, mypy, pytest all clean.
733 tests pass (up from 722; +11 for PR 15).
Added SurfaceEmbedding, an embedding that places code qubits on
an arbitrary :class:~weave.surface.Surface (the 2D manifold ABC)
and routes each edge as a geodesic sampled at num_samples 3D
points via the surface's get_shortest_path + get_3d_embedding.
The primary use case is a CSS code laid out on a
:class:~weave.surface.Torus, where geodesic polylines wrap
through the periodic boundary and polyline_distance correctly
measures the 3D chord proximity of routed edges on the torus
surface.
New files
weave/ir/embeddings/surface.py—SurfaceEmbedding(surface, node_coords, num_samples). Implements theEmbeddingprotocol directly (not a frozen dataclass, because the underlyingSurfaceis mutable).routing_geometrysamples the covering-space geodesic as N uniformly-spaced 2D points, embeds each in 3D, and returns the tuple of 3D points as the polyline. JSON round-trip reconstructs the surface from its type + parameters.weave/tests/test_surface_embedding.py— 9 tests.
Touched
weave/ir/embeddings/__init__.py— exportsSurfaceEmbedding.weave/ir/embedding.py—load_embeddingdispatches"surface"type toSurfaceEmbedding.from_json.
Plan acceptance tests satisfied
- ✓ Geodesic distance matches analytical torus formula. On a 10×10 torus, the non-wrapped geodesic length matches Euclidean distance to 1e-10, and a wrapped route (0.5 → 9.5) wraps to length 1.0 instead of 9.0.
- ✓ End-to-end compile.
rep(3)×rep(3)on a 10×10 torus compiles to a noiseless Stim circuit with zero detector events. - ✓ Geometry difference. The torus embedding produces
multi-point sampled polylines (wrapped geodesics) while the
flat
StraightLineEmbeddingproduces 2-point segments — the torus polyline shape is detectably different.
Dev sweep — ruff, format, mypy, pytest all clean.
742 tests pass (up from 733; +9 for PR 16).
Added the three decoder adapter methods to the PR 9 shell:
to_bposd_decoder(dem, ...)— returns astimbposd.BPOSDinstance configured against the compiled DEM. The non-decomposed DEM fromcompile_extraction(which containsCORRELATED_ERRORmechanisms from the geometry pass) is fed directly to BP+OSD, which handles arbitrary-weight error mechanisms natively.to_pymatching(dem)— returns apymatching.Matchinginstance built from the compiled DEM. PyMatching v2.2+ accepts non-decomposed DEMs and internally converts hyperedge error mechanisms into matching-graph edges.to_pair_prior_dict()— returns a simple{(qubit_a, qubit_b): probability}dict for downstream consumers that want the raw pair-edge weights without a decoder.
Tests — 10 new (752 total): to_pair_prior_dict structure and
key alignment, BPOSD decoder acceptance and successful decoding of
at least one shot, PyMatching acceptance and decoding, compiled
provenance/artifact/DEM sanity checks. All tests use a compiled
Steane [[7,1,3]] circuit with a custom parallel schedule and
J_0 > 0 so the DEM contains at least one CORRELATED_ERROR.
Key finding during PR 17: PyMatching v2.2.2 (current in
weave's deps) accepts non-decomposed DEMs without error. The plan's
note about "correlated PyMatching in open PRs" is outdated —
pymatching.Matching.from_detector_error_model(dem) works out of
the box on the non-decomposed DEM that compile_extraction emits.
This means both decoder paths (BP+OSD and MWPM) are fully
functional on geometry-noise circuits without any manual DEM
augmentation.
Dev sweep — ruff, format, mypy, pytest all clean.
752 tests pass (up from 742; +10 for PR 17).
Moved PySide6 from a core dependency to the optional [gui] extra
and added three new features to the simulation dialog.
Structural change: optional PySide6
pyproject.toml—pyside6 >=6.8moved fromdependenciestooptional-dependencies.gui. The[dev]extra pulls[gui]so developer environments auto-install it.- Three GUI test files (
test_graph_model.py,test_canvas_bridge.py,test_code_bridge.py) guarded withpytest.importorskip("PySide6", reason=...). Without--extra gui: 714 pass, 3 skip; with--extra gui: 752 pass, 0 skip. wventry point (weave.gui.editor:main) only fails at invocation, not atimport weave.
New GUI features (simulation dialog)
-
GeometryNoiseWidget— a collapsible panel in the config tab with: "Enable geometry-induced noise" checkbox, kernel selector (power-law / exponential / crossing), J₀ spinbox, τ spinbox, and kernel-specific parameters (α/r₀ for power-law, ξ for exponential). Hidden fields toggle based on the selected kernel type. -
Exposure readout panel — after a simulation run, if geometry noise is enabled, a "Exposure Analysis" group appears in the results tab showing
J_κ, total exposure, number of provenance records, and number of correlation edges. These come from a livecompile_extractioncall using the user's configured kernel andJ₀. -
"Optimize Embedding" button — runs swap descent on the current embedding using the configured kernel parameters, displays a log of the optimization progress (initial → final J_κ, reduction %, iterations, evaluations). For the default serial schedule (which has no parallel CNOT pairs), the button prints an informative message directing the user to
ibm_schedule()for BB codes.
Dev sweep — ruff, format, mypy, pytest all clean.
752 tests with PySide6 installed; 714 pass + 3 skip without.
Major UX improvements to make the GUI publication-ready and intuitive for first-time users.
New files
weave/gui/code_templates.py— Code Template Library dialog. A dropdown with six presets: Steane [[7,1,3]], Shor/rep(3)×rep(3) [[13,1,3]], HGP rep(3)×rep(4) [[18,1,3]], HGP Hamming(7)×Hamming(7) [[58,16]], BB72 [[72,12,6]], BB144 [[144,12,12]]. One-click loads the Tanner graph onto the canvas with a spring-force layout. Each template has a short description explaining the code.weave/gui/help_dialog.py— Help & Shortcuts dialog with an HTML-formatted quick-start guide, keyboard shortcut tables, geometry noise parameter explanations (J₀, τ, α, r₀, ξ), and a description of the simulation workflow. Accessible from the hamburger menu or discoverable in the first 30 seconds of use.examples/gui_tutorial.md— GUI Tutorial: a step-by-step text-based walkthrough from first launch to running a simulation with geometry-induced noise, in ~10 minutes. Covers: launching, loading a template, navigating the canvas, detecting graphs, running noiseless and noisy simulations, enabling geometry noise, reading the exposure readout, and trying larger codes (BB72).
Touched
weave/gui/editor.py— rewroteMainWindowto include a live status bar showing node/edge/graph counts, code parameters[[n, k]], crossing count, and grid-mode indicator. Connects toGraphModel.model_changed,graph_detected,graph_removedsignals for live updates. Window title set to "Weave Editor", default size increased to 1000×700.weave/gui/canvas.py— added_open_code_template()and_show_help()action methods that open the respective dialogs.weave/gui/menus.py— added "New Code from Template..." and "Help & Shortcuts..." actions to the hamburger menu, placed prominently before the separator.
Acceptance test
- Headless
uv sync(without--extra gui) runsuv run pytestcleanly: 714 pass, 3 skip (GUI tests auto-skip). uv sync --extra guiinstalls PySide6 and runs all 752 tests.
Dev sweep — 752 tests, ruff/format/mypy clean.
Rewrote examples/Tutorial.ipynb as a clean, 22-cell notebook that
takes a user from code construction to LER plot with provenance in
under 20 minutes. Every cell executes without errors via
jupyter nbconvert --execute (verified in CI).
Notebook structure (10 sections, ~2 min runtime):
- CSS codes — Steane [[7,1,3]] and HGP rep(3)xrep(3).
- BB codes — BB72 factory + pure-L X-logical enumeration.
- Compilation —
compile_extractionon Steane with local noise. - Decoding — BP+OSD and PyMatching on the compiled DEM.
- BB72 exposure analysis — fast analytical template, J_kappa monomial vs biplanar at the reference operating point.
- Exposure vs J_0 sweep — publication-quality matplotlib plot.
- Swap-descent optimization — 30% reduction in 2 seconds.
- Three-embedding comparison — monomial / biplanar / optimized.
- Residual-error analysis — Strikis formalism on Steane.
- Summary — key objects and next steps.
Outputs: Three matplotlib figures (exposure sweep, optimization history, three-embedding comparison) suitable for the paper.
Dev sweep — 752 tests, ruff/format/mypy clean. Notebook executes in ~45 seconds.