wai-quantum 0.4.0: wai.quantum.circuit2 — a correctly rounded phase table, circuit/1 stated exactly, both read by stated rules #207
Merged
dcharlot
merged 14 commits from 2026-10-10 15:52:03 -04:00
wai-quantum-circuit2 into main 2026-10-10
Merge remote-tracking branch 'origin/main' into wai-quantum-circuit2
All checks were successful
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 52s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 46s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 34s
standards workspaces build + test / map-rs (pull_request) Successful in 31s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 1m59s
standards workspaces build + test / mesh-rs (pull_request) Successful in 30s
standards workspaces build + test / openpay (pull_request) Successful in 1m26s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 5m39s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 23s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 1m51s
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Successful in 1m5s
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Successful in 1m9s
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Successful in 1m16s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 4s
WAI section references / cited sections exist (pull_request) Successful in 10s
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Successful in 4m30s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 2m54s
workflow hygiene / every workflow supersedes its own queue (pull_request) Successful in 4s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 59s
WAI HTTP delivery / HTTP corpus · falsification and differential (pull_request) Successful in 1h29m9s
wai-pack / wai-pack · linux (pull_request) Successful in 1m7s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 1m45s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 20s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 1m12s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 2m51s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 7m18s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 10m12s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 29s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 3m26s
wai-pack / wai-pack · macos (pull_request) Successful in 2m12s
# Conflicts: # wai/CHANGELOG.md
2026-10-09
wai-quantum circuit2: the number range decided on the literal's digits in both readers
A third review upheld six findings. - std's f64 parse stops adding exponent digits once the exponent reaches 0x10000, so a literal whose exponent offsets about 655,000 zeros was judged wrong both ways: exactly 1 refused, 10^309 accepted. The Rust reader now decides the range rule on the digits, as the referee does: the significant digits and the decimal exponent, with saturating exponent arithmetic, against the 309 digits of 2^1024 − 2^970. The limit is one constant, RANGE_LIMIT, and a test derives it in decimal from 2^1024 and 2^970 and checks it against std's parse where that parse is exact. - The referee passed a zero-padded exponent of over 4300 digits to int(), which Python refuses; it now converts only the significant exponent digits. - quantum-sim §8, the CHANGELOG and SPEC §5 now list both directions of the range change: numbers at or above the limit that earlier builds let through are refused, numbers below it that some builds refused are accepted. §2 says the rule is decided on the digits, at any length. - The corpus README and the wasip2 job comment were stale. The corpus holds both long literals and the padded exponent: an accept file (exactly 1 and exactly 10, written long) and a reject file (10^309). Co-Authored-By: Claude Opus 5.5 <[email protected]>
wai-quantum circuit2: a strict I-JSON reader, the rules stated in full, an accept corpus
A second review of the I-JSON fix upheld two defects. - The Rust reader left the number-range rule to serde_json. Built without float_roundtrip, its fast path is not correctly rounded, so near the largest double the reader and the referee disagreed both ways, and in WQC2 on the class; and whether wai-rs features that enable float_roundtrip were linked changed the verdict. - The spec claimed I-JSON, but neither reader refused noncharacters. wai-quantum now reads a contract or measurement section with a strict reader of its own (src/ijson.rs) that decides every rule from the text: UTF-8; one JSON value with JSON whitespace; no member name twice, compared after unescaping; no surrogate or noncharacter, raw or escaped; no number of magnitude 2^1024 − 2^970 or more, decided by std's correctly rounding parse; at most 127 levels. A number keeps its literal, and an unsigned integer is one written with no sign, fraction or exponent. The serde dependency added for the visitor is gone. The referee decides the range rule on the literal's digits, integers only, and names the rule it refuses by. quantum-sim §2 states the rules in full, says that only the class is normative when a section breaks several, and that a reader reads integers exactly. §8 adds the noncharacter to the newly refused payloads and notes the numbers just below the limit that some earlier builds refused. `accept_corpus()` holds payloads at the edges of the rules that a reader must accept, each with its circuit and measurement; the corpus gains `accept/` and `accept.json`. `gen`, `verify`, the referee and the browser check read it. The reject corpus gains the boundary cases (the integer 2^1024 − 2^970, a literal just above it, the WQC2 n_qubits forms on both sides of the class order), noncharacters, a lone low surrogate, a BOM, NaN and a raw control character, and the referee and the crate check each JSON case's rule. The wasm32-wasip2 leg runs the reader's tests. Co-Authored-By: Claude Opus 5.5 <[email protected]>
wai-quantum circuit2: sections read as I-JSON, a reject payload for every refusal, review fixes
The review of the branch upheld nine findings; this fixes all of them. - The contract and the measurement are read as I-JSON (RFC 7493, quantum-sim §2): UTF-8, no member name twice in one object, no unpaired surrogate escape, no number that rounds to an infinite double, at most 127 nested levels. An unsigned JSON integer has no sign, fraction or exponent, so `-0` is not one. The Rust reader (a visitor refuses repeated names) and the referee enforce the same profile; the repeated member name is the one newly refused payload, listed in §8 and SPEC §5. - `reject_corpus()` in wai-quantum is the one list of reject cases: the crate's tests and `wai_quantum_vectors gen` both read it. It now holds a payload for each refusal a payload can carry: the short payload, a table or section past the end, a missing contract or op-log, a short op-log, truncated controls, target or control out of range, a control on its target, P(0) and P(33), the n_qubits forms, the JSON profile, and the measurement rules. - wai-energy-receipts runs on a change to wai/quantum-conformance, since its wai_quantum_vectors tests require the committed corpus to be what gen writes. - Wording: the statevector changes where H, T, T†, P(3) or P(k ≥ 5) act (P(1), P(2) and P(4) are equal in both tables); each component of every circuit2 P(k) is within half a unit; only circuit2's P(k) gives the QFT ladder; the original vectors' hashes move with a P(k) entry only through H. Co-Authored-By: Claude Opus 5.5 <[email protected]>
wai-quantum 0.4.0, wai-quantum-serve 0.5.0
wai-quantum is 0.4.0, the breaking release that carries the circuit2 capability and the gateset-tagged circuits. wai-quantum-serve is 0.5.0, since its /run, /qasm and /receipt take a gateset and build circuit2 by default. wai-rs requires wai-quantum 0.4.0. The five lockfiles that pin wai-quantum (wai-quantum, wai-quantum-serve, wai-rs, wai-web and wai-pack) change only in their version lines, and each resolves with cargo metadata --locked --offline. The changelog names the serve component's new version. Co-Authored-By: Claude Opus 5.5 <[email protected]>
wai-quantum circuit2: the specification, registry and documentation for both capabilities
The quantum-sim extension now specifies both capabilities to the bit. Its header and section 1 name wai.quantum.circuit2 for new content and wai.quantum.circuit as frozen. Section 2 states the little-endian layout, overflow-free section bounds, the refusal of a repeated known section, and the three refusal classes in the order a reader checks them. Section 2.1 gives both canonical contracts, the ext convention, the circuit/1 reader rules and circuit2's byte-canonical rule, and defines wai.det.amp64 and the meaning of wai.det.fixed64 inside a circuit/1 contract. Sections 2.2 and 2.3 state the op-log and measurement rules per capability. Section 3.0 states the shared arithmetic, the index convention and the gate matrices. Section 3.1 gives the exact circuit/1 recurrence and the deviation of every entry and no longer claims that table gives the QFT. Section 3.2 defines the circuit2 table as the nearest integers, makes the literal table normative with its digest, and gives the interval-certified derivation as informative. Sections 4 to 6 state the hash and sampler layouts, the error per capability and how receipts name their capability, and a new section 8 says why circuit2 is a capability of its own, what older readers do, and which circuit/1 payloads become non-conforming. Appendix B holds both phase tables and digests, rendered from PHASE_V1 and PHASE_V2 by a new wai_quantum_vectors appendix subcommand. A bin test fails when the text differs from the constants, and the referee requires Appendix B to state its literal tables; wai-conformance now triggers on the extension. SPEC.md names wai.det.amp64 and lists the newly non-conforming circuit/1 payloads. The registry adds wai.det.amp64 as an identifier and annotates wai.det.fixed64, and the export is regenerated. The crate rustdoc, the crate, wai and quantum-conformance READMEs, and the changelog describe both capabilities, the corpus coverage and its limits, and the 0.4.0 migration. Co-Authored-By: Claude Opus 5.5 <[email protected]>
wai-quantum circuit2: an integer-only referee, a wasm32-wasip2 leg and the browser check for both capabilities
wai/quantum-conformance/tools/referee.py is a second implementation of the quantum-sim extension in Python, standard library only and integers only. It shares no code with wai-rs or wai-quantum. It checks its own BLAKE3 against the published test vectors. It derives the circuit/1 table by its recurrence and the circuit2 table by the interval-certified u128 procedure, and requires both literal tables and their digests. It rebuilds every file of both vector trees byte for byte, reads each back, and recomputes every manifest entry with its own simulator and sampler; the four original circuit/1 vectors must also keep their frozen hashes. It requires reject.json to list exactly the 26 reject cases and refuses each file with its listed class and by the rule its name states. It requires dyadic_ladder to change its statevector hash under every change of 1 to either component of every P(1..32), in both tables. wai-conformance.yml runs the referee with python3 -I in the independent-verifiers job on Linux. A new quantum-wasip2 job on Linux runs tests/bit_identity.rs and the unit tests of the quantum, receipt_pins, quantum_fabric, quantum_source, quantum_channel, quantum_qudit and quantum_receipt modules under wasmtime, as debug builds so that overflow panics, and fails unless every one of those modules ran a test. It then runs wai_quantum_vectors verify over the corpus under wasmtime. wasmtime is the 49.0.2 release binary, pinned by its SHA-256. browser_verify.mjs now opens every file of both vector trees in QuantumSink against its manifest entry (capability, n_qubits, n_ops, circuit, statevector and histogram hashes), requires every reject file to be refused with its listed class, and checks QuantumIde's bell, ghz and qft against the circuit2 manifest by default and against the circuit/1 manifest after set_gateset(1). It remains a manual tool. The corpus README describes the referee, the CI leg and the browser check. Co-Authored-By: Claude Opus 5.5 <[email protected]>
wai-quantum circuit2: the browser sink, the IDE and the serve component follow the gateset
QuantumSink reads WQC1 and WQC2 containers. It reports the capability the magic names and the circuit hash. A refused container throws a message that names its error class, the word reject.json lists. QuantumIde builds in circuit2 by default. set_gateset(1) moves the current circuit and every later build to circuit/1, and gateset() and capability() report it. The debugger's prefix circuit keeps the IDE's gateset. add() stores no parameter on a gate other than P, so an editor's leftover parameter no longer makes a circuit2 circuit invalid. circuit_hash_hex() is empty for an invalid circuit. A circuit/1 circuit with P(k>=8) gets the command-line tool's note on the browser console, once per circuit state, never in a returned value. wai-quantum-serve takes gateset=v1 or v2 (default v2) on /run, /qasm and /receipt. /run names the capability in its JSON, and /run and /receipt name it in a wai-capability header. The /receipt body stays the receipt JSON alone, so it is still the bytes a native seal gives. An unknown gateset or a circuit with no circuit hash is a 400. Native tests cover both corpora, the reject corpus, the IDE's gateset switching, the add() parameter rule, the empty hash, and the serve endpoints against the corpus GHZ3 hashes. The wai-web native test count in CI moves from 24 to 31, and that workflow now triggers on changes to the quantum corpora it reads. Co-Authored-By: Claude Opus 5.5 <[email protected]>
wai-quantum circuit2: wai-rs registers circuit2 and carries the corpus in both capabilities
The codec registry registers wai.quantum.circuit2 beside wai.quantum.circuit, with the same media class, tier, licence and replicate class. The registry export, the vocabulary and the generated REGISTRY.md table are regenerated from it, and SPEC.md section 5 gains its row and qualifies the circuit/1 row's QFT sentence. wrap_envelope returns a Result and tags the envelope with the circuit's own capability. open_envelope decodes the payload and refuses an envelope whose capability is not the one the container's magic names. wai_quantum_vectors gen writes the circuit/1 corpus with three table-sensitive vectors after the four frozen ones, the seven vectors again in circuit2 under v2/, and 26 reject files with reject.json naming the class each is refused as. verify checks all three trees: each vector decodes to the capability its entry names and reproduces its hashes, and each reject file fails with its listed class. The four original files and their manifest entries are unchanged. The wai-quantum CLI takes --gateset v1|v2 (default v2) on every command that builds a circuit and on fabric, reports the capability in its run and fabric JSON, and prints the circuit/1 table note on standard error only. wai_quantum_meter names the capability of the circuit it meters. Co-Authored-By: Claude Opus 5.5 <[email protected]>
wai-quantum circuit2: the circuit2 pins beside the frozen circuit/1 pins
The legacy pins keep their values and build their circuits in circuit/1. No pinned hex or float changed. New circuit2 pins: - receipt_pins::quantum_circuit_v2_bytes seals GHZ3 at seed 7, 1024 shots. It asserts the circuit, statevector and histogram leaves first, then the receipt hash and JSON hash, which were frozen after the leaves matched. The circuit/1 twin of its ops does not verify against it. - bit_identity::mps_truncation_v2 pins 0xe408_42e9_2c89_20ed. - quantum_source pins the circuit2 Bell pair's entropy at exactly 1.0. - quantum_channel pins the circuit2 half-depolarized |+> entropy at 0.811278124459133, next to the ideal H2(1/4). the_conformance_corpora_are_pinned rebuilds every vector of both corpora from code and checks its file size, op count, circuit hash, statevector hash and, for bell, histogram hash. The circuit/1 rows include the nine manifest hashes of the four original files. The other rows are the values an independent integer-only implementation computed. The new pins pass natively and under wasm32-wasip2. Co-Authored-By: Claude Opus 5.5 <[email protected]>
wai-quantum circuit2: the crate's consumers follow the circuit's gateset
QuantumReceipt gains gateset_of, which names the gateset under which a circuit's ops hash to the receipt's circuit_hash, and gateset_note, which turns that into the text to show when a reconstruction fails for that reason. verify_reconstruction stays strict. from_json refuses an n_qubits that does not fit a u8 and an n_ops that does not fit a u32 instead of truncating them. The fabric names its gate kernel and random corpus once per gateset: wai.quantum.circuit2/apply and wai.quantum.circuit2/random-v1 join the circuit/1 names. certify_lanes and lanes::random_circuit take a gateset and draw the same ops from a seed in both. detect_local offers the reference kernel of each gateset. The lanes32 corpus digests for seed 7 and 200 cases are pinned: d18454c6 for circuit/1, as 0.3.41 certified it, and 3dbde276 for circuit2. noisy_expect_z and route_line build their circuits with empty_like, so a derived circuit keeps its source's gateset. from_qasm refuses a gate that names one qubit twice as an invalid circuit when it reads it, in every gateset. quantum_spd's controlled_phase refuses a phase on 32 or more qubits instead of shifting out of range. The QIR and QASM export docs say that a circuit/1 circuit exports ideal angles, not its own numerics. The bell and receipt examples print the capability and handle the fallible calls. Co-Authored-By: Claude Opus 5.5 <[email protected]>
wai-quantum circuit2: the container reads and writes both capabilities
to_wqc writes the container that a circuit's gateset names: WQC1 for circuit/1 and WQC2 for circuit2, with the same layout. The contract and the measurement are rendered with format!, so no serializer setting can change them. circuit/1 output is byte for byte what 0.3.x wrote: the four corpus files and their manifest circuit hashes reproduce. to_wqc and circuit_hash run validate first and return a Result. circuit_hash covers the canonical contract of the circuit's gateset, so it binds the capability and its table. The op-log writes its counts with try_from. validate now also bounds a circuit at u32::MAX ops, because the op-log counts them in a u32, and to_wqc refuses a section too long for the table's u32 fields. from_wqc reads both magics and applies its checks in a fixed order: - the magic; - each section's range, computed with checked arithmetic so that nothing wraps on a 32-bit usize; a known section that repeats; a missing contract or op-log; - the contract; - the op-log; - validate; - the measurement. A WQC1 contract must be a JSON object that carries circuit/1's ext, numeric, gateset and frac, where frac is the integer 30. Its formatting stays free. A WQC2 contract must be the canonical bytes. Anything else is a contract mismatch. n_qubits must be a JSON unsigned integer of at most 255, or the file is malformed, so 258 no longer reads as 2. The op-log reserves space for the ops its bytes can hold, not for the count it claims. A WQC2 op-log must end at its last op, and a WQC2 measurement must be canonical. WQC1 still ignores trailing op-log bytes and reads a lax measurement. An error quotes at most 64 characters of the file. quantum_receipt's seal propagates a circuit_hash error, and reconstruction_matches treats one as a mismatch. quantum_toolchain::circuit_id returns a Result. New tests: - circuit1_files_are_written_as_before pins the digests and circuit hashes of the four corpus files, and checks the measurement render against the serializer's. - wqc_round_trips covers both gatesets, the sizes and circuit hashes of the four circuit2 corpus circuits, and what circuit/1 still tolerates. - container_rejects builds the 26-case reject corpus in code and checks each case's error class. It also covers structural edge cases, the order of the rules, the 255-control limit, the widest circuit2 gate, the op-log reservation and section ranges that cannot wrap. wai-rs, wai-web and wai-quantum-serve still call the old signatures. They are updated in later stages. Co-Authored-By: Claude Opus 5.5 <[email protected]>
wai-quantum circuit2: circuits carry their gateset
Circuit gains a public gateset field and is non_exhaustive. Circuit::new gives Gateset::V2, Circuit::with_gateset picks one, and Circuit::empty_like copies the qubit count and gateset with no ops. Circuit::qft goes through new, so it is V2. PartialEq compares the gateset, so the same ops in V1 and V2 are different circuits. validate follows the gateset. In both, a gate has at most 255 controls, since the op-log counts them in a u8. V2 also requires pairwise distinct controls, checked with a 256-bit bitmap, and a parameter of 0 on every gate other than P. BaseGate::matrix and apply take the gateset, and simulate passes the circuit's own, so H, T, T† and P(k) read that gateset's table. The free phase(), inv_sqrt2() and CAPABILITY are removed in favour of the Gateset methods. quantum_comm names Gateset::V2 once and uses it for preparation, the X-basis measurement and the cloning circuits. quantum_qudit's d = 2 normaliser is Gateset::V2.inv_sqrt2(), so its Fourier gate is that gateset's Hadamard. The fabric's lane matrix reads the circuit's gateset, and random_circuit builds circuit/1 circuits, as the kernel and generator names it is certified under say; the lanes32 corpus for seed 7 and 200 cases is still d18454c6. QuantumError is non_exhaustive and gains Contract, shown as "contract mismatch: ...". kind() names each class: malformed, invalid, contract or too-many-qubits. The container is unchanged in this commit. It reads and writes WQC1 only, and from_wqc builds V1 circuits, so wqc_round_trips builds its circuit in V1. The legacy pins build their circuits in Gateset::V1 and keep their values: the circuit/1 receipt, the MPS truncation and sparse Pauli digests, the two-qubit entropy and the depolarised spectrum. New tests: every P(k) is copied exactly onto |1>, plain and controlled, in both gatesets; QFT-14 of |1...1> is within 3 units of an f64 reference in V2 (2.03 measured) and more than 8 off in V1 (14.43); P(19) applied 2^18 times turns |1> by pi in V2 and leaves it at (1073479680, 0) in V1; one V2 gate on 14,000 seeded normalised pairs stays within 2 units per component (1.40 measured) while V1 fails the same check; V2 rejects the repeated controls and non-P parameters V1 accepts. The apply equivalence test now runs 600 random circuits over both gatesets with P(1..32). Two small tests cover the constructors and the error kinds. Co-Authored-By: Claude Opus 5.5 <[email protected]>
wai-quantum circuit2: the two phase tables and the Gateset type
PHASE_V1 and PHASE_V2 are literal [Amp; 33] constants. PHASE_V1 holds the
values of the circuit/1 half-angle recurrence, unchanged, and PHASE_V2 holds
the integer nearest to 2^30·cos(2π/2^k) and 2^30·sin(2π/2^k) for k = 1..32.
The OnceLock and the runtime recurrence are gone; the recurrence now lives in
a test that checks it against PHASE_V1.
Gateset { V1, #[default] V2 } is non_exhaustive and names, per variant, the
capability, magic, contract ext, numeric identifier, gateset string, table,
phase(k), inv_sqrt2, canonical contract bytes (rendered with format!) and a
BLAKE3 table digest, with from_magic and from_capability as inverses.
The free phase() and inv_sqrt2() still return the circuit/1 values, now read
from Gateset::V1, so every existing circuit, hash and pin is unchanged. Their
removal comes with the Circuit gateset field.
New tests: phase_table_v1_is_frozen (recurrence, digest 3fa9805f…8466, the
P(19..32) defect, inv_sqrt2 759250124, the dyadic_ladder statevector hash
9754af24…851d); phase_table_v2_is_certified_nearest (u128 interval
enclosures, both ends round alike and sit more than 2^20 ulps from a tie,
digest d34cb793…3510, inv_sqrt2 759250125); every_v2_phase_is_within_one_unit_of_ideal
(against repro::sin_cos, with circuit/1 failing the same check for every
k >= 8 as the negative control); table_identities_per_gateset, which replaces
dyadic_table_is_unit_modulus; and gateset_identifiers_are_consistent, which
checks the identifiers round-trip and that the V1 contract bytes equal the
serializer render for every n_qubits.
Co-Authored-By: Claude Opus 5.5 <[email protected]>