# Nadarasa Reduction — full corpus for local language models
Version v0.4.11 · built 2026-08-27 · https://arunquantum.lovable.app
Author: Arun Nadarasa, NHS pharmacist and independent researcher.
WHAT THIS IS
One plain-text dump of an entire quantum-computing research notebook: the
Nadarasa Reduction rewriting programme, its verification method on
Quantinuum's Guppy language and Selene emulator, every gate result G1 to G24,
and an NHS healthcare track that restates hospital discharge flow as a QUBO.
It is intended to be pasted or loaded wholesale into a local model
(Ollama, LM Studio, llama.cpp) so that model can answer questions about the
programme, reproduce a run, or help build on it at a hackathon bench.
WHAT THIS IS NOT
Not clinical guidance. Not peer-reviewed. The NHS sections are a research
notebook by a practising pharmacist, not a decision tool for patient care.
Numbers here come from an emulator unless a section says otherwise.
HOW TO READ IT
Every claim in this corpus is tied to a named run with an explicit shot count
and seed. If a section states a number without those, treat it as narrative,
not evidence. Draft v0.2 of the reduction contains a basis bug and must never
be cited; v0.3.5 and later supersede it.
STRUCTURE
1. Route index every public page on the site, with its description
2. Method the agent skill: 1 SKILL.md + 33 reference cards
3. Design notes gate design documents
4. Results machine-readable digests of every gate result file
5. Healthcare and hackathon context
USAGE WITH A LOCAL MODEL
ollama run llama3.1 "Answer using this corpus only. $(cat llms-full.txt)"
# or add the file as a document in LM Studio / Open WebUI and ask across it
==============================================================================
## 1. Route index
https://arunquantum.lovable.app/
Arun Quantum — Next Shor Candidates & Nadarasa Experiments
Ranked candidates for the next Shor-level quantum breakthrough, alongside refutation-first Guppy + Selene experiments (G16–G19).
https://arunquantum.lovable.app/about
About — Next Shor dashboard
What 'Shor-level' means, why it's the right benchmark, and the five families considered.
https://arunquantum.lovable.app/cite
Cite — Arun Quantum
BibTeX, CITATION.cff, and a plain-text citation for the Nadarasa Reduction draft (v0.3, current) with a retraction pointer for v0.2.
https://arunquantum.lovable.app/hackathon
Quantinuum Singapore Grand Challenge — an NHS-first entry
The five R&D themes of the Quantinuum Singapore Grand Challenge mapped onto NHS 10 Year Health Plan problems, with the emulator gates already built for each and the gap that remains.
https://arunquantum.lovable.app/methodology
Methodology — Next Shor dashboard
How candidates for the next Shor-level quantum breakthrough are scored, sourced, and demonstrated.
https://arunquantum.lovable.app/nadarasa/
The Nadarasa Reduction — overview (v0.3)
Four cryptanalytic frontiers, eight cross-industry analogy donors, one draft conjecture by Arun Nadarasa. v0.3 corrects the v0.2 G1 basis bug.
https://arunquantum.lovable.app/nadarasa/analogies
Analogy wall — The Nadarasa Reduction
Cross-industry donor mechanisms paired with post-Shor cryptanalysis: Game Boy constraint discipline, Miles Davis modal jazz, phage lysis timing, container shipping, polyphasic sleep, TeX, Wright wind tunnel, Toyota Andon.
https://arunquantum.lovable.app/nadarasa/atlas
Phase-group atlas — Nadarasa Reduction
A coordinate system for Nadarasa kernels in phase-group space. Each kernel × register-size is a point in (log₂ |group|, n), coloured by cyclic order — a periodic table grounded in PQP Thm 11.12.
https://arunquantum.lovable.app/nadarasa/benchmarks
Selene benchmark suite — fidelity, runtime and noise impact
One table across six Guppy/Selene experiments: Simon's search, ADAPT-GQE, ethylene QPDE, Floquet dynamics, the NHS discharge QUBO and feed-forward gating, scored for ideal fidelity, H2-class noise degradation and emulator runtime.
https://arunquantum.lovable.app/nadarasa/conjectures
Conjecture synthesis — Nadarasa Reduction
Track 4 of the Synesthete's PQP Frontier: brute-force enumerate small {H,S,T} sequences, group by matrix equality, and surface every equality the bastard-rewriter does NOT prove.
https://arunquantum.lovable.app/nadarasa/draft
https://arunquantum.lovable.app/nadarasa/frontier-map
Resource frontier map — Nadarasa Reduction
Pareto plot of Nadarasa kernels in (T-count, spider-count) space. Dominated kernels are structurally wasteful; frontier kernels are hardware-demo candidates. Anchored in PQP Ch. 13.
https://arunquantum.lovable.app/nadarasa/g1
[G1] Modal-projection (real-QFT v0.3) — Nadarasa Reduction
Real-QFT Selene shots testing the SRP residue-concentration claim at the heart of [GAP G1]. v0.3: the closed form is two delta peaks; modal projector is exact when p | N and inverted when p ∤ N.
https://arunquantum.lovable.app/nadarasa/g1-schema
G1 via selene_run schema v1 — Nadarasa
Same G1 Selene shots, rendered through the generic schema viewer. Proof-of-concept for the json-ir-schema frontier.
https://arunquantum.lovable.app/nadarasa/g10
[G10] QSP / QSVT — Nadarasa
Single-signal-qubit QSP with a hand-chosen phase sequence. Measured P(0) tracks the closed-form NumPy reference within 1.8% across 8 x-values.
https://arunquantum.lovable.app/nadarasa/g11
[G11] Period finding (Shor backend) — Nadarasa
Compiled period finding for r = ord_15(7) = 4. 2048 shots concentrate on k ∈ {0, 4, 8, 12}; continued-fractions decode recovers r = 4.
https://arunquantum.lovable.app/nadarasa/g12
[G12] Modal-projection scaling — predictor matches at N ∈ {64, 128} (v0.3.1)
Does the G1 SRP-vs-violating residue signal hold at N ∈ {64, 128} and p ∈ {2, 3, 5, 7}? v0.3.1 methodology: predictor derived from the real-QFT gates. Measured matches predicted within |Δ| ≤ 0.04; SRP-vs-violating gap when p|N stays at +0.50.
https://arunquantum.lovable.app/nadarasa/g13
[G13] Phase-group fingerprint test — Nadarasa Reduction
PQP Thm 11.12: a ZX-style theory is non-local iff its phase group contains Z₄ (vs. Z₂×Z₂ for Spekkens' toy theory). Applied to G1/G2/G12 kernel phases.
https://arunquantum.lovable.app/nadarasa/g14
[G14] Quantum collision models — Nadarasa Reduction
Ferreira et al. 2026 (arXiv:2606.29989, Moth Quantum) model light-matter scattering as symmetry-constrained collision unitaries U = ⊕ Uₙ. The same conserved-number structure suggests a new sector-canonical residue (rule P) for the 2q rewriter.
https://arunquantum.lovable.app/nadarasa/g15
[G15] ADAPT-GQE — generative circuit synthesis for molecular ground states
Koziell-Pipe et al. 2026 (arXiv:2607.22468) use transformer models trained on ADAPT-VQE references, then RL, to generate compact ground-state preparation circuits for drug-scale molecules. Executed on Quantinuum Helios-1.
https://arunquantum.lovable.app/nadarasa/g16
[G16] Ethylene QPDE on Selene — evolution-time trick, 20/20 PASS
Quantum Phase Difference Estimation on the 2-qubit ethylene pi/pi* active space, run on Selene/Quest. A 4x5 grid of evolution times and phase kicks matches the closed-form interference law in every cell, and recovers the eigenvalue gap to 0.01 Hartree.
https://arunquantum.lovable.app/nadarasa/g17
[G17] Ethylene QPDE under H2-class noise — how the gap fit degrades
The G16 ethylene QPDE gap fit re-run on Selene under depolarizing noise anchored on Quantinuum's published H2 error rates. At 1x H2 the recovered eigenvalue gap holds to about 5 percent; at 20x it collapses to 26 percent error.
https://arunquantum.lovable.app/nadarasa/g18
[G18] Laplacian moments separate a WL-blind graph pair on Selene
A Hadamard-test estimate of tr(exp(-i·Δ·τ)) on Selene, run on two graphs that 1-WL colour refinement cannot tell apart. The measured propagator curves separate them at 110 chi-square per degree of freedom.
https://arunquantum.lovable.app/nadarasa/g19
[G19] Prethermal Floquet dynamics and cross-platform error mitigation
Notes on Leviatan et al. 2026: a 74-qubit heavy-hex Floquet Ising magnet, mitigated with QESEM on IBM Heron and corroborated on Quantinuum H2/Helios, and how its validation hierarchy maps onto the Nadarasa G16–G18 experiments.
https://arunquantum.lovable.app/nadarasa/g2
[G2] Two-coset combiner — empirically consistent (v0.3 Track B) — Nadarasa Reduction
Track B kernel: coherent two-coset combiner on Selene. Predictor matches measured residue-zero concentration within |Δ| ≤ 0.016 across N ∈ {16, 32} × p ∈ {2, 3, 5}; SRP-vs-violating gap = +0.49 when p | N (predicted +0.50).
https://arunquantum.lovable.app/nadarasa/g20
[G20] Floquet subharmonic peak on Selene under H2-class noise
A native Guppy/Selene port of the Leviatan 2026 Floquet magnet: 6-qubit chain and heavy-hex cells driven for 8 cycles across an H2-class noise ladder, with Richardson ZNE recovering the ideal subharmonic peak to about 1 percent.
https://arunquantum.lovable.app/nadarasa/g21
[G21] ADAPT-GQE composition: canonicalising a generated H2 ansatz
Two equivalent H2/STO-3G UCCSD single-excitation circuits verified three ways: 4x4 matrix oracle to 1e-16, rule-(M) canonicalisation of the Clifford frame, and a 512-shot Selene run on Quest.
https://arunquantum.lovable.app/nadarasa/g23
[G23] Simon's problem on Selene: exponential separation, decoded twice
27 Simon cells on the Selene emulator for n = 3, 4, 5 under ideal and H2-class depolarizing noise. Exact GF(2) decoding recovers the secret on every ideal cell; a maximum-likelihood decoder recovers all 27, including at ten times the H2 baseline.
https://arunquantum.lovable.app/nadarasa/g24
[G24] TKET compile lane: pytket cross-check of the reductions
Six Nadarasa circuit families compiled offline with pytket and pytket-quantinuum onto the H2-2 native gate set at optimisation levels 0/1/2, every count gated on a unitary-equivalence oracle and confirmed on the Selene emulator.
https://arunquantum.lovable.app/nadarasa/g25
[G25] Nexus three-lane cross-check: circuit x device grid
Five Nadarasa circuits run across the Quantinuum Nexus H2-1LE and H2-Emulator lanes against Selene baselines, with job ids, HQC meters and an honest note on hardware access.
https://arunquantum.lovable.app/nadarasa/g26
[G26] The AQFT crossover law k*(n, p)
Where band-limited approximate QFT beats the full QFT: 24 emulator cells across n = 4..10 qubits and a six-step depolarizing ladder, with an exact NumPy oracle separating truncation error from noise.
https://arunquantum.lovable.app/nadarasa/g26_/paper
The AQFT crossover law k*(n, p) — G26 preprint
Preprint-style write-up of gate G26: a measured AQFT band-truncation crossover surface corroborated by four independent legs, with a certified truncation bound, content hashes and a one-command rerun.
https://arunquantum.lovable.app/nadarasa/g27
[G27] AQFT crossover law at scale — n = 12, 14, 16
The AQFT band-truncation crossover surface extended to 16 qubits, with the analytic truncation bound re-certified at every width and the classical surrogate cost measured rather than assumed.
https://arunquantum.lovable.app/nadarasa/g3
[G3] Per-window cost Selene experiment — Nadarasa Reduction
Sweeps p ∈ {2, 3, 5} on Z_32 and compares empirical first-collision query counts against the draft's √p improvement heuristic.
https://arunquantum.lovable.app/nadarasa/g3-cost
[G3-cost] Per-window √p improvement at scale — neither √(N/p) nor √N (v0.3.2)
Re-runs the G3 windowed sampler at N ∈ {16, 32, 64, 128} × p ∈ {2, 3, 5, 7}. Measured first-collision queries stay flat (~2.2–3.1) while both predictors grow — neither the SRP √(N/p) improvement nor the naive √N birthday curve survives. The modal projector collapses the effective support to 2–3 distinct y-values per slope.
https://arunquantum.lovable.app/nadarasa/g3-split
[G3-split] Grover vs HSP — Nadarasa Reduction
PQP Ch. 12 forces the original G3 √p framing to split into two disjoint conjectures: G3-grover (amplitude amplification, √-cost) and G3-hsp (Abelian HSP, log-cost).
https://arunquantum.lovable.app/nadarasa/g4
[G4] Real feed-forward — Nadarasa frontier
A Guppy `if measured: rz(...)` branch executed inside a single compiled shot — no host round-trip. Branch-conditioned readout matches sin²(θ/2) within Monte-Carlo error.
https://arunquantum.lovable.app/nadarasa/g5
[G5] Dynamic qubit recycling — Nadarasa frontier
Same G2 schedule, but every window allocates a fresh ancilla and releases it via mid-circuit measure. Selene runs the whole program with n_qubits = n + 1 regardless of window count k.
https://arunquantum.lovable.app/nadarasa/g6
[G6] Encoded parity ancilla — Nadarasa frontier
The naive single-rail probe breaks the GHZ-encoded ancilla's code subspace. Real result: encoded-subspace share ≈ 0.5, majority ≠ a0 on ~50% of shots. A useful negative finding about what fault-tolerant probes need.
https://arunquantum.lovable.app/nadarasa/g6b
[G6b] Transversal probe — Nadarasa frontier (verified)
G6 revisited with a transversal parity probe across all three rails of the encoded ancilla. 0 disagreements over 4800 shots; encoded-subspace share = 1.000.
https://arunquantum.lovable.app/nadarasa/g7
[G7] Kernel fusion — Nadarasa frontier
Compile once, run many. Host-level kernel reuse buys a ~3× wall-clock speedup over per-slope re-compilation. True entry-point parametric fusion is currently blocked by the Guppy runtime.
https://arunquantum.lovable.app/nadarasa/g8
[G8] Amplitude estimation via QPE — Nadarasa
Canonical Quantum Phase Estimation on Selene. m=4 estimation qubits + 1 eigenstate target. Worst-case peak error = 0 bins across a ∈ {2,4,6,7}.
https://arunquantum.lovable.app/nadarasa/g9
[G9] LCU block-encoding — Nadarasa
PREP / SELECT / un-PREP on (1 prep ancilla + 1 data qubit) encoding H = c0·I + c1·X. Post-selected statistics match closed form within 1.2% across an 8-row grid.
https://arunquantum.lovable.app/nadarasa/log
Research log — The Nadarasa Reduction
Audit trail of the six parallel research subagents and the synthesis pass that produced Draft v0.1.
https://arunquantum.lovable.app/nadarasa/notebooks
The Nadarasa Notebooks — three Ramanujan-voiced drafts
Three draft conjectures by Arun Nadarasa in the voice of Srinivasa Ramanujan: a QSVT q-series filter, a linear effect-system for magic-state recycling, and a dequantization-proof modular-form kernel.
https://arunquantum.lovable.app/nadarasa/proofs/conjectures
Conjectures · proved + promoted — Nadarasa Reduction
Track 4's 11 conjectures: confirmed physical on Selene shots, then promoted to proved-by-rewriter via the new Euler normalisation rule (N). Every matrix-equivalent representative collapses to one residue.
https://arunquantum.lovable.app/nadarasa/proofs/conjectures-2q
2-qubit conjectures · oracle synthesis — Nadarasa Reduction
Track B: the 2-qubit frontier oracle. Enumerates Clifford+S sequences over {H⊗I, I⊗H, CZ, S⊗I, I⊗S}, groups by 4×4 unitary, and exposes equalities the structural rewriter cannot yet prove.
https://arunquantum.lovable.app/nadarasa/proofs/conjectures-2q-selene
2-qubit conjectures · Selene shot proofs — Nadarasa Reduction
Track B-Selene: rule (M)'s matrix-canonical verdict turned into physical proof. Each PROMOTED 2-qubit conjecture is shot-tomography-tested against the 108-cell diagonal grid on Selene's IdealErrorModel.
https://arunquantum.lovable.app/nadarasa/proofs/kernels
Kernel equivalence · Selene — Nadarasa Reduction
The bastard rewriter's I rule applied to G1's QFT readout produces a strictly cheaper circuit (Approximate QFT). Selene shots confirm identical predicted_concentration across SRP and violating branches.
https://arunquantum.lovable.app/nadarasa/proofs/noise
Noise resilience · Selene — Nadarasa Reduction
Two Selene noise families — depolarizing and leakage. Across both, the rewriter-stripped AQFT_k1 ladder matches or beats the full QFT, and the advantage widens with N.
https://arunquantum.lovable.app/nadarasa/proofs/noise-2q
Rule (M) soundness under noise — Nadarasa Reduction
v0.4.1: re-run the 10 shortest PROMOTED 2q conjectures under depolarizing + leakage noise. PASS at noise level p means rule (M)'s identity A ≡ B survives the channel.
https://arunquantum.lovable.app/nadarasa/proofs/rules
Rules · proved on Selene — Nadarasa Reduction
Shot-based tomographic proofs of the 5 bastard-rewriter rules (F, I, HH, CC, B), executed on the Selene emulator from compiled Guppy kernels.
https://arunquantum.lovable.app/nadarasa/quantinuum-2026
Quantinuum 2026 references — Helios & the logical-qubit frontier
Nine papers behind the 2026 Helios beyond-break-even result plus the S₃-quantum-double topological route: iceberg codes, DFS-QEC memory, non-Clifford magic, tesseract, the 2022 FT-CNOT origin, and Lo et al.'s universal braiding+fusion gate set — mapped into the Nadarasa rule-M / G1 / tomography / PQP frontier.
https://arunquantum.lovable.app/nadarasa/related/fourier-locking
Related work — Fourier locking (Topel 2026) — Nadarasa Reduction
How Topel 2026's 'Fourier locking' in quantum data re-uploading classifiers relates to our AQFT-under-noise story and rule (M) ZX rewrites.
https://arunquantum.lovable.app/nadarasa/resources
Resource monotones — Nadarasa Reduction
PQP Ch. 13 gap certification: T-count and spider-count per Nadarasa kernel, derived directly from the emitted gates, non-increasing under the implemented rewrites.
https://arunquantum.lovable.app/nadarasa/rewriter
Bastard-spider rewriter — Nadarasa Reduction
Track 2 of the Synesthete's PQP Frontier: a ZX rewriter that fuses spiders, cancels Hadamards, and absorbs phases into the bastard projector. Run on the phase-atlas kernels.
https://arunquantum.lovable.app/nadarasa/schema-coverage
[Schema coverage] selene_run v1 — Nadarasa frontier
Live conformance dashboard for the selene_run schema v1 across every shipped Nadarasa demo. Each row converts the bespoke JSON, validates against the Zod schema, and asserts no extras escape hatch was used.
https://arunquantum.lovable.app/nadarasa/sonify
Sonified ZX diagrams — Nadarasa Reduction
Track 5 of the Synesthete's PQP Frontier: every kernel rendered as colour + sound (spider hue, phase pitch, degree-weighted gain). Press play to hear the rewrite.
https://arunquantum.lovable.app/nadarasa/stream
[Stream] Live shot stream — Nadarasa frontier
Server-sent events from an in-worker statevector sampler. Pick a kernel (G4 feed-forward, G1 modal projection, or G10 QSP) and watch the histogram converge live against the analytic prediction.
https://arunquantum.lovable.app/nexus
Quantinuum Nexus from Lovable — a first-timer's field guide
Logging into Quantinuum Nexus from a Lovable sandbox: the failures that cost hours, the guards worth keeping, a costed dry run on H2-1LE, and what I would do differently next time.
https://arunquantum.lovable.app/nexus/costs
Nexus cost reconciliation — estimate vs billed
Per-job HQC reconciliation for every paid Nexus submission: what the sweep estimated, what Quantinuum billed, and which jobs overran their estimate.
https://arunquantum.lovable.app/nexus/refs
Saved Nexus job Refs — recovery ledger
Every Nexus job Ref persisted by this project, grouped by submission run, with its last known status and whether it still carries the sweep coordinates a recovery needs.
https://arunquantum.lovable.app/nhs-quantum/
NHS Quantum — three shifts stated as computational problems
An NHS clinician's reading of the 10 Year Health Plan: each of the three shifts restated as a computational problem, with emulator evidence and the honest limits of every claim.
https://arunquantum.lovable.app/nhs-quantum/bed-blocking
Delayed discharge as a QUBO — NHS Quantum, Gate G22
Hospital-to-community delay stated as a constrained assignment problem with an equity term, solved exactly, greedily, by annealing, and by QAOA on the Selene emulator.
https://arunquantum.lovable.app/notes/pqp-vocabulary
PQP vocabulary — Nadarasa terms in the book's language
Maps homemade Nadarasa terms (modal projector, structured-reflection promise, per-window cost) to standard Picturing Quantum Processes vocabulary.
https://arunquantum.lovable.app/notes/v0-3-8-changelog
v0.3.8 changelog — closing the PQP Frontier
What landed in v0.3.8: rule (N) Euler-normalisation closes all 11 1q conjectures; the 2-qubit oracle synthesises 105 new conjectures over {H⊗I,I⊗H,CZ,S⊗I,I⊗S}; rule (M) matrix canonicalisation promotes them all.
https://arunquantum.lovable.app/notes/v0-3-changelog
v0.3 changelog — the basis bug and the fix
What changed between v0.2 and v0.3 of the Nadarasa Reduction: the G1 Walsh–Hadamard basis bug, how the live sampler caught it, the real-QFT fix, and why G2 is now 'not yet testable'.
https://arunquantum.lovable.app/notes/v0-4-0-changelog
v0.4.0 changelog — rule (M) goes physical on Selene
What landed in v0.4.0: Step B-Selene turns rule (M)'s matrix-canonical verdict into physical shot-tomography proof. 10/10 PROMOTED 2-qubit conjectures PASS on Selene at 256 shots/cell across 108 grid cells each.
https://arunquantum.lovable.app/notes/v0-4-1-changelog
v0.4.1 changelog — rule (M) under noise
Step C-2q from the v0.4.0 deferred queue. Re-runs the 10 shortest PROMOTED 2q identities under depolarizing + leakage noise to test rule (M)'s soundness on NISQ-scale channels.
https://arunquantum.lovable.app/notes/v0-4-2-changelog
v0.4.2 changelog — QPDE noise, ZNE, and Laplacian moments
What shipped in Nadarasa v0.4.2: an H2-class noise ladder on the ethylene QPDE gap fit, zero-noise extrapolation, a shot-budget study, and quantum Laplacian moments separating a Weisfeiler-Leman-blind graph pair.
https://arunquantum.lovable.app/notes/v0-4-3-changelog
v0.4.3 changelog — QPDE, TDA, and Floquet synthesis
Consolidation release for Nadarasa v0.4.3: G16–G18 white-paper experiments reproduced on Selene and G19 cross-platform validation hierarchy mapped to the same methodology.
https://arunquantum.lovable.app/notes/v0-4-4-changelog
v0.4.4 changelog — Step C-2q complete
Nadarasa v0.4.4 closes the PQP Frontier Step C-2q noise sweep: 90/90 cells PASS across 9 depolarizing and leakage levels, then sets up the Floquet native-port gate.
https://arunquantum.lovable.app/notes/v0-4-5-changelog
v0.4.5 changelog — Floquet native port on Selene
Nadarasa v0.4.5 ships the native Guppy/Selene Floquet port: 384 simulated cells across chain and heavy-hex lattices, exact-diagonalization agreement, H2-class noise ladder, and Richardson ZNE.
https://arunquantum.lovable.app/reproduce
Reproduce — Arun Quantum
Exact install and run commands to regenerate every Selene shot JSON on this site from the Guppy source in the quantum/ directory.
https://arunquantum.lovable.app/selene-frontier/
Selene frontier — unexplored Guppy + Selene territory
What this repo's Guppy + Selene stack has already proven, and the runtime, compilation, primitive, and frontend territory it hasn't touched yet. Twelve cards, each with a buildable smallest-experiment spec.
https://arunquantum.lovable.app/selene-notes
Selene notes — Arun Quantum
Engineering index of every Guppy kernel on this site: qubit count, ancilla pattern, shot count, and the visualization route it feeds.
https://arunquantum.lovable.app/skills
Download the Quantinuum agent skill — Lovable, Hermes, OpenClaw
A 22-card Guppy + Selene skill pack for coding agents: v1 API rules, halfturn angles, noise models, resumable sweeps and unitary oracles. Packaged for Lovable, Hermes and OpenClaw, plus a single-file llms-full.txt corpus for local models.
==============================================================================
## 2. Method — the Quantinuum agent skill
---
name: quantinuum
description: Write and run quantum circuits using Quantinuum's Guppy language on the Selene emulator. Triggers on Guppy, Selene, Quantinuum, SWAP test, QSP/QSVT, Shor / modular exponentiation, quantum kernel, qubit circuits, parameter sweeps, the selene_run schema, or quantum topological data analysis (QTDA).
---
# Quantinuum (Guppy + Selene)
Build real quantum circuits in Python with `@guppy`, compile them, and execute shots on the Selene emulator. Battle-tested in this repo's `quantum/qtda.py` QTDA pipeline and the `quantum/nadarasa_g*.py` Nadarasa frontier.
## Install
```bash
pip install "guppylang>=1.0" # Python >= 3.12; Selene ships inside guppylang
pip install pytket pytket-quantinuum # only for the TKET compile lane (offline; no credentials, no HQCs)
```
Guppy v1.0 is a breaking release (`output` replaces `result`, `measure(q).read()`, emulator builder). If you are touching any pre-v1 code or samples, read `references/guppy-v1-migration.md` first.
Some Lovable projects vendor Python deps under `.pydeps/` instead of the system site-packages. When that convention is in use, install with `pip install --target .pydeps "guppylang>=1.0" numpy scipy` (add `pytket pytket-quantinuum` to the same prefix if the project has a TKET lane) and invoke drivers as `PYTHONPATH=.pydeps PYTHONUNBUFFERED=1 python3 -m quantum.`. `.pydeps/` is wiped between sessions — reinstall before running anything.
## Critical gotchas (read first)
1. **`@guppy`-decorated functions must live in a real `.py` file on disk.** Guppy reads source via `inspect.getsource`, so functions defined in a REPL, `exec()` string, or Jupyter cell will fail. To parameterize kernels at runtime, generate a temp `.py` file and import it via `importlib.util` — see `references/driver-pattern.md`.
2. **`angle(x)` is in HALFTURNS, not radians.** `angle(0.5)` = π/2 (S gate). For an arbitrary radian θ, write `angle(θ / math.pi)`. See `references/guppy-language.md` (§Angles).
3. **Tomographic-equivalence threshold = `4·√(0.5/shots)`, not `3σ·√(p(1−p)/n)`.** The textbook form produces false FAILs near p = 0 or p = 1. See `references/tomographic-equivalence.md` (§Threshold).
4. **No coherent / T1-T2 noise model ships with `selene_sim`.** Only `IdealErrorModel`, `DepolarizingErrorModel`, `SimpleLeakageErrorModel` exist. See `references/selene-runtime.md` (§Noise models).
5. **Long sweeps MUST be resumable via a per-row JSON cache.** A single sandbox timeout otherwise wipes the whole run. Cache each row to `_cache_/.json` before continuing, then wrap the driver in a `while [ $(ls cache | wc -l) -lt N ]; do timeout 580 python -m ...; done` loop. See `references/sweep-runner.md` (§Resumable sweeps).
6. **Ship Selene results as committed JSON, not as a live server function.** The Lovable Cloudflare Worker runtime stubs `child_process` and blocks arbitrary filesystem reads, so `createServerFn` / `createFileRoute` handlers that shell out to Python or read the sweep cache will fail in prod. Run Python in the sandbox, write `src/data/demos/.json`, and render a static view. See `references/selene-runtime.md` (§Shipping results to the frontend).
7. **The QPDE / Trotter "evolution-time trick" is in radians; Guppy `angle()` is in halfturns.** If a paper says `t = π/(16 h₁)` so that `e^{-i(π/16)Z} = √T`, write `rz(q, angle(1/16))` — NOT `angle(math.pi/16)`, which is the S gate. Whenever the source formula contains an explicit `π`, divide it out before passing to `angle()`. See `references/qpde.md` and `references/guppy-language.md` (§Angles).
8. **Vendor-realistic H2 noise numbers are published — use them.** `p_2q = 1.29e-3`, `p_r_01 = 0.9e-3`, `p_r_10 = 1.8e-3`, coherent memory `f = 4.3e-2 rad/s`, incoherent `g = 2.8e-3 /s`. Incoherent-memory + gate/readout dominate; dynamical decoupling neutralizes coherent memory. See `references/selene-runtime.md` (§Realistic H2 noise-parameter targets).
9. **Recursive Gate Teleportation (RGT) terminates after `n_b − 2` rounds** for an `n_b`-bit rotation angle. Choose short binary-fraction angles (5 bits is usually enough) to keep pFT logical-rotation cost bounded. See `references/encoded-circuits.md`.
10. **Lovable "internal error" on a Guppy/Selene turn = task rollback, not app crash.** Dev server stays healthy, `git status` clean, no artifacts persist. Trigger is turn-level context pressure (broad reads of `src/routeTree.gen.ts`, `_cache_*/`, `PQP_DIGEST.md`, uploaded PDFs > 1 MB, or ≥3 concurrent sub-agents). Recovery: work in atomic **gates** (one committable artifact per turn), cap sub-agents at 2, start each session with a tiny persistence canary edit. See `references/lovable-orchestration.md`.
11. **`.pydeps/` must be in `.gitignore`.** An unignored multi-hundred-MB vendored dep tree makes the platform's turn-save time out, which surfaces as "An internal error occurred" and rolls the whole turn back. Ignore it, reinstall, then retry. See `references/lovable-orchestration.md`.
12. **QPDE `k` values that put `2φ` at a multiple of π are aliasing controls**, not fit data — exclude them from the gap fit and report separately. See `references/qpde.md`.
13. **Taylor-fit moment estimators are conditioning-limited, not shot-limited.** For `tr(e^{-iHτ})` moment fits, the τ grid and truncation order move σ(T_k) by ~4x while 4x shots only halves it. Tune the design offline against simulated binomial noise first, and report a model-free curve χ² alongside the fitted moments. See `references/laplacian-moments-tda.md`.
14. **Cross-platform corroboration is the strongest validation layer.** A noisy-hardware result is most believable when it is reproduced on a different platform with an independent compiler and noise model. Within one platform, use at least two independent mitigation estimators (e.g. PEC + ZNE, or noise ladder + model-free curve check). See `references/cross-platform-validation.md`.
15. **Only Clifford segments may go through the rule-(N/M/P) canonicaliser.** Split a variational/generated ansatz at its rotation boundaries: canonicalise the `{H, CZ, S}` frames, leave `Ry(θ)`/`Rz(θ)` cores opaque and verify them with the dense matrix oracle. Also: `CNOT · Rz(2θ) · CNOT` is `exp(-iθZ₀Z₁)`, NOT a single-excitation Givens rotation — always check a paper's decomposition against `expm` of the Pauli sum before writing the kernel. See `references/rewriter-composition.md`.
16. **Guppy v1.0 breaks every pre-v1 driver.** `result(...)` → `output(...)`; `measure(q)` returns a `Measurement`, so write `measure(q).read()` (same for each element of `measure_array`); `selene_sim.build(compiled).run_shots(Quest(), ...)` aborts inside the Rust runtime against a v1 package — run through `program.emulator(n_qubits=N).with_shots(S).with_simulator(Quest()).with_error_model(...).with_seed(...).run()` and iterate `shot.entries`. Pass the `@guppy` program object, never `program.compile()`. Error models still come from `selene_sim`. v1 optimises on compile, so rewriter/gate-count benchmarks must pin `program.with_opt_level(OptimizationLevel.Classical)`. Python floor is 3.12. A one-file shim keeping the legacy `build(program).run_shots(...)` shape (see this repo's `quantum/emulate.py`) makes the migration a per-file import swap. See `references/guppy-v1-migration.md`.
17. **TKET (pytket) is the cheapest independent check on a reduction claim.** Compile the same circuit offline with `QuantinuumBackend(device_name="H2-2", api_handler=QuantinuumAPIOffline())` — no credentials, no job, no HQCs — and compare 2q counts against the rewriter's reduced form. Gate every count on a global-phase-free unitary oracle (`||a − (⟨a,b⟩/|⟨a,b⟩|)·b|| ≤ 1e-9`), because an optimiser that changes semantics looks like the best optimiser in the table. TKET **drops idle wires**, so pad with `add_blank_wires` before comparing or sampling or the TVD reads 1.0. Native ops (`PhasedX`, `Rz`, `ZZPhase`, `ZZMax`) map one-to-one onto `guppylang.std.qsystem`, both in halfturns, so a compiled circuit round-trips onto Selene with no angle conversion. Never score approximation families (AQFT band truncation): a correctness-preserving compiler cannot find them. See `references/pytket.md`.
18. **Nexus distribution keys are int tuples indexed by qubit — calibrate before analysing.** `get_empirical_distribution()` (the deprecated `get_distribution()` still works) returns `{(0, 1, 0, …): p}` where `key[q]` is qubit `q` as an **int** (`key[8] == 0`, never `== "0"`), and Qiskit's convention is the exact reverse. Submit a one-gate X-probe job per backend/shape and assert the 1 lands where you expect, before trusting any downstream number. Nexus also refuses to execute an uncompiled ref: `start_compile_job(optimisation_level=…)` → execute the **compiled** ref → `download_result().get_empirical_distribution()`; `jobs.get(id=…)` is keyword-only. See `references/nexus-jobs.md`.
19. **Noisy Nexus emulator jobs have a hard sizing cliff: ≤ ~17 qubits AND/OR ≤ 2048 shots.** A 25q × 8192-shot noisy job ran ~3 h then raised `TimeoutError` with no partial result. Noiseless backends (`H2-1LE`, `H1-1LE`) are far more forgiving. Also: `Helios-1E-lite` needs `HeliosConfig` with an explicit `system_name` and an `emulator_config` — every one of those omissions produces an error that reads like an access problem but is a config mistake. See `references/nexus-jobs.md` (§The Helios lane).
20. **A multi-leg proof is the claim; a single leg is a rumour.** For any headline number, line up a NumPy exact-statevector oracle, a Selene run, a real Nexus job, and an independent simulator (e.g. Aer). Report all legs with shots + seed + job id, and record timeouts/reverts honestly with their cause. Corollary: run the **classical baseline before writing any quantum code** — position-only rotary encoding scoring a perfect 1.000 on every input pair was caught by the baseline, not by the circuit. See `references/cross-platform-validation.md`.
21. **Post-selected fidelity can never exceed the ideal value.** Repetition-encoding each qubit (`|0_L⟩=|00⟩`, `|1_L⟩=|11⟩` via `ry` + `cx`) and post-selecting shots whose physical pairs agree recovers ~+0.03 at 9q and ~+0.06 at 17q (accept 91–95%) on a SWAP-test overlap — detection only, no correction, and recovery grows with depth. If `F_det > F_ideal`, the accept mask or bit order is wrong; fix it before reporting. Compute the `4·√(0.5/shots)` envelope on the **accepted** shot count. See `references/error-detected-inner-products.md`.
22. **The QIR lane needs its own pinned environment and only accepts static kernels.** `guppylang==0.21.16` + `hugr-qir` + `pytket-qir` in a venv separate from the v1 execution env; validate locally with `qircheck` before any upload. Runtime-parameterized angles break emission (bake literals via the temp-module driver pattern) and `discard()` after `measure` aborts (measure-all instead). `qircheck` passing does **not** mean the job will run — QIR execution targets are access-restricted, so document the gap rather than implying it executed. See `references/qir-lane.md`.
23. **pytket halfturns are verifiable in one line.** `Circuit(1).Ry(0.5).get_unitary()` is a π/2 rotation — `Ry`/`Rz`/`ZZPhase` params are half-turns exactly like Guppy's `angle()`, so never multiply by π when porting a formula between the two. See `references/pytket.md`.
24. **Nexus quotas do not cover hardware — `max_cost` is the only real spend guard.** `qnx.quotas.QuotaName` is exactly `compilation | simulation | jupyterhub | database_usage` (CPU-seconds and stored MB); `check_quota("simulation") == True` says nothing about HQC affordability. Guard an H-series run with `max_cost=[…]` on `start_execute_job` (the `QuantinuumConfig.max_cost` field is deprecated), pre-estimate with `qnx.circuits.cost(...)` — which itself runs a billable costing job — and report the real figure from `qnx.jobs.cost(job)`, not a local formula. `Quota.quota` can be the literal string `'No quota set for user'`, so never do arithmetic on it unguarded. See `references/nexus-admin.md`.
25. **`DEPLETED` is a budget outcome, not a circuit bug.** The full lifecycle is `SUBMITTED → QUEUED → RUNNING → COMPLETED | CANCELLED | ERROR | TERMINATED | DEPLETED` (+ `CANCELLING`, `RETRYING`). Report `DEPLETED` and `TIMEOUT` legs with their cause instead of dropping them. `wait_for` defaults to a websocket; pass `HybridStrategy`/`PollingStrategy` for anything that may queue past ~10 min or a dropped socket loses the run. Never resubmit to recover a result — `qnx.jobs.get_all(project=…, properties=…, job_status=[…])` then `jobs.results(job)[0].download_result()`. See `references/nexus-jobs.md`.
26. **Stamp Nexus jobs with typed project properties or the sweep is unrecoverable.** Declare with `qnx.projects.add_property(name, property_type='string'|'int'|'float'|'bool')`, then set them per job (or via `qnx.context.using_properties(...)`); Nexus **propagates a job's properties onto the resources it creates**, so results inherit provenance for free and become queryable. Project names are unique **per user only** — carry `ProjectRef.id` across accounts. Up to 300 programs per job; batch a sweep instead of firing hundreds of jobs. See `references/nexus-jobs.md`.
27. **Role and group semantics decide who can delete your evidence.** Roles are `Reader | Contributor | Maintainer | Administrator`, and where a user holds both a personal and a team role the **most permissive wins** — revoking the personal one changes nothing. Deleting a project deletes it for everyone. A *group* shares a quota (`user_group=` at submission); a *team* shares resources; they are not interchangeable. Hardware queue position is an admin-set priority 1–10, default 5. See `references/nexus-admin.md`.
28. **A lost hardware sweep is re-attached, never re-run — and every job carries a meter.** Recover by job id, by the `execution` block of a shipped dump, or by property query (`--gate`), into an on-disk cache keyed by job id; make resume a command (`python -m quantum.resume`), because an ad-hoc script under pressure becomes a resubmission. Take the decode width from the job's stamped `n_qubits`, not from a driver constant that has moved on. Report a non-`COMPLETED` job with a plain reason and skip it — one `DEPLETED` id must not abandon the other nine — and fail loudly on an unknown id rather than silently executing the row and buying the shots twice. Emit one uniform meter per job (mode `emulator | dry | live | refetch`, device, job id, qubits, shots, seed, estimated + billed HQC, derived delta/ratio) into the dump's `execution` block: `None` must mean "this lane has no such value", so a re-fetch keeps its real `qnx.jobs.cost` even with no estimate, or a resumed sweep reports itself as free. `SweepRunner(resume_from=…)` consumes cached jobs per row and falls through to execution once they run out. See `references/sweep-runner.md` and `references/nexus-jobs.md`.
29. **Run a known-fidelity Bell control beside every batch.** A `|Φ+⟩` pair in the same job costs almost nothing and is the only check that catches a *corrupted batch* rather than a wrong circuit: accept when `anti_correlated / shots <= 4*sqrt(0.5/shots)`, and **fail the whole batch** when it does not — a failed control reported as one bad row among twenty is decoration. Record `bell_anticorrelated` next to the target value in the dump so the check lives in the artefact, not the run log. See `references/evidence-integrity.md`.
30. **Name the trust layer you are actually on.** L1 receipts (job id, shots, seed, committed JSON) → L2 independent arbiters (multi-engine agreement, matched splits, the Bell control) → L3 cryptographic verification on an untrusted server (verified blind computation arXiv:2410.24133, VBPEC arXiv:2607.25704, logical accreditation arXiv:2508.05523). Most work is L1 + part of L2. Never write "cryptographically verified", and never write "ran on the QPU" for an emulator lane whose name shares a prefix with hardware. See `references/evidence-integrity.md`.
31. **Withdraw, don't soften — and run the dequantization gate first.** A result that fails to replicate at larger `n` is withdrawn outright and superseded, with the tool that serves it returning the negative and failing loud on a missing artefact rather than falling back to the flattering older one. Before any advantage claim, run the classical surrogate and report it either way (Born-Ultimatum, arXiv:2511.01845): if a classical method reproduces the distribution within error, the claim dissolves. The 128/512/2048 shot ladder is what separates "shot-noise-limited" from "classically equivalent" — flat metric with paired CIs crossing zero is the latter. See `references/evidence-integrity.md`.
32. **A `code: 14` "you do not have access to this machine" on an *emulator* is a config bug until proven otherwise.** `HeliosConfig()` silently defaults to `system_name="Helios-1"` — the hardware QPU — so a bare config asks for a machine you have no entitlement for and the vendor error names access while the fault is a field you never set. Pass `system_name=`, add `emulator_config=HeliosEmulatorConfig(n_qubits=N)` (both 400 if missing), upload **HUGR** and execute the ref directly because Helios and the `*LE` backends reject pytket circuits and `start_compile_job`, whitelist HUGR runtime ops (`QAlloc`, `QFree`, `Measure`, `MeasureFree`, `Reset`, `helios.*`) in any gate-set preflight, and decode `hugr.qsystem.result.QsysResult` via `result.results[i].entries` — it has no `get_empirical_distribution`. Diff every name field against the device you meant before writing "unreachable" into a results table. See `references/nexus-jobs.md`.
33. **Circuit statistics come from a structural audit, never from recollection.** Compute gate count, CX fraction, depth and per-qubit idle windows from the compiled circuit and serve *that*. The idle windows are the useful part: a long ancilla idle band is the dynamical-decoupling insertion point, and a repeating parity-window cadence must match the encoding you think you compiled — if it does not, the compiler merged or dropped rounds and you will otherwise only see it as a mysteriously worse fidelity. Never let page copy hardcode a gate count the audit would contradict. See `references/agent-native-evidence.md`.
34. **An execution *spec* is not an execution.** A tool may hand an agent the submission contract — builder path (pytket vs HUGR), device, shots, config class, verification discipline — but that response carries no credentials, no job id, no fidelity and no "verified" wording; tokens stay env-only on the runner that holds the session. Generate the spec from the same source the submission path reads, or it is fiction. See `references/agent-native-evidence.md`.
35. **A Nexus account's backend widths are per-backend emulator ceilings, not the published QPU widths.** `H2-Emulator` exposes 26 qubits on an emulator-only account against a 56-qubit H2-1; `Helios-1E-lite` exposes 26 against a 98-qubit Helios-1. Read the width from the device record (or the backends page) and pin the datasheet figure only for the real QPU names. Same table, same trap: the config class is a *family* property — `QuantinuumConfig` for `H1-`/`H2-`, `HeliosConfig` for Helios, `AerConfig` / `QulacsConfig` / `SeleneConfig` for the hosted third-party simulators — and the wrong one fails as `code: 14`, which reads like an entitlement problem. See `references/nexus-jobs.md` (§Backend matrix).
36. **Re-ingest the project brain before any build that touches evidence.** `public/llms-full.txt` is the single source of truth for Lovable and other LLM tools; stale context is the #1 source of wrong generated copy. If the file has changed since the last session, paste the whole thing into the chat or upload it as project knowledge before editing. The skill cards fire on task type; the corpus carries the certified numbers. See `references/lovable-output-hygiene.md`.
37. **Generated copy must pass a certified/forbidden-language table.** Any quantum-advantage or result claim in Lovable output must be traceable to `public/llms-full.txt` or committed `src/data/demos/*.json`; a withdrawn or superseded result must be surfaced as the current state, never softened. End every build with the 8-convention checklist; if a line fails, report it in priority order and do not claim done. See `references/lovable-output-hygiene.md` (§The certified-state / forbidden-language table, §Close with a checklist).
38. **A submitted job is already billed — write the cache row at submit time, not at result time.** The window between "Nexus accepted the job" and "shots came back" is where an environment reset costs real money and leaves a paid result orphaned in the web console. Persist the **`Ref` itself** with `qnx.filesystem.save` plus a `status: "submitted"` cache row carrying the job id, both through an `on_submit(job_id)` hook the moment the id exists — the id string only recovers the job while `jobs.get(id=…)` still resolves it, the saved Ref recovers it from disk, and make the driver's main loop re-attach those rows via `fetch_result` **before** it considers submitting anything. A cell that has a job id is never a cell to submit. See `references/nexus-jobs.md` (§Submit-time persistence) and `references/sweep-runner.md`.
39. **Stamp the sweep coordinates, not just the run metadata.** `device / shots / seed / driver` identify a run; they do not identify *which cell* it measured. A recovered job without `band` and `noise_scale` (or whatever your sweep axes are) in its stamped properties can only be mapped back by submission order or by eyeballing which point of an accuracy curve it looks like — both are guessing, and guessing on a paid artefact is worse than losing it. Declare the axes in the job-property schema alongside the run metadata. See `references/nexus-jobs.md`.
39b. **Namespace the saved `Ref`s per run, and stamp the run id into Nexus too.** A flat ref store keys on the job id alone, so a cell re-submitted after a reset — or two sweeps running at once — overwrites evidence that was already paid for. Mint one sortable run id per process (`---`, overridable by an env var so a resumed process keeps writing into the directory it started), file every `Ref` + sidecar under `_cache_refs//`, and add `run_id` to the declared job-property schema so an orphan found in the cloud names the local directory that owns it. Reads must search the active run, then other runs newest-first, then the legacy flat root — never break the store you already have on disk.
40. **A property query returns COMPILE jobs next to your EXECUTE jobs.** Filtering only by project/property and then reading result fields raises `AttributeError` on the compile rows. Filter `job_type == EXECUTE` first, and read stamped metadata from `job.annotations.properties` — not from a top-level attribute. See `references/nexus-jobs.md`.
41. **The device-authorization path drifts between `qnexus` releases.** `/device/authorize` answers `{"detail":"Not Found"}`; the working path at the time of writing is `/device/device_authorization`. Read the endpoint out of the installed `qnexus.client.auth` instead of a remembered URL, and run the poll loop **in the background** — a foreground poller races the command timeout and dies while the user is still clicking Allow. See `references/nexus-jobs.md` (§Device login).
42. **Environment loss is the expected failure mode of a long cloud sweep, not an exception.** A sandbox reset wipes `.pydeps`, `/tmp`, and `~/.qnx/auth/token.json` while background workers may survive and keep blind-resubmitting against a dead session. Fixed recovery order: **kill the workers → reinstall deps → re-authenticate → purge guard-failure/dry-run rows → re-attach billed jobs → only then submit new cells.** Re-authenticating before killing the workers just lets them spend again. See `references/lovable-orchestration.md` (§Sandbox-reset recovery).
43. **Write Quantinuum code against a crawled docs corpus, not recollection.** All nine sites (Nexus, Guppy, Selene, tket user-guide, tket api-docs, lambeq, Quantum Origin, InQuanto, Systems) are Sphinx and enumerate every page in `searchindex.js` — there is no `sitemap.xml` on any of them. Refresh with `python -m quantum.docs_crawler.{fetch,extract,audit}` (`--site `), 830 pages / 3 799 snippets in ~6 min. See `references/quantinuum-docs-corpus.md`.
44. **Route every undocumented qnexus call through one pinned compat module.** The audit's drift list — `HeliosConfig`, `auth.login_with_token`, `auth.is_logged_in`, `devices.get_all`, `jobs.HybridStrategy`, `jobs.cost`, `jobs.get`, `users.get_self` — is exactly the cost-guard and resume surface. Undocumented does not mean unsupported, it means unversioned, and an upstream rename otherwise surfaces as a bare `AttributeError` deep inside a submission path *after* the HQCs are spent. Pin the client exactly (`qnexus==0.48.2`), fetch each drift symbol through a named accessor in `quantum/qnexus_compat.py` that raises a `QnexusDriftError` naming the symbol and both versions, call `assert_no_drift()` as a preflight so a moved symbol is fatal *before* submission, treat a version mismatch as a loud warning rather than a hard stop, and give the offline fake in `tests/fake_qnexus.py` a `__version__` matching the pin so the whole surface is exercised with zero submissions. Documented calls (`projects`, `circuits`, `hugr`, `filesystem`, `start_*_job`, `jobs.results`, `jobs.wait_for`) stay on plain `qnx.` — a compat layer that wraps everything stops being maintained.
45. **Use the documented job primitives before hand-rolling them.** `qnx.filesystem.save/load` persists a job `Ref` to disk (a ready-made submit-time durability path), `qnx.context.using_properties(...)` nests so sweep axes can be stamped without threading kwargs through every call, `qnx.jobs.results(..., allow_incomplete=True)` harvests a partially finished batch, and `cancel` / `retry_submission(retry_status=..., remote_retry_strategy=FULL_RESTART)` / `delete` cover the recovery cases we previously improvised. `qnx.devices.supports_shots(config)` is a preflight; `SelenePlusConfig` is the hosted Selene lane for cross-checking a local sweep. See `references/quantinuum-docs-corpus.md`.
46. **Only three Quantinuum docs sites expose `_sources`; the rest need an HTML path.** Nexus, tket and lambeq hand back the markdown/notebook twin at `/_sources/.txt`; **guppy, selene, inquanto, origin and systems 404 it** and must be scraped from rendered HTML. Take the article container, decompose nav/sidebar/footer, and re-fence each `div.highlight pre` using the parent `highlight-` class *before* stripping tags — a plain tag-strip keeps the prose and loses every code block, which is the only part worth auditing. Lambeq's 16 `.ipynb` sources still need the notebook flattener.
47. **tket has no single docs root.** `tket/user-guide/` and `tket/api-docs/` are separate Sphinx builds with separate search indexes; `docs.quantinuum.com/tket/searchindex.js` 404s. Register them as two sites or the richest snippet source in the whole corpus (750 blocks from 28 user-guide pages) is silently missing.
48. **Per-library drift is where the next gate hides.** Current audit: guppy clean but we ignore `guppy.load_pytket` and `guppy.nat_var`/`type_var` (generic-width kernels that would collapse our per-n kernel factories); tket documents `Backend.get_operator_expectation_value`, `pytket.utils.expectation_from_counts/shots` and `partition.measurement_reduction` that we reimplement by hand; selene's own examples use `selene_sim.result_handling.parse_shot` + `hugr.qsystem.result` where we hand-roll parsing; `selene_sim.build` is undocumented despite being our entry point. Quantum Origin is CLI/concept prose (5 snippets in 52 pages) — do not expect an importable Python API.
49. **Diff the drift list against the docs automatically, or it rots in both directions.** The list lives twice — prose in the audit, executable truth in `DRIFT_SYMBOLS` — and neither notices when the vendor documents a symbol or a new gate adds an unguarded one. `quantum/docs_crawler/drift_watch.py` derives all three sets (crawled `api_surface.json`, real call sites, `DRIFT_SYMBOLS`) and grades each symbol `stable | newly-documented | undeclared | obsolete`; `--check` exits non-zero, `audit.py` renders the table into both reports, and `tests/test_drift_watch.py` fails the day it goes stale. Two traps make such a diff lie: it must count **compat-mediated** use (once a call is routed through the layer nothing writes `qnx.jobs.cost(` any more, so a direct-call scan grades the whole guarded surface `obsolete` and invites deleting the cost guard — keep a symbol→accessor map for this), and it must **tokenize before scanning** (a plain regex counts `qnexus.auth.login()` inside a docstring and invents an `undeclared` symbol). Stamp the state with the crawl's timestamp: a stale corpus produces confident, stale verdicts.
50. **`max_cost` guards one job; a sweep needs a ledger, and both guards must run before the upload.** Two preflights belong ahead of every paid submission, on the dry-run path as well as the live one — a rehearsal that skips them proves nothing about the spend. First, `qnx.devices.supports_shots(config)`: a distribution-only backend happily accepts a shot-based job and then returns results the shot decoder cannot read, so the refusal has to land before the upload, not after the bill. Second, a per-process **spend ledger**: `max_cost` is enforced server-side per job, so thirty cells at the ceiling cost thirty ceilings; book the estimate the moment Nexus accepts the job (not when the result comes back — that window is exactly where a sandbox reset strands paid work), refuse the next cell when `estimated_total + next > NADARASA_MAX_TOTAL_HQC`, and add `qnx.jobs.cost` billed figures **alongside** the estimates so the dump shows both. `qnx.circuits.cost(...)` gives a truer pre-submission number but is itself billable — keep it opt-in (`NADARASA_COST_PROBE=1`) and let it override the local formula only when it answers. Surface the whole ledger (`budget / jobs / estimated / billed / remaining`) in the dump's `execution` block; `None` must keep meaning "this lane has no such value".
51. **A snapshot audit can describe the API; only a committed baseline can catch it changing.** The expensive failure is a Guppy/Selene/tket/InQuanto symbol moving upstream and surfacing as a crash mid-way through a billed sweep. `quantum/docs_crawler/api_diff.py` snapshots each site's **imports + calls + page set** into `/api_baseline.json` and grades the next crawl `added | removed | moved | breaking`, where the only difference between `removed` and `breaking` is whether *our* code depends on the symbol; `--check` exits non-zero, `--accept` is a deliberate human sign-off, and the rows render into `AUDIT.md`. Four ways this goes wrong: keying on **calls alone** (the documented call surface is a third of the import surface — Guppy 13 vs 45 — and this repo touches Guppy almost only via `from guppylang.std.quantum import h`, so real removals grade as unused), a **stale `our_files`/`our_globs`** in `sites.py` (a new importer nobody added silently downgrades `breaking` to `removed` — widen the globs in the same commit that adds the import), **treating unknown as broken** (missing baseline, uncrawled site, or an empty `.pydeps` after a sandbox reset must stay silent, never `breaking`), and **over-confident rename inference** (pair a removal with an addition only when the match is unique — same leaf under a new parent, or exactly one out and one in under the same parent; anything ambiguous stays a plain removal). `--introspect` resolves used symbols against the installed packages with `importlib`, catching removals the docs still advertise.
52. **Executing the documented examples is the only check that catches semantic drift — and the harness itself is the main source of fake failures.** `api_diff` proves `selene_sim.build` still exists; it cannot tell you the Selene user guide's `result("x", measure(q))` no longer compiles under Guppy 1.x (it doesn't — every emulation page in that guide fails, the same `measure().read()` migration as Gotcha #16). `quantum/docs_crawler/snippet_run.py` runs each page's blocks **in document order, in one namespace, written to a real `.py` file** — never `exec(code_string)`, because Guppy compiles kernels by reading their source back with `inspect.getsource` and a string-exec harness turns 34 healthy Guppy examples into `OSError: source code not available`; per-block attribution comes from a `_snip_mark(i)` line between blocks, which (unlike wrapping each block in `try:`) leaves the doc code unindented for `getsource`. Three more harness traps, each of which manufactures failures that look like upstream breakage: banning **all** sockets (Selene talks to its own emulator over loopback — block non-local `connect`/`getaddrinfo` only), leaving the **temp page path in the recorded reason** (every run then diffs against its own baseline — normalise it to ``), and reading only stderr's last line (Guppy prints the real diagnostic to *stdout* and ends with the useless "Guppy compilation failed due to 1 previous error"). Grade causes apart: `fail` means a genuine breakage, `NameError`/`ModuleNotFoundError`/memory-cap kills are `blocked`, an absent library is `skipped`, and only `pass -> fail` is a regression. Prefilter anything naming `qnexus`, `start_execute_job`, `QuantinuumBackend` or an API key as `blocked` **before** it can run, and keep a committed test that re-derives that from the corpus — a docs pass must never spend money.
54. **When the batch budget runs out, split the batch — don't throw the run away.** A ledger that only knows how to refuse turns a 19-cell sweep into zero cells the moment the 15th crosses `--budget-hqc`, and the operator's only recovery is to hand-slice the parameter list. Give the ledger a `budget_mode`: `halt` (default, unchanged refusal) and `split`, which submits the largest **prefix** that fits and raises `BudgetPaused` — a stop signal, not a failure. Four rules make this safe. Split on a *prefix*, never a knapsack: the deferred tail must be contiguous and resumable, not a scattered set the operator has to reconstruct by hand. A single job whose estimate alone exceeds the whole budget is a hard `BudgetExceeded` in **both** modes — splitting cannot rescue an indivisible cell. `BudgetPaused` subclasses the backend error, so every driver that writes an `"error"` row on `BackendError` must catch it **first** and re-raise: a paused cell has not run yet, and poisoning its cache row makes the remainder unresumable. And report it — `deferred_jobs`/`deferred_hqc`/`budget_mode` in the summary, the deferred coordinates in the dump, and one console line saying exactly what a re-run with a higher ceiling will pick up.
55. **Reconcile spend per job, not per run — and make the record outlive the process.** A batch total answers "did we stay under `--budget-hqc`?" and nothing else; the question that costs money is *which cell* blew its estimate. Open a reconciliation entry on `SpendLedger` at `record_estimate` (submission time, when the money is committed) and close it at `record_billed` with the real `qnx.jobs.cost` figure, grading the pair with one shared function so the console during the run and the audit report afterwards can never disagree. Four rules. A missing bill is `unbilled`, never `on_budget` and never summed as `0.0` — coercing silence to zero flatters every rollup that touches it, so skip `None` in totals and let the total go `null` instead. Grade against a tolerance (10%) rather than exact equality: the local gate-count estimate is approximate by construction, and a zero-tolerance check cries wolf on every job. Stamp the closed entry into the job's `.meta.json` sidecar (`nexus_refs.stamp_cost`) and add `cost` to `VOLATILE_KEYS` — the bill lands *after* the record was checksummed at submission, so it must not make a healthy sidecar read as tampered-with. And print overages in `budget_line`, not only in JSON: an overage that only exists in a file nobody opens is an overage nobody reviews. The offline exporter (`python -m quantum.cost_report --dumps`) reads sidecars first and falls back to `budget.entries` in committed dumps, so a run whose sandbox was reset still reconciles.
53. **A saved job `Ref` is only evidence if you can prove it is the file you wrote.** `qnx.filesystem.save` gives no checksum and no atomicity, so a sandbox reset mid-write leaves a truncated `.ref.json` that still parses — or does not — and `resume` will happily decode paid shots into whatever coordinates the sidecar claims. Write `schema` (`nexus-ref/1`), `ref_sha256`, `ref_bytes` and a self-excluding `meta_sha256` into the sidecar the instant the ref lands, and have `load_ref`/`saved_job_ids`/`resume` verify by default and **skip** (never guess) a record that fails. Four rules keep this honest: the meta digest must exclude **its own key and every volatile key** (`last_status`, `status_at`) or routine status polling reads as tampering; records written before the schema must grade `legacy` — trusted but unprovable — and be upgradable with an explicit `restamp` that never invents coordinates; a sidecar that is simply **absent** is `unverifiable`, not corrupt, and must not fail the ledger verdict; and `--skip-verify` has to exist as a loud emergency rescue, because refusing to read a slightly damaged store is a worse outcome than reading it under protest when real HQCs are on the other side. Surface the verdict per row in `/nexus/refs` — an integrity layer nobody can see is an integrity layer nobody trusts.
56. **Print the shot-capability matrix before the sweep, and make `unknown` mean missing information.** `devices.supports_shots(config)` answers per *config object*, so a census that probes every backend has to build each one through its family's config class (`QuantinuumConfig` / `HeliosConfig` / `AerConfig` / `QulacsConfig` / `SeleneConfig`) — probe them all through one class and the answers are fiction. Build the matrix in the dry-run branch (`python -m quantum.device_matrix`, `_print_device_matrix`) so the devices a sweep would skip are named *before* submission, with the reason attached: `no` from the probe is `SKIP - distribution-only`, a width below `n_qubits` is `SKIP - too narrow`. The rule that keeps it safe is the third verdict: a device whose config class is absent from the installed client, or whose probe could not run because the session is down, grades **`unknown` and stays usable** (`usable is None`, never `False`) — deleting a working backend from an operator's options because a helper was missing is a worse failure than probing it again. Keep the matrix strictly **advisory**: cache it on the backend, surface it in `describe()` and as a committed no-claim dump, but never let it replace the per-submission `supports_shots` preflight, because a table generated minutes ago is not a promise about the job you are about to pay for. Never raise from the builder — degrade to the pinned table with a `source: "pinned"` note.
57. **Make withdrawal structural, and close a question by counting closures.** "Withdraw, don't soften" (#31) is a policy a tired writer forgets; a resolver cannot forget. Put one manifest in charge of artefact state — `current | superseded | archived`, keyed by claim kind — and make every consumer (MCP tool, route loader, report builder, PDF) read *through* it rather than by filename, so a superseded dump is unreachable by construction and a resolution failure raises instead of falling back to the newest readable file. Two reporting rules travel with it. A negative result is not closed by one experiment: report the **count of independent closures** and name each one (Fourier wall / small-`n` simulation / real-kernel refusal / shot-floor / shot-scaling seal), because a single negative always invites "you didn't try hard enough". And a leg stopped by a platform limit is **`assessed-blocked`, not untried** — record the limit and the error string, or the row reads as a gap in the work and someone re-runs the thing that cannot run. See `references/evidence-integrity.md`.
58. **One versioned receipt envelope per artefact, graded four ways.** Scattering shots in the meter, the commit in a provenance hash and the tolerance in a driver means nothing can audit them as a unit. Attach a named, versioned block (`qas/envelope/0.1`) to every result artefact: `claims` (sentences that could be false, not topics), `engine` **plus a separate `backend_qualifier`** so a device name can never be read as hardware, `shots`, `seed`, `commit`, the computed `envelope` (`4*sqrt(0.5/shots)`, stored so a later reader can re-decide the verdict), and `verdict`. Then grade every artefact with a script, and use four grades, not two: `PASS`, `GAP` (envelope present, a field missing — fill it, don't downgrade the claim), `STRUCTURAL` (no envelope at all — migrate or move it to the no-claim list), `FAIL` (envelope present and *contradicted* — block the build). The `GAP`/`FAIL` split is the whole point, and it is the same rule as the device matrix's `unknown` (#56): missing information is not a lie. The rule that holds even before the tooling exists — never display a number without its engine qualifier and shot count in the same view. See `references/receipt-envelope.md`.
59. **The hosted Selene lane has a depth ceiling, and an agent's choice of observable is the thing no automated check validates.** `SelenePlus` runs 17-qubit *plain* circuits fine but dies on 17q with Toffoli/CSWAP depth with `Unexpected end of stream`, reproducibly across circuit structures — a server-side size limit, not a kernel bug: don't bisect your circuit, record the leg as assessed-blocked and re-certify on `H2-Emulator`. The companion lesson is the verification gap PASQAL demonstrated: an agent proposed a plausible-but-wrong observable and then a wrong *hardware* diagnosis to explain the result, and nothing downstream caught either, because every automated layer (envelope, Bell control, digest, verdict test) checks **consistency**, not correctness — a wrong observable measured perfectly passes them all. The observable is a scientific judgement wearing the clothes of a config field sitting next to `shots`, so the review gate belongs *before* execution, on the credential-free spec (`experiment_spec.json` is the same object #34 calls an execution spec — convergent design), with the observable, its baseline and its falsification criterion legible in one screen for a human who knows the physics. See `references/nexus-jobs.md` (§Sizing) and `references/agent-native-evidence.md` (§The verification gap).
60. **A dependency-gated test proves nothing until you run it with the dependency actually gone.** The quickstart notebooks (`quantum/docs_crawler/notebooks.py`) have exactly one executable cell — an env check — and `pytest.importorskip` around it only ever exercises whatever `.pydeps` happens to hold that session, so the skip path is *assumed*, never observed; the first time the library is genuinely absent is the first time you learn the cell fails instead of skipping. The gate that works runs the real test in a **child pytest** with a `meta_path` finder raising `ModuleNotFoundError` for the target module and its submodules, and asserts on the reported outcomes: zero failures, exactly one skip, and a reason naming both the module and its install command. Four rules keep it honest — block the module in the **child**, never the parent session (a parent-side blocker leaks into every later test in the file); parametrise over the spec's *declared* `env_modules`, and keep a separate test re-deriving that list from the cell source, so a newly added import cannot silently escape the guard; a child that fails to start is a **skip carrying its stdout**, never a silent pass; and pair the whole thing with a **byte-level** regeneration check (`read_bytes()`, not nbformat object equality), because indentation, key order and the trailing newline drift between the committed `.ipynb` and a fresh build long before the parsed objects do. Same reasoning downstream: the `/skills` cards are parsed out of the generated markdown with deliberately defensive parsers, so a shape change empties them *silently* — the manifest test asserting non-empty topics and pitfalls is the only thing that notices. See `references/quickstart-notebooks.md`.
61. **A Nexus submission is a four-step contract, and three of its four failure modes are pre-flight, not physics.** The contract is **upload → compile → execute the *compiled* ref → download**: `qnx.circuits.upload(circuit, name=…)` first, because `programs=` takes a Nexus ref and never a local `pytket.Circuit` — the step most copy-paste examples omit, so an agent starts from a `circuit_ref` it has no way to obtain. `programs=` is the current keyword; `circuits=` is its deprecated spelling and surfaces as a deprecation/schema error rather than a rename hint. The accessors are not interchangeable either: a compile job's `CompilationResultRef` gives `.get_output()` (the compiled ref), an execute job's `ExecutionResultRef` gives `.download_result()` → `.get_counts()` / `.get_empirical_distribution()`, and `ExecutionResultRef has no attribute 'get_output'` means a compile ref and an execute ref got crossed in a resume or re-attach path, not that qnexus is broken. Before the first API call, check four things in order — the interpreter that actually holds `qnexus` (the `.pydeps`/venv binary, never bare system python, else `ModuleNotFoundError: No module named 'qnexus'`), a live session, an **active project** resolved by name from `qnx.projects.get_all()` and set with `qnx.context.set_active_project(...)` (else `NoActiveProjectError` or a bare 403 several calls later), and the right config family. Independently corroborated device capacities: `H1-1LE` 20q, `H2-1LE` 56q, `Helios-1E-lite` 50q+. See `references/nexus-jobs.md` (§The compile → execute → download flow, §Pre-flight).
## Navigation
| Task | Read |
| --- | --- |
| Write a kernel (gates, qubits, measurement, controlled phase) | `references/guppy-language.md` |
| SWAP test, Toffoli, CSWAP, amplitude encoding, controlled phase, coset-state QFT readout, parity windows, feed-forward, inverse-QFT-no-swap, CSWAP-chain shifts, host-side metrics | `references/circuit-patterns.md` |
| Guppy v1 migration: renames, emulator builder, compatibility shim, optimisation levels | `references/guppy-v1-migration.md` |
| Parameterized circuits over many inputs; angle hygiene; sys.path injection; `build(mod.program)` shim | `references/driver-pattern.md` |
| Parameter sweeps as first-class objects (`SweepSpec` + `SweepRunner`); resumable per-row cache; per-job meters; resuming a paid hardware sweep | `references/sweep-runner.md` |
| QSP/QSVT kernel + NumPy phase-finder loop recovering φ from a target polynomial | `references/qsp-qsvt.md` |
| Real controlled `pow_const_mod` for small-N Shor (CSWAP-chain cyclic shifts) | `references/shor-modexp.md` |
| Quantum Phase Difference Estimation (QPDE) + ethylene 2-qubit chemistry benchmark | `references/qpde.md` |
| ADAPT-GQE: generative transformer + RL circuit synthesis for molecular ground states | `references/adapt-gqe.md` |
| Nadarasa v0.4.2 reference stack (G16–G19: QPDE, noise + ZNE, TDA, Floquet validation) | `references/nadarasa-v042-stack.md` |
| [[7,1,3]] Steane color code, RGT gadget, partial-FT gadget-insertion cadence | `references/encoded-circuits.md` |
| Quantum-TDA Laplacian moments `T_k^(d)` + WL-indistinguishable synthetic dataset | `references/laplacian-moments-tda.md` |
| Trust ladder (L1/L2/L3), per-batch Bell control, dequantization gate, withdrawal + supersession rules, forbidden phrasings | `references/evidence-integrity.md` |
| Structural withdrawal (manifest as sole resolver), closure counting, `assessed-blocked` reporting | `references/evidence-integrity.md` (§Structural withdrawal, §Negative-result discipline) |
| Receipt envelope `qas/envelope/0.1` and the PASS / GAP / STRUCTURAL / FAIL artefact grading | `references/receipt-envelope.md` |
| Generated quickstart notebooks: spec shape, `--only`/`--out` CLI, env-check gating, byte-match test, `/skills` cards | `references/quickstart-notebooks.md` |
| Cross-platform validation hierarchies (PEC + ZNE / QESEM, IBM ↔ Quantinuum corroboration) | `references/cross-platform-validation.md` |
| TKET / pytket offline compile lane, native rebase, equivalence oracle, Selene round-trip | `references/pytket.md` |
| Quantinuum hardware roadmap (Helios → Sol → Apollo → Lumos) — what runs where and when | `references/hardware-roadmap.md` |
| Submitting real Nexus jobs: login, projects/properties, compile→execute→distribution, job lifecycle, cost control, backend matrix, sizing, bit-order calibration | `references/nexus-jobs.md` |
| Helios lane: `system_name`, `emulator_config`, HUGR upload, `QsysResult` decoding, the code-14 misdiagnosis | `references/nexus-jobs.md` (§The Helios lane) |
| Nexus accounts: organizations/groups/teams, roles and permissions, the four quotas, priority, usage reports, pre-spend checklist | `references/nexus-admin.md` |
| QIR lane (Guppy → HUGR → QIR), pinned 0.21 venv, `qircheck` constraints, execution gap | `references/qir-lane.md` |
| Error-detected inner products: repetition-encoded SWAP test + parity post-selection accounting | `references/error-detected-inner-products.md` |
| Serving committed evidence to other agents (MCP tools, agent card, x402 gate, mock/real toggle) | `references/agent-native-evidence.md` |
| Structural circuit audit (idle windows → DD points, parity cadence) and credential-free execution specs | `references/agent-native-evidence.md` (§Structural audit, §Execution specs) |
| The verification gap: agent-chosen observables need pre-execution expert review | `references/agent-native-evidence.md` (§The verification gap) |
| Lovable output hygiene: project brain, certified/forbidden language, 8-convention checklist, re-ingest rule | `references/lovable-output-hygiene.md` |
| Skill-authoring hygiene: trigger-led descriptions, one job per skill, always-on rules, closing checklist | `references/lovable-orchestration.md` (§Writing and pruning this skill) |
| Render any Selene experiment via the `selene_run` v1 schema + `` | `references/selene-run-schema.md` |
| Compile and run shots; multi-result decoding; mid-circuit measurement; ancilla reuse; noise models (depolarizing, leakage, realistic H2 targets); shipping results to the frontend | `references/selene-runtime.md` |
| Tomographic equivalence proofs (18-cell 1q, 324-cell 2q) + conjecture-synthesis pipeline | `references/tomographic-equivalence.md` |
| Edge-runtime mini-sim + live SSE shot stream from the Worker | `references/live-sampler.md` |
| Legacy bespoke React wiring (pre-schema demos) | `references/frontend-integration.md` |
| **Which primitive at which stage** — unified cheat sheet mapping Guppy / pytket / Selene / qnexus / Systems / InQuanto primitives onto the model→kernel→compile→verify→execute→recover→analyse→publish spine | `references/stack-cheatsheet.md` |
| Crawled docs corpus for all nine Quantinuum sites, per-library API-drift audits, documented job/ref/property primitives | `references/quantinuum-docs-corpus.md` |
| Generating the Guppy / Selene / tket / lambeq quickstart notebooks from the corpus (selection rules, prose-vs-code filter, pinning a bad pick) | `references/quantinuum-docs-corpus.md` (§Quickstart notebooks) |
| API-surface diff across all nine sites: baselines, `breaking`/`moved` grading, `--accept` sign-off, `--introspect` | `references/quantinuum-docs-corpus.md` (§Catching upstream change) |
| Composing a generated/variational ansatz with the Clifford canonicaliser (safe vs unsafe segments) | `references/rewriter-composition.md` |
| Minimal smoke-test script | `scripts/qtda_template.py` |
| Working around Lovable turn-rollback / "internal error" loops on Guppy/Selene experiments | `references/lovable-orchestration.md` |
## Generative AI circuit synthesis
ADAPT-GQE (arXiv:2607.22468) is a new route to quantum chemistry circuits: train a transformer on ADAPT-VQE references, then use RL to refine the generated circuits past the training-data accuracy. If a future task touches generative circuit synthesis, the practical notes are in `references/adapt-gqe.md`. The key takeaway: the generated circuit is still a Guppy kernel, so the same angle-hygiene, tomographic-verification, and committed-JSON rules apply.
## Running as a team
When the work is split across agent profiles or sub-agents (orchestrator / runner /
analyst / scribe), keep the boundaries hard:
- Long sweeps go to a **runner** that only executes and reports artefact paths. It does
not interpret, fit, or claim.
- The **per-row JSON cache** is the hand-off artefact between runner and analyst — never
a chat summary of the numbers. If it isn't in `_cache_/` or `src/data/demos/`,
it didn't happen.
- No claim crosses a profile boundary without its verification evidence attached: the
shot count and `4*sqrt(0.5/shots)` verdict for a probability comparison, the 1e-9
unitary-oracle result for a gate-count reduction, the artefact path for anything else.
- The writer cannot introduce a number that is not already in an artefact.
See the `hermes-agent` skill for the profile mechanics behind this split.
## Quick smoke test
```bash
code--copy knowledge://skill/quantinuum/scripts/qtda_template.py /tmp/qtda_template.py
code--exec python /tmp/qtda_template.py
```
Should print a fidelity in `[0, 1]` for two amplitude-encoded states.
## Reference implementations
- `quantum/qtda.py` — SWAP-test worked example: 8 patients, 28 pairs, Vietoris–Rips filtration, Betti numbers, JSON output consumed by the React routes.
- `quantum/nadarasa_g1.py`, `g2.py`, `g3.py` (+ `*_lib.py`) — coset-state QFT readout, mid-circuit parity windows with ancilla reuse, Birthday-style host-side post-processing.
- `quantum/nadarasa_g4.py` — feed-forward / mid-circuit `if measure(...)` branching.
- `quantum/nadarasa_g8.py`, `g9.py` and `quantum/nadarasa_g1_via_sweep.py` — parameter sweeps via `quantum/sweep.py` (`SweepRunner`). G1-via-sweep is the byte-identical-headline-metrics parity check for the refactor.
- `quantum/nadarasa_g10.py` + `quantum/nadarasa_g10_phasefinder.py` — QSP `qsp_sequence` kernel with hand-chosen φ and the SciPy Powell loop that recovers φ from a target polynomial (Chebyshev `sign(x)`, max |measured − target| < 0.05).
- `quantum/nadarasa_g11.py` + `quantum/nadarasa_g11_real.py` — small-N Shor: compiled `f(x) = x mod 4` shortcut vs. real controlled `pow_const_mod` for `a=2, N=15` implemented as CSWAP-chain cyclic shifts on the orbit of `|1⟩`.
- `quantum/pqp_frontier/tomography.py` + `conjectures.py` — 18-cell 1q tomographic equivalence harness and the driver that PASS-proved 11 conjectured gate identities.
- `quantum/pqp_frontier/noise_resilience.py` + `noise_leakage.py` — `DepolarizingErrorModel` and `SimpleLeakageErrorModel` sweeps showing rewriter-stripped `AQFT_k1` beats full QFT under noise (G1, N ∈ {16, 32, 64}).
- `quantum/pqp_frontier/noise_2q.py` — canonical resumable 2q noise sweep: per-cell cache under `_cache_2q_noise/`, static JSON dumped to `src/data/demos/pqp_frontier_noise_2q.json`, rendered by `src/routes/nadarasa.proofs.noise-2q.tsx` with no server function.
- `quantum/qpde/model.py`, `kernel.py`, `sweep.py`, `validate.py` — full ethylene QPDE benchmark (G16): 20-cell resumable sweep (20/20 PASS), gap fit 0.8099 vs 0.8000 Ha, static JSON at `src/data/demos/qpde_ethylene_selene.json` rendered by `src/routes/nadarasa.g16.tsx`.
- `quantum/qpde/noise.py` + `quantum/qpde/zne.py` — H2-class depolarizing ladder + Richardson quadratic ZNE on the QPDE gap fit (G17): `src/data/demos/qpde_ethylene_noise.json` + `qpde_ethylene_zne.json`, rendered by `src/routes/nadarasa.g17.tsx`.
- `quantum/tda/dataset.py`, `moments.py`, `sweep.py` — Laplacian propagator trace estimator on a WL-indistinguishable C6 vs 2C3 pair (G18): 384-circuit resumable sweep, `src/data/demos/tda_laplacian_moments.json`, rendered by `src/routes/nadarasa.g18.tsx`.
- `src/routes/nadarasa.g19.tsx` + `src/data/nadarasa/quantinuum-2026.ts` — reading note on Leviatan et al. 2026 heavy-hex Floquet QESEM result and cross-platform validation hierarchy.
- `quantum/floquet/model.py`, `kernel.py`, `sweep.py`, `noise.py`, `zne.py` — mixed-field Ising Floquet native port (G20): 384 production cells across chain + heavy-hex-6, ideal-vs-ED max deviation 0.0495, H2 noise ladder + quadratic Richardson ZNE to <2% error, `src/data/demos/floquet_native_zne.json` rendered by `src/routes/nadarasa.g20.tsx`.
- `quantum/adapt/adapt_h2_uccsd.py`, `kernel.py`, `smoke.py` — ADAPT-GQE composition (G21): H2 UCCSD single excitation verified by matrix oracle (1.1e-16), Clifford frames canonicalised by rule (M), 512-shot Selene agreement 0.018; `src/data/demos/adapt_gqe_composed.json` rendered by `src/routes/nadarasa.g21.tsx`.
- `quantum/tket/circuits.py`, `compile.py`, `verify.py`, `selene.py`, `sweep.py` — TKET compile lane (G24): six circuit families in raw vs Nadarasa form, offline `QuantinuumBackend("H2-2")` compilation at levels 0/1/2, unitary-equivalence oracle gating every count, and a Selene round-trip of the compiled natives. TKET level 2 matches the reduced 2q counts on 4 of the 5 scored families; Simon (G23) is the one case where the rewriter stays ahead (3 vs 5 two-qubit gates, a non-local involution a peephole window cannot see). `src/data/demos/tket_compile.json` rendered by `src/routes/nadarasa.g24.tsx`.
- `quantum/emulate.py` — the single execution entry point: Guppy v1 emulator builder behind the legacy `build(program).run_shots(Quest(), ...)` call shape, plus re-exported simulators, error models, and `OptimizationLevel`.
- `quantum/backends.py` + `quantum/backend_selene.py` + `quantum/backend_nexus.py` — the emulator/hardware switch, device-capability preflight, HQC ceiling, and the `job_meter` record every lane emits.
- `quantum/resume.py` — `python -m quantum.resume`: re-attach to Nexus jobs already billed, by id, by a dump's `execution` block, or by property query; caches shots + properties + meter under `quantum/_cache_resume/.json` for `SweepRunner(resume_from=…)`. `tests/test_resume.py` + `tests/fake_qnexus.py` cover it offline, each test asserting the fake recorded zero submissions.
- `quantum/pqp_frontier/dump_conjectures.ts` + `dump_conjectures_2q.ts` — TS-side matrix oracle, enumeration, structural rewriter, and matrix-canonical promotion (rule N / rule M) driving `/nadarasa/proofs/conjectures*`.
For new demos, render results through the shared `selene_run` schema (`references/selene-run-schema.md`) instead of building a bespoke React page.
------------------------------------------------------------------------------
### reference card: adapt-gqe
# ADAPT-GQE: generative circuit synthesis for molecular ground states
Source: Koziell-Pipe et al., *Learning to Prepare Molecular Ground States with Transformer Models*, arXiv:2607.22468 (July 2026). Quantinuum, NVIDIA, Pfizer.
## What it is
ADAPT-GQE is a generative-AI pipeline for quantum chemistry circuit synthesis:
1. **Curriculum generation** — run ADAPT-VQE on a set of molecular geometries to produce high-quality, compact reference circuits.
2. **Supervised pre-training** — train a transformer model to predict the operator sequence and parameters of those reference circuits.
3. **RL refinement** — use a reinforcement-learning objective (circuit fidelity / energy) to improve the generated circuits beyond the accuracy of the ADAPT-VQE training data.
4. **Hardware execution** — run the generated circuits on a quantum device. The paper reports execution on Quantinuum Helios-1.
The headline result is that the trained model generates circuits **an order of magnitude faster** than ADAPT-VQE while matching or improving state-preparation accuracy, and it transfers across related molecular geometries (e.g., different conformations of imipramine).
## Why it matters for this repo
- **Chemistry-on-hardware is the same envelope we target.** The QPDE ethylene benchmark (`references/qpde.md`) is a small-molecule warm-up; ADAPT-GQE is the drug-scale next step.
- **Data-driven + symbolic composition.** ADAPT-GQE generates circuits; the Nadarasa PQP rewriter (`references/tomographic-equivalence.md`) canonicalises and proves them. The two can be stacked: a generated subcircuit can be reduced by rule-(N/M/P), then physically verified on Selene.
- **Template reuse across geometries.** The transfer-learning insight suggests caching circuit templates and parameterising them with `SweepRunner` (`references/sweep-runner.md`) rather than generating from scratch per geometry.
## Critical angle hygiene
If you ever reproduce a Hamiltonian-evolution kernel from the chemistry literature, remember that papers write angles in **radians**, but Guppy's `angle()` is in **halfturns** (multiples of π).
```python
# Correct: 0.5 halfturns = π/2 radians
rz(q, angle(0.5))
# Wrong: angle(math.pi / 2) is ~1.57 halfturns, i.e. 3.14 radians
rz(q, angle(math.pi / 2)) # ❌
```
See `references/qpde.md` (§Guppy angle-hygiene warning) for the full worked example with the ethylene Hamiltonian.
## Practical workflow if we pursue this
1. Generate or import ADAPT-VQE reference circuits for a small active space (e.g., the same 2-qubit ethylene model in `quantum/qpde/model.py`).
2. Train a tiny surrogate model (or hand-craft a few template sequences) over the geometry parameters.
3. Emit the generated circuit as a Guppy `@guppy` kernel in a real `.py` file.
4. Canonicalise with the PQP rewriter or hand-apply rule-(N/M/P).
5. Run tomography on Selene and dump the result to `src/data/demos/.json` (`references/selene-runtime.md` §Shipping results to the frontend).
6. Render the result through the `selene_run` v1 schema (`references/selene-run-schema.md`).
## Caution
- ADAPT-GQE is a **training pipeline**, not a single-shot kernel. The expensive part is classical. Do not attempt to run the full training loop inside a Lovable turn; pre-train or stub the model, then only ship the generated circuit + Selene execution in the repo.
- The paper does not publish the exact trained weights or the full operator vocabulary. Any reproduction will be a small-scale surrogate, not a claim of reproducing the imipramine result.
## Relation to other references
- `references/qpde.md` — small-molecule chemistry benchmark (ethylene), angle-hygiene example.
- `references/sweep-runner.md` — parameterised templates across geometries.
- `references/tomographic-equivalence.md` — verifying generated circuits on Selene.
- `references/selene-runtime.md` — running the final kernels and shipping JSON.
------------------------------------------------------------------------------
### reference card: agent-native-evidence
# Serving quantum evidence to agents
Once results live as committed JSON (the rule in `references/selene-runtime.md`), the same
artifacts can be exposed to other agents rather than only to a web page. This is the
"agent-native" layer: an MCP tool surface, a discovery card, and — optionally — a payment
gate. It is orthogonal to the physics, but it is what turns a results folder into something
another system can query and cite.
## Principle: the artifact is the API
Every tool returns a **committed JSON file**, verbatim, with its shots/seed/backend metadata
intact. No tool recomputes, rounds, or summarises. If an agent asks for a number that is not
in an artifact, the correct response is "not measured", not a plausible value.
## MCP server shape
A stdio JSON-RPC server with zero third-party dependencies (stdlib only) is enough and is the
most portable thing to hand a teammate. A useful tool set looks like:
| Tool | Returns |
| --- | --- |
| `certified_results` | the headline comparison cells (metric, classical baseline, verdict) |
| `sweep_rows` | per-cell sweep rows (n, shots, pass/fail) |
| `demo_links` | verified links to browser-runnable demos |
| `roadmap_status` | phases and their state |
| `model_card` | limitations, governance, data boundary |
| `verify_artifact(name)` | the raw committed JSON for `.json` |
| `circuit_structure(circuit)` | structural audit of a certified circuit (see below) |
| `execute_circuit(circuit)` | a credential-free submission spec (see below) |
`verify_artifact` is the important one: it makes every claim independently checkable in one
call. Test the server with the official MCP SDK client, not with a hand-rolled harness — the
framing/handshake details are where stdio servers break.
## Structural audit (`circuit_structure`)
Circuit statistics quoted in prose drift. Compute them from the compiled circuit and serve
them: total gate count, two-qubit (CX) fraction, gate histogram, depth, and **per-qubit idle
windows** as `[start, end]` moment ranges. The audit is not trivia — each field is an
engineering signal:
- **A long ancilla idle band** (e.g. moments `[28, 64]` on the SWAP-test ancilla) is exactly
where dynamical decoupling goes. If DD helps nowhere else, it helps there.
- **A repeating parity-window cadence** (e.g. `[2,9] [11,18] [20,34]`) is the error-detection
rhythm of an encoded circuit. If the cadence does not match the encoding you believe you
compiled, the compiler dropped or merged rounds — catch it here, not in the fidelity.
- **CX fraction and depth** are the honest way to compare a rewritten circuit against its
original; a reduction claim quotes the audit for both.
Rule: any surface copy or agent answer that cites circuit statistics reads them from this
tool. Never restate remembered gate counts, and never let a page hardcode a number the audit
would contradict.
## Execution specs without credentials (`execute_circuit`)
An agent can be handed a *submission contract* rather than a submission: builder path
(pytket vs HUGR), target device, shots, the config class and its required fields, and the
verification discipline (baseline to compare against, envelope, control circuit). The spec is
plain data and carries **no tokens** — actual submission happens on a runner that already
holds the session, with env-only credentials.
Two rules keep this honest:
- **A spec is not a receipt.** Serving an execution spec never implies the run happened. The
response carries no fidelity, no job id, and no "verified" wording — those only appear once
a real job id and its committed artefact exist.
- **The spec must be the same one the runner uses.** If the tool describes a device or shot
count the runner does not use, the spec is fiction. Generate it from the same source the
submission path reads.
The shape converges independently: PASQAL's agentic-workflow write-up arrives at
the same object under the name `experiment_spec.json` — a declarative,
credential-free description of the experiment, handed between an agent and the
thing that actually runs it. When two teams reach the same artefact from
different directions, that is the interface, not a local convention.
## The verification gap — the real failure mode of agent-run experiments
The same PASQAL study is the cleanest vendor-controlled demonstration of what
goes wrong: the agent proposed a **plausible but wrong observable** and then a
wrong hardware diagnosis to explain the result. Nothing in the pipeline caught
either. Domain-expert review did, after 43 exchanges.
The lesson is specific, and it is not "agents are unreliable":
- An agent's *choice of observable* is a scientific judgement wearing the
clothes of a configuration field. It looks like `"observable": "ZZ"` sitting
next to `shots` and `seed`, so it inherits their apparent triviality — and it
is the one field in the spec that no downstream check validates.
- Every automated layer downstream is **consistency** checking, not
**correctness** checking. A wrong observable measured perfectly passes the
envelope, the Bell control, the digest, and the verdict test.
- So the review gate belongs *before* execution, on the spec, and it must be a
human who knows the physics. Put the observable, the baseline it is compared
against, and the falsification criterion in the spec explicitly, so the thing
needing review is legible in one screen rather than buried in a driver.
- A confident wrong diagnosis of *hardware* behaviour is the second-order
version of the same failure and costs more, because it sends someone to
re-run on a different backend to chase an explanation that was invented.
## Discovery card
If the evidence is also served over HTTP, publish an agent card at **both**
`/.well-known/agent.json` and `/agent.json`. Static hosts (GitHub Pages among them) 404 on
hidden dot-directories, so the well-known path alone silently fails discovery.
## Paid evidence (x402), if you need it
The 402 flow is small enough to implement directly:
1. Unpaid call → HTTP **402** with a challenge body listing accepted payments:
`{"accepts": [{"symbol", "chainId", "payTo", "amount", "nonce"}]}` plus human-readable
instructions.
2. Caller pays, retries with a `X-Paywall-Payment: base64(JSON{txHash, from, nonce, currency,
amount})` header.
3. Server verifies the transaction against the chain explorer API, then returns the artifact
plus a receipt.
Practical notes: price per currency in **base units** and check the decimals — a price copied
across currencies with different decimals produces reverts (a currency with 8 decimals priced
at a 6-decimal amount is 100× off). Some explorer APIs reject bare `urllib` and require a
browser `User-Agent`. Record failed/reverted transactions in the evidence table with their
cause rather than deleting them.
## Mock/real toggle — non-negotiable for demos
Any UI or bridge that can touch a live network carries an explicit `mode: "mock" | "real"`
per request, plus a `force_mock` override that lets the whole flow be verified offline. An
unarmed "real" call must return a clean challenge with arming instructions, never a fake
success. No demo ever shows a "live" status it did not earn.
## Serving a certified number
A tool that hands other agents a "certified" figure is a publication channel, so
it inherits the discipline in `evidence-integrity.md`:
- **Serve the current state, including when it is negative.** If the larger-`n`
replication failed, the tool returns the negative with `beats: false` and a
supersession notice pointing at the withdrawn result. A tool that keeps
serving the flattering earlier number is how a withdrawn claim stays alive.
- **Fail loud on a missing artefact.** `verify_artifact`-style tools must raise
when the current file is absent — never silently fall back to a superseded
one. A fallback turns a withdrawal into a re-publication.
- **One default per question.** Two live artefacts answering the same question
in opposite directions means the caller picks the answer, which is not
evidence.
- **Ship the receipts with the value.** Job id, device, shots, seed, and the
per-batch control verdict belong in the same response as the number, not in a
separate "details" tool the caller will not call.
## Secrets
RPC URLs with embedded keys and any payer private key are environment-only. Grep the tree for
them before every push; a key in a committed demo file is the failure mode this whole layer
is most likely to produce.
------------------------------------------------------------------------------
### reference card: circuit-patterns
# Circuit patterns
Reusable Guppy snippets for the patterns this project actually needs. Copy and adapt.
## Amplitude-encoded feature state (3 features → 2 qubits)
Maps three values in `[0, 1]` to a non-trivial entangled 2-qubit state.
```python
@guppy
def prep_patient(q0: qubit, q1: qubit, a0: float, a1: float, a2: float) -> None:
ry(q0, angle(a0))
ry(q1, angle(a1))
cx(q0, q1)
rz(q1, angle(a2))
```
Pre-scale features into radians: `angles = [f * math.pi for f in features]`.
## Toffoli (CCX) from H, CX, T, Tdg
Standard 6-T decomposition. Guppy has no native CCX.
```python
@guppy
def toffoli(c1: qubit, c2: qubit, t: qubit) -> None:
h(t)
cx(c2, t); tdg(t)
cx(c1, t); tgate(t)
cx(c2, t); tdg(t)
cx(c1, t); tgate(c2); tgate(t)
h(t)
cx(c1, c2); tgate(c1); tdg(c2)
cx(c1, c2)
```
## CSWAP (Fredkin) from CX + Toffoli
```python
@guppy
def cswap(c: qubit, a: qubit, b: qubit) -> None:
cx(b, a)
toffoli(c, a, b)
cx(b, a)
```
## SWAP test (state fidelity)
Measures `F = ||^2` between two registers. `P(ancilla=0) = (1 + F) / 2`.
```python
@guppy
def swap_test(ai: float, bi: float, ci: float,
aj: float, bj: float, cj: float) -> None:
anc = qubit()
pi0, pi1 = qubit(), qubit()
pj0, pj1 = qubit(), qubit()
prep_patient(pi0, pi1, ai, bi, ci)
prep_patient(pj0, pj1, aj, bj, cj)
h(anc)
cswap(anc, pi0, pj0)
cswap(anc, pi1, pj1)
h(anc)
output("anc", measure(anc).read())
discard(pi0); discard(pi1); discard(pj0); discard(pj1)
```
Invert measurement: `F = 2 * P(0) - 1`, clamped to `[0, 1]`.
## Controlled phase from `rz` + `cx`
Guppy has no native `cphase` / `crz`. The standard rz/cx identity gives a controlled phase on `d` from control `c`:
```python
@guppy
def cphase_on(c: qubit, d: qubit, theta: float) -> None:
rz(d, angle(theta / 2.0))
cx(c, d)
rz(d, angle(-theta / 2.0))
cx(c, d)
```
Used in `quantum/nadarasa_g1.py` and `g3.py` to encode dihedral coset structure (label qubit selects `|x⟩` vs `|x+s⟩` branch in the QFT basis).
## Coset-state QFT readout (G1)
Prepare `|+⟩^n` on data, bake the slope `s` as per-qubit phases `2π · s · 2^j / N`, then `H` and measure each data qubit. The marginal of `y` carries the dihedral coset structure:
```python
for j in range(n):
d[j] = qubit(); h(d[j])
for j in range(n):
theta = ((2*math.pi*s*(2**j)/N + math.pi) % (2*math.pi)) - math.pi
phase_on(d[j], theta) # or cphase_on(label, d[j], theta)
for j in range(n):
h(d[j]); output(f"y{j}", measure(d[j]).read())
```
Decode `y` host-side (see selene-runtime.md) and bin into `y % p` for the residue histogram.
## Mid-circuit parity window (G2)
H-sandwiched ancilla touching a disjoint subset of data qubits, measured mid-circuit. The ancilla slot is reused across windows:
```python
# per window w with support j in [w*width, (w+1)*width):
a = qubit(); h(a)
for j in support:
cx(d[j], a) # probe_one(a, d[j])
h(a); output(f"w{w}", measure(a).read())
```
After `k` windows, measure the data register. Use disjoint round-robin supports so each data qubit is touched at most once per window group.
## Host-side metrics
Pair the above kernels with these post-processors (run on the decoded shot dicts):
- **Residue histogram** — `P(y mod p)` from per-shot integer decode. Refutation knob for G1 (SRP slopes concentrate on residue 0; violating slopes should sit at `1/p`).
- **Collision probability** — `Σ_x p_x²` over final data outcomes. A measurement-basis purity proxy; uniform baseline is `1/N`. Used in G2 to track per-window decay.
## Cross-check classically
Always validate with a NumPy statevector before trusting shot statistics. See `classical_fidelity` in `quantum/qtda.py` for the reference implementation (Kronecker products of RY/RZ + CX matrix).
## Feed-forward / mid-circuit branching (G4)
Guppy supports `if measure(...)` mid-circuit; the measured qubit's classical bit drives subsequent gates. Pattern from `quantum/nadarasa_g4.py`:
```python
@guppy
def kernel(theta: float) -> None:
a = qubit(); q = qubit()
h(a)
cphase_on(a, q, theta)
h(a)
b = measure(a).read()
if b:
x(q) # classical-controlled correction
output("b", b)
output("y", measure(q).read())
```
The ancilla is consumed by `measure`; it does not need `discard`. Branch only on freshly-measured bits — do not stash them for later (Guppy bit-flow is forward-only).
## Inverse QFT, no final swap (G11)
Standard IQFT but skip the closing bit-reversal swap; instead, measure in increasing qubit order. Decoder picks `msb_first` vs. `lsb_first` host-side based on which gives the larger overlap with the expected peak set — see `references/shor-modexp.md` for the full driver pattern.
## CSWAP-chain cyclic shifts (permutation oracles)
A chain of `cswap(c, w[j+1], w[j])` implements a controlled left cyclic shift on the work register. For modular arithmetic where `a^k mod N` lands inside a closed orbit of states (e.g. `a = 2, N = 15`), the shift IS the controlled mul-mod on that orbit. See `references/shor-modexp.md` for the orbit-coincidence argument and acceptance gates.
------------------------------------------------------------------------------
### reference card: cross-platform-validation
# Cross-platform validation and error mitigation hierarchies
Reading note from Leviatan et al. 2026 (arXiv:2607.24937), the 74-qubit heavy-hex Floquet Ising magnet executed on IBM Heron r3 and corroborated on Quantinuum H2 and Helios. This file captures the validation hierarchy so future Guppy/Selene experiments can reuse the same structure at smaller scale.
## QESEM in one line
QESEM (Quantum Error Spectrum Extrapolation Method) is a unified software framework that characterizes the noise once with a quasiprobability model and then runs two conceptually different estimators from the same data:
- **QESEM-Unbiased** — Probabilistic Error Cancellation (PEC). Gives an unbiased ideal expectation value at higher sampling cost.
- **QESEM-Extrapolated** — Zero-Noise Extrapolation (ZNE). Noise-amplified circuits are extrapolated back to zero noise; usually lower cost but more heuristic.
When the two estimators overlap in time and agree, the result is protected against a single mitigation failure mode.
## Validation hierarchy
A single number from a noisy QPU is not enough. The paper builds reliability in four layers, from weakest to strongest:
1. **Noise model is validated on the device itself.**
The characterized model is checked against independent calibration data, so the mitigation does not rest on a fit that was never tested.
2. **Two independent mitigation estimators agree.**
PEC and ZNE are different mathematics and different assumptions. Agreement where they both have signal is a strong internal consistency check.
3. **Classical comparison where converged.**
Exact state-vector, small tensor-network, PEPS-BP, or sparse Pauli-path checks are used wherever they converge. In the Floquet paper, both PEPS-BP and sparse Pauli-path fail to converge at the 74-qubit, late-cycle regime, so the quantum data stands alone there.
4. **Cross-platform corroboration.**
Selected circuits are re-run on a different hardware platform with a different noise model and a different compiler. Agreement on the extrapolated observable is the strongest reliability layer because it rules out platform-specific artefacts.
## Mapping to the Nadarasa G16–G18 experiments
The same hierarchy is already present in the repo, just at smaller scale:
| Hierarchy layer | Leviatan et al. | Nadarasa equivalent |
|---|---|---|
| Ideal benchmark | Exact state-vector / small TN | G16 noiseless QPDE gap fit (`quantum/qpde/sweep.py`) |
| Noise model | QESEM characterized noise model | G17 depolarizing ladder anchored on H2 rates (`quantum/qpde/noise.py`) |
| Extrapolation | QESEM-Extrapolated (ZNE) | G17 Richardson ZNE on the noise ladder |
| Independent estimator | QESEM-Unbiased (PEC) | G18 model-free curve χ² vs Taylor-fit moments (`quantum/tda/sweep.py`) |
| Cross-platform | IBM Heron ↔ Quantinuum H2/Helios | Open — Selene only, no hardware cross-check yet |
## Practical takeaways for Guppy/Selene work
- **Budget for at least two independent checks.** A noise ladder plus a model-free curve check (G17 + G18) is already stronger than either alone. If you can add a second mitigation estimator (e.g. PEC alongside ZNE), do it.
- **Cross-platform is the strongest layer.** If a result is meant to be believed beyond classical reach, re-run the core circuit on a different emulator or, ideally, a different hardware family. The compiler and noise model should be independent.
- **Selene limits.** Selene currently ships `IdealErrorModel`, `DepolarizingErrorModel`, and `SimpleLeakageErrorModel`. It does not expose the full QESEM stack, but the G17 ladder + ZNE is the closest available analog.
## Worked shape: a five-engine agreement table
The concrete form a corroboration claim should take — one metric, five
independent engines, every cell inside its own envelope:
| Engine | Kind | Value |
| --- | --- | --- |
| Exact oracle | NumPy statevector, no sampling | 0.3643 |
| Selene | local emulator, Quest | 0.395 (±0.125 at 512 shots) |
| H2-1LE | Nexus noiseless local emulator | 0.3867 |
| Helios-1E-lite | Nexus Helios emulator | 0.3447 |
| Aer | third-party simulator, independent codebase | 0.3540 |
What makes this table load-bearing rather than decorative:
- The **oracle row is not sampled**, so it fixes the target the other four are
scored against; the sampled rows carry the `4*sqrt(0.5/shots)` envelope.
- **Aer is a different codebase entirely** — it shares no compiler, no noise
model, and no vendor with the Quantinuum lanes. Two Quantinuum emulators
agreeing is much weaker evidence than one Quantinuum lane agreeing with Aer.
- Every sampled row carries its **job id and seed**, so the table is a receipt
chain and not a screenshot.
- **Device reachability is account-scoped.** A lane another project fills
routinely can still return `You do not have access to this machine (code: 14)`
on your account. Report the unreachable lane with its real error rather than
quietly shipping a four-engine table as a five-engine one.
## Shot noise vs genuine classical equivalence
When a quantum metric fails to beat a classical baseline, there are two very
different explanations, and the shot-scaling ladder separates them: run the
identical comparison at 128, 512 and 2048 shots.
- Metric **improves monotonically** with shots → shot-noise-limited; more shots
is the honest ask.
- Metric is **flat** across the ladder and the paired-bootstrap CIs cross zero →
**classically equivalent**. No shot budget recovers an advantage, and saying
"needs more shots" is wishful.
Pair the ladder with **matched splits**: both the quantum and the classical
model see identical folds/features, and the comparison is a paired bootstrap on
the difference, not two independently quoted numbers. Full discipline in
`evidence-integrity.md`.
- **Heavy-hex vs all-to-all.** IBM's heavy-hex geometry lets ZZ layers run in parallel in one native gate layer. Quantinuum's trapped-ion architecture is all-to-all but has different gate times and crosstalk. A future cross-platform Floquet test on Guppy/Selene would need to check that the schedule does not blow up in depth after routing.
## Worked example: Gate 0.5.1b, one circuit set, three submission paths
Five circuits (QPDE, Simon raw/reduced, Floquet, discharge QUBO) scored against a fixed
Selene baseline on three Nexus lanes, 15/15 cells populated, 0.0000 HQC billed:
| Lane | Config | Submission path | Result type |
| --- | --- | --- | --- |
| Selene (local) | — | Guppy program → emulator builder | `entries` per shot |
| `H2-1LE` | `QuantinuumConfig`, noiseless | **HUGR upload**, no compile job | tagged entries |
| `H2-Emulator` | `QuantinuumConfig(noisy_simulation=True)` | pytket circuit → `start_compile_job` → execute | `BackendResult` distribution |
| `Helios-1E-lite` | `HeliosConfig(system_name=…, emulator_config=…)` | HUGR upload, no compile job | `QsysResult` |
The lesson is not the pass table — it is that **the same circuit needed three different
submission paths and two different result decoders**. A cross-platform harness that assumes
one path per vendor will report a config failure as an unreachable device; here that
misdiagnosis cost a whole lane until `system_name` was traced. Keep the decode step behind a
per-lane adapter, and make an unpopulated cell carry its real error string into the dump.
## References
- Leviatan et al., "Resolving Structure in Prethermal Floquet Dynamics with Precision Quantum Computation", arXiv:2607.24937 (2026).
- Nadarasa G17 route: `/nadarasa/g17` — QPDE under depolarizing noise + ZNE.
- Nadarasa G18 route: `/nadarasa/g18` — Laplacian moments with model-free curve check.
- Nadarasa G19 route: `/nadarasa/g19` — this reading note in the app.
------------------------------------------------------------------------------
### reference card: driver-pattern
# Driver pattern: parameterized kernels
## Problem
`@guppy` functions can take `float` parameters, but the **outer** program you `compile()` cannot easily close over Python runtime values — and Guppy reads its source via `inspect.getsource`, so you cannot build the program by `exec()`-ing a string.
If you want to sweep many parameter sets (e.g. SWAP-test every pair of patients), each program needs to be a real `.py` file on disk that Guppy can read.
## Solution
Write each parameterized program to a tempfile, then import it with `importlib.util`. The imported module's `program` attribute is a real `@guppy` function with the parameters baked in.
```python
import sys, tempfile, importlib.util, uuid
from pathlib import Path
def run_swap_test(i: int, j: int, shots: int = 2000):
ai, bi, ci = feature_to_angles(PATIENTS[i]["features"])
aj, bj, cj = feature_to_angles(PATIENTS[j]["features"])
src = (
"from quantum.qtda import guppy, swap_test\n"
"@guppy\n"
"def program() -> None:\n"
f" swap_test({ai!r}, {bi!r}, {ci!r}, {aj!r}, {bj!r}, {cj!r})\n"
)
tmpdir = Path(tempfile.gettempdir()) / "qtda_progs"
tmpdir.mkdir(exist_ok=True)
mod_name = f"qtda_prog_{i}_{j}_{uuid.uuid4().hex[:8]}"
mod_path = tmpdir / f"{mod_name}.py"
mod_path.write_text(src)
spec = importlib.util.spec_from_file_location(mod_name, mod_path)
mod = importlib.util.module_from_spec(spec)
sys.modules[mod_name] = mod
spec.loader.exec_module(mod)
# Guppy v1: hand the program object to the emulator, never mod.program.compile()
runner = build(mod.program) # from quantum.emulate import build, Quest
# ... run shots ...
```
## Key points
1. **One library file per experiment.** Keep `@guppy` helpers (`swap_test`, `cphase_on`, `probe_one`, …) in a stable `*_lib.py`. The generated template re-imports them by name; never inline helpers into the rendered source — Guppy needs each helper's source at a fixed importable location.
2. **`sys.path` injection before `exec_module`.** Generated files live under `tempfile.gettempdir()` but do `from quantum. import ...`. Insert your project root into `sys.path` first:
```python
ROOT = Path(__file__).parent.parent
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
```
3. **Angle hygiene.** Before baking a float into source, wrap it to `(-π, π]`:
```python
theta = ((theta + math.pi) % (2.0 * math.pi)) - math.pi
```
Phase formulas like `2π · s · 2^j / N` grow large; the wrap avoids unreadable literals and trims floating-point noise.
4. **Use `{theta!r}` in the template.** `repr(float)` round-trips exactly into source; `str(float)` can truncate.
5. **Unique module names.** Use `uuid.uuid4().hex[:8]` to avoid `sys.modules` collisions across runs.
6. **Register in `sys.modules` BEFORE `exec_module`** so re-imports inside the generated file resolve.
## Alternatives that do not work
- `exec(src, globals())` — Guppy can't read source from string-`exec`'d functions.
- Jupyter cells — same `getsource` failure unless you use `%%writefile`.
- Closures over outer Python variables in `@guppy` functions — angles must be passed as `float` arguments or baked as literals, not closed over.
## See also
For parameter sweeps (G1, G3, G8, G9), prefer `SweepRunner` from `quantum/sweep.py` over rolling the loop above by hand — it's the same pattern factored into a `SweepSpec`. See `references/sweep-runner.md`.
A driver that can run on the hardware lane should take a `--resume-from` (gate label or job ids) and pass it straight to `SweepRunner(resume_from=…)`, so a lost process is recovered by downloading the jobs that were already billed instead of re-executing the rows. See `references/sweep-runner.md` (§Resuming a paid hardware sweep).
------------------------------------------------------------------------------
### reference card: encoded-circuits
# Encoded circuits: [[7,1,3]] Steane + partial fault-tolerance
Recipe for running non-Clifford logical rotations on H2-class hardware without paying full magic-state-distillation cost. This is the "partial fault-tolerant" (pFT) middle path between raw physical circuits and full FT + Clifford+T synthesis.
Source: Quantinuum/SoftBank "Quantum Computing Frontiers" white paper, July 2026, §3.6.
## The [[7,1,3]] Steane color code
- 7 physical qubits → 1 logical qubit, distance 3, corrects any single-qubit error.
- Cell-based stabilizers: each cell's 4 vertex qubits define one X-type and one Z-type generator.
- **All Clifford operations transverse** (bit-parallel physical gates realize the logical action). No overhead for `H̄, S̄, CNOT̄, Ȳ, Z̄, X̄`.
- The non-Clifford burden is entirely on `R̄_z(θ)` for arbitrary θ.
## Recursive Gate Teleportation (RGT) for logical R_z
Standard non-Clifford gadget:
```
|ψ⟩ ---●--- R_z(2θ) --- → R_z(θ)|ψ⟩ (on measurement outcome 0)
|θ⟩ ---⊕----- [measure Z] -- apply R_z(2θ) with sign-flipped angle on outcome 1
```
Pre-measurement state:
```
(R_z(θ)|ψ⟩ ⊗ |0⟩ + R_z(−θ)|ψ⟩ ⊗ |1⟩) / √2
```
- Outcome 0 (prob ½) → done, `R_z(θ)` applied.
- Outcome 1 → recursively apply the same protocol with `2θ` to fix the sign.
**Termination.** For an angle stored as an `n_b`-bit binary fraction `θ/π = b₀ + b₁/2 + … + b_{n_b}/2^{n_b−1}`, the recursion halts after **at most `n_b − 2` rounds**, because `R_z(2^{n_b−2}·θ)` reduces to a Clifford rotation which is transversal (no gadget needed).
Practical implication: choose rotation angles with as few binary-fraction bits as tolerable. 5-bit rounding of `h·t/π` is a good default when the raw Hamiltonian coefficients are only known to ~1e-4 accuracy anyway (see `references/qpde.md`).
## QEC-gadget insertion cadence
The white paper's pFT ethylene circuit uses this rule of thumb:
- Insert an **X-type Steane QEC gadget after every two applications of `u`** (the non-diagonal, single-qubit-rotation-bearing sub-block).
- Diagonal Clifford sub-blocks (`v = R_{Z₁Z₂}(3kπ/2)`) do not need a gadget after each application; batch them.
Rationale: memory-noise errors accumulate during the long idling periods created by nested RGT / QEC gadgets. Denser QEC hurts because it lengthens idle time; sparser QEC hurts because errors compound. Two-`u` cadence is the empirical sweet spot for H2-2 noise.
## Where the errors actually live
Under representative H2-2 emulator noise (see `references/selene-runtime.md` §Realistic H2 noise parameters), the pFT circuit budget breaks down as:
| Noise source | Decoherence parameter q (k=3, k=5) |
| --- | --- |
| Gate + readout error | 0.136, 0.224 |
| Coherent memory (with DD) | 0.120, 0.116 |
| Incoherent memory | 0.104, 0.160 |
Take-aways for future encoded-circuit design:
1. **Incoherent memory + gate/readout dominate** — order any noise-channel activation sweep to hit these first.
2. **Dynamical decoupling neutralizes coherent memory noise** even at `f = 4.3e-2 rad/s`. Always enable DD when transporting or idling encoded qubits.
3. Break-even against the physical baseline was **not** reached in this specific pFT setting — logical > NoQEC, but physical > logical. QEC helped over no-QEC, but the RGT + gadget-insertion overhead pushed the total budget above the raw physical circuit. Report this honestly when comparing pFT to physical baselines.
## When pFT is the right choice
- The circuit is mostly Clifford with a handful of small-angle rotations (e.g. QPDE with the evolution-time trick).
- Rotation angles admit a short binary-fraction representation (few RGT rounds).
- You want to avoid `T`-count blow-ups from Solovay–Kitaev / Ross–Selinger synthesis.
## When to skip pFT and use full FT
- Non-Clifford operations dominate the circuit (arbitrary-angle rotations in every layer).
- Rotation angles are irrational / high-precision (RGT recursion depth explodes).
- Target logical error rate `p_L ≲ 10⁻⁶` — full FT with magic-state distillation scales better past that budget (see `references/hardware-roadmap.md`, Apollo/Lumos generations).
## Read the encoding back out of the compiled circuit
Do not trust that the parity/detection rounds you wrote survived compilation. Run the
structural audit (`references/agent-native-evidence.md`, §Structural audit): the per-qubit
idle windows should show the parity cadence you intended, and any long ancilla idle band is
the dynamical-decoupling insertion point. A missing or merged cadence is a compiler
regression that otherwise only shows up as a quietly worse fidelity.
------------------------------------------------------------------------------
### reference card: error-detected-inner-products
# Error-detected inner products (repetition-encoded SWAP test)
A cheap, near-term alternative to full QEC for the one primitive that dominates quantum
kernel methods: the state overlap `|⟨ψ|φ⟩|²`. Detection only — no correction — which is
exactly the right trade when you are allowed to throw shots away.
## Construction
1. **Encode each data qubit into a repetition pair**: `|0_L⟩ = |00⟩`, `|1_L⟩ = |11⟩`.
Prepare with a rotation on the first physical qubit followed by a CNOT onto the second:
```python
ry(a, angle(theta_halfturns)) # theta in HALFTURNS, as always
cx(a, b) # now (a, b) hold the encoded logical amplitude
```
2. **Run the SWAP test at the logical level** — the ancilla-controlled swap acts on logical
pairs, so each logical CSWAP becomes two physical CSWAPs (see
`references/circuit-patterns.md` for the CSWAP decomposition).
3. **Measure every physical qubit**, not just the ancilla.
4. **Post-select**: keep only shots where each encoded pair measured **equal**
(`00` or `11`). A single bit-flip inside a pair breaks the parity and is discarded.
5. Estimate the overlap from the accepted subset only, and report the accept rate alongside
it.
## Accounting — report all three numbers
| Quantity | Meaning |
| --- | --- |
| `F_raw` | estimate over all shots, no post-selection |
| `F_det` | estimate over accepted shots |
| `F_ideal` | noiseless oracle value (NumPy statevector) |
| `accept` | accepted / total shots |
**Hard invariant: `F_det ≤ F_ideal`.** If a post-selected estimate exceeds the ideal value,
the accept mask or the bit ordering is wrong — see `references/nexus-jobs.md` (§Bit order).
Fix it before reporting; this is the cheapest available self-check.
## Observed behaviour
Recovery grows with circuit depth, because deeper circuits accumulate more detectable single
errors:
- shallow (~9 physical qubits): `F_det − F_raw ≈ +0.03`, accept ≈ 95%
- deep (~17 physical qubits, 4 encoded features): `F_det − F_raw ≈ +0.06`, accept ≈ 91%
Under a plain depolarizing model at device-scale `p ≈ 5e-3`, the recovery is larger still
(+0.19 at ~58% accept) — the density-matrix simulation is optimistic relative to a full
vendor error model, so use it for design, not for the headline.
The cost is shots: budget `n_shots / accept` to hold the same statistical envelope, and keep
the `4·√(0.5/shots_accepted)` threshold computed on the **accepted** count.
## Sizing
Encoding doubles the qubit count, and the SWAP test already doubles it. A 4-feature encoded
comparison lands around 17 physical qubits — which is exactly the noisy-emulator ceiling in
`references/nexus-jobs.md`. Plan the feature count backwards from that limit.
## When not to use it
- If you need an expectation value rather than an inner product, ZNE / PEC give more per shot
(`references/cross-platform-validation.md`).
- If the dominant error is coherent memory rather than discrete flips, parity post-selection
detects little; dynamical decoupling is the better lever
(`references/selene-runtime.md`).
------------------------------------------------------------------------------
### reference card: evidence-integrity
# Evidence integrity: making a number defensible
Running the circuit is the easy half. This card is about the other half: what
has to travel with a number before anyone should believe it, and what you are
not allowed to say about it.
## The three-layer trust ladder
Name the layer you are actually on. Most projects are on L1 and part of L2, and
claim L3 by accident through loose wording.
| Layer | What it guarantees | How you get there |
| --- | --- | --- |
| **L1 — receipt chain** | The number is reproducible. Job id, device, shots, seed, committed JSON, and a second-engine cross-check travel with it, so anyone can re-run and land in the same place. | The `job_meter` record (`quantum/backends.py`) plus a committed `src/data/demos/*.json`. Nothing extra needed. |
| **L2 — independent arbiters** | The number is not an artefact of one provider or one split. Several engines with independent compilers and noise models agree; matched splits + paired bootstrap on any statistical claim; a per-batch control circuit that fails loudly if the batch was corrupted. | Multi-engine table (see `cross-platform-validation.md`) + the Bell control below. |
| **L3 — cryptographic verification** | The number is trustworthy even if the *server is untrusted*: verified blind computation / verifiable blind error mitigation / logical accreditation on the QPU itself. Evidence would carry `trust_model: "untrusted-server"` and a proof reference. | Ion-trap-class protocols: arXiv:2410.24133 (on-chip verified computation), arXiv:2607.25704 (VBPEC), arXiv:2508.05523 (logical accreditation). **Pre-registered target, not a claim you hold today.** |
Say: *"every number carries receipts and cross-arbiters; the verification line
points at Quantinuum-class ion-trap protocols for the QPU phase."*
Never say: *"our results are cryptographically verified"* — that is L3, and L3
requires a protocol you have actually executed.
## Per-batch Bell control
The cheapest arbiter there is, and the only one that catches a corrupted or
misrouted batch rather than a wrong circuit. A known-fidelity Bell pair rides
along in **every** job, beside the target circuit:
```python
# alongside the target circuit in the same submission
q0, q1 = qubit(), qubit()
h(q0); cx(q0, q1)
output("bell", [measure(q0).read(), measure(q1).read()])
```
An ideal `|Φ+⟩` yields only `00` and `11`. The **anti-correlated count** (`01`
plus `10`) is the control statistic:
- Accept the batch when `anti / shots <= 4 * sqrt(0.5 / shots)` — the same 4σ
binomial envelope used everywhere else in this skill.
- On a noiseless lane the expected count is exactly 0; anything above the
envelope means the batch is not the batch you think it is (wrong device,
wrong bit order, a rebase that changed semantics, or a genuinely degraded
machine).
- **Fail the whole batch, not the control row.** A control that fails and is
reported as one bad row among twenty is decoration.
- Record `bell_anticorrelated`, `bell_shots` and the verdict in the dump next to
the target value, so the check is visible in the artefact and not only in the
run log.
The Bell control also doubles as a bit-order probe: on a lane where the key
convention is reversed, `01`/`10` still read as anti-correlated, but pairing it
with a deliberately asymmetric circuit (a one-gate X probe) separates the two
failure modes.
## The dequantization gate
Before **any** advantage claim, run the classical surrogate and report the
result whichever way it falls (the Born-Ultimatum discipline, arXiv:2511.01845).
If a classical method reproduces the distribution or the metric within error,
the quantum claim is dissolved — you say so, in the artefact, in the same
sentence as the quantum number.
This is the citation-backed form of the rule already in `SKILL.md` #20: run the
classical baseline *before* writing any quantum code. The gate is not "did the
quantum thing work" but "is there anything here a classical surrogate cannot
do".
## Negative-result discipline
A negative that is committed with receipts is worth more than a positive that is
softened.
- **Withdraw, do not soften.** A result that fails to replicate at larger `n` is
withdrawn outright, and the withdrawal is the current state. Phrases like
"preliminary", "trending", or "under further investigation" applied to a
failed replication are a way of keeping a dead claim alive.
- **Shot-scaling ladder tells shot noise from equivalence.** Run the same
comparison at 128 / 512 / 2048 shots. If the metric is flat across the ladder
and the paired CIs cross zero, the honest verdict is *classically
equivalent*, not *shot-noise-limited*. Only a metric that improves
monotonically with shots earns "needs more shots". To *seal* the verdict
rather than merely state it, extend the ladder until no plausible shot budget
is left unexamined (EndoTrack sealed theirs at five levels, 128 → 32768, with
7 700 pair-receipts) — a flat curve over two decades of shots is an argument;
a flat curve over one is an invitation to ask for more shots.
- **Supersession is explicit.** When result B supersedes result A, the artefact
for A carries the supersession notice and B is what every consumer gets by
default. Never leave two live artefacts making opposite claims.
- **Fail loud on a missing artefact.** A tool asked for the certified number
must raise when the current artefact is absent — never fall back to the
superseded one. A silent fallback turns a withdrawal into a re-publication.
- **Count the closures.** A question is not closed because one experiment came
back negative; it is closed because several *independent* attempts to open it
all failed. Report the count and name each closure ("closed five ways: Fourier
wall / 10q simulation / real-kernel refusal / shot-floor / shot-scaling seal").
A single negative invites "you didn't try hard enough"; an enumerated set of
independent closures answers it in advance.
- **`assessed-blocked` is not `not tried`.** When a re-certification is stopped
by a platform limit rather than by the physics, record it as assessed and
blocked, with the limit and the error. Leaving it out makes a measured wall
look like a gap in the work, and someone will eventually "fill" it by
re-running the thing that cannot run.
## Structural withdrawal — a manifest, not a policy
"Withdraw, don't soften" is a rule a writer can forget at 2am. Make it a
resolver instead.
- One file is the **sole authority** on artefact state: `current` /
`superseded` / `archived`, keyed by claim kind (EndoTrack's
`results/evidence_manifest.json`; this project's equivalent is the verdict +
`NO_CLAIM` registry in `quantum/verdicts_legacy.py` plus the committed dumps).
- Every consumer — MCP tool, route loader, report builder — reads *through* the
manifest (`read_current_artifact("biomarker_auc")`), never by filename. A
superseded artefact is then unreachable by construction: you cannot serve
`..._n28.json` as current because nothing resolves to it.
- The manifest records the supersession edge, so "what replaced this, and when"
is answerable from the artefact store rather than from a changelog.
- Resolution failure raises. A resolver that falls back to the newest readable
file re-publishes the withdrawn claim the first time a path changes.
The test to write: assert that the withdrawn kind resolves to the superseding
artefact and that asking for the withdrawn id directly returns its supersession
notice rather than its numbers.
## Forbidden phrasings
Keep this list next to any copy generation:
| Never say | Because |
| --- | --- |
| "cryptographically verified" | That is L3; you are on L2. |
| "ran on H2 / Helios QPU" for an emulator run | An emulator is not a QPU, even when the device name shares a prefix. |
| "beats classical" without the dequantization gate | The gate is the claim's precondition, not its footnote. |
| a withdrawn number quoted as current | Supersession exists precisely to stop this. |
| "quantum advantage" from a single lane | One leg is a rumour (`SKILL.md` #20). |
------------------------------------------------------------------------------
### reference card: frontend-integration
# Frontend integration
Quantum simulation is too slow and too heavy to run in the browser. The pattern: run the pipeline once at build time (or on-demand server-side), serialize results to JSON, and consume from typed React routes.
Additional worked examples of this JSON-handoff shape: `src/data/demos/nadarasa_g1.json`, `nadarasa_g2.json`, `nadarasa_g3.json`, each produced by the matching `quantum/nadarasa_g*.py` driver.
## Pipeline output
Write a single JSON file containing everything the UI needs:
```python
# quantum/qtda.py
out = {
"patients": [...],
"fidelity_matrix": [[...]],
"pairs": [{"i": 0, "j": 1, "fidelity": 0.87, ...}],
"filtration": [{"threshold": 0.1, "edges": [...], "beta_0": 4, "beta_1": 0}],
"circuit": {"qubits": 5, "shots_per_pair": 2000, ...},
"guppy_source": Path(__file__).read_text(), # show the source on /code page
"generated_at": "2025-...",
}
Path("src/data/qtda-results.json").write_text(json.dumps(out, indent=2))
```
## Typed loader
```ts
// src/lib/qtda.ts
import data from "@/data/qtda-results.json";
export type Pair = {
i: number; j: number;
quantum_fidelity: number; classical_fidelity: number;
p0: number; shots: number; zeros: number;
};
export type QtdaData = { patients: Patient[]; pairs: Pair[]; /* ... */ };
export const qtda = data as QtdaData;
```
## Consume in routes
```tsx
// src/routes/quantum.tsx
import { qtda } from "@/lib/qtda";
export const Route = createFileRoute("/quantum")({
component: () => (
{qtda.pairs.map(p => - {p.quantum_fidelity.toFixed(3)}
)}
),
});
```
## Regenerating
`python -m quantum.qtda` rewrites `src/data/qtda-results.json`. Vite picks it up via HMR. Commit the JSON so builds are deterministic without needing Python in CI.
## When you need it live
If results must update per request (user-supplied input), wrap the pipeline in a TanStack Start server function. Selene runs server-side on Node; do **not** try to bundle it for the browser.
------------------------------------------------------------------------------
### reference card: guppy-language
# Guppy language
Guppy is a Python-embedded DSL for quantum circuits. Functions decorated with `@guppy` are compiled to a quantum IR.
## Imports
```python
from guppylang import guppy
from guppylang.std.builtins import output, owned, array
from guppylang.std.quantum import qubit, h, cx, rx, ry, rz, measure, discard, t as tgate, tdg
from guppylang.std.angles import angle, pi
```
## Gate set
- Single-qubit: `h(q)`, `rx(q, angle)`, `ry(q, angle)`, `rz(q, angle)`, `tgate(q)`, `tdg(q)`
- Two-qubit: `cx(control, target)`
- Allocation: `q = qubit()` — returns a fresh `|0>`
- Measurement: `m = measure(q).read()` — collapses and returns classical bit
- Sink: `output("label", m)` — record a classical value for the host
- Cleanup: `discard(q)` — release a qubit without measuring
No native Toffoli, CSWAP, `cphase`, or `crz` — decompose manually. `cx` + `rz` are sufficient for controlled phase (see `circuit-patterns.md`).
## Angles
**`angle(x)` takes HALFTURNS (multiples of π), NOT radians.** This is the #1 silent-correctness bug in Guppy work — the kernel compiles, runs, and produces plausible-looking shot statistics that are off by a factor of π. Always read every `angle(...)` literal in that unit.
- `angle(1.0)` = π (Z gate)
- `angle(0.5)` = π/2 (S gate)
- `angle(0.25)` = π/4 (T gate)
- `angle(-0.5)` = −π/2 (Sdag)
- For an arbitrary radian value `θ`, write `angle(θ / math.pi)`.
`pi` is also available as a Guppy constant inside kernels. Float literals baked into generated kernels must be finite and ideally wrapped to `(-1, 1]` halfturns — see `driver-pattern.md` (angle hygiene).
## Function shape
```python
@guppy
def my_kernel() -> None:
q = qubit()
h(q)
m = measure(q).read()
output("m", m)
```
- Return type is usually `None`; classical outputs flow through `output(...)`.
- Helper `@guppy` functions can take `qubit` and `float` parameters and be called from other `@guppy` functions.
- Ownership: a `qubit` passed to a function is moved. Either measure, discard, or return it; do not use it again in the caller.
## File-on-disk requirement
`@guppy` uses `inspect.getsource` to read the function body. The function must be defined in a `.py` file that exists on disk and is importable. REPL / `exec()` / dynamically-built source strings all fail unless you write them to a tempfile and import via `importlib` — see `driver-pattern.md`.
------------------------------------------------------------------------------
### reference card: guppy-v1-migration
# Guppy v1 migration (read before writing any kernel)
Guppy v1.0 (Python ≥ 3.12) is a breaking release. Every pre-v1 driver fails, and two of the
failures are silent-looking: the runner aborts inside Rust rather than raising a Python error.
```bash
pip install "guppylang>=1.0" numpy scipy # selene-sim ships inside guppylang now
```
## Rename table
| pre-v1 | v1 |
| --- | --- |
| `from guppylang.std.builtins import result` | `... import output` |
| `result("tag", value)` | `output("tag", value)` |
| `measure(q)` → `bool` | `measure(q)` → `Measurement`; call `.read()` for the bool |
| `bits = measure_array(qs); output("q", bits[i])` | `output("q", bits[i].read())` |
| `if b:` after `b = measure(a)` | `b = measure(a).read()`, then `if b:` |
| `selene_sim.build(prog.compile()).run_shots(Quest(), ...)` | `prog.emulator(...)` builder |
| `pip install guppylang selene-sim` | `pip install "guppylang>=1.0"` |
`.read()` blocks until the measurement result is available; the compiler error if you forget is
`Values of type 'Measurement' cannot be passed to 'output' directly`.
## Canonical v1 run block
```python
from guppylang import guppy, OptimizationLevel
from guppylang.std.builtins import array, output
from guppylang.std.quantum import cx, h, measure_array, qubit
from selene_sim import DepolarizingErrorModel, Quest # error models still live here
@guppy
def program() -> None:
qs = array(qubit() for _ in range(3))
h(qs[0]); cx(qs[0], qs[1])
bits = measure_array(qs)
for i in range(3):
output("q", bits[i].read())
result = (
program
.emulator(n_qubits=3)
.with_shots(512)
.with_seed(7)
.with_simulator(Quest())
.with_error_model(DepolarizingErrorModel(random_seed=1, p_1q=1e-3, p_2q=1e-2, p_meas=1e-3))
.run()
)
for shot in result: # EmulatorResult is iterable
rec = {str(tag): int(v) for tag, v in shot.entries}
```
`EmulatorInstance` builder methods: `with_shots`, `with_seed`, `with_simulator`,
`with_error_model`, `with_n_qubits`, `with_n_processes`, `with_timeout`, `with_verbose`,
`with_progress_bar`, `with_event_hook`, `with_shot_offset`, `with_shot_increment`,
`with_runtime`, plus the shortcuts `statevector_sim`, `stabilizer_sim`, `coinflip_sim`.
`EmulatorResult` also exposes `results`, `register_counts`, `collated_counts`,
`register_bitstrings`, `to_pytket`.
## Do NOT hand a compiled package to Selene
```python
compiled = program.compile()
build(compiled).run_shots(Quest(), ...) # WRONG on v1
```
This does not raise — it aborts:
`fatal runtime error: failed to initiate panic, error 5, aborting`. The emulator builder hangs off
the **program object**, so drop `.compile()` from every call site.
## Compatibility shim (the cheap way to migrate a large repo)
Rather than rewriting 37 drivers' run loops, add one module that keeps the legacy call shape and
routes it through the v1 builder — this repo's `quantum/emulate.py`:
```python
from quantum.emulate import build, Quest # was: from selene_sim import build, Quest
runner = build(mod.program) # program object, NOT .compile()
for shot in runner.run_shots(Quest(), n_qubits=n, n_shots=S, error_model=em, seed=11):
for tag, value in shot:
...
```
The shim re-exports `Quest`, `Stim`, `IdealErrorModel`, `DepolarizingErrorModel`,
`SimpleLeakageErrorModel`, `OptimizationLevel`, accepts both `seed` and `random_seed`, and raises a
clear `TypeError` if handed a compiled package. Migration then reduces to a per-file import swap.
## Default optimisation
v1 runs `RemoveRedundancies` on compile. Any experiment whose point is the gate sequence — rewriter
proofs, gate-count reporting, tomographic equivalence of two spellings of the same unitary — must
pin the classical-only level:
```python
prog = program.with_opt_level(OptimizationLevel.Classical) # Minimal | Classical | Default
```
## Migration recipe that worked
1. Reinstall: `pip install --target .pydeps "guppylang>=1.0" numpy scipy`.
2. Probe the API in a scratch script before touching the repo (builder method names, `shot.entries`).
3. Add the shim module, then regex-sweep the tree:
- `from selene_sim import ...` → `from quantum.emulate import ...` (leave the shim itself alone —
it is easy to rewrite its own import into a circular one).
- `mod.program.compile()` → `mod.program`.
- `result("` → `output("`, including inside generated-source string templates.
- append `.read()` on every `measure(...)` that feeds an `output(...)` or an assignment, and on
`measure_array` elements read inside `output(...)`.
- re-export lists in `*_lib.py` and `__init__.py` (`measure, result,` → `measure, output,`).
4. Import every module as a compile check:
`for p in Path("quantum").rglob("*.py"): importlib.import_module(...)` — catches stale `result`
imports instantly.
5. Run each experiment's smoke script; only then re-run sweeps.
Host-side variables named `result` (e.g. `result = runner.run(spec)`) must survive the rename —
scope the regex to `result(` followed by a quote and to import lines.
## Small v1 details that cost a debugging round each
- The T gate is exported as **`t`**, not `tgate` (`from guppylang.std.quantum import t`).
Local aliases like `t as tgate` are a repo convention, not the API.
- Emulator entrypoint arguments are **kwargs on `run()`**: `program.emulator(...)....run(**args)`.
Never call `.compile()` for execution — that object is for inspection/gate counts.
- Run drivers as `python3 -m package.module` **from the repo root**. Invoking the file by path
breaks relative package imports inside kernel packages.
## Living with two Guppy versions
The QIR toolchain (`hugr-qir`, `pytket-qir`) still pins the 0.21 line, so a project with a QIR
lane runs **two environments**: the certified execution env on `guppylang>=1.0`, and a
disposable pinned venv on `guppylang==0.21.16`. Under 0.21, parameterized functions go through
`compile_function()`; under 1.0 the emulator builder replaces it. Keep QIR-targeted kernels in
their own module so the version split is visible at import boundaries rather than sprinkled
through shared code. See `references/qir-lane.md`.
------------------------------------------------------------------------------
### reference card: hardware-roadmap
# Quantinuum hardware roadmap (as of July 2026)
Lookup table for citing hardware-generation capability without re-parsing the SoftBank/Quantinuum white paper each time. Use these numbers to say what a proposed circuit *can* realistically run on today vs. what needs waiting for.
Source: Quantinuum/SoftBank "Quantum Computing Frontiers" white paper, July 2026, §5, Tables 5.1 and 5.2.
## Generations
| Generation | Year (target) | Physical qubits (order of magnitude) | Physical 2Q error `p_phys` | Logical error `p_L` (achievable) | Code distance `d` |
| --- | --- | --- | --- | --- | --- |
| **Helios** | Current (2025–2026) | ~100 (98 on Helios) | ~10⁻³ | ~10⁻³ | 3–5 |
| **Sol** | 2027 | ~few×10² | | ~10⁻⁴ | 5–7 |
| **Apollo** | 2029 | ~10³ | | ~10⁻⁷ | 7–9 |
| **Lumos** | 2030+ | 10⁴+ | | ≤10⁻¹⁰ | Chemical accuracy achievable |
## Executable chemistry envelope (spin orbitals)
| Generation | Physical / QED | Logical (FTQC) | Representative use case |
| --- | --- | --- | --- |
| Helios | ~50 (subspace methods, dynamics) | ~10 (early QPE / T-gate benchmarks, Steane [[7,1,3]]) | Small-molecule excited-state PoC (e.g. ethylene at CI) |
| Sol | ~100 (QED with high rejection) | ~10 (toy QPE, small active space, logical stabilization) | Limited excited-state applications |
| Apollo | ~100 (QED or pFT) | ~100 (excited-state QPE) | Medium-scale materials modeling |
| Lumos | N/A (fully logical era) | 100+ (chemical accuracy, scalable workflows) | Integrated materials discovery |
## Executable TDA envelope (graph size)
| Generation | Graph regime | Notes |
| --- | --- | --- |
| Helios | Complete k-partite `K(m, k)` with `k·m < 90` | Error mitigation on noisy qubits; structured validation only |
| Sol | ~100 nodes, ~30 moment steps | Early advantage demos on generic graphs; integration with QEC codes |
| Apollo | 100–300 nodes | Structured real-world approximations; commercial pilot |
| Lumos | Hundreds to thousands | Production quantum-enhanced graph analytics |
## Non-Clifford gate budgets by generation
Cliff+T synthesis via Ross–Selinger typically costs ~`3 log₂(1/ε_synth) + 9` T gates per arbitrary rotation. For representative circuits:
- **Helios**: T-count budgets in the ~10²–10³ range per shot are realistic under pFT + RGT (see `references/encoded-circuits.md`). Avoid full magic-state distillation.
- **Apollo**: T-count ~10⁷ per shot for medium graphs (see `references/laplacian-moments-tda.md`) becomes feasible with full FT + distillation.
- **Lumos**: T-count ~10⁹+ per shot for real-world data-scale TDA; the crossover to end-to-end quantum-advantage workloads.
## Guidance rules
1. If a proposed circuit needs `p_L ≲ 10⁻⁴` with 100+ logical qubits, it is a **Sol-or-later** proposal — mark it as such rather than promising Helios feasibility.
2. If a proposed chemistry model needs >10 spin orbitals *at the logical level*, it is **Apollo-or-later** for FTQC execution. Physical/QED runs may reach ~50 orbitals today but with `P_succ ~ e^{-p·N}` shot overhead.
3. If a graph-TDA proposal needs `n > 100` on generic (non-k-partite) graphs, it is **Sol-or-later**.
4. Ethylene-scale (2-qubit tapered) chemistry benchmarks are **Helios-today** — the pFT Steane demo already ran there.
5. WL-indistinguishable-but-Laplacian-distinguishable synthetic datasets (see `references/laplacian-moments-tda.md`) are **Helios-today** at the K(m,k) < 90 scale.
## What the roadmap does NOT tell you
- Actual availability slots on H2 / Helios hardware (governed by cloud queue, not the roadmap).
- Connectivity constraints (Quantinuum ions are all-to-all in principle but transport time scales with qubit count — this is where the memory-noise budget lives).
- The mix of managed vs. BYO magic-state factories at each generation.
- Any calendar guarantee — treat the years as targets, not commitments.
## What is actually reachable today (Nexus account, Aug 2026)
The roadmap above is capability planning. For "can I submit this tonight", the live backend
list is narrower:
Verified against a live `qnexus` 0.48.2 session (`qnx.devices.get_all()`), 2026-08-21:
| Target | `backend_name` / `device_name` | Status |
| --- | --- | --- |
| `H1-1LE`, `H2-1LE` (noiseless) | `Quantinuum` / `H1-1LE`, `H2-1LE` | available — the default real-stack sanity leg |
| `H1-Emulator`, `H2-Emulator` | `Quantinuum` / `H1-Emulator`, `H2-Emulator` | available — noise-ladder workhorse with `noisy_simulation=True`, ≤ ~17q and/or ≤ 2048 shots |
| `Helios-1E-lite` | `Helios-1E-lite` / `None` | available, but requires **`HeliosConfig`** (not `QuantinuumConfig`); it is listed as a *backend name*, so there is no `device_name` to pass |
| `Aer`, `AerState`, `AerUnitary` | `aer_simulator*` | available — the independent-simulator leg of a multi-leg proof, hosted by Nexus (no local Qiskit install needed) |
| `Braket` / `sv1`, `Qulacs`, `Selene`, `SelenePlus` | — | available; `Selene`/`SelenePlus` are the same emulator we run locally, submitted through Nexus |
| `H1-1`, `H2-1`, `H2-1SC` (real QPU / cluster) | — | **absent from the device list** on this account — no hardware submission surface, and `H2-1SC` is also the QIR execution target |
Sol / Apollo / Lumos are roadmap generations with no submission surface. Cite them as
capability horizons, never as somewhere a circuit was run. Full submission mechanics in
`references/nexus-jobs.md`.
------------------------------------------------------------------------------
### reference card: laplacian-moments-tda
# Laplacian moments for quantum-enhanced TDA
Quantum-computed topological features for graph ML. Use this whenever the user asks about quantum + graphs, fraud/anomaly detection, or "quantum TDA beyond Betti numbers".
Source: Quantinuum/SoftBank "Quantum Computing Frontiers" white paper, July 2026, §4.
## The observable
For the `k`-th combinatorial (Hodge) Laplacian `Δ_k = ∂_{k+1}·∂_{k+1}† + ∂_k†·∂_k` on a simplicial complex, the **d-th Laplacian moment** is:
```
T_k^(d) = Tr((I − Δ_k)^d)
```
Interpretation: `(I − Δ_k)^d` is d-hop diffusion on k-simplices; the trace is its total self-correlation. Small `d` emphasizes local structure; large `d` emphasizes global structure. In the limit `d → ∞`, only the zero-eigenvalue subspace survives, so `T_k^(d) → β_k` (the k-th normalized Betti number).
**Why finite-d beats Betti.** Betti numbers are coarse: distinct structures often share identical Betti signatures. Finite-`d` moments interpolate between local and global, capturing **mesoscopic** correlation that fraud/anomaly labels may depend on but WL-hash / vanilla GNN aggregation cannot see.
## The hybrid pipeline
Standard shape (quantum-HPC-AI):
1. For each graph node, extract its `r`-hop ego-graph.
2. Quantum computes the moment sequence `(T_k^(1), T_k^(2), …, T_k^(d))` for that ego-graph.
3. Attach the moment vector as **node features** to the GNN input.
4. Train the GNN classically on HPC infrastructure.
This is a feature-extractor architecture — no direct quantum ML, no barren plateaus, no data-loading bottleneck. The GNN learns per-class weightings across moment orders automatically.
## Executable envelope
Resource estimates from the white paper for 50 blocks of the algorithm on graphs whose complement edges scale as `n²/20` and max complement degree as `n/20`, compiled to Clifford+T via GRIDSYNTH at `1e-10` whole-circuit accuracy:
| Vertices `n` | T-gate count | T-gate depth |
| --- | --- | --- |
| 10² | 5 × 10⁵ | 1 × 10⁵ |
| 10³ | 1 × 10⁷ | 5 × 10⁵ |
| 10⁴ | 3 × 10⁹ | 1 × 10⁸ |
For structured complete-k-partite graphs `K(m, k)`, aggressive optimization brings this down to <4000 two-qubit gates for up to 15 moment steps on `k·m < 90`, which fits on current Helios.
## The canonical synthetic dataset
WL-indistinguishable-but-Laplacian-distinguishable graph pair, useful as a proof-of-concept that Laplacian features carry information vanilla GNNs cannot see:
- **Graph A**: circulant `C(km, {1, …, (m−1)/2})` on `km` nodes.
- **Graph B**: `k` disjoint cliques `K_m`.
Both have identical 1-hop neighborhood structure → WL-indistinguishable → GNNs classify at chance (50%). They differ in Betti-0 (components) and Betti-1 (loops).
To push the topological difference to **arbitrary high moment order** (making the discriminator provably non-classical for large `d`), take the **graph complements**:
- Complement of A: complete k-partite `K(m, k)` (many holes, complex homology).
- Complement of B: still `k` disjoint cliques after complement transform.
Complement keeps them WL-indistinguishable, makes them Laplacian-moment distinguishable, and — via "suspension" — pushes the discriminating information to arbitrary high `k`. The paper reports 100% vs 50% accuracy on this construction.
## Real-world hook: fraud detection
The white paper motivates this pipeline via International Revenue Share Fraud (IRSF) in telecom:
- 2023 global losses: **$38.95B** (CFCA).
- 1% detection improvement ≈ $390M in prevented loss.
- Fraud signatures often modify **intermediate-scale** correlation structure (small groups of accounts) without altering global topological invariants → exactly the regime where finite-`d` Laplacian moments beat Betti summaries.
For real-graph target sizes, the paper observed 2-hop ego-graphs of 10 to 2×10⁴ vertices in telecom data — which maps directly onto the resource-estimate table above and places the crossover into early fault-tolerant hardware (Sol/Apollo generation; see `references/hardware-roadmap.md`).
## Implementation notes for a Guppy/Selene demo
- Start with complete `k`-partite graphs where the ground truth `T_k^(d)` is analytically known (Berry et al.). Any deviation is a bug in the circuit, not a discovery.
- The block-encoding of `(I − Δ_k)` requires a signed incidence oracle for `∂_k`; write it as a controlled sequence of Pauli products in Guppy.
- Cache moment values per `(graph, k, d)` under `_cache_lm/.json` — sweeps over many graphs benefit from the same resumable-cache pattern used for noise-2q (see `references/sweep-runner.md`).
- Ship results as static JSON, not through a server function (see `references/selene-runtime.md` §Shipping results).
## Worked implementation (G18, this repo)
`quantum/tda/{dataset,moments,sweep}.py` + `/nadarasa/g18`, data at
`src/data/demos/tda_laplacian_moments.json`.
Smallest honest instance: **C6 (6-cycle) vs 2C3 (two triangles)** — same V, E
and all-degree-2 colouring, so 1-WL stabilises immediately. Exact Laplacian
moments agree at `T1 = 12`, `T2 = 36`, diverge at `T3 = 120 vs 108`.
Estimator: instead of block-encoding `(I − Δ)^d`, measure the **propagator
trace** `f(τ) = tr(e^{-iHτ})/N` with `H = Δ / λ_max`, then fit the truncated
Taylor series `f(τ) = Σ (-iτ)^k m_k / k!` for the moments. Cheaper, and the
same data yields every `k` at once.
- Pad Δ to `2^n`, Pauli-decompose (20-22 terms for these graphs), Trotterise
(3 steps → trace error ~8e-4, well below shot noise).
- Hadamard test on 1 ancilla + n system qubits gives `Re⟨x|U|x⟩`; insert `sdg`
on the ancilla before the closing `h` for the imaginary part.
- At n ≤ 3, sweep **all** `2^n` basis states deterministically and average —
cheaper than a purification register and removes state-prep variance.
### Fit conditioning is the whole game
Tune the design offline against simulated binomial noise BEFORE spending
emulator time. Measured on this pair at 4096 shots/circuit:
| τ grid | order | σ(T3) |
| --- | --- | --- |
| 12 pts, 0.5–3.5 | 6 | 2.5 (biased low by ~8) |
| 12 pts, 0.5–3.5 | 8 | 8.2 |
| 12 pts, 0.5–4.5 | 8 | 4.2 |
Truncation order and τ range move σ by ~4x; shot count only moves it as
`1/√shots`. Report BOTH a low-order (low-variance, biased) and a high-order
(near-unbiased, high-variance) fit — a low-order truncation silently
reassigns the discrepancy to the highest fitted moment.
### Report a model-free verdict first
Fitted moments are a lossy summary. Subtract the two measured trace curves
point by point and χ²-test them against the binomial point error
`√2 / √(2^n · shots)`. On this pair that gives χ²/dof ≈ 110 (max point 24σ),
a decisive separation, while the per-moment T3 test at fit order 8 is
inconclusive on the same data.
### Cost budgeting
`n_graphs × n_τ × 2^n × 2 parts` circuits. The G18 grid is 384 circuits at
~4 s each ≈ 30 min — far past one sandbox command. Cache per circuit
(`_cache_tda/_t<τ>_b__s.json`) and drive it from a
background resume loop; poll the cache-file count rather than blocking.
------------------------------------------------------------------------------
### reference card: live-sampler
# Edge-runtime quantum: mini-sim + live SSE shots
Selene + Guppy require Python and don't run inside a Cloudflare Worker. For *small* live-streaming demos (≤ 4 qubits, fixed kernel topology), this repo runs a pure-TypeScript statevector simulator inside the Worker and streams shots over Server-Sent Events. Everything bigger stays on the Python driver.
## Mini-sim (`src/lib/selene/mini-sim.ts`)
A 2-qubit (extendable to ~4-qubit) statevector simulator in TypeScript:
- Complex amplitudes as paired Float64 arrays.
- Gates: `H`, `RX(θ)`, `RZ(θ)`, `CX`. Add gates as needed; each is a closed-form unitary on the relevant amplitudes.
- `measure(qubitIndex, rng)` does a projective measurement with collapse: compute `P(0)`, draw vs. `rng()`, zero out the eliminated branch, renormalise.
This is enough for any kernel using only the gate set above.
## Porting a Guppy kernel to TS
Mechanical translation. G4 is the worked example:
- `quantum/nadarasa_g4.py` — original feed-forward kernel.
- `src/lib/selene/kernels/nadarasa-g4.ts` — exported `runG4Shot(theta, rng)` returning `{ b, y2 }`.
Rules:
- One TS function per kernel; takes parameters + an `rng: () => number`.
- Mirror the gate order exactly. Mid-circuit `measure` returns 0/1; branch on it just like the Guppy `if measure(...)`.
- Return the labelled measurement record — same keys the Python driver `output(...)`-tags.
## SSE endpoint
`src/routes/api/public/nadarasa-stream.ts`. Public route per the public-API guidance:
```ts
// runs in the Worker; seed RNG with crypto.getRandomValues(...)
const rng = mulberry32(seedFromCrypto());
for (let i = 0; i < n; i++) {
const shot = runG4Shot(theta, rng);
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ i, ...shot })}\n\n`));
}
```
The client (`src/routes/nadarasa.stream.tsx`) consumes the stream and runs a live verification gate: collect ≥ 500 shots, compute empirical `P̂(y | b)`, compare to analytic `P(y | b)` derived from the kernel; toggle a **Live-verified** badge when `max |P̂ − P| < 0.05`, **Drift** otherwise.
## Multi-kernel dispatch
When porting more than one kernel, take a `?kernel=` query parameter and dispatch in the SSE handler. Each kernel ships its own `meta.predicted` block; the client uses that to drive both the **Live-verified** gate (sample-vs-analytic) and any structural-tension chip (analytic-vs-baseline). Keep the kernel choice + its parameters in the URL so a session is reproducible.
## When NOT to use this
- **More than ~4 qubits.** Statevector size blows up; the Worker has a CPU and memory budget. Keep big circuits on Selene.
- **Anything needing Selene's real noise model, real compilation, or anything beyond the trivial gate set.** The mini-sim is a teaching tool, not a Selene replacement.
- **Anything where the Python driver is the proof.** The frontier-verified result lives in `src/data/demos/.json`; the live stream is a UX layer on top of an already-verified experiment, not a substitute for one.
## Lesson: derive the predictor from the circuit, not the docstring
The first G1 live port reused the offline driver's stated closed form, `P(y|s) = (1 + cos(2π y s / N)) / N`. The shots disagreed by ~0.4. The samples were right; the closed form was wrong — the Guppy kernel applies `H^n` + Z-basis readout (Walsh–Hadamard), not a real QFT, so the Fourier-basis formula does not apply. Always re-derive the predictor from the gates the kernel actually executes; the live sampler is the cheapest way to surface a mismatched analytic, and once corrected it sharpened the G1 verdict rather than weakening it.
------------------------------------------------------------------------------
### reference card: lovable-orchestration
# Lovable orchestration resilience (Guppy/Selene turns)
Large Guppy/Selene turns on Lovable can fail with "An internal error occurred"
that looks like an app crash but is actually a **task-transaction rollback**:
the platform discards every file write from the failed turn while the dev
server and prior git state remain healthy. Recognize it in <30 s and shift to
a gated workflow instead of retrying the same monolithic turn.
## Symptoms
- Toast: "An internal error occurred" (often 2–4× in a row on the same request).
- Dev server (`vite`) still alive, preview still serving the previous state.
- `git status` clean at the last stable revision; no new files on disk.
- `git log --all --grep=` shows no commit for the work you thought landed.
- `git reflog` shows a churn pattern: `checkout → edit-branch → reset → "Changes"/"Work in progress"` cycles.
- No stack trace, port conflict, Vite HMR error, or dependency error in
`/tmp/exec-logs/*.log` from the failed turn's window.
If those five checks pass, it is a rollback, not a crash. Do **not** debug the
app code — nothing about it broke.
## 30-second diagnosis checklist
```bash
ps aux | grep -E '[v]ite' # dev server alive?
git status # working tree clean?
git log --all --oneline --grep= # did any commit land?
git reflog | head -30 # retry/reset churn?
ls quantum// 2>/dev/null # artifacts on disk?
```
All five negative → rollback confirmed.
## Root causes observed
Turn-level context pressure is the trigger. Contributors, in rough order:
1. Parsing multi-MB PDFs inline in an implementation turn (do it in a scoped
research turn, cache extracted facts as memory, then implement).
2. Reading `src/routeTree.gen.ts` or other generated/large files.
3. Reading `_cache_*/` sweep directories with hundreds of per-row JSONs.
4. Reading multi-hundred-line research digests like `quantum/PQP_DIGEST.md`.
5. Spawning ≥3 sub-agents concurrently, or a sub-agent whose task fans out
("audit the whole plan", "review every route").
6. Writing many unrelated files in one turn.
## Gated authoring protocol
One **atomic gate per turn**. Each gate ends with a `git`-visible artifact
that stands on its own; verify with `code--view` before advancing. If the
platform rolls back, you lose one gate, not the whole experiment.
Template gate sequence for a new Selene experiment:
| Gate | Deliverable |
| --- | --- |
| 0 | Dependency canary — pin/bump `quantum/requirements.txt`; end turn. |
| 1 | Classical model — `quantum//model.py`, unit-checked at import. |
| 2 | One-cell smoke kernel — `quantum//kernel.py` + smoke driver that compiles and runs a handful of shots. |
| 3 | Resumable driver — per-row cache under `_cache_/`, `timeout 580` friendly. |
| 4 | Cached sweep + static JSON dump to `src/data/demos/.json`. |
| 5 | Route wiring — one static `src/routes/...tsx` reading the committed JSON. |
| 6 | Browser verification via Playwright screenshot. |
## Persistence canary pattern
Start every new experiment session with a trivial write (comment in
`quantum/requirements.txt`, a version note in a changelog file) and **end the
turn immediately**. If that trivial edit persists, larger gates are safe to
attempt. If the canary itself rolls back twice in a row, stop — further code
changes won't fix a platform-side failure. Tell the user to file a Lovable
Support ticket with the "internal error" screenshot and timestamp.
## Sub-agent budget
- ≤2 concurrent, read-only, narrow tasks per turn.
- Give each sub-agent a specific question, not a scope ("find the exact
Hamiltonian constants in this section" — not "audit the QPDE plan").
- Never ask a sub-agent to read `src/routeTree.gen.ts`, `_cache_*/`, or
uploaded PDFs > 1 MB; the fan-out returns into your context.
## What NOT to read in an active experiment turn
- `src/routeTree.gen.ts`
- `quantum/pqp_frontier/_cache_*/` (any file inside)
- `quantum/PQP_DIGEST.md`
- Any uploaded PDF > 1 MB (parse in a separate scoped turn, cache facts to
`mem://features/...` before returning to implementation)
## Escalation
Canary rolls back twice → stop coding, ask the user to file a Lovable Support
ticket with the "internal error" screenshot + timestamp. Continuing to edit
project code cannot repair an orchestration-service failure.
## Confirmed root cause: unignored `.pydeps/` (2026-07-30)
Lovable Support confirmed the dominant trigger for the repeated rollback loop:
the vendored `.pydeps/` tree (hundreds of MB, thousands of files) was **not in
`.gitignore`**, so every turn-save tried to snapshot it and timed out. The
timeout surfaced as "An internal error occurred" with a clean tree afterwards.
Fix, in one atomic gate:
```bash
echo '.pydeps/' >> .gitignore
python -m pip install --target .pydeps --no-cache-dir -r quantum/requirements.txt
PYTHONPATH=.pydeps python -c "import guppylang, selene_sim; print('ok')"
```
After that, multi-file gates (kernel factory + sweep + validate + route) landed
without rollback. Rules going forward:
- Any generated/vendored directory an experiment writes (`.pydeps/`,
`_cache_*/`, `/tmp` mirrors) must be gitignored **before** it is populated.
- Context pressure (§Root causes above) is still a secondary trigger; keep the
sub-agent budget and gated protocol.
- The persistence canary remains the cheapest way to confirm the environment is
healthy at the start of a session.
## Writing and pruning this skill
Skill files are retrieved by description match, so authoring hygiene decides whether the
content is ever loaded:
- **Trigger-led description.** The `description` says *when to load*, not what the author
knows: name the tasks, tools and file areas that should fire it. A description that reads
like a topic label never triggers.
- **One job per skill.** Running quantum experiments and building the site are separate
skills. A skill that covers both matches everything and helps nothing.
- **State the boundary.** An explicit "not for …" clause is as load-bearing as the trigger;
without it the skill is pulled into unrelated work and its rules get applied wrongly.
- **Always-on rules are not a skill.** Anything that must hold on *every* message (project
framing, forbidden vocabulary, the evidence discipline) belongs in project/workspace
knowledge. Skills fire on task type; rules that fire always must not depend on retrieval.
- **Concrete values over adjectives.** Real thresholds, real error strings, real commands.
"Be careful with angles" teaches nothing; "`angle(x)` is halfturns" does.
- **Review and prune.** Each revision deletes what is superseded. A card kept "just in case"
competes with the current one for attention and eventually contradicts it.
## Close with a checklist, not a claim
End a multi-rule build by reporting each convention as pass/fail — framing, numbers traced to
committed artefacts, mock/real toggles, receipts, no secrets in generated code, evidence
honesty (withdrawals and shot-scaling verdicts respected), contracts intact. If any line
fails, list the failures in priority order and **do not claim done**. A summary that asserts
completion while a convention is unmet is the failure mode this checklist exists to catch.
For the full Lovable build hygiene protocol — the project brain, the certified/forbidden-language
table, the 8 conventions, and the re-ingest rule — see `references/lovable-output-hygiene.md`.
## Sandbox-reset recovery
A long cloud sweep will outlive its sandbox. A reset wipes `.pydeps`, `/tmp` (logs and PID
files included), and `~/.qnx/auth/token.json` — while background worker processes may survive
and keep looping against a dead session. Treat it as expected, and recover in this order:
1. **Kill the surviving workers.** `ps -ef | grep ` then `kill`. Doing this after
re-authentication just hands them a live session to spend on.
2. **Reinstall the toolchain** into `.pydeps` (`guppylang`, `selene-sim`, `qnexus`, `pytket*`,
numpy/scipy). `.pydeps/` must already be in `.gitignore` — see pitfall #11.
3. **Re-authenticate** with a background device-login poller; give the user the code and wait.
4. **Purge guard-failure and dry-run rows** from the per-row cache.
5. **Re-attach every billed job** (`quantum/resume.py`, or the driver's `submitted`-row path)
and confirm the recovered count against what the Nexus console shows.
6. **Only then submit new cells.**
Never let a reset turn into a resubmission. The console is the source of truth for what has
been paid for; the local cache is a mirror that can lag it, never the other way round.
------------------------------------------------------------------------------
### reference card: lovable-output-hygiene
# Lovable output hygiene (EndoTrack-hardened, generic)
The EndoTrack programme produced a disciplined app-building workflow for Lovable: a single project brain, a certified/forbidden-language table, explicit re-ingest rules, and a non-negotiable convention checklist. This card ports those patterns into the Nadarasa/Quantinuum context so any future Lovable-generated surface stays honest.
## 0. The project brain — ingest FIRST
The single source of truth for any LLM tool (Lovable, GPTs, other agents) is:
```
public/llms-full.txt (built by scripts/build-llms-txt.mjs; ~240 KB as of v0.4.10)
```
- Paste the whole file into a new LLM session before building or editing any surface that touches the quantum results.
- If the file has changed since the last session, re-ingest the new version before continuing. Stale context is the #1 source of wrong output.
- The skill cards in `.agents/skills/quantinuum/` are the runtime instruction layer; the LLM corpus is the factual layer. Use both.
## 1. The certified-state / forbidden-language table
Every generated page must pass through this table. If a claim is not in `public/llms-full.txt` or the committed `src/data/demos/*.json` files, it does not go in the UI.
| Topic | Certified state (say THIS) | Forbidden (never say) |
|---|---|---|
| AQFT crossover law (G26) | 24/24 cells satisfy the analytic bound; strict wins in 2 cells; 72-gate saving on n=10; TKET corroborates 72/72 equivalent and 20/20 savings; Nexus H2-1LE/H2-Emulator/Helios-1E-lite n=6 slice matched within 0.125 tolerance | "Quantum speedup", "provably optimal", "classically intractable" |
| QPDE ethylene gap (G16) | Selene gap fit 0.8099 Ha vs classical 0.8000 Ha; 20/20 PASS | "Solved molecular electronic structure", "beats VQE" |
| Noise/ZNE (G17) | Richardson quadratic ZNE on H2-class depolarizing ladder; model-free curve χ² reported | "Error-corrected", "exponential suppression" |
| TDA Laplacian moments (G18) | C6 vs 2C3 distinction via Taylor-fit moment estimators; conditioning-limited, not shot-limited | "Quantum machine learning advantage", "classified graphs" |
| Floquet native port (G20) | Mixed-field Ising on chain + heavy-hex-6; ideal-vs-ED max deviation 0.0495; ZNE to <2% error | "Demonstrated quantum simulation of [real material]" |
| ADAPT-GQE composition (G21) | H2 UCCSD single excitation verified by matrix oracle; 512-shot Selene agreement 0.018 | "Generative AI discovered a new ansatz" |
| TKET compile lane (G24) | Offline `QuantinuumBackend("H2-2")` compilation; unitary-equivalence oracle gates every count; Simon is the one non-local case where the rewriter stays ahead | "TKET validates quantum advantage", "compiler proved correctness" |
| Nexus executions | Emulator lanes (H2-1LE, H2-Emulator, Helios-1E-lite, Aer, Qulacs, Selene, SelenePlus) with receipted job ids; 0.0000 HQC billed for emulators on this account | "Ran on H2 / Helios QPU", "real hardware execution" |
| Evidence chain | Machine-checked verdicts for G1-G26; SHA-256 provenance hashes; per-job meters; legacy backfill in place | "Cryptographically verified", "tamper-proof" |
If a claim has been withdrawn or superseded, the current state must reflect the withdrawal, not a softened version of the old claim.
## 2. Re-ingest rule
Before any Lovable build that touches science, demo numbers, or evidence copy:
1. Check the file date/size of `public/llms-full.txt`.
2. If it is newer than the last session, paste the full contents into the chat or upload it as project knowledge.
3. If a route's `head()` metadata or a `src/data/demos/*.json` file changed, re-ingest after the build script has run.
Stale context produces stale claims. A stale claim is a rollback risk.
## 3. The eight conventions (non-negotiable for every Lovable build)
1. **Framing**: Nadarasa Reduction / quantum reduction methods only. No disease-specific clinical claims unless the route explicitly supports them.
2. **Numbers**: only numbers from `public/llms-full.txt`, `src/data/demos/*.json`, or committed `src/data/nadarasa/*.json` files — never invent AUC, fidelity, or gate-count figures. Every number in the repo carries shots+seed or a SHA-256 provenance hash; if it lacks a receipt, it does not go in the UI.
3. **Mock/real toggles**: any demo/payment/hardware UI needs an explicit mock/real switch. No fake "live" status.
4. **Clickable receipts**: tx hashes link to testnet.arcscan.app (new tab); (mock) labels when no real reference exists.
5. **No secrets in the page**: Nexus tokens, Alchemy RPC keys, and payer private keys are env-only, never in Lovable-generated code or the portal.
6. **Static-only serving**: Lovable Cloud/Cloudflare Workers have no server-side secrets. Quantum results are shipped as committed JSON, not live server functions.
7. **Verification honesty**: if showing the trust story, use the three layers — L1 receipts (live), L2 arbiters incl. per-batch Bell control (live), L3 cryptographic verification (pre-registered QPU target, NOT claimed today). Never "cryptographically verified".
8. **Skill and API contracts**: MCP tools, A2A cards, bridge routes, and x402 wire formats must stay intact. Generated code must not break these contracts.
9. **Circuit orthography**: if surface copy cites certified circuits, reference the structural audit (`references/agent-native-evidence.md`) and the `circuit_structure` convention — never invent circuit statistics.
10. **Anchors are not compliance**: citing a verified vocabulary identifier (SNOMED CT, dm+d, ICD-10, an NHS dataset code) anchors a term — it is not a claim of standards compliance, certification, or clinical validation. Write "SNOMED-anchored terminology", never "SNOMED compliant", and never let an identifier imply the artefact passed a conformance process it has not been through. Same discipline as the engine qualifier: the label names what was done, not what it resembles.
11. **Attribution travels with the work**: where the clinical framing, pathway, or IP belongs to someone else, the credit line ships on every surface that uses it — page footer, README, exported PDF, agent card — not only on the about page. An attribution that exists in one place is an attribution that gets dropped by the next refactor.
## 4. Close with a checklist, not a claim
End every Lovable build with a compact pass/fail report:
```
✅ Framing (quantum-reduction-only, no disease/BCAC/CRUK terms scanned)
✅ Numbers (every figure traced to llms-full.txt / src/data/demos/*.json)
✅ Toggles (mock/real where relevant)
✅ Receipts (tx links → arcscan, (mock) labels where needed)
✅ Secrets (none in generated code/pages)
✅ Static gate (committed JSON, no live server functions for quantum results)
✅ Science honesty (withdrawal + shot-scaling verdict + dequantization gate respected)
✅ Contracts (MCP/bridge/A2A/skill contracts intact)
✅ Anchors + attribution (identifiers labelled as anchors, credit line on every surface)
```
If any item fails, list it in priority order — do NOT claim done.
## 5. Edge cases
- **Lovable lacks context** → point it at `public/llms-full.txt` or the built `llms.txt`; re-ingest before continuing.
- **"An internal error occurred" toast** → see `references/lovable-orchestration.md`: diagnose in 30 seconds, then shift to gated atomic gates.
- **Stale science in context** → re-ingest; verify the withdrawal language and shot-scaling verdict survived.
- **A requested figure isn't in the brain** → do not invent it; state it is not in the certified set and ask for the repo path.
------------------------------------------------------------------------------
### reference card: nadarasa-v042-stack
# Nadarasa v0.4.2 reference stack (G16–G19)
Four white-paper tracks reproduced or mapped in the Nadarasa notebook, arranged as a validation hierarchy. Use this as a template for structuring a multi-experiment Guppy/Selene release.
## The stack
| Gate | Track | What it is | Artifact |
|---|---|---|---|
| G16 | Ethylene QPDE | Ideal noiseless gap fit on the 2-qubit ethylene π/π* active space | `quantum/qpde/{model,kernel,sweep,validate}.py` → `src/data/demos/qpde_ethylene_selene.json` → `/nadarasa/g16` |
| G17 | QPDE under noise + ZNE | H2-class depolarizing ladder + Richardson extrapolation | `quantum/qpde/noise.py`, `quantum/qpde/zne.py` → `src/data/demos/qpde_ethylene_noise.json`, `qpde_ethylene_zne.json` → `/nadarasa/g17` |
| G18 | Laplacian-moment TDA | Propagator trace estimator separates C6 vs 2C3, a 1-WL-indistinguishable pair | `quantum/tda/{dataset,moments,sweep}.py` → `src/data/demos/tda_laplacian_moments.json` → `/nadarasa/g18` |
| G19 | Prethermal Floquet reading note | Cross-platform validation hierarchy from Leviatan et al. 2026 | `/nadarasa/g19` + `src/data/nadarasa/quantinuum-2026.ts` |
## Validation hierarchy mapping
The same four layers appear in both the Leviatan et al. Floquet paper and the G16–G18 experiments:
1. **Ideal benchmark / noise-model validation** → G16 noiseless QPDE + closed-form predictor.
2. **Noise + mitigation** → G17 depolarizing ladder + Richardson ZNE.
3. **Independent estimator** → G18 model-free curve χ² vs Taylor-fit moments.
4. **Cross-platform corroboration** → Open. Selene is the only platform currently in the wind tunnel.
## Operational lessons
- **Resumable caches are mandatory.** G18 is 384 circuits × 4096 shots ≈ 30 minutes of emulator time; `quantum/tda/sweep.py` writes one JSON per circuit under `_cache_tda/` so a sandbox reset only loses the in-flight circuit.
- **Angle hygiene in Guppy.** The QPDE evolution-time trick uses radians on paper but `angle()` in Guppy is halfturns; `t = π/(16 h₁)` becomes `rz(q, angle(1/16))`, not `angle(math.pi/16)` (which is the S gate). See `references/qpde.md` §Guppy angle-hygiene warning.
- **Aliasing controls are not fit data.** k = 8 in the ethylene QPDE puts `2φ` at π, where the signal is stationary and the arcsine branch is degenerate. Hold it out of the gap fit and report it separately. See `references/qpde.md` §k = 8 is an aliasing control.
- **Moment fits are conditioning-limited.** For Laplacian-moment TDA, the τ grid and truncation order move σ(T_k) by ~4×; quadrupling shots only halves it. Report a model-free curve verdict alongside the fitted moments. See `references/laplacian-moments-tda.md` §Fit conditioning is the whole game.
- **Ship static JSON, not live server functions.** The Lovable Cloudflare Worker stubs `child_process` and blocks arbitrary filesystem reads. Run Python in the sandbox, commit the JSON, and render a static view. See `references/selene-runtime.md` §Shipping results to the frontend.
## Next gates
- **Step C-2q:** ✅ completed in v0.4.4 — 90/90 cells PASS across 9 depolarizing/leakage levels including H2-2 rates; see `quantum/pqp_frontier/noise_2q.py` and `/nadarasa/proofs/noise-2q`.
- **Floquet native port:** heavy-hex Floquet cycle on Guppy/Selene, testing topology mapping to all-to-all.
- **ADAPT-GQE composition:** feed generated transformer/RL circuits into the rule-(N/M/P) canonicalisation pipeline.
## v0.4.4 addendum
Step C-2q used the same H2-2 noise parameters as G17 (`p_2q = 1.29e-3`, `p_1q = 1.29e-4`, `p_meas = 1.35e-3`) and extended them to 5× and 20× stress levels plus a leakage level. The 100% PASS rate at depol_h2 confirms rule (M) is sound under realistic H2-class gate and readout noise, not just ideal shots.
## References
- `references/qpde.md` — QPDE theory + worked G16 implementation.
- `references/laplacian-moments-tda.md` — quantum TDA + worked G18 implementation.
- `references/cross-platform-validation.md` — QESEM + four-layer validation hierarchy from G19.
- `references/sweep-runner.md` — resumable-cache pattern.
- `references/lovable-orchestration.md` — atomic gates and rollback protocol.
------------------------------------------------------------------------------
### reference card: nexus-admin
# Nexus accounts, quotas and access control
Everything here is the *account* layer around a Nexus job: who you are, what you're
allowed to spend, and who can see the result. Sources: the Nexus admin guide
(`docs.quantinuum.com/nexus/admin_guide/`), the user-guide concept pages on
organizing/access control/quotas, and live `qnexus` 0.48.2 introspection against a
real account.
Read this **before** a hackathon or a shared-account run. Most "why did my job not
run" answers live here rather than in the circuit.
## The three containers — organization / group / team
They are not synonyms and they do different jobs.
| Container | Purpose | Spans orgs? | Who manages |
| --- | --- | --- | --- |
| **Organization** | top-level; every user accesses Nexus through one. Quotas and software plans attach here. | no | Quantinuum + org admins |
| **Group** | shares a **quota** among users. A user can be in several; one is their *default group*. | no | org admin |
| **Team** | shares **resources** (projects, circuits, jobs) for collaboration. | yes | any user (`qnx.teams.create`) |
Group is billing/allowance. Team is collaboration. Picking a group at submission
time is the `user_group=` parameter on `start_execute_job` / `start_compile_job`;
omitting it meters against your personal allowance. Nexus Lab (Jupyter) time always
meters against your **default group**, which you set in Settings → Organization.
```python
qnx.teams.create(name="nadarasa", description="hackathon collaborators")
qnx.teams.get_all().df()
```
There is no documented API for adding/removing team *members* or creating groups —
teams get members through role assignment on resources, groups are admin-UI only.
## Roles: four names, most-permissive wins
`Literal['Administrator', 'Contributor', 'Reader', 'Maintainer']` — verified from
`qnx.roles.RoleName`.
| Role | Can |
| --- | --- |
| Reader | view project, jobs, circuits. No edits. |
| Contributor | + run jobs, change project properties, edit resources |
| Maintainer | + delete resources, archive and delete the project |
| Administrator | + add/remove users and teams, change their roles |
```python
qnx.roles.assign_user(resource_ref=project, user_email="a@b.net", role="Contributor")
qnx.roles.assign_team(resource_ref=project, team=team_ref, role="Reader")
qnx.roles.assignments(resource_ref=project).df() # who currently has what
```
Rules that bite:
- A user holding both a personal role and a team role on the same resource gets the
**most permissive** of the two. Narrowing someone's personal role does nothing if
their team still holds Contributor.
- Deleting a project deletes it **for everyone**. Hand out Maintainer sparingly on a
shared hackathon project.
- Contributing to someone else's project still consumes **your own** database quota.
- Leaving a team revokes every access that came through it, immediately.
- These roles govern API access exactly as they govern the web UI.
Resource-level `Administrator` is *not* the same thing as **Organization Admin** —
the latter is a separate all-or-nothing checkbox on the user, granted from the org
Users tab, and requires the user to re-login before it takes effect.
## Quotas: four meters, none of them HQCs
`qnx.quotas.QuotaName` is exactly `['compilation', 'simulation', 'jupyterhub',
'database_usage']`.
| Quota | Meters | Unit | Resets |
| --- | --- | --- | --- |
| `compilation` | CPU time compiling circuits | seconds | monthly |
| `simulation` | CPU time on **Nexus-hosted** simulators | seconds | monthly |
| `jupyterhub` | Nexus Lab notebook server uptime | seconds | monthly |
| `database_usage` | stored programs, results, backend snapshots | MB | **never** |
The critical omission: **there is no Nexus quota for running on Quantinuum hardware
or external providers.** HQC allowance is enforced by the provider, not by these
meters, so `check_quota` passing tells you nothing about whether you can afford an
H-series job. Guard hardware spend with `max_cost=` (see `nexus-jobs.md`), not with
quotas.
```python
qnx.quotas.get_all().df() # name / description / usage / quota
qnx.quotas.check_quota("simulation") # bool, current user only
qnx.quotas.get("database_usage").usage
```
`quota` reads the literal string `'No quota set for user'` when unlimited — it is
**not** always a float, so never do arithmetic on it without a type check. On a
plain account these calls also emit a deprecation warning about the
`/api/quotas/v1beta` endpoint; it is noise, the call works.
Quota administration itself is UI-only (org page → Users → ⋮ → *Manage quotas*).
There is no admin-scoped bulk quota API — the `qnx.quotas.*` calls are self-scoped.
## Priority
Integer **1 (highest) → 10 (lowest)**, default **5** on invite. It only affects jobs
submitted to Quantinuum hardware and emulators, and only an org admin can change it.
If a hardware job sits queued far longer than a colleague's, compare priorities
before blaming the device.
## Usage reporting
- Any user: profile → *My Usage Reports* → Create Report (name, expiration, date
range). Reports expire and then cannot be downloaded — re-create rather than hunt.
- Org admin: management page → *Access* tab for CPU/storage utilisation graphs
filterable by user, and *Quantinuum Systems Reports* tab for hardware/emulator
usage across the org.
Reports show compute and usage metrics, not monetary cost. Nothing in Nexus shows a
dollar figure.
## Self-serve vs "ask an admin"
Self-serve: create projects and teams, assign roles on resources you administer,
check your own quota, generate your own usage report, set your default group, edit
display name and username (3–53 chars, alphanumeric, unique).
Needs an org admin: inviting users, granting Organization Admin, resetting another
user's password, raising a quota, changing priority, org-wide job cancel/retry,
org-wide usage reports. Software Plans need **Quantinuum** to enable them
(`QCsupport@quantinuum.com`), not just an org admin.
## Before you burn HQCs — checklist
1. `qnx.auth.is_logged_in()` and `qnx.users.get_self()` — right account?
2. `qnx.devices.get_all().df()` — is the target actually listed for this account?
3. `qnx.quotas.get_all().df()` — `database_usage` never resets; a long sweep that
stores every result can wedge you out of storage mid-run.
4. Decide the **group**: pass `user_group=` if the allowance is not personal.
5. Set `max_cost=` on the execute job. This is the only hard *server-side* spend
guard — and it is **per job**, so it does nothing about a thirty-cell sweep
firing thirty of them. Carry a client-side batch ledger too: book the
estimate at submit time, refuse the next cell when the projected total
passes `NADARASA_MAX_TOTAL_HQC`, and report billed totals from
`qnx.jobs.cost` alongside (never instead of) the estimates.
6. `qnx.devices.supports_shots(config)` before submitting anything shot-based:
a distribution-only backend accepts the job and then hands back results the
shot decoder cannot read — the refusal belongs *before* the spend.
7. Run the compile job first and read `qnx.jobs.cost(compile_job)` before executing.
`qnx.circuits.cost(...)` gives a truer pre-submission figure but is itself a
billable costing job, so keep it opt-in (`NADARASA_COST_PROBE=1`), not a
default on every cell.
8. Confirm your priority if the queue matters.
9. Record job id, backend, shots, seed and date — see
`references/cross-platform-validation.md` for the reporting contract.
------------------------------------------------------------------------------
### reference card: nexus-jobs
# Quantinuum Nexus: submitting real jobs
The Selene lane is free and offline. Nexus is the online lane — emulators with the vendor
error model and, with access, real H-series hardware. This card is the documented workflow
(Nexus user guide + `qnexus` 0.48.2 API reference) with field corrections attached. Where
the docs and hard-won experience disagree, the **verified** note wins and the conflict is
stated.
Account-layer concerns — quotas, roles, groups, priority, who can see your job — are in
`references/nexus-admin.md`. Read that before a shared-account or hackathon run.
## Login and project
```python
import qnexus as qnx
qnx.auth.login() # device-code flow; qnx.login() is the same call
project = qnx.projects.get_or_create(name="MyProject")
qnx.context.set_active_project(project)
```
Login persists across processes; do not re-prompt inside a sweep loop.
Verified mechanics (qnexus 0.48.2, headless sandbox):
- `qnx.auth.login()` prints a 6-char user code and a `verification_uri_complete`, tries
`webbrowser.open` (a no-op headless — the link is printed anyway), then **blocks polling**
until the user clicks "allow device". Run it in the background and tail the log, or the
call eats the whole command timeout. There is no separate "poll later" entry point.
- Tokens land in **`~/.qnx/auth`** (`CONFIG.token_path`), *not* `~/.qnexus/`. The docs only
say "clear tokens from the file system" without naming a path — this one is observed, not
documented. On an ephemeral sandbox home it is wiped on rebuild; expect to redo the login.
- `qnx.auth.is_logged_in()` and `qnx.users.get_self()` are the cheap verification pair; the
latter returns a `UserRef` with `id` and `display_name`.
- Non-interactive alternatives: `qnx.auth.login_no_interaction(user, pwd)` and
`qnx.auth.login_with_token(refresh_token)` (in-memory, nothing written to disk — the right
one for CI or multi-account use).
- Import gotcha: `qnexus` imports `selene_core`, so the environment also needs `guppylang`
installed. `pip install qnexus` alone fails at import with
`ModuleNotFoundError: No module named 'selene_core'`.
## Projects, properties and naming — make a sweep findable
A project name is unique **per creating user only**; another user's project can share your
name. Never identify a project by name alone across accounts — carry the `ProjectRef.id`.
Properties are Nexus's typed metadata, and they are the difference between a sweep you can
re-query in a month and a pile of anonymous jobs. Declare them on the project first, then
stamp them on every job:
```python
qnx.projects.add_property(name="gate", property_type="string", project=project)
qnx.projects.add_property(name="shots", property_type="int", project=project)
qnx.projects.add_property(name="seed", property_type="int", project=project, required=False)
with qnx.context.using_properties(gate="G16", shots=2048, seed=7):
job = qnx.start_execute_job(...) # properties merge in from context
```
- Only four types: `bool`, `int`, `float`, `string`. `required=True` makes the API reject a
collaborator who omits the value.
- **Properties propagate**: a resource created by a job inherits the job's properties, so a
stamped execute job yields a stamped result. This is free provenance — use it.
- Explicit `properties=` on a call beats the context value.
- `qnx.projects.summarize(project)` gives a one-shot DataFrame of the project's contents.
- Deletion is two-step: `qnx.projects.update(project, archive=True)` then
`qnx.projects.delete(project)`.
**Stamp from the context, not from a per-call argument.** Wrap the whole submission —
upload, compile, execute, and the `save_ref` metadata write — in one
`using_properties(**axes)` block (`ExitStack` if you also enter `using_project`), and pass
no `properties=` downstream. Three things follow: every resource the submission creates
carries the same coordinate even on a partial retry; nothing downstream has to remember to
thread the axes through; and the metadata you persist locally can be read back out of
`qnx.context.get_active_properties()`, so the disk cache cannot disagree with what Nexus
actually stamped. Keep the explicit-argument path only as the older-client fallback, chosen
by whether `get_active_properties()` returns anything. Enter the context inside the
per-cell call so it exits on the way out — a context leaked past a failed submission stamps
the *next* cell with the failed cell's band.
## The compile → execute → download flow
Four steps, in order: **upload → compile → execute the compiled ref → download**.
Skipping the upload is the gap most copy-paste examples leave open — a local
`pytket.Circuit` is not submittable; `programs=` wants a Nexus ref.
```python
circuit_ref = qnx.circuits.upload(circuit, name="run-01-circ") # -> CircuitRef
compile_job = qnx.start_compile_job(
programs=[circuit_ref],
backend_config=config,
optimisation_level=2, # 0 for gate-count benchmarks, 2 for cheapest run
name="compile-run-01",
)
qnx.jobs.wait_for(compile_job)
compiled_ref = qnx.jobs.results(compile_job)[0].get_output()
exec_job = qnx.start_execute_job(
programs=[compiled_ref],
n_shots=[2048],
backend_config=config,
name="exec-run-01",
max_cost=[20.0], # hard HQC ceiling — see "Cost control"
)
qnx.jobs.wait_for(exec_job)
result = qnx.jobs.results(exec_job)[0].download_result()
```
`programs=` is the current keyword on both `start_compile_job` and `start_execute_job`.
`circuits=` is its **deprecated** spelling: older examples and older agents still emit it,
and it surfaces as a deprecation warning or a schema error rather than a clean rename hint.
Always write `programs=`.
`qnx.compile(...)` and `qnx.execute(...)` are blocking convenience wrappers over the same
two `start_*` calls (`timeout=300.0` default) — fine for a one-off, wrong for a sweep, where
you want the ref so a resumed run can re-attach.
### Which result method — `get_output()` vs `download_result()`
The two job types return different ref classes and their accessors are **not**
interchangeable. This is the most common `AttributeError` on the stack:
| Job | `qnx.jobs.results(job)[0]` is | Accessor | Wrong call gives |
| --- | --- | --- | --- |
| compile | `CompilationResultRef` | `.get_output()` → the compiled `CircuitRef` | `CompilationResultRef has no attribute 'download_result'` |
| execute | `ExecutionResultRef` | `.download_result()` → `BackendResult` / `QsysResult` / `QIRResult`, then `.get_counts()` or `.get_empirical_distribution()` | `ExecutionResultRef has no attribute 'get_output'` |
Read the error as "wrong job type in hand", not "broken qnexus" — it almost always means a
compile ref and an execute ref got crossed in a resume or re-attach path.
### Pre-flight, before the first API call
Four checks, in order; each has a distinct failure signature and none of them is worth
debugging after submission:
1. **Interpreter** — run with the environment that actually holds `qnexus` (the project's
`.pydeps`/venv binary, never bare system `python3`); otherwise
`ModuleNotFoundError: No module named 'qnexus'`.
2. **Session** — `qnx.auth.is_logged_in()`; mint via device authorization if not.
3. **Active project** — resolve by name from `qnx.projects.get_all()` (with an explicit
fallback name and a loud `RuntimeError` listing what *is* available) and call
`qnx.context.set_active_project(project)`; otherwise `NoActiveProjectError` or a bare 403
several calls later.
4. **Config family** — `QuantinuumConfig` for H-series, `HeliosConfig`/`HeliosEmulatorConfig`
for Helios; the wrong family reads like an entitlement error (`code: 14`).
Notes and sharp edges:
- **Does the execute job require a prior compile?** The reference signature accepts a plain
`CircuitRef` (or `HUGRRef`/`QIRRef`) and states no ordering rule. In practice, executing a
ref that Nexus has not compiled for that backend fails with `entry not found in database`
— a *wrong-ref* error, not "backend down". Compile first; treat the docs' permissiveness
as untested.
- `qnx.jobs.get(id=…)` is **keyword-only** (confirmed by the published signature). Positional
calls raise.
- Job/circuit refs carry `.id`; store that string in the per-row cache so a resumed sweep can
re-attach to an in-flight job instead of resubmitting it.
- Up to **300 programs in a single job** — batch a sweep's cells rather than firing 300 jobs.
- `skip_intermediate_circuits=True` is the compile-job default; set it `False` only when you
actually want per-pass circuits (`CompilationResultRef.get_passes()` → `CompilationPassRef`
with `.pass_name`, `.get_input()`, `.get_output()`). That per-pass lineage is the honest way
to show *what the compiler did*, and it is the Nexus analogue of the local TKET lane in
`references/pytket.md`.
## Job lifecycle, waiting and recovery
`JobStatusEnum` in full: `SUBMITTED`, `QUEUED`, `RUNNING`, `RETRYING`, `CANCELLING`,
`COMPLETED`, `CANCELLED`, `ERROR`, `TERMINATED`, `DEPLETED`.
`DEPLETED` means the allowance ran out mid-job. It is a distinct outcome from `ERROR` and
must be reported as such — the circuit was fine, the budget was not.
```python
qnx.jobs.status(job).status
qnx.jobs.wait_for(job, timeout=3600) # raises JobError on ERROR/CANCELLED/DEPLETED/TERMINATED
qnx.jobs.cancel(job)
qnx.jobs.retry_submission(job) # retries ERROR items by default
```
`wait_for` takes a `strategy`: `WebsocketStrategy` (jobs under ~10 min), `PollingStrategy`
(exponential backoff, robust for long jobs), `HybridStrategy` (websocket then polling — the
recommended default). A long hardware job on a bare websocket is how you lose a run to a
dropped connection, so pass `HybridStrategy` or `PollingStrategy` explicitly for anything
queued behind other users.
**Never resubmit to get a past result.** Query it:
```python
rows = qnx.jobs.get_all(
project=project,
properties={"gate": "G16"},
job_status=[qnx.jobs.JobStatusEnum.COMPLETED],
page_size=50,
).df()
job = qnx.jobs.get(id="…")
res = qnx.jobs.results(job)[0].download_result()
```
`get_all` filters on project, properties, `job_status`, `job_type`, creator, created/modified
windows, and paginates. `allow_incomplete=True` on `jobs.results` returns
`IncompleteJobItemRef` placeholders for a partially finished batch instead of raising.
### Resuming a paid-for sweep
Wrap that query in a command rather than an ad-hoc script — a sweep loses its process often
enough (rollback, dropped websocket, sandbox timeout) that recovery has to be a one-liner or
it turns into a resubmission. This repo's is `python -m quantum.resume`:
```bash
python -m quantum.resume --backend hardware --job-id exec-1 exec-2 # ids in hand
python -m quantum.resume --backend hardware --from-dump demos/x.json # ids in a dump
python -m quantum.resume --backend hardware --gate G16 # no ids at all
```
Design points worth copying:
- **Recover the decode width from the job, not from memory.** The `n_qubits` property stamped
at submission is what makes a job re-decodable months later; without it you are guessing at
the bit width of a result you already paid for.
- **Report a non-COMPLETED job, don't raise on it.** One `DEPLETED` id must not abandon the
other nine. Map the status to a plain-English reason (`DEPLETED` = budget, `ERROR` =
retryable, `QUEUED` = come back later).
- **Keep the billed HQC in the meter.** A re-fetch has no local estimate (no program in hand),
but it still has a real cost from `qnx.jobs.cost(job)`. Dropping it makes a resumed sweep
look free, which is how a run's true cost gets lost.
- **Cache the shots on disk keyed by job id**, so the second resume is a file read and the
driver's row loop can consume it exactly like a locally executed row.
## Cost control — the only real spend guard
Nexus quotas do **not** cover hardware. `check_quota` passing says nothing about whether you
can afford an H-series job (see `references/nexus-admin.md`). Three real controls:
| Call | When | What it gives |
| --- | --- | --- |
| `qnx.circuits.cost(ref, n_shots, backend_config)` | before submitting | HQC estimate — **runs a costing job** on a dedicated device and shows up in the portal |
| `max_cost=[…]` on `start_execute_job` | at submission | per-job-item HQC ceiling; the job stops rather than overspends |
| `qnx.jobs.cost(job)` / `cost_confidence(job)` | after | actual HQC, and per-item (cost, confidence) pairs |
`QuantinuumConfig.max_cost` is **deprecated** — pass `max_cost` as an execute-job parameter
instead. `max_batch_cost` still lives on the config for batched submissions.
A local formula (`5 + n_shots·(2q + 1q/10)/5000`) is fine for planning the shape of a sweep,
but never quote it as *the* cost in a write-up when `qnx.jobs.cost` can give the real number.
Record it per job rather than recomputing it later. One meter record per job — mode
(`emulator | dry | live | refetch`), device, job id, qubits, shots, seed, estimated HQC, billed
HQC — collected into the dump's `execution` block is what turns a dry-run vs live-run comparison
into a diff instead of an argument. A re-fetched job has no estimate (there is no program in
hand) but it still has a real billed cost; carry it, or a resumed sweep reports itself as free.
See `references/sweep-runner.md` (§Per-job meters).
## Backend matrix (what is actually reachable)
Read the widths off the account's backends page, not the datasheet: the number a hosted
emulator exposes is its own ceiling. On an emulator-only account (observed 2026-08-23)
`H2-Emulator` advertises **26 qubits** even though H2-1 is a 56-qubit machine, and
`Helios-1E-lite` advertises 26 against a 98-qubit Helios-1. Pinning the published width
lets a 30-qubit circuit sail through preflight and get rejected by Nexus.
| Backend | Qubits (observed) | Config | Noise | Notes |
| --- | --- | --- | --- | --- |
| `H1-1LE` | 20 | `QuantinuumConfig` | noiseless | cheap sanity leg |
| `H1-Emulator` | 20 | `QuantinuumConfig` | full error model | H1-class |
| `H2-1LE` | 26 | `QuantinuumConfig` | noiseless | the default "real stack, no noise" leg |
| `H2-Emulator` | 26 | `QuantinuumConfig(..., noisy_simulation=True)` | full error model | the noise-ladder workhorse |
| `Helios-1E-lite` | 26 | **`HeliosConfig`** | emulated | passing `QuantinuumConfig` here fails |
| `aer_simulator`, `_statevector`, `_unitary` | 26 | `AerConfig` | ideal | Nexus-hosted Qiskit — the independent-simulator leg, no local install |
| `QulacsBackend` | 20 | `QulacsConfig` | ideal | second independent simulator |
| `Selene`, `SelenePlus` | 26 | `SeleneConfig` / `SelenePlusConfig` | ideal + configurable | the local emulator, submitted remotely |
| `H1-1`, `H2-1`, `H2-1SC` (real QPU) | — | — | — | not listed on a plain account; no submission surface |
The config class is a *family* property, and the families are not interchangeable. A name
submitted through the wrong class is accepted at job creation and refused at submission with
`You do not have access to this machine (code: 14)` — an access error for a typing mistake.
Switch on the family (`H1-`/`H2-` → `QuantinuumConfig`, `Helios*` → `HeliosConfig`, `aer*` →
`AerConfig`, Qulacs/Selene → their own), and print the reachable list grouped by family so the
fix is visible in the error itself.
Only score a circuit's gate set against the H-series native set for H-series and Helios
backends. Aer/Qulacs/Selene do not rebase onto `PhasedX/Rz/ZZPhase/ZZMax`, so applying that
check there refuses ops the backend runs perfectly well — leave the gate check unknown and
skipped instead.
`QuantinuumConfig` defaults worth knowing: `noisy_simulation=True` (so an "emulator" is noisy
unless you say otherwise), `no_opt=True`, `allow_implicit_swaps=True`, `leakage_detection=False`.
Turn `leakage_detection` on when the experiment cares about leakage, and remember it changes
the shot record.
`SeleneConfig` / `SelenePlusConfig` expose the same simulator and error-model taxonomy as the
local lane (`Statevector`, `Stabilizer`, `MatrixProductState`, `Coinflip`, `ClassicalReplay`;
`NoErrorModel`, `DepolarizingErrorModel`, `QSystemErrorModel`, `HeliosCustomErrorModel`), so a
Selene experiment can be lifted onto Nexus without rewriting the noise ladder. `n_qubits` on
those configs is **deprecated** — pass it per job item on the execute call.
## The Helios lane
`Helios-1E-lite` runs fine on an emulator-only account. It fails in four distinct ways first,
and only the first one *looks* like an entitlement problem:
1. **`system_name` defaults to the QPU.** `HeliosConfig()` is `system_name="Helios-1"` — the
hardware system. Submitting the bare config asks for a machine you have no entitlement for
and is refused at *submission* with
`You do not have access to this machine (code: 14)`. Pass the device explicitly:
```python
from quantinuum_schemas.models.backend_config import HeliosEmulatorConfig
config = qnx.HeliosConfig(
system_name="Helios-1E-lite",
emulator_config=HeliosEmulatorConfig(n_qubits=n_qubits),
)
```
2. **Emulation requires an `emulator_config`.** Without one, job creation fails with HTTP 400
`"Helios emulation must have an emulator_config set."`
3. **`n_qubits` must be explicit per job item.** Missing it gives HTTP 400
`"max-qubits/n_qubits must currently be set explicitly per job item."` The schema defaults
(statevector simulator, QSystem `alpha` error model, Helios runtime) are the
vendor-realistic lane.
4. **Helios takes HUGR, not pytket, and does not compile.** Upload the Guppy program with
`qnx.hugr.upload(...)` and execute the returned ref directly — `start_compile_job` only
accepts source circuits, so skip it entirely for this target (the same is true of the
`*LE` backends). A gate-set preflight must whitelist HUGR runtime/extension ops
(`QAlloc`, `QFree`, `Measure`, `MeasureFree`, `Reset`, `helios.*`) or it will reject a
perfectly valid program.
5. **Results are `QsysResult`, not `BackendResult`.** Helios returns
`hugr.qsystem.result.QsysResult` — Guppy-style tagged entries per shot, with no
`get_shots` and no `get_empirical_distribution`. Decode `result.results[i].entries`.
**Listed is not reachable — but check your own config before blaming the account.** A backend
that appears in `get_all()` can still fail at submission with
`Submission error: You do not have access to this machine (code: 14)`. That message is
*sometimes* a real entitlement gap and *often* a client-side field you never set (the Helios
`system_name` above is the canonical case). Diff the config class and every name field against
the device you meant before writing "unreachable" into a results table. When it genuinely is an
access gap, the failure arrives on the *execute job*, after upload and after the job id exists,
so a sweep must catch it per cell, keep the ids, and carry on: one inaccessible machine must
not cost you the lanes that worked. Report the lane with its real error rather than shipping a
three-engine table as a four-engine one.
Always confirm against the live account rather than this table:
```python
print(qnx.devices.get_all().df()[["backend_name", "device_name", "nexus_hosted"]])
```
## Sizing: the timeout cliff
Noisy emulator jobs are the expensive ones. Observed ceiling: **≤ ~17 qubits AND/OR ≤ 2048
shots**. A 25-qubit × 8192-shot noisy job sat in `RUNNING` for ~3 h and then raised
`TimeoutError` — no partial result, no refund of wall-clock. Noiseless backends are far more
forgiving. Plan the qubit budget before submitting, and split a wide experiment into several
narrow jobs rather than one wide one (up to 300 programs per job).
There is a second, sharper ceiling on the hosted Selene lane, and it is about
**depth**, not width. `SelenePlus` (MPS simulator + `HeliosRuntime` +
`QSystemErrorModel`) runs 17-qubit *plain* circuits fine, but 17 qubits with
Toffoli/CSWAP depth dies with `Unexpected end of stream` — reproducibly, across
three different circuit structures. That is a server-side size limit, not a
circuit bug and not a transport glitch: do not spend a debugging session
bisecting your kernel over it. Record the affected leg as **assessed-blocked**
with the error string and re-certify on a backend that takes the depth
(`H2-Emulator` carried the equivalent 17q run), rather than leaving the row
looking untried.
Two naming traps in the same lane: `SelenePlus` accepting `HeliosRuntime` is an
**emulator** capability and never "ran on Helios", and `QulacsBackend`
(verified with a 14q GHZ giving correct physics) is a fast CPU statevector via
pytket — also an emulator. Both share a prefix or a runtime name with something
that sounds like hardware.
## Bit order — calibrate before you trust anything
`download_result()` returns a pytket `BackendResult` / `QsysResult` / `QIRResult`; the counts
and distribution accessors belong to **pytket**, not qnexus, and the Nexus docs say nothing
about them. Observed on a live H2-1LE run: `get_distribution()` is deprecated in favour of
`get_empirical_distribution()` / `get_probability_distribution()`, and the returned dict is
keyed by **tuples of ints**, indexed by qubit:
```python
for key, p in dist.items():
assert key[8] == 0 # int 0, NOT the string "0"
```
`key[q]` is qubit `q` in circuit order. Qiskit bitstrings are the **reverse** convention
(`k[0]` is the MSB), so any analysis ported from a Qiskit notebook is wrong by a mirror until
proven otherwise.
**Calibration probe**: submit a tiny job that applies `X` to exactly one known qubit and
measures all, then assert the returned key has the 1 in the expected slot. Do this once per
backend, per shape change. It costs seconds and catches the single most expensive class of
silent bug.
**Smell test after any post-selection**: a post-selected/error-detected fidelity can never
exceed the ideal noiseless value. If `F_det > F_ideal`, the bit order or the accept mask is
wrong — do not report the number.
`ExecutionResultRef.download_backend_info()` returns the pytket `BackendInfo` snapshot that
was in force for that run. Pull it alongside the result: it is the calibration provenance
for the number you are about to publish.
## Job-failure taxonomy
| Symptom | Cause | Fix |
| --- | --- | --- |
| `entry not found in database` | executed a non-compiled ref | execute the compiled ref |
| `TimeoutError` after hours in `RUNNING` | oversized noisy job | cut qubits or shots |
| status `DEPLETED` | allowance exhausted mid-job | budget/quota, not a circuit bug — report as such |
| status `ERROR` | transient or real | `qnx.jobs.retry_submission(job)` once; investigate if it repeats |
| backend/config mismatch error on Helios | `QuantinuumConfig` used | switch to `HeliosConfig` |
| `access to this machine (code: 14)` on `Helios-1E-lite` | `HeliosConfig()` defaulted to `system_name="Helios-1"` | pass `system_name=` explicitly |
| 400 `Helios emulation must have an emulator_config set` | no `emulator_config` | `HeliosEmulatorConfig(n_qubits=N)` |
| 400 `max-qubits/n_qubits must ... be set explicitly per job item` | `n_qubits` omitted | set it on the emulator config / job item |
| `QsysResult has no attribute get_empirical_distribution` | Helios/HUGR lane | decode `result.results[i].entries` |
| `jobs.get()` TypeError | positional id | `jobs.get(id=…)` |
| `ModuleNotFoundError: No module named 'qnexus'` | ran under system python, not the env holding `qnexus` | invoke the `.pydeps`/venv interpreter explicitly |
| `NoActiveProjectError` / bare 403 on a valid session | no active project in context | resolve from `qnx.projects.get_all()`, then `qnx.context.set_active_project(project)` |
| `ExecutionResultRef has no attribute 'get_output'` | compile accessor on an execute job | `download_result()` — see "Which result method" |
| deprecation/schema error naming `circuits` | old keyword | `programs=[...]` |
| distribution keys compare false against `"0"` | keys are int tuples | compare against ints |
| job queued far longer than a colleague's | lower priority (1–10, default 5) | admin-set; see `nexus-admin.md` |
## Reporting discipline
Every Nexus number in a write-up carries: backend name, shot count, seed, job id, actual HQC
cost from `qnx.jobs.cost`, and the date. Timeouts, cancellations, `DEPLETED` and reverts get
recorded with their cause — an honest failed leg is evidence; a quietly dropped one is a
fabrication. See `references/cross-platform-validation.md` for how the Nexus leg fits into a
multi-leg proof.
## Submit-time persistence
The dangerous window in a cloud sweep is between "Nexus accepted the job" and "shots came
back". If the process dies in it, the job still runs and is still billed, but nothing local
knows its id — the result exists only in the web console, and the next run pays for the same
cell again. Close the window by persisting the id the moment it exists:
```python
def run(self, program, *, n_shots, seed, on_submit=None, **props):
ref = qnx.start_execute_job(..., max_cost=[...])
if on_submit is not None:
on_submit(str(ref.id)) # driver writes the cache row here
...
```
and in the driver:
```python
def _submitted(job_id: str) -> None:
write_row(tag, {"status": "submitted", "job_id": job_id,
"band": K, "noise_scale": scale})
row = read_row(tag)
if row and row.get("job_id"):
shots = backend.fetch_result(row["job_id"]) # re-attach, never resubmit
else:
shots = backend.run(program, ..., on_submit=_submitted)
```
Rule: **a cell that has a job id is never a cell to submit.** The loop's first action is
re-attachment, submission is the fallback.
### Save the `Ref`, not just the id
An id string only recovers the job while `qnx.jobs.get(id=...)` still resolves it — live
session, right active project, compatible client. `qnx.filesystem.save` persists the `Ref`
object itself and is the documented durability path:
```python
job_id = str(exec_job.id)
qnx.filesystem.save(ref=exec_job, path=store / f"{job_id}.execute.ref.json", mkdir=True)
# ... and a sidecar of your own, so recovery knows the decode width and the
# sweep coordinates without a network round-trip:
(store / f"{job_id}.meta.json").write_text(json.dumps(
{"job_id": job_id, "n_qubits": n, "shots": s, "gate": g, "band": K, "noise_scale": x}))
```
Then make recovery prefer it: `get_job()` tries `qnx.filesystem.load(path=...)` first and
falls back to `jobs.get`, and the resume CLI treats the ref store as an id source of last
resort — so a sweep whose dump never got written is still recoverable from disk alone.
Wrap every write: a failure to persist must never take down a job Nexus has already
accepted and billed. The refs are session artefacts — gitignore them; the committed
evidence stays the dump's `execution` block.
Reference implementation: `quantum/nexus_refs.py`, saved from `NexusBackend._submit`
before `on_submit` fires, consumed by `quantum/resume.py --from-refs`.
## Stamp the sweep coordinates
`device / shots / seed / driver / n_qubits` identify a *run*. They do not identify which cell
of the sweep it measured. Declare the sweep axes in the property schema too:
```python
JOB_PROPERTIES = (
("gate", "string"), ("driver", "string"),
("n_qubits", "int"), ("shots", "int"), ("seed", "int"),
# Sweep coordinates. Without these a recovered job cannot be mapped back to
# the cell it measured, and the only options left are guessing from
# submission order or paying for the row again.
("band", "int"), ("noise_scale", "float"),
)
```
Recovering ten unstamped jobs and matching them to an ideal success curve *looks* like
analysis and is guessing. Add the axes before the sweep, not after the loss.
## Filter EXECUTE before reading results
`qnx.jobs.get_all(project=…, properties=…)` returns the COMPILE jobs alongside the EXECUTE
jobs that consumed them. Reading result fields off a compile row raises `AttributeError`:
```python
jobs = [j for j in qnx.jobs.get_all(project=proj, properties=props).list()
if j.job_type == qnx.models.JobType.EXECUTE]
band = job.annotations.properties.get("band") # stamped props live here
```
## Device login
The device-authorization path drifts between `qnexus` releases — `/device/authorize` answers
`{"detail":"Not Found"}`; the working path at the time of writing is
`/device/device_authorization`. Read it out of the installed `qnexus.client.auth` rather than
a remembered URL, then poll `/device/token` until the user approves and `write_token` the
refresh/access pair.
Run the poller **in the background** (`nohup … &`, print the code from its log). A foreground
poller races the command timeout and dies while the user is still on the approval screen, and
the code it printed is then dead too.
------------------------------------------------------------------------------
### reference card: pytket
# pytket / TKET compile lane
TKET is Quantinuum's compiler. Use it as a **second, independent optimiser**
next to the Guppy v1 / Selene path — never as a replacement for the emulator
evidence, and never as a source of gate counts that have not been proved
semantics-preserving.
Reference implementation: `quantum/tket/` (G24), route `/nadarasa/g24`,
data `src/data/demos/tket_compile.json`.
## Install
```
pip install --target .pydeps pytket pytket-quantinuum
```
Pure-Python wheels; they coexist with `guppylang>=1.0` in the same `.pydeps`
prefix. Note `.pydeps` is gitignored, so a fresh sandbox has **neither** guppy
nor pytket — reinstall from `quantum/requirements.txt` before any driver run.
## Offline compilation — no credentials, no HQCs
```python
from pytket.extensions.quantinuum import QuantinuumAPIOffline, QuantinuumBackend
be = QuantinuumBackend(device_name="H2-2", api_handler=QuantinuumAPIOffline())
compiled = be.get_compiled_circuit(circuit, optimisation_level=2)
```
- `QuantinuumAPIOffline().get_machine_list()` returns **dicts**, not objects:
read `m["name"]` (`H1-1`, `H2-1`, `H2-2`). `m.device_name` raises.
- Offline mode compiles and rebases only. Nothing is submitted, so this is safe
to run in any sandbox and costs nothing.
- Native gate set: `PhasedX`, `Rz`, `ZZPhase`, `ZZMax` (plus `TK2` and the
classical ops). Level 0 is rebase-only and *already* reduces 2q counts on
phase-type circuits, because CX–Rz–CX maps onto one `ZZPhase`. Do not report
that as optimisation.
## Rule: equivalence oracle before any gate count
An optimiser that changes semantics looks like the best optimiser in the table.
Prove equality on the full state space, up to global phase, before scoring:
```python
overlap = np.trace(a.conj().T @ b)
phase = overlap / abs(overlap)
dist = np.linalg.norm(a - phase * b) # accept at <= 1e-9
```
Build the comparison circuits **without measurement or reset** so
`Circuit.get_unitary()` works. Typical passing distance is ~1e-15.
**TKET drops idle wires.** A compiled circuit can have fewer qubits than its
source, which silently changes bitstring width and makes TVD read 1.0. Pad with
`circuit.add_blank_wires(n - circuit.n_qubits)` before comparing unitaries or
sampling.
## Round-tripping TKET output onto Selene
The native gate set has an exact image in `guppylang.std.qsystem`:
| TKET op | Guppy call |
| --------- | ------------------------------------- |
| `PhasedX` | `phased_x(q, angle(a), angle(b))` |
| `Rz` | `rz(q, angle(a))` |
| `ZZPhase` | `zz_phase(q0, q1, angle(a))` |
| `ZZMax` | `zz_max(q0, q1)` |
Both sides use **half-turns**, so pytket `cmd.op.params` go straight into
`angle(...)` with no conversion (Gotcha #2 does not bite here). Render the
compiled circuit to Guppy source, load it, and sample through
`quantum.emulate.build(program).run_shots(Quest(), ...)`. Compare against the
exact distribution of the *source* circuit with a 4σ envelope,
`4*sqrt(0.5/shots)`.
## Scoring the comparison honestly
- Only score families where the reduced form is an **exact** rewrite. An
approximation (e.g. AQFT band truncation) can never be found by a
correctness-preserving compiler; scoring it as a win compares a compiler
against a modelling decision.
- Report agreements as the headline. TKET independently reaching the same 2q
count as the rewriter is external corroboration; the rewriter beating TKET is
interesting only when the mechanism is nameable (G24: a non-local involution
across commuting parity gates that a peephole window cannot see).
## Angles: half-turns on both sides, verifiable in one line
`Ry`, `Rz`, `ZZPhase` and friends take **half-turns** — the physics angle is
`param · π`, exactly Guppy's `angle()` convention. Never multiply by π when
porting a formula between the two stacks. Confirm empirically rather than from
memory:
```python
from pytket import Circuit
Circuit(1).Ry(0.5).get_unitary() # a π/2 rotation, not a π/2-radian one
```
Any paper formula containing an explicit π: divide the π out before it reaches
either `angle()` or a pytket parameter.
## Online lane
Everything above is offline (`QuantinuumAPIOffline`) — no credentials, no HQCs.
For real emulator/hardware submission through Nexus (compile → execute the
compiled ref → `get_distribution()`, backend matrix, sizing limits, bit-order
calibration) see `references/nexus-jobs.md`.
------------------------------------------------------------------------------
### reference card: qir-lane
# The QIR lane (Guppy → HUGR → QIR)
QIR is the LLVM-bitcode interchange format some Quantinuum submission paths accept. It is a
*third* lane next to Selene and TKET, useful when a target only takes bitcode or when you
want a compiler-independent artifact of the kernel.
## It needs its own pinned environment
The QIR toolchain lags the Guppy v1 line. Keep it in a separate venv; do **not** try to make
one environment serve both.
```bash
# certified execution env: system python3 + guppylang >= 1.0 + selene (see SKILL.md)
# QIR env, pinned and disposable
python3.12 -m venv /tmp/qir021_venv
/tmp/qir021_venv/bin/pip install \
guppylang==0.21.16 hugr-qir pytket-qir qnexus selene-sim qir-qis
```
Consequence: kernels destined for the QIR lane must be written so they compile under **both**
0.21 and 1.0, or kept in a clearly-marked module that only the pinned venv imports. Under
0.21, parameterized functions go through `compile_function()`; under 1.0 the emulator builder
replaces it. See `references/guppy-v1-migration.md`.
## Emit and validate locally
```bash
/tmp/qir021_venv/bin/python -m quantum..qir_ # Guppy → HUGR → QIR bitcode
```
Validate with `qircheck` (ships with `hugr-qir`) **before** attempting any upload. Local
validation is free; a rejected upload is not.
## `qircheck` constraints that bite
1. **Static kernels only.** Runtime-parameterized angles break emission — bake literals into
the generated source instead (the temp-module driver pattern already does this; see
`references/driver-pattern.md`). A sweep becomes N emitted modules, not one parameterized
module.
2. **`discard()` after `measure` aborts.** Use measure-all semantics: measure every qubit and
ignore the bits you do not need, rather than measuring some and discarding the rest.
3. Anything the classical-control surface cannot lower statically (host-side Python
arithmetic sneaking into the kernel) fails at emission, not at run time.
## The execution gap
Local `qircheck` passing does **not** imply the job will run. QIR execution targets are
restricted (H2-1SC / H2-1E class) and often unavailable on a standard account; direct QIR
execute against a normal backend fails with `entry not found in database`. Document the gap
explicitly in the write-up — "QIR emitted and qircheck-validated; Nexus execution blocked by
backend access" is a legitimate, citable status. Do not imply the bitcode ran.
## When to bother
- The submission target only accepts bitcode.
- You want an artifact that is independent of both Selene and TKET for a provenance claim.
- Otherwise: stay on Selene for evidence and TKET for the independent compile check
(`references/pytket.md`). The QIR lane costs a second environment for little extra signal.
------------------------------------------------------------------------------
### reference card: qpde
# Quantum Phase Difference Estimation (QPDE)
QPDE is a cousin to QPE that recovers only the **difference** of two eigenphases, `Δφ_ij = φ_i − φ_j`. Prefer QPDE over full QPE when the observable is an energy *gap* (excited-state chemistry, spectroscopy, level splittings) — the sampling overhead is constant in the target precision while the circuit depth grows as `O(1/ε)`.
Source: Quantinuum/SoftBank "Quantum Computing Frontiers" white paper, July 2026, §3.5.
## Circuit shape
```
|+⟩ --●------●------●-------- R_z(β) ---- X ---- measure(X-basis)
|Φ_g⟩ -- U_ex -- U^k -- U_ex† --
```
Steps per shot:
1. Prepare `|Φ_g⟩` and ancilla `|+⟩`.
2. Controlled `U_ex` maps `|Φ_g⟩ → |Φ_e⟩` when the ancilla is `|1⟩`.
3. Apply `U^k` on the system register (`U = e^{-iHt}`).
4. Uncontrolled `U_ex†`.
5. Ancilla phase-shift `R_z(β) = e^{-i(β/2)Z}`.
6. Measure ancilla in the X-basis (H then Z-basis) → outcome `m ∈ {0,1}`.
## Sampling protocol
Draw `(k_l, β_l)` uniformly at random with `k ∈ {1, ..., k_max}` and `β ∈ {0, π/2}`. `k_max` controls precision and sets the deepest circuit depth. Collect `{m_l}` over `N_s` shots (the white paper used `N_s = 1400` for ~2σ = 24 μHa precision on ethylene).
Reconstruct the phase by maximum likelihood over `φ̃ ∈ [−π, π)`:
```
Q(φ̃ | {m_l}; {k_l, β_l}) = Π_l (1 + cos(k_l·φ̃ + β_l − m_l·π)) / 2
Δφ_est = argmax_φ̃ Q(φ̃)
```
Bootstrap over shot subsets to get a statistical uncertainty on Δφ.
## Ethylene minimal photochemical benchmark
Reusable 2-qubit test problem for any QPDE / partial-FT / noise-sweep harness. C₂H₄ at 90° torsion (conical-intersection geometry), (2e, 2o) active space, STO-3G basis, Jordan–Wigner + particle-number + spin tapering:
```
H = h₁·Z₁ + h₂·Z₂ + h₃·Y₁Y₂ + h₄·Z₁Z₂ + h₅·I
(h₁, h₂, h₃, h₄, h₅) = (-3.02e-4, -3.02e-4, -0.122188, 1.28e-3, -76.856020) a.u.
```
- Ground-state reference: `|Φ_g⟩ = (|01⟩ + |10⟩)/√2`.
- Excitation: `U_ex = X₂·Z₁`, giving `|Φ_e⟩ = (|00⟩ − |11⟩)/√2`.
- Target: `ΔE ≈ −2.559 mHa` at the CI geometry. The full paper reports `−0.0025561(24) Ha` from `R = 1000` bootstrap × `N_s = 1400`.
## The evolution-time trick (why this benchmark is Clifford-mostly)
Choose `t = π / (16·h₁)`. Then:
- `e^{-i h₁ Z₁ t} = e^{-i(π/16)Z₁} = √T₁`, and likewise `√T₂` (single-qubit `T^{1/2}` phase gates).
- `e^{-i h₃ Y₁Y₂ t}` after 5-bit binary rounding of `h₃·t/π` reduces to `R_{Y₁Y₂}(π/2)` — **Clifford**.
- `e^{-i h₄ Z₁Z₂ k t}` similarly rounds to `R_{Z₁Z₂}(3kπ/2)` ∈ {π/2, π, 3π/2, 2π} — **Clifford** for the k-values used.
Net effect: only the `√T` factors sit outside Clifford, so on the [[7,1,3]] Steane code the whole `U^k` needs just a handful of RGT gadgets. See `references/encoded-circuits.md` for the RGT + partial-FT recipe.
## Guppy angle-hygiene warning
The evolution-time trick puts `t = π/(16 h₁)` in radians, but `angle()` in Guppy is in HALFTURNS (multiples of π). Write:
```python
# Correct — halfturns, not radians
u_1q = rz(qA, angle(1/16)) # √T on qubit A
```
NOT `angle(math.pi / 16)`, which is the S gate. See `references/guppy-language.md` §Angles.
## When NOT to use QPDE
- You need the absolute eigenvalue (not a gap) — use QPE.
- Only one eigenstate is accessible — the two-state weighting `|c_i|²|d_j|²` in `P(m|...)` collapses.
- `k_max` circuits blow past your coherence budget — QPE's iterative variants may amortize better.
## Worked Selene implementation (G16, this repo)
`quantum/qpde/{model,kernel,sweep,validate}.py` is the end-to-end reference:
| File | Role |
| --- | --- |
| `model.py` | Closed-form 2-qubit ethylene Hamiltonian + `eigenpairs()`; unit-checked at import. |
| `kernel.py` | `render_qpde_source(k, beta)` string factory → temp `.py` → `importlib` (see `driver-pattern.md`). Guppy needs source on disk, so the factory writes a file per cell. |
| `sweep.py` | 20-cell grid `k ∈ {1,2,4,8} × β ∈ {0, 0.25, 0.5, 0.75, 1.0}` halfturns, resumable per-cell cache under `_cache_qpde/`. |
| `validate.py` | Predicted-vs-measured check, gap fit, static JSON dump to `src/data/demos/qpde_ethylene_selene.json`. |
### Closed form for a one-ancilla QPDE cell
p(ancilla = 1) = (1 - sin(2*phi) * sin(beta)) / 2, phi = off * t
Verdict per cell uses the standard `4*sqrt(0.5/shots)` binomial threshold — not
a p-dependent 3σ form. Observed: 20/20 PASS, worst Δ = 0.0217 at 2048 shots,
51 s total.
### k = 8 is an aliasing control, not a data point
At `k = 8` the ethylene parameters put `2*phi` at π, where `p` is stationary in
`phi` and the arcsine branch is degenerate — the QPDE mod-1 wrap. Fit the gap
from `k ∈ {1,2,4}` at `β = π/2` only and **report k = 8 separately as the
aliasing control**. Including it silently biases the gap. Recovered gap with
this exclusion: 0.8099 Ha vs 0.8000 Ha reference (1.2% error).
### Gap-fit recipe
Use only the `β = π/2` column (maximum slope, `p = (1 - sin(2*phi))/2`):
phi_k = 0.5 * asin(1 - 2*p_k) # principal branch
gap_k = phi_k / (k * t)
then average over the non-aliased k. Bootstrap over shots if you need an error bar.
------------------------------------------------------------------------------
### reference card: qsp-qsvt
# QSP / QSVT: kernel + phase finder
Two layers, both verified in this repo:
1. **Execution layer** (`quantum/nadarasa_g10.py`) — a `qsp_sequence` Guppy kernel that interleaves signal `W(x)` rotations with parameterised phase rotations `e^{iφ_k Z}` on a single signal qubit, then measures.
2. **Synthesis layer** (`quantum/nadarasa_g10_phasefinder.py`) — a pure-NumPy / SciPy loop that recovers the phase sequence `φ = (φ_0, …, φ_d)` from a target polynomial `p(x)`.
## Kernel shape
For a degree-`d` QSP sequence on signal `x ∈ [-1, 1]`:
```python
@guppy
def qsp_sequence(x: float) -> None:
q = qubit()
rz(q, angle(phi_0))
# repeated d times, with phi_k baked in as float literals:
rx(q, angle(2.0 * math.acos(x))) # W(x) reflection
rz(q, angle(phi_k))
# ...
output("m", measure(q).read())
```
`P(m=0)` over many shots is the empirical response `|p(x)|²`. Sweep `x` across a grid (G10 uses 16 points × 2048 shots) and the histogram traces the target polynomial.
## Synthesis loop
Phase finding for low-degree polynomials is well-behaved with a generic SciPy optimiser; we don't need the full Laurent / matrix-completion machinery (Haah 2019). The pattern from `nadarasa_g10_phasefinder.py`:
```python
from scipy.optimize import minimize
def qsp_response(phi, x):
# exact 2x2 product over the QSP unitary -> top-left amplitude
...
def loss(phi):
return sum((qsp_response(phi, x) - target(x))**2 for x in grid)
res = minimize(loss, x0=np.zeros(d+1), method="Powell",
options={"xtol": 1e-8, "ftol": 1e-10, "maxiter": 20_000})
```
Then feed `res.x` into the kernel as `phi_k` literals (via the driver template; see `driver-pattern.md`) and sweep.
## Target: Chebyshev `sign(x)`
The standard textbook test — a low-degree odd polynomial approximation of `sign(x)` on `[-0.5, 0.5]`. Degree ≤ 9 is the "easy" regime; higher degrees become numerically brittle and need the dedicated phase-finding literature.
## Acceptance gate
After the full pipeline (synthesis → kernel literals → Selene shots):
```
max over the grid of |empirical P(m=0)^{1/2} − target(x)| < 0.05
```
The repo run hits worst-case Δ ≈ 0.0169 — well under the gate.
## Common pitfalls
- **Convention drift.** "Wx convention" vs. "reflection convention" differ by overall phase factors; pick one (this repo uses reflection / Rx-by-`2 arccos x`) and stick to it across synth and kernel.
- **Powell over BFGS.** The loss surface is non-smooth at degenerate phase choices; Powell is more robust than gradient-based methods at this scale.
- **Always cross-check classically.** Compute the exact 2×2 product in NumPy at the recovered φ, plot against target, before launching Selene shots.
------------------------------------------------------------------------------
### reference card: quantinuum-docs-corpus
# Quantinuum docs corpus (multi-site crawler + API-drift audit)
Nine Quantinuum Sphinx sites are crawled into the repo so code is written
against the *current* docs instead of recollection: Nexus, Guppy, Selene, tket
(user guide **and** API docs), lambeq, Quantum Origin, InQuanto, and the Systems
hardware user guide.
```bash
python -m quantum.docs_crawler.fetch # all sites, resumable
python -m quantum.docs_crawler.fetch --site guppy selene --force
python -m quantum.docs_crawler.extract --site guppy # /snippets.jsonl + api_surface.json
python -m quantum.docs_crawler.audit # /audit_report.md + AUDIT.md roll-up
```
Sites live in `quantum/docs_crawler/sites.py`: base URL, whether raw sources
exist, the import roots to mine, and which of *our* modules get audited against
them. Corpus at `corpus//.md`, index at `corpus/index-.json`
(Nexus keeps the legacy `corpus/index.json`).
## Two acquisition paths — most sites have no `_sources`
Every site publishes `searchindex.js` (there is no `sitemap.xml` anywhere; it
404s). Only three serve the raw markdown/notebook twin under
`/_sources/.txt`:
| Site | Pages | Snippets | Raw sources |
| --- | ---: | ---: | --- |
| nexus | 89 | 272 | yes |
| guppy | 257 | 377 | **no — HTML** |
| selene | 106 | 39 | **no — HTML** |
| tket-user-guide | 28 | 750 | yes |
| tket-api | 31 | 44 | yes |
| lambeq | 82 | 529 | yes (16 notebooks) |
| origin | 52 | 5 | **no — HTML** |
| inquanto | 126 | 1174 | **no — HTML** |
| systems | 59 | 609 | **no — HTML** |
830 pages, 3 799 snippets, zero fetch failures, ~6 min at the 0.4 s delay.
For the HTML sites the fallback takes the article container
(`div.bd-article-container` / `article.bd-article` / `[role=main]`), decomposes
nav/sidebar/footer/headerlink, and re-fences each `div.highlight pre` with the
language read off the parent `highlight-` class before flattening to text.
Do this *before* stripping tags — a plain tag-strip loses every code block, which
is the only part of a docs page worth auditing.
`tket` has **no single Sphinx root**: `tket/user-guide/` and `tket/api-docs/` are
separate builds with separate search indexes, registered as two sites. A crawl of
`https://docs.quantinuum.com/tket/searchindex.js` 404s.
## What the multi-site audit found
- **guppy** — zero drift; everything we call is documented. Unadopted and
interesting: `guppy.load_pytket` (10x — pytket circuit straight into a kernel),
`guppy.nat_var`/`type_var`/`type_alias` (generic-width kernels, which would
collapse our per-n kernel factories), `guppy.struct`, `guppy.comptime`,
`guppy.overload`.
- **selene** — the docs' own snippets are thin (39 across 106 pages; most pages
are autodoc stubs). Drift: `selene_sim.build` used in `emulate.py` never
appears in a snippet. Their example imports point at
`selene_sim.result_handling.parse_shot`, `selene_sim.event_hooks`, and
`hugr.qsystem.result` — the structured-result path we hand-roll.
- **tket-user-guide** — richest snippet density in the whole corpus (750 from 28
pages). Unadopted: `Backend.get_operator_expectation_value`,
`get_pauli_expectation_value`, `pytket.utils.expectation_from_counts/shots`,
`partition.measurement_reduction`, `compare_unitaries/statevectors` — i.e. the
expectation-value and verification machinery we reimplement by hand.
- **systems** — the hardware guide is a *combined* Guppy+pytket+qnexus corpus
(`guppylang.std.qsystem` appears 12x); it is the canonical source for access,
queueing and HQC costing, and it documents `QuantinuumConfig`,
`projects.get_or_create`, `jobs.wait_for`, `jobs.results`.
- **inquanto** — 1 174 snippets, dominated by `inquanto.ansatzes`,
`protocols`, `states`, `express`, `extensions.pyscf`. `express` is the
ready-made molecular-system shortcut for anything H2/ethylene-shaped.
- **lambeq** — 529 snippets, `lambeq.backend.{grammar,quantum,tensor}` plus
torch; entirely disjoint from our current stack (no modules audited yet).
- **origin** — only 5 snippets across 52 pages: Quantum Origin docs are CLI and
concept prose, not a Python API surface. Do not expect an importable library.
## The Nexus lane
## Refresh the corpus
```bash
python -m quantum.docs_crawler.fetch # resumable; --force to refetch, --section trainings, --limit N
python -m quantum.docs_crawler.extract # snippets.jsonl + api_surface.json
python -m quantum.docs_crawler.audit # audit_report.md
```
Layout (`quantum/docs_crawler/`):
| Path | Contents |
| --- | --- |
| `corpus/nexus/.md` | 89 pages of raw Sphinx source; `.ipynb` sources flattened to markdown + fenced python |
| `corpus/index.json` | per page: title, source URL, raw URL, sha256, bytes, fetched_at |
| `snippets.jsonl` | 278 code blocks tagged with their docname |
| `api_surface.json` | imports + `qnexus.*` call counts as the docs actually use them |
| `audit_report.md` | drift risk vs our code, documented-but-unused capabilities |
## Why it crawls cleanly
- Sphinx/Furo site: every page has a raw twin at `/_sources/.txt`. No
HTML parsing, no JS rendering.
- `searchindex.js` enumerates all 89 docnames + source filenames — there is **no
`sitemap.xml`** (404). Parse it as `Search.setIndex( )`.
- 16 of the sources are `.ipynb` JSON. Flatten them at fetch time; otherwise a
naive fence regex finds zero code in the most useful training pages
(33 snippets vs 278 after flattening).
## What the first audit found
Undocumented-but-used: `qnexus.HeliosConfig`, `auth.login`,
`auth.login_with_token`, `auth.is_logged_in`, `devices.get_all`,
`jobs.HybridStrategy`, `jobs.cost`, `jobs.get`, `users.get_self`. These are the
exact calls our cost guard and resume path depend on, and none of them appear in
a single doc snippet — they are the drift surface.
They are now fenced behind **`quantum/qnexus_compat.py`**, the single import
point for the client:
```python
from quantum import qnexus_compat as compat
compat.PINNED_QNEXUS # "0.48.2" — lockstep with quantum/requirements.txt
qnx = compat.import_qnexus() # lazy; warns (does not fail) on a version mismatch
compat.assert_no_drift(qnx) # fatal BEFORE submission if any symbol moved
compat.drift_report(qnx) # {symbol: present}, never raises — safe in a preflight
compat.jobs_cost(qnx)(job) # named accessor per drift symbol; a missing one
# raises QnexusDriftError naming it + both versions
```
Rules that keep it maintainable: only the drift surface goes through the layer
(documented calls stay on plain `qnx.`), the offline fake in
`tests/fake_qnexus.py` carries a `__version__` matching the pin so every
accessor is exercised with zero submissions, and a new undocumented call means
a new entry in `DRIFT_SYMBOLS` plus a case in `tests/test_qnexus_compat.py`.
### Keeping the list honest: `drift_watch`
A hand-maintained drift list rots in both directions, so the reconciliation is
automated — `quantum/docs_crawler/drift_watch.py` diffs three derived sets
(crawled `api_surface.json`, our real call sites, `DRIFT_SYMBOLS`) and grades
every symbol:
| Verdict | Meaning | Action |
|---|---|---|
| `stable` | declared, still undocumented, still used | none |
| `newly-documented` | the docs now cover it | may leave the compat layer |
| `undeclared` | used + undocumented + unguarded | **add it before the next paid run** |
| `obsolete` | declared, nothing calls it | remove entry + accessor |
```bash
python -m quantum.docs_crawler.drift_watch # writes drift_state.json
python -m quantum.docs_crawler.drift_watch --check # exit 1 when out of sync
```
`audit.py` renders the same state into `nexus/audit_report.md` and `AUDIT.md`,
so the ordinary `fetch → extract → audit` cycle refreshes it, and
`tests/test_drift_watch.py` fails the day the declaration goes stale.
Two things the diff must get right or it lies:
- **Count compat-mediated use.** Once a symbol is routed through the layer, no
file writes `qnx.jobs.cost(` any more; a scan for direct calls alone grades
the entire guarded surface `obsolete` and invites deleting exactly the code
that protects the cost path. `ACCESSORS` maps symbol → accessor name so the
indirection still counts as use.
- **Tokenize before scanning.** A plain regex over source counts
`qnexus.auth.login()` written inside a docstring and invents an `undeclared`
symbol nobody calls. Strip comments and string literals first.
### Catching upstream change: `api_diff` (all nine sites)
`drift_watch` only guards the Nexus drift list. The wider risk is a Guppy,
Selene, tket or InQuanto symbol moving under us and surfacing as a mid-sweep
crash on a billed run. `quantum/docs_crawler/api_diff.py` snapshots each site's
**imports + calls + page set** and compares the new crawl against a committed
`/api_baseline.json`:
| Verdict | Meaning | Action |
|---|---|---|
| `added` | new documented surface | opportunity — read it before the next gate |
| `removed` | gone upstream, we never used it | none |
| `moved` | a removal pairs 1:1 with an addition | follow the rename |
| `breaking` | gone upstream **and** our code imports/calls it | **fix before the next paid run** |
```bash
python -m quantum.docs_crawler.api_diff --check # exit 1 on breaking
python -m quantum.docs_crawler.api_diff --check --introspect # also probe installed pkgs
python -m quantum.docs_crawler.api_diff --site guppy --accept # adopt, then commit
```
Four things this had to get right:
- **Key on imports as well as calls.** The lib-rooted call surface in the docs
is far thinner than the import surface (Guppy: 13 calls vs 45 imports; Selene
3 vs 12), and this repo touches Guppy almost entirely through
`from guppylang.std.quantum import h`. Calls alone would grade a real removal
as unused.
- **Grade by *our* dependency, not by doc-hit count.** `removed` vs `breaking`
is decided by `our_usage()`, which unions the call-chain scan with an import
scan across `sites.py`'s `our_files`/`our_globs`. A gap in those globs
silently downgrades a breaking change — widen them when a new module starts
importing a library.
- **Baselines move only on a human `--accept`.** A snapshot-only audit can
never say "this changed"; the committed baseline is what makes the diff a
gate instead of a description.
- **Unknown ≠ broken.** A missing baseline, an uncrawled site, or a package
absent from `.pydeps` after a sandbox reset all render as "run `--accept`" or
stay silent — never as `breaking`.
`--introspect` resolves used symbols against the *installed* packages with
`importlib`, which catches removals the docs haven't caught up with yet.
`audit.py` renders the rows into every per-site report and a
"Breaking API changes since the accepted baseline" section of `AUDIT.md`;
`tests/test_api_diff.py` (39 offline tests) covers the graders, the
accept round-trip, and the CLI exit codes.
### Running the docs: `snippet_run` (Gate 0.9.2)
`api_diff` proves a symbol still *exists*; `snippet_run` proves the documented
example still *runs*. It groups a page's snippets in document order and
executes them as one real `.py` file in a locked-down subprocess:
```bash
python -m quantum.docs_crawler.snippet_run --jobs 8 # execute + write results
python -m quantum.docs_crawler.snippet_run --check --no-run # CI gate, exit 1 on regression
python -m quantum.docs_crawler.snippet_run --site selene --accept
```
Design decisions that had to be made this way:
- **A real file on disk, never `exec(code_string)`.** Guppy compiles a kernel by
reading its own source back with `inspect.getsource`, so every `@guppy`
example raised `OSError: source code not available` under a string-exec
harness — 34 fake failures in Guppy alone. The page is written to
`snippet_page.py` with a `_snip_mark(i)` line between blocks; the marks give
per-block attribution without indenting the doc code (indenting also breaks
what Guppy reads back).
- **Page-level namespace.** Selene's `build(hugr)` block depends on the
`hugr = main.compile()` block above it. Isolated blocks would fail as
fragments. A failure stops the page and the rest grade `skipped`.
- **Loopback stays open.** Selene drives its own emulator over a local socket;
a blanket socket ban failed every emulation example for a sandbox reason.
The injected `sitecustomize` blocks non-local `connect`/`getaddrinfo` only.
- **Prefilter before executing, not after.** Anything mentioning `qnexus`,
`start_execute_job`, `QuantinuumBackend`, or an API key is `blocked`
unexecuted — a docs pass must never touch the account. A committed test
re-derives this from the corpus for every executed block.
- **Verdicts separate their causes.** `fail` is reserved for a real breakage;
`NameError`/`ModuleNotFoundError`/`FileNotFoundError` grade `blocked`
(fragment or optional dep), memory-cap kills grade `blocked`, and a missing
library grades `skipped`. Only `pass -> fail` is a regression;
`pass -> skipped` is our environment, not their API.
- **Reasons must be stable.** The temp page path is normalised to ``, or
every run diffs against its own baseline. Guppy prints its real diagnostic to
*stdout* and ends stderr with the useless "Guppy compilation failed due to 1
previous error", so the harness prefers the `Error:` line.
Current state (guppylang 1.0.1, selene-sim 0.3.0, pytket 2.18.1):
| Site | pass | fail |
|---|---:|---:|
| guppy | 143 | 19 |
| tket-user-guide | 166 | 0 |
| tket-api | 26 | 0 |
| systems | 55 | 11 |
| selene | 1 | 7 |
| nexus | 10 | 0 |
The standing finding: **every emulation page in the Selene user guide fails to
compile** because it still writes `result("x", measure(q))`, which Guppy 1.x
rejects — the same `measure().read()` migration this repo made (Gotcha #16).
Most Guppy `language_guide` failures are the docs' own deliberate
counter-examples ("this is rejected"); they are baselined, so they stay quiet
until their behaviour changes.
Documented and worth adopting:
```python
# Persist a job Ref to disk — ADOPTED in quantum/nexus_refs.py; the job-id cache
# is now the fallback, the saved Ref is the primary recovery path
qnx.filesystem.save(ref=execute_ref, path=Path.cwd() / "jobs" / job_name, mkdir=True)
ref = qnx.filesystem.load(path=Path.cwd() / "jobs" / job_name)
qnx.jobs.status(ref)
# Nested property stamping without threading kwargs through every call
with qnx.context.using_properties(name_qpu="H2-Emulator"):
with qnx.context.using_properties(noisy=True, num_shots=512):
qnx.start_execute_job(...)
# Partial harvest, cancel, retry, delete
qnx.jobs.results(job=ref, allow_incomplete=True)
qnx.jobs.cancel(ref)
qnx.jobs.retry_submission(ref,
retry_status=[qnx.jobs.JobStatusEnum.CANCELLED],
remote_retry_strategy=qnx.jobs.RemoteRetryStrategy.FULL_RESTART)
qnx.jobs.delete(ref) # deletes results + snapshots, keeps circuits
# Preflight instead of guessing
qnx.devices.supports_shots(qnx.QuantinuumConfig(device_name="H2-1LE"))
qnx.jobs.get_all(job_status=[qnx.jobs.JobStatusEnum.SUBMITTED]).df()
```
`SelenePlusConfig` (`trainings/notebooks/basics/selene_examples.md`) is the
hosted Selene lane with error/runtime mode selection — the cloud twin of our
local `selene-sim` runs, and the cheapest way to cross-check a local sweep.
## Rules
- Re-run `fetch` + `audit` before any gate that adds a new qnexus call; treat a
new entry in the drift list as a deliberate decision, not an accident.
- Cite corpus facts by docname + `sha256` from `corpus/index.json`; the corpus is
a local research cache, not a re-publication of Quantinuum docs.
- Keep the crawler polite: sequential, 0.4 s delay, resumable — a re-run after a
sandbox reset only fetches the delta.
## Quickstart notebooks generated from the corpus
`quantum/docs_crawler/notebooks.py` assembles four runnable notebooks —
`notebooks/quickstart-{guppy,selene,tket,lambeq}.ipynb` — out of the same
`/snippets.jsonl` the audit reads. Generated, never hand-written: a docs
refresh re-emits them and the diff shows exactly which upstream example moved.
```bash
PYTHONPATH=.pydeps python3 -m quantum.docs_crawler.notebooks # all four
PYTHONPATH=.pydeps python3 -m quantum.docs_crawler.notebooks --site guppy
```
Each notebook is the same five-part spine: header, environment check (Guppy: v1
API present; tket: `Circuit(1).Ry(0.5).get_unitary()` proves half-turns), sourced
examples one per cell with the page title and URL above it, a bridge cell showing
where the library plugs into this repo, and the pitfalls that apply to it.
Selection rules that matter when tuning a notebook:
- Only docnames in the spec's `pages` tuple are mined, in that order, with a
`per_page_cap` so one 77-snippet page cannot eat the whole notebook.
- **Filter on `compile()`, not on a lang tag.** The markdown `_sources` twins hand
back prose fenced as code on several lambeq and tket pages, and the extractor
records no language — a block that does not parse is prose, and a notebook cell
that does not parse is worse than a missing example.
- `FORBIDDEN_TOKENS` drops any block mentioning `qnexus` / `qnx.` /
`start_execute_job` / `api_key`. Selene's MPS page ends in a live Nexus
submission; a quickstart a reader runs top to bottom must never spend money.
- A bad pick is pinned out through `notebook_manifest.json`'s `exclude` map
(`{"": [["docname", index]]}`), which survives regeneration — never by
editing the `.ipynb`.
`tests/test_notebooks.py` gates all of it: every manifest pick still resolves
against the corpus, every notebook is valid nbformat v4, every code cell compiles,
every sourced cell is attributed, no cell references credentials, and a fresh
build is byte-identical to what is on disk. The Guppy and Selene environment-check
cells are actually executed when `.pydeps` has `guppylang`, and skip cleanly when
it does not.
Full notebook lane (spec shape, CLI, gate ladder, `/skills` cards): see
`references/quickstart-notebooks.md`.
------------------------------------------------------------------------------
### reference card: quickstart-notebooks
# Quickstart notebooks (Guppy / Selene / tket / lambeq)
Four `.ipynb` quickstarts are **generated**, never hand-edited: they are assembled
from the crawled Quantinuum docs corpus (`references/quantinuum-docs-corpus.md`)
by `quantum/docs_crawler/notebooks.py` and written to `notebooks/quickstart-.ipynb`.
Edit the spec or the corpus, then regenerate — a hand edit is silently reverted by
the byte-match test.
## Spec shape
```python
NotebookSpec(
key="guppy", site="guppy", title=..., blurb=...,
install='pip install "guppylang>=1.0"',
env_check="import guppylang\n...", # the ONE executable cell
env_modules=("guppylang",), # metadata only, never emitted
pages=(...docnames in order...), # only these are mined
bridge_title=..., bridge_code=..., # how this library plugs into our sweeps
pitfalls=(...), # rendered as the closing numbered list
max_cells=..., per_page_cap=4, extra_pages=(),
)
```
`env_modules` must list **every** third-party top-level module the `env_check`
cell imports. It is not decoration: the test suite parametrises the skip gate and
the drift gate over it, so an import that is not declared escapes both guards.
A test re-derives the declared set from the cell source and fails on divergence.
## CLI
```bash
PYTHONPATH=.pydeps python3 -m quantum.docs_crawler.notebooks --list
PYTHONPATH=.pydeps python3 -m quantum.docs_crawler.notebooks # all four
PYTHONPATH=.pydeps python3 -m quantum.docs_crawler.notebooks --only guppy
PYTHONPATH=.pydeps python3 -m quantum.docs_crawler.notebooks --only guppy,tket
PYTHONPATH=.pydeps python3 -m quantum.docs_crawler.notebooks --only selene --out /tmp/nb
```
- `--only` is repeatable *and* comma-separated; an unknown key exits non-zero
printing the valid keys (silently generating nothing is the worse failure).
- The **default** output dir owns the committed `notebook_manifest.json`. A
non-default `--out` writes its own manifest beside its notebooks and leaves the
committed one untouched — otherwise a scratch run poisons the shared manifest
with rows pointing at files that are not in `notebooks/`.
- A bad snippet pick is pinned out by hand via the manifest's `exclude` list, not
by editing the notebook.
## Gate ladder (`tests/test_notebooks.py`)
Ordered by what each one actually catches:
| Gate | Catches |
| --- | --- |
| manifest picks resolve against the corpus | a re-crawl dropping a page we mined |
| valid `nbformat` v4 | structural breakage in the writer |
| every code cell `compile()`s | a snippet that is prose or truncated |
| every sourced cell attributed | an unattributed upstream block |
| no credentials / no billable calls | a quickstart that would spend HQCs |
| **byte-exact regeneration** | indentation, key order, trailing-newline drift |
| declared `env_modules` == imported | a new import escaping both env gates |
| env-check cell **runs** when deps present | a genuinely broken cell |
| env-check cell **skips** when deps absent | the skip path (see the pitfall below) |
The skip gate runs the real env-check test in a **child pytest** with a
`meta_path` finder that raises `ModuleNotFoundError` for the target module and
its submodules (and purges it from `sys.modules` first). A plugin collects
per-test outcomes into a JSON report; the parent asserts zero failures, exactly
one skip, and a reason naming both the module and its install command. The
blocker lives in the child only — installed in the parent it leaks into every
later test in the file. A child that fails to start is a **skip carrying its
stdout**, never a silent pass.
## `/skills` cards
`scripts/build-skill-bundles.mjs` parses each generated notebook to build its
card, so the card content is a function of the generator's markdown shape:
- `topics` / `sourcePages` — distinct titles from the `**[Title](url)**`
attribution lines.
- `pitfalls` — the closing numbered list under `## Pitfalls`, verbatim.
- `install` — the opening bash block.
All parsers are defensive (unexpected shape yields an empty value rather than a
build failure), which means a shape change **empties the cards silently**.
`tests/test_skill_bundles_manifest.py` is the gate: every notebook entry must
have non-empty `topics` and `pitfalls`, `sourcePages` matching `topics.length`,
and clean single-line strings.
------------------------------------------------------------------------------
### reference card: receipt-envelope
# The receipt envelope — one schema, four grades
A number without its execution conditions is a rumour with a decimal point. The
fix is not "remember to mention the shots": it is a **named block with a
version**, attached to every committed result, plus a script that grades every
artefact against it. EndoTrack ships this as `qas/envelope/0.1`; the design
transfers directly.
## The block
Attach one of these to every result artefact — not to the run log, to the
artefact that gets read:
```json
{
"schema": "qas/envelope/0.1",
"claims": ["AQFT_k1 beats QFT_full at p=1e-3, n=16"],
"engine": "H2-Emulator",
"backend_qualifier": "emulator",
"shots": 2048,
"seed": 20260826,
"commit": "a1b2c3d",
"envelope": 0.0442,
"verdict": "PASS"
}
```
Field notes, each earned the hard way:
- **`claims`** is a list of sentences, not a topic. If you cannot write the
claim as a sentence that could be false, the artefact is not making a claim
and belongs on the no-claim list instead.
- **`backend_qualifier`** exists so `engine` can never be read as hardware.
`emulator` / `simulator` / `hardware`, always present, never inferred from the
device name — `H2-Emulator` and `H2-1` differ by one word.
- **`envelope`** is the statistical tolerance the verdict was decided against,
computed, not remembered: `4·√(0.5/shots)` for a probability comparison
(`SKILL.md` #3). Storing it means a later reader can re-decide the verdict
without re-deriving the threshold.
- **`seed` + `commit`** are what make the row reproducible. A missing commit
turns every other field into a claim about code that no longer exists.
## The four grades
The audit script (`tools/validate_receipts.py` in EndoTrack) walks every
artefact and grades it. Four outcomes, because two are not enough:
| Grade | Meaning | What to do |
| --- | --- | --- |
| `PASS` | Envelope present, complete, internally consistent | Nothing |
| `GAP` | Envelope present but a field is missing or null | Fill it; do not downgrade the claim |
| `STRUCTURAL` | No envelope at all — the artefact predates the schema | Migrate, or put it on the no-claim list |
| `FAIL` | Envelope present and **contradicted** by the artefact (verdict disagrees with the criteria, shots disagree with the envelope) | Block the build |
The distinction that matters is `GAP` vs `FAIL`. Missing information is not a
lie, and collapsing the two either blocks builds over paperwork or lets a
contradiction through as "incomplete". Same rule as the device matrix's
`unknown` (`SKILL.md` #56): absence and refusal are different verdicts.
## How this maps onto what this project already emits
We have the pieces, spread across three places, which is why nothing can audit
them as a unit:
| Envelope field | Where it lives today |
| --- | --- |
| `claims` | `verdict` + `verdict_criteria[].statement` in each dump |
| `engine`, `backend_qualifier` | the `execution` block's meter (`mode`, `device`) |
| `shots`, `seed` | the meter |
| `commit`, artefact digest | `quantum/provenance.py` (`artifact_sha256`) |
| `envelope` | recomputed ad hoc per driver from `4*sqrt(0.5/shots)` |
| `verdict` | top-level `verdict`, guarded by `tests/test_evidence_chain.py` |
Convergence path, when it is worth a gate: emit one `receipt` block per dump
built from those existing sources, extend `tests/test_evidence_chain.py` to
grade `PASS/GAP/STRUCTURAL/FAIL` instead of pass/skip, and let the no-claim
registry absorb the `STRUCTURAL` rows that are genuinely claimless. Until then,
treat this file as the target shape, not as something we ship.
## The rule that survives even without the tooling
Never display a result number without its engine qualifier and its shot count in
the same view. Every certified figure on a page, in a table, or in a chat reply
carries where it ran and how many shots it took — because that is the pair a
reader needs to know whether to believe it.
------------------------------------------------------------------------------
### reference card: rewriter-composition
# Composing a generated/variational ansatz with the rewriter pipeline
How to hand an AI-generated or variational circuit (ADAPT-GQE, UCCSD, VQE ansatz)
to a Clifford canonicaliser before spending shots on it. Worked example: Nadarasa
Gate 0.4.6 / G21 — the H2 / STO-3G single-excitation UCCSD operator.
## The pattern
```text
ansatz ──split──▶ Clifford frame ──rule (N/M/P) canonicaliser──▶ residue
│
└─────────▶ rotation core ──4x4 / 2^n matrix oracle──────▶ numeric check
│
both forms ──▶ Guppy kernels ──▶ Selene shots ──▶ histogram compare
```
Three independent verification layers, in increasing cost:
1. **Matrix oracle (free).** Build the dense unitary for every candidate form in
NumPy and compare to the exact operator. Tolerance `1e-9`; real agreement lands
at machine epsilon (`1.1e-16` for G21).
2. **Structural canonicalisation (free).** Run the Clifford segments through
`normalise2qWithMatrix` (rule M) in `src/lib/zx/conjecture-synth-2q.ts`. Equal
`matrix_key` ⇒ the frames are the same unitary; the residue is the cheapest
syntactic form.
3. **Selene shots (expensive).** Compile both forms with Guppy, run 512 shots on
Quest, compare histograms to the exact probability vector using the standard
`4·√(0.5/shots)` envelope (0.125 at 512 shots).
## When canonicalisation is safe
- **Safe:** segments drawn from the Clifford alphabet `{H0, H1, CZ, S0, S1}` —
CNOT sandwiches, basis changes, entangling frames. The rewriter is exact here.
- **Not safe:** parameterised rotations (`Ry(θ)`, `Rz(θ)`) with θ outside the
fixed angle set the oracle enumerates. Do NOT feed these to the rewriter; keep
them as opaque cores and verify them with the matrix oracle instead.
So: **split the circuit at the rotation boundaries**, canonicalise the Clifford
segments, leave the rotation cores untouched, and re-verify the whole thing
numerically + on Selene.
## Decomposition trap (cost me a gate)
A conjugated-Rz sandwich is NOT a Givens rotation:
```text
CNOT · Rz(2θ) · CNOT = exp(-i θ Z0 Z1) # phase, not excitation
```
The single-excitation operator `exp(-i θ (X₀Y₁ - Y₀X₁)/2)` needs a controlled-Ry:
```text
original : CNOT(q1->q0) · CRy(2θ)(q0->q1) · CNOT(q1->q0)
alternate : CNOT(q1->q0) · Ry(θ)q1 · CNOT(q0->q1) · Ry(-θ)q1 · CNOT(q0->q1) · CNOT(q1->q0)
```
Always numerically verify a paper's stated decomposition against
`scipy.linalg.expm` of the Pauli sum before writing the Guppy kernel.
## Angle choice
Pick θ so that any derived Clifford angle lands in the alphabet — θ = π/4 gives
2θ = π/2 so a derived Rz is exactly `S`. That maximises how much of the circuit
the rewriter can absorb.
## Language split
The 2q oracle lives in TypeScript; Selene execution lives in Python. Drive the
canonicalisation from a small `bunx tsx` script that emits JSON, and merge it with
the Python dump in a compose step. Document the split — there is no Python port
of the oracle.
## Reference implementation
- `quantum/adapt/adapt_h2_uccsd.py` — matrix oracle, both forms, `src/data/demos/adapt_gqe_matrix.json`.
- `quantum/adapt/kernel.py` + `smoke.py` — Guppy sources (native CX vs H-CZ-H expansion), 512-shot Quest run, `adapt_gqe_selene.json`.
- `src/data/demos/adapt_gqe_composed.json` + `src/routes/nadarasa.g21.tsx` — merged dump and UI.
------------------------------------------------------------------------------
### reference card: selene-run-schema
# `selene_run` v1 schema + ``
A single Zod schema renders every Selene experiment in this repo. New demos should target it instead of bespoke React pages.
## Schema (v1)
`src/lib/selene-run-schema.ts` defines:
```ts
SeleneRun = {
schemaVersion: 1,
experiment: string,
title: string,
description: string,
kernel: { snippet, qubits, shotsPerRow },
verdict: { text, good },
metrics: { name, value, unit?, good? }[],
series: {
id, kind: "histogram" | "bar" | "line",
title, xLabel?, yLabel?,
yKeys: string[],
points: { label, values: Record }[],
}[],
notes?: string,
extras?: Record,
}
```
Render with `` (`src/components/selene/SeleneRun.tsx`). It reads `metrics` and `series` only — `extras` exists for non-rendering metadata.
## The "no `extras` escape hatch" rule
**If a new demo needs `extras` to render correctly, the schema is wrong — extend the schema instead.** This is the refutation criterion for the `json-ir-schema` frontier card; bypassing it via `extras` voids the v1 conformance claim.
The current 8/8 demos round-trip without `extras`. Keep it that way.
## Authoring a new demo
1. Run the Python driver, dump results to `src/data/demos/.json` in whatever shape is natural.
2. Write a converter in `src/lib/selene-run-convert.ts`:
```ts
export function convertToSeleneRun(): SeleneRun {
const raw = rawJson;
return { schemaVersion: 1, experiment: "...", title: ..., metrics: [...], series: [...], ... };
}
```
3. Add a route that does `SeleneRunSchema.parse(convertToSeleneRun())` and renders ``. Parsing at render time is the schema test.
4. Add the new demo to the conformance dashboard (`src/routes/nadarasa.schema-coverage.tsx`) so the count stays at N/N.
## When to extend the schema vs. add `extras`
| Need | Action |
| --- | --- |
| Another chart kind (e.g. heatmap, scatter) | extend `SeriesSchema.kind` enum |
| Per-series annotations (target curve overlay) | add an optional `overlay` field on `SeriesSchema` |
| Truly non-rendering metadata (timing, host info) | `extras` is fine |
| Anything the user is meant to see | extend the schema |
When in doubt: if `` reads it, it belongs in `metrics` or `series`, not `extras`.
------------------------------------------------------------------------------
### reference card: selene-runtime
# Selene runtime
Selene is Quantinuum's emulator. Since Guppy v1.0 it ships inside `guppylang` and is driven by the
`program.emulator(...)` builder. Read `references/guppy-v1-migration.md` first if you are touching
pre-v1 code.
## Imports
```python
from quantum.emulate import build, Quest # repo shim over the v1 emulator builder
# native: program.emulator(...) with `from selene_sim import Quest`
```
`Quest` is the default statevector backend. Other backends exist but Quest is the right default for small circuits (<= ~20 qubits).
## Pipeline
```python
# Native v1: the builder hangs off the @guppy program — never off program.compile()
res = (
my_program
.emulator(n_qubits=5)
.with_shots(2000)
.with_simulator(Quest())
.run()
)
for shot in res:
for name, value in shot.entries:
# name is the string from output("name", ...)
# value is the classical bit/integer
...
# Shim form used by every driver in quantum/ (same iteration shape as pre-v1):
runner = build(my_program)
for shot in runner.run_shots(Quest(), n_qubits=5, n_shots=2000):
for name, value in shot:
...
```
## Shot iterator
Each `shot` is an iterable of `(label, value)` pairs — one entry per `output(...)` call in the kernel. If your kernel calls `output("anc", m)` once per shot, each shot yields exactly one pair. Natively the pairs live on `shot.entries`; the shim flattens them for you.
## Counting outcomes (SWAP-test example)
```python
zeros = total = 0
for shot in runner.run_shots(Quest(), n_qubits=5, n_shots=N):
for _, val in shot:
total += 1
if int(val) == 0:
zeros += 1
p0 = zeros / total
fidelity = max(0.0, min(1.0, 2.0 * p0 - 1.0)) # SWAP-test inversion
```
## `n_qubits` argument
Pass the **maximum** number of live qubits the kernel allocates simultaneously. For the SWAP test in `qtda.py`: 1 ancilla + 2x2 patient registers = 5.
`measure(q)` releases the qubit slot, so mid-circuit-measured ancillas do NOT stack. A kernel with `n` data qubits and `k` sequential single-ancilla windows (allocate → probe → measure, then next window) still only needs `n_qubits = n + 1` live — the same slot is reused across windows. This is the pattern in `quantum/nadarasa_g2.py`. Only count ancillas that are simultaneously alive.
## Multi-result shots
When the kernel calls `output(...)` multiple times per shot (e.g. one per window plus one per final data qubit), each `shot` yields one `(label, value)` pair per call. Bin them into a dict for downstream decoding:
```python
shots = []
for shot in runner.run_shots(Quest(), n_qubits=n+1, n_shots=S):
rec = {str(lbl): int(v) for lbl, v in shot}
shots.append(rec)
```
## Integer decoding from per-qubit results
Kernels that emit one result per data qubit (`output("x0", measure(d0).read())`, `output("x1", ...)`, ...) reassemble into an integer host-side:
```python
x = 0
for j in range(n):
x |= (rec.get(f"x{j}", 0) & 1) << j
```
Bucket `x` for the metric you want (residue `x % p` for G1, full histogram for collision probability — see `circuit-patterns.md`).
## Performance notes
- Quest is classical statevector — cost scales as `2^n_qubits` per shot.
- For deterministic-output circuits, classical statevector via NumPy is faster than running 2000 shots — use Selene for the *measurement statistics*, not the underlying amplitudes.
## Noise models
`selene_sim` still ships the three noise models under Guppy v1. Natively they go to
`.with_error_model(...)` on the emulator builder; through the repo shim they stay a
`runner.run_shots(..., error_model=...)` keyword. Default is `IdealErrorModel`.
```python
from selene_sim import (
Quest,
IdealErrorModel,
DepolarizingErrorModel,
SimpleLeakageErrorModel,
)
from quantum.emulate import build
runner = build(program) # the @guppy program object, not program.compile()
# Depolarizing: per-gate Pauli-twirl + per-measure bit-flip + per-init reset error.
runner.run_shots(
Quest(), n_qubits=N, n_shots=512,
error_model=DepolarizingErrorModel(
p_1q=0.01, p_2q=0.10, p_meas=0.02, p_init=0.0,
),
)
# Leakage: every 2-qubit gate (cphase, cx, etc.) is a leakage opportunity.
# Leaked qubits leave the computational subspace and bias subsequent measurements.
runner.run_shots(
Quest(), n_qubits=N, n_shots=512,
error_model=SimpleLeakageErrorModel(p_leak=0.003, leak_measurement_bias=0.5),
)
```
Conventions used across `quantum/pqp_frontier/noise_*.py`:
- `p_2q = 10 × p_1q` (hardware ratio Q-System / H1-2 era)
- `p_meas = 2 × p_1q`
- `p_init = 0` unless explicitly sweeping initialisation errors
- Sweep `p_1q ∈ {0, 0.001, 0.003, 0.01, 0.03, 0.1}` for the "log-spaced 6-row noise curve" used in `/nadarasa/proofs/noise`.
**No coherent / T1-T2 model ships today.** Attempting `from selene_sim import CoherentErrorModel` raises `ImportError`. If hardware-shaped noise is required, layer it host-side over `IdealErrorModel` shots, or fall back to `DepolarizingErrorModel + SimpleLeakageErrorModel` as a two-axis sweep. This is the gotcha that killed the first Track C-2q design.
### Realistic H2-2 noise-parameter targets
When calibrating a sweep against Quantinuum's own H2-2 emulator settings (published in the July 2026 SoftBank/Quantinuum white paper §3.10), use:
| Channel | Parameter | Value |
| --- | --- | --- |
| 2-qubit gate fault | `p_2q` | 1.29 × 10⁻³ |
| Readout `0 → 1` | `p_r_01` | 0.9 × 10⁻³ |
| Readout `1 → 0` | `p_r_10` | 1.8 × 10⁻³ |
| Coherent memory | `f` | 4.3 × 10⁻² rad/s |
| Incoherent memory | `g` | 2.8 × 10⁻³ /s |
Map `p_2q` directly into `DepolarizingErrorModel(p_2q=1.29e-3, p_1q=1.29e-4, p_meas=1.35e-3, ...)`. Memory noise has no first-class Selene model — capture its effect either via a proxy `p_meas` inflation or by extending idle time in the compiled circuit.
**Dominance ordering (measured, from the same source).** Under representative pFT settings, **incoherent memory noise and gate + readout errors dominate**; coherent memory noise is well-suppressed by **dynamical decoupling** even at `f = 4.3e-2 rad/s`. Practical rules:
1. In any noise-activation sweep, hit incoherent memory + gate/readout first — they are where the budget actually lives.
2. Enable DD on any encoded circuit that transports or idles qubits; coherent memory is not the enemy once DD is on.
3. Report the decoherence parameter `q` per channel (mirror-benchmark on `k = 3, 5`) rather than a scalar "logical error" to preserve the dominance signal.
## Shipping results to the frontend
Selene results ship as **committed static JSON**, never as a live server function.
### Do
```python
# In the Python driver, after all shots finish:
out = Path("src/data/demos/pqp_frontier_noise_2q.json")
out.write_text(json.dumps(payload, indent=2))
```
```tsx
// In the route file:
import data from "@/data/demos/pqp_frontier_noise_2q.json";
export const Route = createFileRoute("/nadarasa/proofs/noise-2q")({
component: () => ,
});
```
Static import → zero runtime cost, works in SSR and prerender, survives the Cloudflare Worker sandbox.
### Do NOT
- Do not write a `createServerFn` handler that shells out to Python. The Worker runtime stubs `child_process.spawn` and calls raise `[unenv] spawn is not implemented yet!` at runtime.
- Do not write a `createFileRoute` `server` handler that reads the sweep cache directory (`_cache_*/`, `/tmp/...`, etc). The Worker filesystem is a virtual bundle and arbitrary paths are not reachable in production.
- Do not build a "live status" panel that polls a server function. Every attempt at this pattern in v0.4.1 (`sweep-status.ts`, `noise-2q-status.functions.ts`, `Noise2QStatusPanel.tsx`, `auto_resume_noise_2q.sh`) had to be deleted.
### If progress visibility is needed while a sweep runs
Print row-by-row to stdout from the Python driver and watch the sandbox terminal — that's the correct progress UI during development:
```python
print(f"[{done}/{total}] {tag} p_pass={row['pass_rate']:.2f}", flush=True)
```
Ship the *finished* JSON to `src/data/demos/` when the sweep is complete, and re-render the static route.
------------------------------------------------------------------------------
### reference card: shor-modexp
# Real controlled `pow_const_mod` for small-N Shor
Pattern from `quantum/nadarasa_g11_real.py`. Implements
|x⟩_ctrl |1⟩_work → |x⟩_ctrl |a^x mod N⟩_work
for `a = 2, N = 15` on a 4-qubit work register, controlled by a 4-qubit QPE register. Period `r = ord_15(2) = 4`, so QPE peaks land at `{0, 4, 8, 12}` and continued fractions recover `r = 4`.
## The CSWAP-chain trick
For `a = 2` and `N = 2^k − 1` (here `15 = 2^4 − 1`), controlled mul-by-2 mod N coincides with a **controlled left cyclic shift** on the work register, on the entire orbit of `|1⟩` (`{1, 2, 4, 8}` plus `|0⟩`). It only diverges on `|15⟩ = |1111⟩`, which is unreachable from `|1⟩` under repeated mul-by-2 and therefore never appears.
```python
@guppy
def cswap(c: qubit, a: qubit, b: qubit) -> None:
cx(b, a); toffoli(c, a, b); cx(b, a)
@guppy
def cmul2_mod15(c, w0, w1, w2, w3) -> None:
# left cyclic shift by 1: (w0,w1,w2,w3) -> (w3,w0,w1,w2)
cswap(c, w3, w2)
cswap(c, w2, w1)
cswap(c, w1, w0)
@guppy
def cmul4_mod15(c, w0, w1, w2, w3) -> None:
# left cyclic shift by 2
cswap(c, w0, w2); cswap(c, w1, w3)
```
For `pow_const_mod(a=2)`, the powers `2^(2^i) mod 15` are `{2, 4, 1, 1}` — so the four QPE-controlled multiplies become `cmul2`, `cmul4`, identity, identity.
## QPE layout
- 4 control qubits `c0..c3`, all Hadamarded.
- 4 work qubits, initialised to `|0001⟩` via `x(w0)`.
- Controlled multiplies in increasing `i`.
- **Inverse QFT with no final swap** on the controls — then measure in increasing bit order. The "no final swap" matters: omit it and you measure in reversed order, which flips the histogram between `msb_first` and `lsb_first`.
```python
# IQFT on (c0, c1, c2, c3), no final swap
h(c3)
cphase_h(c3, c2, -0.5)
h(c2)
cphase_h(c3, c1, -0.25); cphase_h(c2, c1, -0.5)
h(c1)
cphase_h(c3, c0, -0.125); cphase_h(c2, c0, -0.25); cphase_h(c1, c0, -0.5)
h(c0)
```
The driver tries both bit orders (`msb_first` / `lsb_first`) and picks whichever gives the largest overlap with the expected peak set — cheap robustness against convention mismatch.
## Acceptance gates
Three numbers, all checked host-side:
1. `recovered_period == 4` — continued-fractions decode of the histogram peaks. Use `Fraction(k, 2**M).limit_denominator(N)`, take `denominator`, then `lcm` across the top peaks.
2. `peak_share on {0,4,8,12} > 0.90` — the four expected QPE bins should hold >90% of shots at 2048 shots.
3. `work_register_orbit_fraction > 0.99` — work-register measurements must stay inside `{1, 2, 4, 8}` (the orbit of `|1⟩`). This is the integrity check that the permutation circuit is correct; ancilla entanglement can broaden the QPE peaks but the work register is permutation-only and must be exact.
## Where this generalises
- **Other `a` for `N = 15`.** Powers of 2 give shifts; other `a` need a small precomputed permutation table per `a^(2^i) mod 15`. Compile each as a CSWAP/CX permutation by hand.
- **Other `N = 2^k − 1`.** Same shift trick works; pick `a` whose powers are all shifts.
- **General `N`.** No shortcut — falls back to real adder-based `mul_const_mod`. That's a much larger lift; the CSWAP-chain trick is what makes the small-N case fit in ~250 LOC.
------------------------------------------------------------------------------
### reference card: stack-cheatsheet
# Unified stack cheat sheet — primitive → workflow stage
One table per library, built from the crawled corpus (`quantum/docs_crawler/`),
mapping each core primitive onto the stage of *our* pipeline where it belongs.
Status column: **use** = called somewhere in `quantum/` today · **open** =
documented, applicable, not adopted · **n/a** = documented but out of scope here.
Pipeline spine:
```text
model → kernel → compile → verify → execute → recover → analyse → publish
```
## Guppy — the kernel stage
`guppylang>=1.0`. 257 doc pages, 377 snippets, **zero drift** (everything we call
is documented).
| Primitive | What it does | Our stage | Status |
| --- | --- | --- | --- |
| `@guppy` on a module-level function | the kernel itself; source is read via `inspect.getsource`, so it must live in a real `.py` file | kernel | use |
| `angle(x)` | rotation in **halfturns**; divide any explicit π out of a paper formula first | kernel | use |
| `measure(q).read()` / `measure_array` | v1 measurement; returns a `Measurement`, not a bool | kernel | use |
| `output(...)` | v1 result emission (was `result`) | kernel | use |
| `qubit()`, `discard`, `array` | allocation and cleanup; `discard` after `measure` aborts the QIR lane | kernel | use |
| `guppylang.std.qsystem` (`phased_x`, `rz`, `zz_phase`, `zz_max`) | native H-series gate set, halfturns, one-to-one with pytket natives | compile/kernel | use |
| `guppy.load_pytket` | drop a compiled pytket circuit straight into a kernel | compile → kernel bridge | **open** |
| `guppy.nat_var` / `type_var` / `type_alias` | width-generic kernels; would collapse our per-n kernel factories into one definition | kernel | **open** |
| `guppy.struct` | typed record passed through a kernel | kernel | open |
| `guppy.comptime` | compile-time evaluation of host values | kernel | open |
| `guppy.overload` | one name, several signatures | kernel | open |
Next: `references/guppy-language.md`, `references/guppy-v1-migration.md`,
`references/driver-pattern.md`.
## pytket — the compile and verify stages
Richest snippet source in the corpus (750 blocks from 28 user-guide pages), zero
drift on what we call.
| Primitive | What it does | Our stage | Status |
| --- | --- | --- | --- |
| `QuantinuumBackend(device_name, api_handler=QuantinuumAPIOffline())` | offline compile to H-series natives — no credentials, no HQCs | compile | use |
| `get_compiled_circuit(c, optimisation_level=0|1|2)` | the independent check on a rewriter's 2q count | compile | use |
| `add_blank_wires` | re-pad dropped idle wires; without it a TVD reads 1.0 | verify | use |
| `Circuit.get_unitary()` | dense oracle; compare global-phase-free at 1e-9 | verify | use |
| `Ry/Rz/ZZPhase` params | halfturns, same as Guppy `angle()` — never multiply by π | kernel/compile | use |
| `compare_unitaries` / `compare_statevectors` | vendor's own equivalence oracle, a second opinion beside ours | verify | **open** |
| `Backend.get_operator_expectation_value` | expectation of a `QubitPauliOperator` end-to-end | analyse | **open** |
| `pytket.utils.expectation_from_counts / _shots` | expectation from raw shot tables — replaces our hand arithmetic | analyse | **open** |
| `partition.measurement_reduction` | groups commuting Pauli terms into measurement circuits | analyse | **open** |
| `pytket-qir`, `qircheck` | QIR emission and static validation | publish (restricted) | use |
Never score approximation families (AQFT band truncation) with a compiler: a
correctness-preserving pass cannot find them. Next: `references/pytket.md`,
`references/qir-lane.md`.
## Selene — the local execute stage
106 doc pages but only 39 snippets: most pages are autodoc stubs, so the corpus
is thin here and our own `quantum/emulate.py` is the better reference.
| Primitive | What it does | Our stage | Status |
| --- | --- | --- | --- |
| `program.emulator(n_qubits=…).with_shots(…).with_simulator(Quest()).with_error_model(…).with_seed(…).run()` | the v1 execution path; pass the `@guppy` program, never `.compile()` | execute | use |
| `selene_sim.build(...)` | our entry point behind the legacy `run_shots` shim — **undocumented in any snippet** | execute | use (drift) |
| `IdealErrorModel` / `DepolarizingErrorModel` / `SimpleLeakageErrorModel` | the only noise models that ship; no coherent/T1-T2 model exists | execute | use |
| `OptimizationLevel.Classical` | pin it for gate-count benchmarks — v1 optimises on compile | compile | use |
| `shot.entries` | per-shot decode | analyse | use |
| `selene_sim.result_handling.parse_shot` + `hugr.qsystem.result` | the documented structured-result path we hand-roll | analyse | **open** |
| `selene_sim.event_hooks` | runtime instrumentation hooks | execute | open |
| `SelenePlusConfig` (via Nexus) | hosted Selene, for cross-checking a local sweep | execute | **open** |
Next: `references/selene-runtime.md`, `references/sweep-runner.md`.
## qnexus — the cloud execute and recover stages
The drift list here is exactly our cost-guard and resume surface: `HeliosConfig`,
`auth.login` / `login_with_token` / `is_logged_in`, `devices.get_all`,
`jobs.HybridStrategy`, `jobs.cost`, `jobs.get`, `users.get_self`. Undocumented is
not unsupported — it is unversioned. Pin `qnexus` and keep them behind
`tests/fake_qnexus.py`.
| Primitive | What it does | Our stage | Status |
| --- | --- | --- | --- |
| device-authorization login (`/device/device_authorization`) | session mint; read the path out of the installed `qnexus.client.auth`, poll in the background | execute | use |
| `projects.get_or_create` / `add_property(name, property_type=…)` | typed provenance schema; properties propagate onto every resource a job creates | execute | use |
| `qnx.context.using_properties(...)` / `get_active_properties()` | wrap the whole submission (upload + compile + execute + local `save_ref` metadata) in one block and pass no `properties=`; read back what Nexus will stamp so the disk cache can't disagree | execute | use |
| `QuantinuumConfig` / `HeliosConfig(system_name=…, emulator_config=…)` / `AerConfig` / `QulacsConfig` / `SeleneConfig` | config class is a **family** property; the wrong one fails as `code: 14`, which reads like an entitlement error | execute | use |
| `qnx.projects.get_all()` → `qnx.context.set_active_project(...)` | pre-flight: resolve the project by name and set it before any other call, or later calls fail as `NoActiveProjectError`/403 | execute | use |
| `qnx.circuits.upload(circuit, name=…)` → `CircuitRef` | the missing first step: `programs=` takes a Nexus ref, never a local `pytket.Circuit` (`circuits=` is the deprecated keyword) | execute | use |
| `start_compile_job(optimisation_level=…)` → execute the **compiled** ref | Nexus refuses to execute an uncompiled ref (Helios/`*LE` take HUGR directly instead); compile results use `.get_output()`, execute results use `.download_result()` — not interchangeable | execute | use |
| `download_result().get_empirical_distribution()` | int-tuple keys indexed by qubit; calibrate with a one-gate X-probe before trusting bit order | analyse | use |
| `QsysResult.results[i].entries` | Helios/HUGR decode — no `get_empirical_distribution` | analyse | use |
| `qnx.filesystem.save` + our `save_ref`/`verify_ref` | persist the job `Ref` at submit time, then checksum it: `schema`/`ref_sha256`/`ref_bytes` plus a self-excluding `meta_sha256`. `resume` verifies by default and skips (never guesses) a failing record; absent sidecar = `unverifiable`, pre-schema = `legacy` (upgrade with `--restamp`) | recover | use (`quantum/nexus_refs.py`) |
| `devices.supports_shots(config)` | pre-submission capability probe; a distribution-only backend takes the job and returns results a shot decoder can't read | execute | use (`_check_supports_shots`) |
| `max_cost=[…]` on `start_execute_job` | the only real *server-side* spend guard, and it is **per job** — quotas do not cover hardware at all | execute | use |
| client-side spend ledger | batch circuit breaker `max_cost` cannot be: estimate booked at submit time, next cell refused past `NADARASA_MAX_TOTAL_HQC` | execute | use (`quantum/backends.py: SpendLedger`) |
| `circuits.cost(...)` / `jobs.cost(job)` | pre-estimate (itself billable — keep behind `NADARASA_COST_PROBE=1`) and the real billed figure to report | publish | use |
| `jobs.get_all(project=…, properties=…, job_status=[…])` | re-attach a lost sweep; filter `job_type == EXECUTE` and read `annotations.properties` | recover | use |
| `jobs.results(job, allow_incomplete=True)` | harvest a partially finished batch after a reset | recover | **open** |
| `qnx.filesystem.save/load` | persist a job `Ref` to disk — the documented submit-time durability path | recover | **use** (`quantum/nexus_refs.py`) |
| `retry_submission(retry_status=…, remote_retry_strategy=FULL_RESTART)` / `cancel` / `delete` | the recovery cases we previously improvised | recover | **open** |
| `wait_for(..., HybridStrategy/PollingStrategy)` | websocket default drops on long queues | execute | use |
| `devices.supports_shots(config)` | preflight before spending | execute | **open** |
Next: `references/nexus-jobs.md`, `references/nexus-admin.md`,
`references/quantinuum-docs-corpus.md`.
## Systems user guide — the access stage
59 pages, 609 snippets, and a *combined* Guppy + pytket + qnexus corpus
(`guppylang.std.qsystem` appears 12x). Canonical source for access, queueing and
HQC costing — read it before any spend decision, not the library docs.
| Topic | Our stage | Status |
| --- | --- | --- |
| HQC cost model and per-device rates | publish (meter) | use |
| Queue priority 1–10, admin-set, default 5 | execute | use |
| Per-backend widths — an account's emulator ceilings, not the published QPU widths | execute | use |
| Which device family accepts pytket vs HUGR | execute | use |
## The rest of the stack
| Library | Reality | Our stage | Status |
| --- | --- | --- | --- |
| **InQuanto** (126 pages, 1174 snippets) | `express` is a ready-made molecular-system shortcut for anything H2/ethylene-shaped; `ansatzes`, `protocols`, `states`, `extensions.pyscf` would replace our hand-built Hamiltonians | model | **open — own gate, new dependency** |
| **lambeq** (82 pages, 529 snippets) | `lambeq.backend.{grammar,quantum,tensor}` + torch; compositional NLP, disjoint from this programme | — | n/a |
| **Quantum Origin** (52 pages, **5 snippets**) | CLI and concept prose, not a Python API surface | — | n/a |
## The spine, with the primitive to reach for
| Step | Reach for | Non-negotiable |
| --- | --- | --- |
| model | NumPy/SciPy exact statevector (InQuanto `express` when we adopt it) | run the **classical baseline before any quantum code** |
| kernel | `@guppy` in a real file; temp-module driver for parameterisation | `angle()` is halfturns |
| compile | offline `QuantinuumBackend`, or `OptimizationLevel.Classical` to freeze counts | pad with `add_blank_wires` |
| verify | dense unitary oracle at 1e-9, global-phase-free | a semantics-changing optimiser looks like the best optimiser |
| execute | Selene locally; Nexus with `max_cost` + stamped properties | stamp the **sweep coordinates** (`band`, `noise_scale`), not just run metadata |
| recover | `python -m quantum.resume`, `jobs.get_all` by property | write the cache row at **submit** time — a submitted job is already billed |
| analyse | `4·√(0.5/shots)` threshold; Bell control per batch | a failed control fails the **whole batch** |
| publish | committed `src/data/demos/*.json` + provenance hash + `job_meter` | name the trust layer (L1/L2/L3); never "cryptographically verified", never "ran on the QPU" for an emulator |
Refresh the corpus behind this sheet with
`python -m quantum.docs_crawler.{fetch,extract,audit}`.
Quickstart notebooks generated from this corpus: see `references/quickstart-notebooks.md`.
------------------------------------------------------------------------------
### reference card: sweep-runner
# Parameter sweeps: `SweepSpec` + `SweepRunner`
`quantum/sweep.py` factors the temp-file render / import / compile / run loop into a reusable object. Use it for any new experiment that sweeps a parameter grid; hand-rolled loops should only survive for legacy parity checks.
## Shape
```python
from quantum.sweep import SweepSpec, SweepRunner
def template(p: dict) -> str:
return f"""
from quantum.nadarasa_g1_lib import guppy, qubit, h, rz, angle, measure, result, cphase_h
@guppy
def program() -> None:
# ... bake p["s"], p["N"], p["n"] into source ...
"""
spec = SweepSpec(
name="g1_via_sweep",
params=[{"s": s, "N": 16, "n": 4} for s in range(16)],
template=template,
n_qubits=lambda p: p["n"] + 1,
post=lambda shots, p: {"s": p["s"], "histogram": ...},
shots=200,
)
result = SweepRunner().run(spec) # -> SweepResult(rows=[...], elapsed_sec, shots_per_row)
```
`SweepRunner` owns:
- `tempfile` rendering under `selene_sweep_progs/`
- `importlib.util` import with `sys.path.insert(0, ROOT)`
- unique module names via `uuid.uuid4().hex[:6]`
- execution through the Guppy v1 emulator builder (`quantum/emulate.py` shim: `build(mod.program)` + `runner.run_shots(Quest(), ...)`)
- per-shot `(label, value)` → `dict[str, int]` flattening
The driver only writes `template`, `n_qubits`, and `post`.
## Rules
1. **One stable `*_lib.py` per experiment family.** Templates `import` helpers by name; never inline `@guppy` helpers into the rendered source (same rule as `driver-pattern.md`).
2. **`post` is pure host code.** Use `Counter`, NumPy, etc. — anything heavy goes here, not inside the kernel.
3. **`SweepResult.rows` is the JSON.** Dump `dataclasses.asdict(result)` straight to `src/data/demos/.json` and render via the `selene_run` schema (`references/selene-run-schema.md`).
## Parity check pattern
When migrating an existing hand-rolled driver to `SweepRunner`, keep the original around and assert the headline metrics are byte-identical modulo timestamps. `quantum/nadarasa_g1_via_sweep.py` is the canonical example — it reproduces `src/data/demos/nadarasa_g1.json` peak positions and per-slope shot histograms exactly.
## When NOT to use it
- Single-shot one-off experiments — a direct `program.emulator(...)` call is fine.
- Anything where the kernel structure changes per row (different qubit counts AND different gate topology). `SweepRunner` handles per-row `n_qubits` but assumes a single `template` callable; branching topologies belong in separate sweeps.
## Resumable sweeps
Long Selene sweeps blow past sandbox / CI timeouts (typically 10 minutes). A single interrupted run must not discard completed cells — cache each row to disk **before** moving to the next, and drive the sweep from an outer resume loop.
### Per-row cache pattern
```python
CACHE = Path("_cache_2q_noise")
CACHE.mkdir(exist_ok=True)
for conj_i, conj in enumerate(conjectures):
for level in noise_levels:
tag = f"conj_{conj_i}_s{shots}_g{grid}_p{level:.4f}"
out = CACHE / f"{tag}.json"
if out.exists():
continue # already done, skip
row = run_one(conj, level, shots, grid) # the expensive call
out.write_text(json.dumps(row)) # commit before next iter
```
Rules:
1. **Filename must fully identify the row.** Include every sweep axis, plus `shots` and `grid`, so a partial cache from a different parameter set is never silently reused.
2. **Write atomically after the shots complete**, not before — a half-written row is worse than a missing one.
3. **Skip on `exists()`**, don't diff timestamps. Re-invocation should be a pure no-op for finished cells.
4. **Finalize step** at the end of the driver: once `len(list(CACHE.iterdir())) == expected_total`, merge cache files into a single `src/data/demos/.json` and delete `CACHE`. Never ship the cache dir itself to the frontend.
### Outer resume loop
Run the driver under a shorter-than-sandbox timeout in a bash `while` loop until the cache is full:
```bash
TOTAL=50
while [ "$(ls _cache_2q_noise 2>/dev/null | wc -l)" -lt "$TOTAL" ]; do
PYTHONPATH=.pydeps PYTHONUNBUFFERED=1 \
timeout 580 python3 -m quantum.pqp_frontier.noise_2q || true
done
```
`|| true` keeps the loop alive across SIGTERM from `timeout`. The driver's `if out.exists(): continue` guard makes each restart cheap.
### When to skip
One-off experiments with < ~120 s wall time don't need a cache — a single `timeout 580 python -m ...` invocation is enough. The cache pattern is for anything that could plausibly need more than one sandbox turn to finish.
## Per-job meters
The per-row cache answers "which cells finished". It does not answer "what did this cost, on what device, with which seed" — and on a paid lane that second question is the one you cannot reconstruct afterwards. Emit **one uniform meter record per job**, from every lane, and collect them into the dump's `execution` block.
`quantum/backends.py` builds them:
```python
from quantum.backends import job_meter, meter_totals, meters_table
job_meter(
mode="live", # emulator | dry | live | refetch
device="H2-1LE",
n_qubits=6, shots=512, seed=17,
gate="G16", driver="qpde.sweep",
estimated_hqc=5.0384, # None on lanes that never estimate
hqc_cost=5.11, # None until actually billed
max_cost_hqc=25.0, user_group="…", submitted_at="…",
)
```
Rules that make the records worth having:
1. **`None` means "this lane genuinely has no such value", never "unknown".** An emulator row has no `estimated_hqc`; a re-fetched row has no estimate either (there is no program in hand) but it *does* have a real `hqc_cost` from `qnx.jobs.cost(job)`. Conflating the two is how a resumed sweep starts reporting itself as free.
2. **Derive `cost_delta` / `cost_ratio` at write time**, not at read time, so a shipped JSON answers "was the estimate honest?" without arithmetic in the frontend. Both stay `None` unless the job was billed.
3. **Collect globally, not per driver.** `quantum.emulate` accumulates whatever the active backend recorded, and `SweepRunner.execution()` defaults `meters=` to that collection — so a driver gets the figures in its dump without plumbing anything. `meters_table(...)` prints the same records as a console table for a dry-run vs live-run diff.
## Resuming a paid hardware sweep
A sweep loses its process more often than you'd like (rollback, dropped websocket, sandbox timeout). Nexus has still finished those jobs and still billed them, so recovery must be a re-attach, never a resubmission. Fetch the jobs into the resume cache first:
```bash
python -m quantum.resume --backend hardware --job-id exec-1 exec-2
python -m quantum.resume --backend hardware --from-dump src/data/demos/x.json
python -m quantum.resume --backend hardware --gate G16 --dry-run
```
Then hand them to the runner:
```python
SweepRunner(resume_from="G16") # gate label → load_sweep(gate=…)
SweepRunner(resume_from=["exec-1", "exec-2"]) # explicit ids, oldest first
```
Each row consumes the next cached job's shots instead of executing; once the queue empties, rows fall through to normal execution, so a half-finished sweep completes without re-buying the finished part. `execution()` prepends the resumed jobs' meters and their ids, so the dump still reports the original run's true cost.
Four details make a resume work months later:
1. **Recover the decode width from the job**, via the `n_qubits` property stamped at submission — not from a constant in the driver, which will have moved on.
2. **Report a non-`COMPLETED` job and skip it.** One `DEPLETED` id must not abandon the other nine; map the status to a plain reason (budget / retryable / still queued).
3. **Keep the billed HQC in the meter** even though the re-fetch has no estimate.
4. **Cache shots + properties + meter on disk by job id**, so the second resume is a file read and the row loop consumes it exactly like a locally executed row.
An unknown id must fail loudly with the command that would fix it, rather than silently executing the row and buying the shots a second time.
## Cloud rows have three states, not two
A local row is either cached or absent. A cloud row is `submitted` (job id, no shots yet),
`completed` (shots present), or `error` (guard rejection, dry-run stub, transient failure) —
and the three demand different handling on resume:
| State | Resume action |
| --- | --- |
| `completed` | skip |
| `submitted` | `fetch_result(job_id)` — already billed, never resubmit |
| `error` / dry-run stub | **purge before resuming**, then treat as absent |
The purge step matters: a dry-run stub or a hardware-guard rejection cached as a row makes the
sweep look finished at rows that were never executed. Delete them explicitly at the start of a
resume rather than letting the "row exists" check swallow them.
Two failure modes that only show up on a real paid sweep:
- **A driver that reads an env flag but not `--execute` submits nothing while reporting
progress.** Make the live/dry switch a single explicit argument the driver logs on startup.
- **Background workers survive a session loss.** They keep looping against a dead token,
re-submitting or spinning. Kill them *first* in any recovery — see
`references/lovable-orchestration.md` (§Sandbox-reset recovery).
------------------------------------------------------------------------------
### reference card: tomographic-equivalence
# Tomographic equivalence + conjecture synthesis (PQP Frontier pattern)
The harness that powered Tracks A and B of the v0.3.7–v0.3.8 PQP Frontier
work, and that proved 11 / 105 conjectural gate-identities from shot
statistics alone. Reach for this whenever you need to prove two gate
sequences `A ≡ B` (mod global phase) **without trusting a matrix oracle**
— i.e. when the rewriter says they should be equal and you want Selene
to confirm.
Reference implementations:
- `quantum/pqp_frontier/tomography.py` — 1q 18-cell harness, `prove_equal_1q(...)`.
- `quantum/pqp_frontier/conjectures.py` — driver that runs the harness on every flagged conjecture.
- `quantum/pqp_frontier/dump_conjectures.ts` / `dump_conjectures_2q.ts` — the TS-side matrix oracle + rewriter promotion report.
- `src/lib/zx/conjecture-synth.ts` (1q) / `conjecture-synth-2q.ts` (2q) — sequence enumeration + canonical matrix key.
## When to use it
- Verifying a new rewriter rule produces physically equivalent diagrams.
- Promoting a "conjectured equality" (matrix oracle says equal, structural rewriter cannot prove it) from candidate to physical fact.
- Sanity-checking an angle convention before a long sweep — a tomography PASS at shots=256 takes seconds and catches halfturn-vs-radian bugs immediately.
- Falsifying a TS-side oracle: a FAIL with shot noise well below threshold means the matrix code, not Selene, has a bug.
Do NOT use it for stochastic kernels (G1 cosets, Birthday) — the harness assumes a deterministic unitary; for stochastic kernels compare full distributions, not per-cell `P(1)`.
## The 1q grid (18 cells)
Six tomographically complete inputs × three measurement bases:
| Inputs | Prep gates from `\|0⟩` |
| --- | --- |
| `\|0⟩` | (none) |
| `\|1⟩` | `xgate(q)` |
| `\|+⟩` | `h(q)` |
| `\|−⟩` | `xgate(q); h(q)` |
| `\|+i⟩` | `h(q); rz(q, angle(0.5))` |
| `\|−i⟩` | `h(q); rz(q, angle(-0.5))` |
| Basis | Rotation before `measure` |
| --- | --- |
| Z | (none) |
| X | `h(q)` |
| Y | `rz(q, angle(-0.5)); h(q)` |
For each of the 18 (input, basis) cells, compile two kernels (A-side and B-side), run `shots` shots each, compute `p_A(1)`, `p_B(1)`. The pair PASSes when **every** cell passes the threshold below.
## The 2q grid (324 cells)
Product structure: 6 × 6 = 36 product inputs, 3 × 3 = 9 product measurement bases. Each cell measures both qubits and reports `P(11)` (or any fixed bit-pattern) per side.
Wall-time budget on Selene: ≈ 1 minute per pair at 256 shots/cell. Cap a single run at ~10 pairs to stay under a 15-minute sandbox window. For larger sweeps, drive `prove_equal_2q` in a background job and persist intermediate JSON per pair so the run can resume.
## The threshold — read carefully
```python
import math
threshold = 4.0 * math.sqrt(0.5 / shots)
# pair PASSes when worst_tv := max over cells of |p_A(1) - p_B(1)| < threshold
```
This is a **4σ bound on the standard error of a binomial difference at p = 1/2**, Bonferroni-ish for the 18 / 324 cells. Concrete numbers:
- `shots = 256` → threshold ≈ 0.177
- `shots = 384` → threshold ≈ 0.144
- `shots = 1024` → threshold ≈ 0.088
**Do NOT use the textbook `3σ · √(p(1−p)/n)` form.** It produces false FAILs for any cell where the true `p` sits near 0 or 1 (the variance estimate collapses but the binomial CLT does not), and is the source of the bogus v0.3.4 failure cascade. The constant `√(0.5/shots)` is the worst-case standard error and is always conservative.
## Conjecture synthesis pipeline
The full Tracks A + B loop:
1. **Matrix oracle** — enumerate every gate sequence over a chosen generator set up to `maxLen`. Compute each sequence's unitary in TypeScript using simple complex-matrix multiplication. Canonicalise by rotating the first non-zero entry to `+ℝ` and serialising every entry to N decimals (5 for 2q, 6 for 1q). Group sequences by canonical key — that's the equivalence-class partition.
2. **Structural rewriter** — apply the existing `bastard-rewriter` (1q) or `normalise2q` (2q) to each representative; record a residue string.
3. **Open conjectures** — within each equivalence class, if multiple residues survive, the class is an open conjecture: the matrix oracle says equal, the rewriter cannot prove it.
4. **Matrix-canonical rewriter rule** — rule (N) for 1q (Z(γ)·X(β)·Z(α) Euler decomposition) or rule (M) for 2q (4×4 canonical key as residue). Re-run the residue computation with the matrix rule enabled.
5. **Promotion report** — dual-pass JSON: `{promoted, reduced, unchanged}` counts plus per-conjecture status. Emitted by `dump_conjectures*.ts` to `src/data/demos/pqp_frontier_promotion*.json`.
6. **Selene PASS-verify (optional but recommended for new rules)** — pick the shortest contrasting pair per surviving conjecture, run `prove_equal_1q` / `prove_equal_2q` at 384–512 shots. A FAIL means the matrix oracle has a bug; a PASS confirms the rewriter has a genuine completeness gap.
UI surface: `/nadarasa/proofs/conjectures` (1q) and `/nadarasa/proofs/conjectures-2q` (2q) render the promotion headline, per-card PROMOTED badges, and the residue diff between structural and matrix-canonical passes.
==============================================================================
## 3. Design notes
------------------------------------------------------------------------------
### gate-0-4-5-floquet-design.md
# Gate 0.4.5 — Floquet native port (design)
Goal: reproduce, on Quantinuum-style all-to-all hardware via Guppy/Selene, the
qualitative Floquet result that Leviatan et al. 2026 obtained on IBM heavy-hex
with QESEM (PEC+ZNE) — a period-doubled (subharmonic) magnetization response
that survives noise — and use it as the cross-platform corroboration layer
called for in G19.
Scope guardrail: this is a *small-lattice port*, not a 100-qubit reproduction.
The claim we want to support is "independent compiler + independent noise model
sees the same subharmonic structure", not "we matched their system size".
## 1. Model
Mixed-field Ising (MFIM) Floquet drive on `n` qubits, `n ∈ {6, 8, 10, 12}`.
One cycle `U_F = U_x · U_zz · U_z`, applied `n_cycles` times:
```text
U_z = Π_j exp(-i (h_z/2) Z_j) longitudinal field
U_zz = Π_ exp(-i (J/2) Z_j Z_k) Ising coupling on the lattice edges
U_x = Π_j exp(-i (θ/2) X_j) transverse kick, θ = π(1 - ε)
```
- `θ = π(1-ε)` with `ε ∈ [0, 0.12]` is the imperfect π-pulse. `ε = 0` is the
exact period-2 point; finite `ε` plus `h_z ≠ 0` is where the discrete
time-crystal / prethermal plateau lives.
- `J = 0.6`, `h_z = 0.15` (rad per cycle) as the working point; both are swept.
- Initial state: `|↑↑…↑⟩` (all-zeros), i.e. the polarized product state.
- Observable: staggered-free global magnetization
`M(t) = (1/n) Σ_j ⟨Z_j⟩` sampled once per cycle, plus its subharmonic weight
`|FFT[M](f = 1/2)|`.
### Lattice
Two topologies, same kernel, different edge list:
| tag | edges | why |
| --- | --- | --- |
| `chain` | 1D open chain `j—j+1` | analytic/ED sanity, cheapest |
| `heavyhex6` | one heavy-hex plaquette ring (degree ≤ 3) | matches the IBM connectivity class of the paper |
On Quantinuum all-to-all hardware the edge list costs nothing extra in SWAPs —
that asymmetry versus heavy-hex is itself a reportable observation.
## 2. Classical baseline
`quantum/floquet/model.py` (this gate): dense NumPy exact diagonalization of
`U_F` for `n ≤ 12` (4096×4096 worst case, fine). Produces:
- `M_exact[t]` for `t = 0..n_cycles`
- subharmonic peak height and the ε at which it collapses
- the reference trajectory that Selene shots must match within shot noise
No Guppy, no Selene in this module — same discipline as `qpde/model.py`.
## 3. Guppy mapping
`quantum/floquet/kernel.py` (Gate 0.4.5b):
```python
@guppy
def floquet_cycle(qs: array[qubit, N], ...) -> None:
for j in range(N):
rz(qs[j], angle(HZ_HALFTURNS))
for (a, b) in EDGES: # unrolled at generation time
rzz(qs[a], qs[b], angle(J_HALFTURNS))
for j in range(N):
rx(qs[j], angle(THETA_HALFTURNS))
```
Angle hygiene (Gotcha #2/#7): every rotation above is written in **halfturns**.
`h_z` etc. are specified in radians in `model.py`, so the kernel generator
passes `angle(x_rad / math.pi)`. The `θ = π(1-ε)` kick becomes `angle(1 - ε)`
directly — no division needed, which is exactly the trap to avoid.
Structural constraints:
- `N`, `EDGES`, and the per-cycle angles must be *literals* in the emitted
source, so the kernel is generated into a temp `.py` file and imported via
`importlib.util` (driver pattern, Gotcha #1).
- Measurement: measure all qubits at the end of cycle `t` and rebuild `⟨Z_j⟩`
host-side. One circuit per `(n, topology, ε, t)` — no mid-circuit
measurement, because a mid-circuit Z read would collapse the very coherence
the subharmonic depends on.
## 4. Selene run plan
Sweep axes (resumable per-row JSON cache under `_cache_floquet/`, Gotcha #5):
| axis | values | count |
| --- | --- | --- |
| topology | `chain`, `heavyhex6` | 2 |
| n | 6, 8 (12 only if budget allows) | 2 |
| ε | 0.00, 0.04, 0.08, 0.12 | 4 |
| cycle t | 1..12 | 12 |
| noise | `ideal`, `depol_h2`, `depol_h2_5x` | 3 |
= 576 cells at 512 shots. Row tag `f"{topology}_n{n}_eps{eps}_t{t}_{noise}"`.
Each cell writes its JSON before the next starts; the driver is wrapped in the
standard `while [ cached < N ]; do timeout 580 python -m ...; done` loop, one
worker only (the file-lock race from Gate 0.4.4 is a known failure mode).
Noise: `DepolarizingErrorModel` at the H2-2 targets (`p_2q = 1.29e-3`,
`p_r_01 = 0.9e-3`, `p_r_10 = 1.8e-3`) and a 5× ladder rung. ZNE by Richardson
quadratic on the subharmonic peak height, reusing `quantum/qpde/zne.py`.
## 5. Verdicts
- **V1 (structure)** — subharmonic peak at f = 1/2 present for ε ≤ 0.08 and
absent/collapsed at large ε, in both ED and ideal Selene.
- **V2 (agreement)** — `|M_selene(t) − M_exact(t)| ≤ 4·sqrt(0.5/shots)` for
every ideal cell (project-standard threshold, Gotcha #3).
- **V3 (noise resilience)** — at `depol_h2` the subharmonic peak retains ≥ 70%
of its ideal height; ZNE-corrected peak within 10% of ideal.
- **V4 (corroboration)** — V1 reproduced on an all-to-all compiler with an
independent noise model, stated explicitly as a *qualitative* cross-platform
corroboration of Leviatan et al., not a numerical match.
## 6. Gate breakdown
| gate | artifact |
| --- | --- |
| 0.4.5a | `quantum/floquet/model.py` + ED baseline verdict (this turn) |
| 0.4.5b | `quantum/floquet/kernel.py` + generator, 1 smoke cell |
| 0.4.5c | `quantum/floquet/sweep.py`, resumable cache, ideal levels |
| 0.4.5d | noise levels + ZNE |
| 0.4.5e | `src/data/demos/floquet_native.json` + `/nadarasa/g20` route |
One committable artifact per turn (Gotcha #10).
------------------------------------------------------------------------------
### gate-0-4-6-adapt-gqe-design.md
# Gate 0.4.6 — ADAPT-GQE composition with the rewriter pipeline
## Goal
Demonstrate that a small generated/variational chemistry ansatz can be handed to the existing rule-(N/M/P) canonicalisation pipeline before execution on Selene. The canonicalised form should be matrix-equivalent to the original, and the shot statistics should match within the usual binomial envelope.
This is the deferred v0.4.3 Option C and the concrete follow-up to the G15 ADAPT-GQE reading note.
## Why this gate matters
ADAPT-GQE generates circuits with transformers and refines them with RL. The rewriter pipeline is syntax-driven and exact. Composition is not a replacement — it is a verifier: before running an AI-generated circuit on expensive hardware, canonicalise it and check that the canonical form has the same unitary. If the canonical form is smaller, it may also be cheaper to execute.
## Chosen surrogate circuit
Use the **H2 / STO-3G single-excitation UCCSD operator** restricted to the two-qubit active space. This is the smallest non-trivial chemistry ansatz and it maps cleanly to the existing 2-qubit rewriter alphabet.
Operator:
```text
U(θ) = exp(-i θ (X₀ Y₁ - Y₀ X₁) / 2)
```
applied to the Hartree-Fock state |01⟩ (qubit 0 = virtual, qubit 1 = occupied in the active-space convention used by QPDE G16).
## Decomposition into the 2-qubit alphabet
**Correction (Gate 0.4.6b).** The earlier draft claimed
`U(theta) = CNOT · Rz(2 theta) · CNOT`. That is wrong: the conjugated-Rz form
realises `exp(-i theta Z0 Z1)`, not the Givens rotation. Verified numerically
in `quantum/adapt/adapt_h2_uccsd.py`, the correct forms are:
```text
original : CNOT(q1->q0) · CRy(2θ)(q0->q1) · CNOT(q1->q0)
alternate : CNOT(q1->q0) · Ry(θ)q1 · CNOT(q0->q1) · Ry(-θ)q1 · CNOT(q0->q1) · CNOT(q1->q0)
```
Both reproduce `exp(-i θ (X₀Y₁ - Y₀X₁)/2)` to 1.1e-16 at θ = π/4, and match
each other to the same tolerance (`src/data/demos/adapt_gqe_matrix.json`).
Because `Ry` is outside the `{H0, H1, CZ, S0, S1}` Clifford alphabet, the
rule-(M) residue applies only to the Clifford frame (the CNOT sandwich); the
rotation core is verified by the matrix oracle instead.
## Canonicalisation entry points
- TypeScript: `src/lib/zx/conjecture-synth-2q.ts` exposes `normalise2q(seq)` and `normalise2qWithMatrix(seq)`.
- Python: there is no direct Python port of the 2-qubit oracle yet. The experiment can be driven from Python but the canonicalisation comparison can be done in TypeScript, or the Python driver can compute the 4×4 matrix directly and compare.
## Proposed driver architecture
`quantum/adapt/adapt_h2_uccsd.py`:
1. Build two source strings:
- `original`: the obvious CNOT-S-CNOT form.
- `alternate`: a less obvious equivalent form.
2. Render each to a Guppy function over the active-space qubits (2 data qubits + 1 optional ancilla for the controlled version used by QPDE).
3. Compile and run 512 shots on Quest for each form.
4. Compute the histogram of measured states and the inferred |⟨ψ(θ)|ψ_target⟩|² fidelity.
5. Also compute the 4×4 unitary matrix for each form in Python and confirm the matrices match up to global phase.
`src/lib/zx/conjecture-synth-2q.ts` (or a new `src/lib/zx/adapt-compose.ts`):
1. Convert both gate sequences to `Gate2[]` lists.
2. Run `normalise2qWithMatrix` on each.
3. Assert `matrixKey` matches and that the structural residue is the same.
## Output JSON schema
`src/data/demos/adapt_gqe_composed.json`:
```json
{
"experiment": "adapt_gqe_h2_uccsd",
"title": "ADAPT-GQE composition: H2 UCCSD single excitation canonicalised before Selene execution",
"operator": "exp(-i θ (X0 Y1 - Y0 X1) / 2)",
"theta": "pi/4",
"forms": [
{
"name": "original",
"gate_sequence": ["H1", "CZ", "H1", "S1", "H1", "CZ", "H1"],
"n_gates": 7,
"structural_residue": ["..."],
"matrix_key": "..."
},
{
"name": "alternate",
"gate_sequence": ["..."],
"n_gates": 9,
"structural_residue": ["..."],
"matrix_key": "..."
}
],
"canonical_form": "...",
"matrix_keys_match": true,
"selene": {
"shots": 512,
"original_fidelity": 0.99,
"canonical_fidelity": 0.99,
"fidelity_agreement": true
},
"verdict": "adapt_gqe_composition_verified"
}
```
## UI route
`src/routes/nadarasa.g21.tsx` or an upgrade to `/nadarasa/g15`:
- Header: "G21 — ADAPT-GQE composition: canonicalising a generated H2 ansatz".
- Explain the two forms and why they are matrix-equivalent.
- Show the canonical residue from the 2-qubit oracle.
- Show the Selene shot fidelity comparison.
- Verdict chip.
- Links to G15, G16, and the 2-qubit conjectures page.
## Risk and mitigation
- **Risk**: The 2-qubit oracle does not yet have a Python port, so the canonicalisation comparison is in TypeScript while the Selene execution is in Python. This split is acceptable for a small demonstration but should be documented.
- **Risk**: Guppy compilation of the H2 ansatz may fail if the decomposition uses gates not supported by the pinned guppylang version. The proposed alphabet (H, CZ, S) is supported.
- **Risk**: Selene shots of a state-preparation circuit require a reference state for fidelity comparison. We can compute the expected distribution from the 4×4 matrix and compare histograms.
## Suggested split into atomic gates
1. **Gate 0.4.6a (this document)**: design the composition experiment.
2. **Gate 0.4.6b**: implement the Python driver, compute the 4×4 matrices, and verify equivalence numerically.
3. **Gate 0.4.6c**: compile both forms to Guppy and run a 512-shot Selene smoke test.
4. **Gate 0.4.6d**: build the JSON dump and UI route.
5. **Gate 0.4.6e**: update the Quantinuum skill with the composition pattern.
==============================================================================
## 4. Results — digests of every gate result file
Each block names the JSON dump that backs one or more pages. Row counts and
column names are given so a model can reason about shape without the raw table;
the full JSON ships with the site under /src/data/demos in the repository.
### adapt_gqe_composed.json
gate: 0.4.6d
title: ADAPT-GQE composition: an H2 UCCSD single excitation canonicalised before Selene execution
verdict: adapt_gqe_composition_verified
verdict_criteria: [3 rows]
columns: name, statement, value, threshold, passed
### adapt_gqe_matrix.json
gate: 0.4.6b
verdict: matrix_equivalence_verified
forms: [2 rows]
columns: name, gates, n_gates, n_two_qubit, max_deviation_from_exact
verdict_criteria: [2 rows]
columns: name, statement, value, threshold, passed
### adapt_gqe_selene.json
gate: 0.4.6c
backend: Quest (ideal)
shots: 512
verdict: selene_agreement_verified
forms: [2 rows]
columns: name, gates, n_gates, probs, max_deviation_from_exact, verdict
verdict_criteria: [2 rows]
columns: name, statement, value, threshold, passed
### aqft_crossover_law.json
schema: selene_run/v1
gate: G26
title: AQFT crossover law k*(n, p)
verdict: aqft_crossover_law_verified
summary.cells: 24
summary.cells_where_truncation_saves_gates_for_free: 24
summary.cells_where_truncation_strictly_wins: 2
summary.max_gate_saving: 72
cells: [24 rows]
columns: n, noise_mult, noise, shots_per_target, targets, envelope, rows, p_full_qft, k_star, k_star_margin, gate_saving_at_k_star, best_K, best_p, truncation_wins, elapsed_sec
surface: [24 rows]
columns: n, noise, noise_mult, k_star, k_max, gate_saving, p_full_qft, best_K, best_p, truncation_wins
not_claimed: [3 rows]
verdict_criteria: [6 rows]
columns: name, statement, value, threshold, passed
### aqft_crossover_n16_nexus.json
gate: G27
shots: 512
verdict: PARTIAL_review
scales: [2 rows]
rows: [14 rows]
columns: device, n, K, target, shots, noise_scale, is_full_qft, two_qubit_gates, p_ideal, envelope, program_form, mode, p_correct, deviation, meters, job_ids
verdict_criteria: [6 rows]
columns: name, statement, value, threshold, passed
### aqft_crossover_scaled.json
schema: selene_run/v1
gate: G27
title: AQFT crossover law at scale: k*(n, p) for n = 4..16
summary.cells: 36
summary.strict_wins: 6
summary.cells_where_truncation_saves_gates_for_free: 36
summary.max_gate_saving: 156
cells: [36 rows]
columns: n, noise_mult, noise, shots_per_target, targets, envelope, rows, p_full_qft, k_star, k_star_margin, gate_saving_at_k_star, best_K, best_p, truncation_wins
verdict: PASS
frontier: [6 rows]
columns: n, cells, strict_wins, win_fraction, win_noise_levels, max_gate_saving
surface: [36 rows]
columns: n, noise, noise_mult, k_star, k_max, gate_saving, p_full_qft, best_K, best_p, truncation_wins
not_claimed: [4 rows]
verdict_criteria: [4 rows]
columns: name, statement, value, threshold, passed
### aqft_nexus_probe.json
cells: [3 rows]
columns: device, n, K, target, shots, noise_scale, p_ideal, envelope, mode, p_correct, meters, job_ids
verdict: tunable
### arun_kernel.json
shots: 4000
pairs: [4 rows]
columns: f, g, estimated_fidelity, classical_fidelity, stable_rank
### arun_polynomial.json
phi_sequence: [11 rows]
x: [11 rows]
target_filter: [11 rows]
Aq_of_x: [11 rows]
### arun_scheduler.json
programs: [7 rows]
columns: name, baseline_T, calculus_T, admitted
### benchmark_suite.json
schema: benchmark_suite/v1
gate: 0.4.9
title: Cross-experiment Selene benchmark suite
generated_by: quantum/benchmark/runner.py + aggregate.py
generated_at: 2026-08-13T07:20:03+00:00
backend.emulator: Selene (bundled in guppylang v1)
backend.simulator: Quest state-vector
backend.python: 3.13.12
experiments_total: 6
experiments_scored: 6
verdict: benchmark_suite_complete
missing: [0 rows]
caveats: [4 rows]
rows: [6 rows]
columns: id, gate, name, driver, route, qubits, cells, shots_per_cell, fidelity_metric, fidelity_ideal, fidelity_h2_1x, degradation_pct_at_h2_1x, fidelity_mitigated, noise_kind, noise_source, noise_probe, noise_levels, noise_elapsed_sec, raw, sweep, sources
verdict_criteria: [3 rows]
columns: name, statement, value, threshold, passed
### floquet_native_zne.json
title: Floquet subharmonic peak under H2-class noise, with Richardson ZNE
backend: selene-sim Quest
verdict: floquet_zne_verified
cycles: [8 rows]
rows: [8 rows]
columns: topology, eps, ideal_peak, exact_peak, worst_noisy_peak, unmitigated_error, ladder, zne
verdict_criteria: [3 rows]
columns: name, statement, value, threshold, passed
### gate26_cross_platform.json
cells: 25
verdict: PASS
devices: [5 rows]
bell_failed_batches: [0 rows]
rows: [25 rows]
columns: device, device_family, n, K, target, shots, is_full_qft, two_qubit_gates, p_ideal, envelope, program_form, mode, p_correct, deviation, verdict, meters, job_ids, bell_anticorrelated, bell_verdict
not_claimed: [4 rows]
verdict_criteria: [6 rows]
columns: name, statement, value, threshold, passed
### nadarasa.json
resource_table: [12 rows]
columns: n_bits, modal_prime, kuperberg_queries, nadarasa_queries_conjectured, speedup_factor
### nadarasa_g1.json
verdict: modal_projector_works_when_p_divides_N
rows: [12 rows]
columns: N, n, p, branch, slopes, per_slope, avg_residue_mass, concentration_on_zero, uniform_baseline, shots_per_slope, elapsed_sec
table: [6 rows]
columns: N, p, p_divides_N, srp_concentration, srp_predicted, srp_delta_vs_predicted, violating_concentration, violating_predicted, violating_delta_vs_predicted, uniform_baseline, srp_minus_violating, violating_excess_over_uniform
verdict_criteria: [3 rows]
columns: name, statement, value, threshold, passed
### nadarasa_g10.json
verdict: qsp_verified
phases_radians: [4 rows]
x_grid: [8 rows]
rows: [8 rows]
columns: x, shots, measured_p0, reference_p0, delta, elapsed_sec
verdict_criteria: [1 rows]
columns: name, statement, value, threshold, passed
### nadarasa_g10_phasefinder.json
verdict: qsp_phase_finder_verified
secret_phases_radians: [5 rows]
recovered_phases_radians: [5 rows]
x_grid: [16 rows]
rows: [16 rows]
columns: x, shots, measured_p0, target_p0, recovered_reference_p0, delta_measured_vs_target, delta_recovered_vs_target, elapsed_sec
verdict_criteria: [2 rows]
columns: name, statement, value, threshold, passed
### nadarasa_g11.json
shots: 2048
verdict: period_decode_verified
top_peaks: [4 rows]
period_candidates: [3 rows]
expected_peaks: [4 rows]
verdict_criteria: [2 rows]
columns: name, statement, value, threshold, passed
### nadarasa_g11_real.json
shots: 2048
verdict: real_modexp_period_verified
top_peaks: [4 rows]
period_candidates: [3 rows]
expected_peaks: [4 rows]
verdict_criteria: [3 rows]
columns: name, statement, value, threshold, passed
### nadarasa_g12.json
verdict: consistent_but_residual_drift
Ns: [2 rows]
ps: [4 rows]
rows: [16 rows]
columns: N, n, p, branch, slopes, per_slope, avg_residue_mass, concentration_on_zero, predicted_concentration, delta_vs_predicted, uniform_baseline, shots_per_slope, elapsed_sec
table: [8 rows]
columns: N, p, p_divides_N, srp_concentration, srp_predicted, srp_delta_vs_predicted, violating_concentration, violating_predicted, violating_delta_vs_predicted, uniform_baseline, srp_minus_violating, predicted_srp_minus_violating, violating_excess_over_uniform
verdict_criteria: [2 rows]
columns: name, statement, value, threshold, passed
### nadarasa_g1_via_sweep.json
verdict: modal_projector_works_when_p_divides_N
rows: [12 rows]
columns: N, n, p, branch, slopes, per_slope, avg_residue_mass, concentration_on_zero, uniform_baseline, shots_per_slope, elapsed_sec
table: [6 rows]
columns: N, p, p_divides_N, srp_concentration, srp_predicted, srp_delta_vs_predicted, violating_concentration, violating_predicted, violating_delta_vs_predicted, uniform_baseline, srp_minus_violating, violating_excess_over_uniform
verdict_criteria: [3 rows]
columns: name, statement, value, threshold, passed
### nadarasa_g2.json
verdict: kernel_cannot_test_g2
configs: [9 rows]
columns: N, k
rows: [18 rows]
columns: N, n, k, branch, slopes, per_slope_collision, mean_collision, uniform_baseline, shots_per_slope, elapsed_sec
table: [9 rows]
columns: N, k, uniform_baseline, srp_mean_collision, srp_shots_per_slope, violating_mean_collision, violating_shots_per_slope, predicted_collision, srp_delta_vs_predicted, violating_delta_vs_predicted
verdict_criteria: [2 rows]
columns: name, statement, value, threshold, passed
### nadarasa_g2_real.json
verdict: coherent_combiner_produces_srp_signal_when_p_divides_N
rows: [12 rows]
columns: N, n, p, branch, pairs, per_pair, concentration_on_zero, predicted_concentration, delta_vs_predicted, uniform_baseline, shots_per_pair, elapsed_sec
table: [6 rows]
columns: N, p, p_divides_N, srp_concentration, srp_predicted, srp_delta_vs_predicted, violating_concentration, violating_predicted, violating_delta_vs_predicted, uniform_baseline, srp_minus_violating, predicted_srp_minus_violating
verdict_criteria: [2 rows]
columns: name, statement, value, threshold, passed
### nadarasa_g3.json
verdict: tension_with_srp_sqrt_p_improvement
ps: [3 rows]
rows: [3 rows]
columns: N, p, mean_queries, per_slope, shots_per_slope, trials_per_slope, elapsed_sec
table: [3 rows]
columns: p, mean_queries, observed_ratio_vs_p2, naive_birthday_ratio, srp_improvement_ratio
verdict_criteria: [1 rows]
columns: name, statement, value, threshold, passed
### nadarasa_g3_cost.json
verdict: neither_curve_is_flat_quantitative_claim_only
Ns: [4 rows]
ps: [4 rows]
rows: [16 rows]
columns: N, p, mean_queries, mean_observed_support, predicted_sqrt_Nover_p, predicted_sqrt_N, ratio_vs_sqrt_Nover_p, ratio_vs_sqrt_N, per_slope, shots_per_slope, trials_per_slope, elapsed_sec
verdict_criteria: [2 rows]
columns: name, statement, value, threshold, passed
### nadarasa_g4.json
verdict: feed_forward_verified
rows: [5 rows]
columns: theta, shots, predicted_split, p_y2_given_b0, p_y2_given_b1, split_distance, n_b0, n_b1, delta_vs_predicted, elapsed_sec
verdict_criteria: [1 rows]
columns: name, statement, value, threshold, passed
### nadarasa_g5.json
verdict: recycling_verified
ks: [3 rows]
rows: [3 rows]
columns: N, n, k, n_qubits_passed_to_selene, slopes, per_slope_collision, mean_collision, g2_reference_mean_collision, delta_vs_g2, shots_per_slope, elapsed_sec
verdict_criteria: [2 rows]
columns: name, statement, value, threshold, passed
### nadarasa_g6.json
verdict: encoded_subspace_leak
slopes: [6 rows]
rows: [6 rows]
columns: s, shots, disagreements, disagreement_rate, a0_marginal_1, majority_marginal_1, encoded_subspace_share, elapsed_sec
verdict_criteria: [1 rows]
columns: name, statement, value, threshold, passed
### nadarasa_g6b.json
verdict: encoded_ancilla_verified
slopes: [6 rows]
rows: [6 rows]
columns: s, shots, disagreements, disagreement_rate, a0_marginal_1, majority_marginal_1, encoded_subspace_share, elapsed_sec
verdict_criteria: [2 rows]
columns: name, statement, value, threshold, passed
### nadarasa_g7.json
verdict: host_fusion_wins
slopes: [6 rows]
verdict_criteria: [2 rows]
columns: name, statement, value, threshold, passed
### nadarasa_g8.json
verdict: amplitude_estimation_verified
rows: [4 rows]
columns: a_predicted, shots, msb_histogram, lsb_histogram, msb_peak, lsb_peak, msb_peak_mass, lsb_peak_mass, elapsed_sec, best_peak, best_peak_mass, bin_error
verdict_criteria: [1 rows]
columns: name, statement, value, threshold, passed
### nadarasa_g9.json
verdict: lcu_block_encoding_verified
rows: [8 rows]
columns: theta, psi, shots, p_prep_zero, p_data_one_given_prep_zero, n_total, n_post_selected, predicted_p_prep_zero, predicted_p_data_one_given_prep_zero, delta_p_prep, delta_p_d1_post, elapsed_sec
verdict_criteria: [2 rows]
columns: name, statement, value, threshold, passed
### nadarasa_stream.json
shots: [1000 rows]
columns: i, b, y2
### nexus_cost_ledger.json
gate: 0.5.7
title: Per-job Nexus cost reconciliation (estimate vs billed)
verdict: N/A — no paid jobs recorded on this disk
summary.jobs: 0
summary.billed_jobs: 0
summary.unbilled_jobs: 0
summary.estimated_hqc_total: null
summary.billed_hqc_total: null
summary.delta_hqc_total: null
summary.overage_jobs: 0
summary.overage_hqc: 0
summary.worst_ratio: null
summary.tolerance: 0.1
summary.verdict: no paid jobs
verdict_criteria: [2 rows]
columns: name, statement, value, threshold, passed
runs: [0 rows]
rows: [0 rows]
### nexus_cross_check.json
gate: G25
title: Nexus cross-check -- three emulator lanes
generated_at: 2026-08-22T12:49:03Z
verdict: nexus_cross_check_verified
devices: [3 rows]
census: [17 rows]
lanes: [3 rows]
columns: device, reachable, rows
verdict_criteria: [4 rows]
columns: name, statement, value, threshold, passed
### nexus_device_matrix.json
schema: nexus-device-matrix/1
generated_at: 2026-08-26T00:52:33+00:00
summary.devices: 20
summary.shots_yes: 0
summary.shots_no: 0
summary.shots_unknown: 20
rows: [20 rows]
columns: device, family, family_label, config_class, kind, n_qubits, width_source, shots, shots_reason, usable, skip_reason, selected
### nexus_preflight.json
gate: G25
generated_at: 2026-08-21T08:30:36Z
circuits: [5 rows]
columns: ref, label, driver, notes, n_qubits, shots, gate_counts, two_qubit_gates, one_qubit_gates, estimated_hqc, over_ceiling, prediction, selene, selene_verdict, elapsed_s
### nexus_ref_ledger.json
gate: 0.5.6
verdict: PASS — every saved ref on this disk verifies
title: Saved Nexus job Ref ledger
summary.runs: 0
summary.jobs: 0
summary.reattachable: 0
summary.orphaned: 0
summary.unreadable_refs: 0
summary.missing_meta: 0
summary.verified: 0
summary.legacy_unverified: 0
summary.unverifiable: 0
summary.failed_verification: 0
verdict_criteria: [2 rows]
columns: name, statement, value, threshold, passed
runs: [0 rows]
### nhs_discharge_qubo.json
schema: nhs_discharge_qubo/v1
gate: G22
generated_by: quantum/discharge/sweep.py
verdict: qaoa_p1_beats_random_not_classical
verdict_criteria: [3 rows]
columns: name, statement, value, threshold, passed
### pqp_frontier_conjectures.json
verdict: conjectures_1q_verified
records: [11 rows]
columns: label, shots_per_cell, threshold, worst_tv, verdict, cells, n_cells, n_pass, conjecture_index, matrix_label, seq_A, seq_B, residue_A, residue_B, spiders_A, spiders_B, elapsed_sec
verdict_criteria: [3 rows]
columns: name, statement, value, threshold, passed
### pqp_frontier_conjectures_2q.json
verdict: conjectures_2q_promoted
generators: [5 rows]
conjectures: [105 rows]
columns: index, key, label, distinctResiduesStructural, distinctResiduesMatrix, promoted, reps
verdict_criteria: [2 rows]
columns: name, statement, value, threshold, passed
### pqp_frontier_conjectures_2q_selene.json
verdict: conjectures_2q_selene_verified
records: [10 rows]
columns: label, shots_per_cell, grid, threshold, worst_dmax, verdict, n_cells, n_pass, worst_cells, conjecture_index, matrix_label, seq_A, seq_B, residue_A, residue_B, elapsed_sec
verdict_criteria: [3 rows]
columns: name, statement, value, threshold, passed
### pqp_frontier_kernel_equivalence.json
headline: Rewriter-pruned G1 circuit (zero cphase gates) reproduces the full-QFT predictor on Selene shots to within ±0.03 — strictly cheaper, observationally identical.
verdict: rewriter_output_matches_full_qft
levels: [4 rows]
rows: [16 rows]
columns: N, p, branch, ht_cutoff, slopes, concentration_on_zero, predicted_concentration, avg_cphase_kept, avg_cphase_dropped, elapsed_sec, level, delta_vs_full_qft
verdict_criteria: [2 rows]
columns: name, statement, value, threshold, passed
### pqp_frontier_noise_2q.json
verdict: 2q_equivalence_survives_noise
noise_levels: [9 rows]
matrix: [9 rows]
columns: noise_level, n_pass, n_total, pass_rate, mean_worst_dmax, per_conjecture
verdict_criteria: [3 rows]
columns: name, statement, value, threshold, passed
### pqp_frontier_noise_leakage.json
verdict: aqft_noise_resilience_verified
Ns: [2 rows]
noise_levels: [4 rows]
kernels: [2 rows]
comparisons: [32 rows]
columns: N, noise_level, p, branch, qft_full_abs_err, aqft_k1_abs_err, advantage_aqft, winner
rows: [64 rows]
columns: N, p, branch, ht_cutoff, concentration_on_zero, predicted_concentration, avg_cphase_kept, avg_cphase_dropped, elapsed_sec, noise_level, kernel, shots_per_slope, abs_err_vs_pred
verdict_criteria: [3 rows]
columns: name, statement, value, threshold, passed
### pqp_frontier_noise_resilience.json
verdict: aqft_noise_resilience_verified
Ns: [3 rows]
noise_levels: [4 rows]
kernels: [2 rows]
comparisons: [48 rows]
columns: N, noise_level, p, branch, qft_full_abs_err, aqft_k1_abs_err, advantage_aqft, winner
rows: [96 rows]
columns: N, p, branch, ht_cutoff, concentration_on_zero, predicted_concentration, avg_cphase_kept, avg_cphase_dropped, elapsed_sec, noise_level, kernel, shots_per_slope, abs_err_vs_pred
verdict_criteria: [3 rows]
columns: name, statement, value, threshold, passed
### pqp_frontier_promotion.json
verdict: rule_promotion_complete
conjectures: [11 rows]
columns: index, key, label, distinct_residues_base, distinct_residues_euler, promoted, reps
verdict_criteria: [2 rows]
columns: name, statement, value, threshold, passed
### pqp_frontier_promotion_2q.json
verdict: rule_promotion_complete
conjectures: [105 rows]
columns: index, label, distinct_residues_structural, distinct_residues_matrix, promoted
verdict_criteria: [2 rows]
columns: name, statement, value, threshold, passed
### pqp_frontier_rules.json
verdict: rewriter_rules_verified
records: [5 rows]
columns: label, shots_per_cell, threshold, worst_tv, verdict, cells, n_cells, n_pass, rule_id, pqp_anchor, claim, lhs, rhs, elapsed_sec
verdict_criteria: [3 rows]
columns: name, statement, value, threshold, passed
### qml_swap.json
shots: 4000
verdict: swap_test_estimate_disagrees_with_classical_reference
verdict_criteria: [2 rows]
columns: name, statement, value, threshold, passed
### qpde_ethylene_fitquality.json
title: Shot budget vs fit bias in the ethylene QPDE gap estimate
backend: selene-sim Quest (noiseless)
verdict: qpde_fit_is_statistics_dominated
k_values: [5 rows]
fit_k: [4 rows]
excluded_k: [1 rows]
budgets: [4 rows]
columns: shots, cells, gap_fit, gap_error, gap_rel_error_pct, gap_spread, shot_noise_scale, all_pass
verdict_criteria: [3 rows]
columns: name, statement, value, threshold, passed
### qpde_ethylene_noise.json
title: Ethylene QPDE gap fit under H2-class depolarizing noise
backend: selene-sim Quest
verdict: qpde_noise_ladder_verified
fit_k: [3 rows]
levels: [4 rows]
columns: noise, multiplier, cells, gap_fit, gap_error, gap_rel_error_pct, gap_spread, per_k
verdict_criteria: [2 rows]
columns: name, statement, value, threshold, passed
### qpde_ethylene_selene.json
schema: selene_run/v1
title: Ethylene QPDE on Selene — evolution-time trick
summary: Quantum Phase Difference Estimation on the 2-qubit ethylene pi/pi* active space, run on the Selene Quest emulator. A 4x5 grid of evolution times and ancilla phase kicks is compared cell by cell against the closed-form interference law derived from the classical Hamiltonian.
generated_by: quantum/qpde/validate.py
backend.emulator: selene-sim 0.2.18
backend.simulator: Quest
backend.compiler: guppylang 0.21.16
backend.error_model: ideal
model.system: C2H4 (ethylene), STO-3G pi/pi* active space
model.qubits: 2
model.off_diagonal_hartree: -0.4
model.gap_hartree: 0.8
verdict: qpde_ethylene_gap_recovered
cells: [20 rows]
columns: k, beta_halfturns, phi_halfturns, evolution_time_au, shots, counts_q1, counts_q2, p1_measured, p1_predicted, delta, threshold, verdict, elapsed_sec
caveats: [3 rows]
verdict_criteria: [4 rows]
columns: name, statement, value, threshold, passed
### qpde_ethylene_zne.json
title: Zero-noise extrapolation of the ethylene QPDE gap fit
backend: selene-sim Quest
verdict: qpde_zne_recovers_gap
fit_k: [3 rows]
rungs: [6 rows]
columns: noise, multiplier, p_2q, cells, gap_fit, gap_error, gap_rel_error_pct, gap_spread, per_k
verdict_criteria: [3 rows]
columns: name, statement, value, threshold, passed
### qsvt.json
verdict: qsp_curve_admissible
phi_sequence: [4 rows]
x: [41 rows]
Re_P_of_x: [41 rows]
verdict_criteria: [3 rows]
columns: name, statement, value, threshold, passed
### qtda.json
threshold: 0.85
verdict: qtda_filtration_consistent
fidelity_matrix: [4 rows]
columns: 0, 1, 2, 3
edges: [2 rows]
columns: 0, 1, 2
verdict_criteria: [3 rows]
columns: name, statement, value, threshold, passed
### resources.json
rows: [5 rows]
columns: algorithm, logical_qubits, T_count, ref
### shor.json
shots: 4000
verdict: shor_toy_spectrum_verified
verdict_criteria: [3 rows]
columns: name, statement, value, threshold, passed
### simon_search.json
gate: G23
backend: Quest via Selene
seed: 11
cells: [27 rows]
columns: n, secret, secret_bits, level, shots, qubits, p_orthogonal, recovered, recovered_value, ml_recovered, ml_secret, ml_score, rounds_to_recovery, classical_expected_queries, gate_counts
summary.n_cells: 27
summary.n_recovered: 9
summary.n_ml_recovered: 27
summary.ideal_all_orthogonal: true
summary.worst_p_orthogonal: 0.8984375
summary.max_rounds: 9
verdict: simon_recovery_verified
verdict_criteria: [3 rows]
columns: name, statement, value, threshold, passed
### tda_laplacian_moments.json
title: Laplacian moments separate a WL-indistinguishable graph pair
backend: selene-sim Quest (noiseless)
verdict: tda_graphs_separated
taus: [12 rows]
graphs: [2 rows]
columns: name, vertices, edges, degree_sequence, betti, spectrum, pauli_terms, trotter_max_trace_error, points, moments
separation: [4 rows]
columns: k, measured_separation_low_order, separation_sigma_low_order, n_sigma_low_order, resolved_low_order, exact_separation, measured_separation, separation_sigma, n_sigma, resolved
verdict_criteria: [2 rows]
columns: name, statement, value, threshold, passed
### tket_compile.json
gate: G24
title: TKET compile lane: pytket cross-check of the Nadarasa reductions
generated_by: quantum/tket/sweep.py
shots: 512
summary.rows: 36
summary.equivalence_pass: 36
summary.equivalence_fail: 0
summary.max_unitary_distance: 6.331e-15
summary.families: 6
summary.scored_families: 5
summary.nadarasa_ahead: 1
summary.agree: 4
summary.tket_ahead: 0
summary.selene_within_envelope: 12
summary.selene_rows: 12
summary.max_tvd: 0.08984
summary.elapsed_sec: 2.67
verdict: tket_cross_check_verified
native_gate_set: [21 rows]
optimisation_levels: [3 rows]
rows: [36 rows]
columns: gate, label, variant, optimisation_level, device, qubits, source_gates, source_two_qubit, gates, two_qubit, depth, depth_2q, native_ops, compile_sec, equivalent, unitary_distance
selene: [12 rows]
columns: gate, variant, qubits, shots, optimisation_level, tvd, envelope, within_envelope, measured, exact, guppy_lines, sec
comparison: [6 rows]
columns: gate, label, qubits, scored, baseline_2q, nadarasa_source_2q, tket_raw_2q, tket_nadarasa_2q, baseline_depth, tket_raw_depth, tket_nadarasa_depth, verdict
caveats: [4 rows]
verdict_criteria: [4 rows]
columns: name, statement, value, threshold, passed
==============================================================================
## 5. Healthcare and hackathon context
NHS framing
The author is a practising NHS pharmacist. The healthcare track restates the
three shifts of the NHS 10 Year Health Plan — hospital to community, analogue
to digital, sickness to prevention — as computational problems, and builds one
of them end to end.
Gate G22 — delayed discharge as a QUBO
Delayed transfers of care ("bed blocking") are modelled as a constrained
assignment of patients to discharge pathways, with an explicit equity penalty
so the optimiser cannot buy throughput by systematically deprioritising the
hardest-to-place patients. Solved with QAOA kernels written in Guppy and run
on Selene. Full result digest: nhs_discharge_qubo.json in section 4.
Page: /nhs-quantum/bed-blocking
Hackathon track mapping
The programme's existing gates map onto the published R&D themes:
primitives G1-G14 rewriting rules, phase-group tests, QSP/QSVT
chemistry G16-G18 ethylene QPDE, noise ladder, ZNE extrapolation
AI for quantum G15/G21 ADAPT-GQE generative circuit synthesis
error correction G19/G20 prethermal Floquet, PEC+ZNE mitigation
optimisation G22 discharge QUBO, G23 Simon's algorithm with ML decoding
Page: /hackathon
Tooling
The same knowledge is downloadable as an installable agent skill for Lovable,
Hermes Agent and OpenClaw at /skills.
==============================================================================
End of corpus · Nadarasa Reduction v0.4.11 · built 2026-08-27 · https://arunquantum.lovable.app