- Python: qudit, qudit_check, wasmq (an envelope's bytes, a path or an example's name), wasmq_verify (with the envelope, run again byte for byte) and wasmq_examples, the extension module running an envelope on wai-wasmq's own interpreter (no compiler in the wheel); Service.wasmq and Service.qudit run them on a service. Tests: both kinds run and verified here, a qudit run equal to `wai-quantum qudit run`'s bits and a wasmQ/1 job equal to `wai-quantum run`'s byte for byte, and both run on a service built from this checkout. - npm (wai-web, quantum_engine): QuantumEngine.qudit and QuantumEngine.wasmq (the interpreter, the only engine on wasm32), engine_qudit_check, engine_wasmq_verify, engine_wasmq_examples and engine_wasmq_example. The smoke test runs every example in Node, each sealed, held to its form and run again, a fault sealed and W013 refused. - Quantum Studio: the "Qudits" and "wasmQ" panes (closed until opened), in the browser and in the desktop app (its qudit, qudit_check, wasmq, wasmq_verify and wasmq_examples commands): the qudit counts by digits, amplitudes as integers, bounds, the seal checked and the request run again; an example envelope or a .wai file opened there, its outcome, calls, fuel, counts and energy, its seal and form checked and the envelope run again; a refusal with its code. The browser e2e drives each. - quantum-wasmq §11.9 and quantum-sim §9.10 name every surface. Tested on the build hosts: wai-web for wasm32-unknown-unknown and the npm smoke test in Node 26 (macOS arm64); the Python package's wheel (maturin 1.15.0), clippy -D warnings and pytest (95 passed, the widgets' node test skipped there) on Linux x86_64; the Studio's browser e2e (macOS arm64, STUDIO_SKIP_SERVICE=1), every qudit and wasmQ check passing. Co-Authored-By: Claude Opus 5.5 <[email protected]>
1283 lines
81 KiB
Markdown
1283 lines
81 KiB
Markdown
# WAI Extension: Hybrid Quantum-Classical Execution (`wai.quantum.wasmq`)
|
||
|
||
Status: **Draft.** Reference implementation: `wai-wasmq` (the profile, `src/profile.rs`;
|
||
the prepared module, `src/meter.rs`; the interpreter, `src/interp.rs`; the engines,
|
||
`src/engine.rs`; the lowering, `src/abi.rs`; the envelope, `src/envelope.rs`; the run and
|
||
its receipt, `src/run.rs`), the OpenQASM reader's callouts (`wai-quantum`,
|
||
`qasm::read_callouts`), and the command line's `pack-wasmq`, `check-wasmq`, `run` and
|
||
`verify` (`wai-quantum-cli`, `src/wasmq.rs`). A job's form (§6.5) is the engine's, where
|
||
every verifier of its receipts reaches it (`wai-quantum`, `engine::wasmq`). Interface: [`quantum-wasmq.wit`](quantum-wasmq.wit).
|
||
Vectors: [`../quantum-conformance/wasmq/`](../quantum-conformance/wasmq/).
|
||
|
||
Keywords **MUST**, **MUST NOT**, **SHOULD** and **MAY** are RFC 2119/8174.
|
||
|
||
## 0. What this extension adds
|
||
|
||
A hybrid quantum program runs classical code between its gates: it decodes a syndrome
|
||
and corrects the error before the next round, or plans the moves that fill an atom
|
||
array. OpenQASM 3 declares such a function with `extern` and leaves its body, its binary
|
||
form and how it is linked outside the language. This extension fills that gap for WAI
|
||
Quantum, and nothing else:
|
||
|
||
- **One envelope.** A WAI1 envelope of capability `wai.quantum.wasmq` carries the
|
||
OpenQASM 3 program, the WebAssembly modules that define its `extern`s, and the modules'
|
||
parameter files (priors), each pinned by BLAKE3 in a canonical contract (§2).
|
||
- **One profile.** A module is a core WebAssembly module in the Lime1 feature set, which
|
||
imports nothing, whose memory, size and stack are bounded, and which uses nothing whose
|
||
result a host may choose (§3).
|
||
- **The same bytes and the same fuel everywhere.** Fuel, the stack's weight and the NaN a
|
||
float instruction makes are defined on the module as written (§4), so every runner
|
||
gives a callout the same results, the same fuel and the same fault. The reference runs
|
||
each module on two engines, a native one and an interpreter that also runs on wasm32,
|
||
and every vector holds on both, on every host.
|
||
- **A receipt.** A run seals its result in the job receipt the engine already signs
|
||
(`engine::receipt`): the counts, the fuel each callout used, the modules' digests, a
|
||
digest of every call's inputs and outputs, and the fault that stopped the run, if one
|
||
did (§6).
|
||
|
||
This version of the profile runs Clifford programs, in whole numbers from end to end
|
||
(§5.1, §6.1). A run simulates the quantum program and runs the classical code for real.
|
||
§7 says what holds when the quantum part runs on hardware, and §10 what this version
|
||
leaves out. Its second version, `wai.quantum.wasmq/2` (§11), adds a method that runs `t`,
|
||
Toffoli and gates at dyadic angles on the engine's whole-number state vector, the same bits
|
||
on every host; angle arguments for callouts; and the energy a run took, read where its host
|
||
meters it and stated unmetered where it does not. A first-version envelope runs, seals and
|
||
verifies under it exactly as it did.
|
||
|
||
## 1. Principles
|
||
|
||
1. **Nothing outside reaches the result.** A module imports nothing: no clock, no
|
||
random source, no file, no network, no host function. Its only inputs are its bytes,
|
||
its priors, the seed the run gives it and the bits the program passes (§3.2, §5.3).
|
||
2. **The host chooses nothing.** Every place where WebAssembly lets an engine choose (a
|
||
NaN's bits, a relaxed vector instruction, how deep calls may go, when memory runs out,
|
||
when to stop a long computation) is fixed by the profile or refused (§3, §4).
|
||
3. **Fuel is the measure, not time.** A callout's budget is a number of weighted
|
||
instructions (§4.2). It is the same on every host; wall-clock time is not, and the
|
||
profile does not claim one.
|
||
4. **The direction is the hardware's.** The quantum program calls the module, as a
|
||
real-time controller calls classical code between gates. A module drives no gates.
|
||
5. **One form for one bundle.** A bundle has exactly one envelope, so its BLAKE3 names
|
||
it, and a receipt that names the digest names the program, the modules, the priors and
|
||
the run (§2.4).
|
||
6. **What is read is what runs.** A reader refuses on reading every bundle a run could
|
||
not finish for a reason of the bundle's own (§2.3). A run that starts ends in a
|
||
receipt, unless the host itself fails (§6.3).
|
||
|
||
## 2. The envelope
|
||
|
||
### 2.1 Container and manifest
|
||
|
||
The envelope is a WAI1 container (SPEC §2). Its manifest is, in this order and with no
|
||
other member:
|
||
|
||
```json
|
||
{"wai":"1.0","media":"binary","intent":"replicate",
|
||
"model_requirement":{"capability":"wai.quantum.wasmq","fallback":null},
|
||
"conditioning":{"kind":"wasmq"},
|
||
"target":{"qubits":17,"shots":32,"seed":1234}}
|
||
```
|
||
|
||
`fallback` is `null`: a sink without this capability holds no way to run the program's
|
||
callouts, so the envelope is inert there (SPEC §4). `target` is informational (SPEC §3)
|
||
and is the contract's: the program's qubits (§2.2), the shots and the seed.
|
||
`conditioning` carries no `prior`: SPEC §3.1's pin names a parameter set the sink
|
||
supplies, and a wasmQ envelope carries its modules' priors itself, each pinned in the
|
||
contract (§2.2).
|
||
|
||
### 2.2 Payload and contract
|
||
|
||
```text
|
||
payload = "WQM1" | u32 contract_len | contract | frames
|
||
frames = program | for each module, in order: the module, then each of its priors in order
|
||
```
|
||
|
||
`contract_len` is little-endian. The contract is JSON in the canonical form of RFC 8785
|
||
(JCS), with exactly these members:
|
||
|
||
| member | |
|
||
|---|---|
|
||
| `profile` | `"wai.quantum.wasmq/1"` |
|
||
| `program` | `{ "blake3", "bytes", "inputs" }`: the OpenQASM 3 source's digest and length, and the values of its integer `input`s (an object, empty when there are none) |
|
||
| `modules` | for each module: `{ "name", "blake3", "bytes", "priors": [{ "name", "blake3", "bytes" }] }` |
|
||
| `callouts` | one for each `extern` the program declares, in the order it declares them: `{ "extern", "module", "export" }`, the module by its index |
|
||
| `run` | `{ "shots", "seed", "method", "state", "fuel": { "per_call", "per_job", "per_instance" } }` (§6.1, §4.2) |
|
||
|
||
`blake3` is the BLAKE3 digest of the frame as 64 lowercase hexadecimal digits; `bytes`
|
||
its length. A `name` is for people. `method` is `stabilizer`, the one method of this
|
||
version (§5.1). `state` is `shot` (a fresh instance of each module every shot) or `job`
|
||
(one instance for the run, its shots in order). `shots` is 1 to 2^20. `seed` and the
|
||
size of each input are at most 2^53 − 1, so that every JSON reader holds them exactly, and
|
||
each budget at most 2^39 (§4.8). `per_job` is held to less with any module: its eight
|
||
operations for each unit of fuel take all of a run's work at 2^39, and a run's other work
|
||
is never nothing (§4.8). Every module is named by
|
||
at least one callout. The program's text is at most 1 MiB, and the modules'
|
||
bytes at most 512 KiB among them.
|
||
|
||
**The program's qubits** are the qubits it declares, all its registers together; a program
|
||
that declares none and names physical qubits (`$0`, `$5`) has one more than the highest
|
||
index it names, the qubits below it among them.
|
||
|
||
### 2.3 Reading an envelope
|
||
|
||
A reader MUST refuse an envelope, with the code of §3.3, when:
|
||
|
||
- it is longer than 128 MiB, its first four bytes are not `WAI1`, its capability is not
|
||
`wai.quantum.wasmq`, its manifest is not the one §2.1 gives for its contract byte for
|
||
byte, or any byte follows the payload (`W013`);
|
||
- the payload's magic is not `WQM1`, the contract is not in its RFC 8785 form, holds a
|
||
member §2.2 does not list or lacks one, or the frames do not end exactly where the
|
||
payload ends (`W013`);
|
||
- a frame's BLAKE3 is not the one the contract pins: the program, a module, a prior
|
||
(`W014`);
|
||
- a module breaks the profile (§3: its code), the modules hold more than 4096 pages among
|
||
them (`W003`, §4.7), or more than 512 KiB of bytes (`W010`);
|
||
- the program is longer than 1 MiB, is refused by the reader, calls nothing, has a gate
|
||
outside §5.1's, or is past a bound §4.8 reads it within (`W016`);
|
||
- the callouts are not one for each `extern` in the order declared, a module is named
|
||
by none, a callout's export is not the type its `extern` lowers to, or a module the
|
||
host must write to or read a list from does not export what §5.2 asks (`W015`);
|
||
- the run is outside §2.2's ranges, or past what §4.8 lets a run ask of its host (`W017`).
|
||
|
||
A writer checks the same rules and writes nothing that breaks one. An envelope that
|
||
passes them runs: nothing about a bundle is left for a run to find (§6.3).
|
||
|
||
### 2.4 One envelope for one bundle
|
||
|
||
A writer MUST write the frames in §2.2's order, the contract in its canonical form with
|
||
its callouts in the order the program declares its `extern`s, and the manifest exactly
|
||
as §2.1 gives it. A reader that holds these rules reads every bundle from exactly one
|
||
sequence of bytes, so the envelope's BLAKE3 (`envelope_blake3`) names the bundle, and a
|
||
receipt that binds it binds every frame.
|
||
|
||
## 3. The module profile
|
||
|
||
### 3.1 A core module
|
||
|
||
A module is a core WebAssembly module (WebAssembly 3.0, §5 Binary Format), not a
|
||
component. It MAY be the core module of a component: its exports are the canonical
|
||
lowering of the functions [`quantum-wasmq.wit`](quantum-wasmq.wit) declares, so a
|
||
component host can wrap it, and a host of this profile calls it directly.
|
||
|
||
### 3.2 The rules
|
||
|
||
A module MUST validate under the **Lime1** feature set of the WebAssembly tool
|
||
conventions: WebAssembly 1.0 with mutable globals, multi-value, sign extension,
|
||
non-trapping float-to-int conversion, `memory.copy` and `memory.fill`, extended constant
|
||
expressions and the overlong `call_indirect` encoding. Every instruction is in the
|
||
encoding that feature set gives it, and every function body ends with the `end` of its
|
||
own block. And:
|
||
|
||
| rule | why |
|
||
|---|---|
|
||
| It imports nothing. | Nothing outside the module reaches its result. A clock, a random source or any host function would. |
|
||
| It has at most one memory, 32-bit, unshared, of 64 KiB pages, with a maximum of at most 4096 pages (256 MiB). | A memory without a maximum grows as far as a host lets it, which differs by host. |
|
||
| It has no start function. | Nothing runs when an instance is made: every instruction runs in a metered call (§5.3). |
|
||
| Nothing shared, no atomic instruction. | Lime1 has no threads. |
|
||
| No vector instruction or type, relaxed or not. | A relaxed vector instruction's result is the engine's choice; the profile takes none. |
|
||
| No instruction or type of another feature outside Lime1: references, exceptions, tail calls, garbage collection, passive segments. | Lime1's values are numbers. |
|
||
| At most one table, of `funcref`, at most 2^16 entries, filled once from active segments of function indices; no instruction reads or changes it but `call_indirect`. | `table.grow` stays non-deterministic in the deterministic profile (below). |
|
||
| In every function, at most 4096 parameters, locals and operands at once. | It bounds what one frame holds in an interpreter. |
|
||
| At most 512 KiB of bytes, which is also what the modules of a bundle hold among them; at most 2^16 types, functions, globals, data segments and element segments each; at most 2^10 exports, each name at most 256 bytes; a function type of at most 2^8 parameters and 2^8 results; a function's entry in the code section of at most 2^14 bytes. | They bound what a reader, an engine and a frame need (§4.3), with room for what preparing a module adds (§4.4): a body of calls grows some 53 times prepared, and an engine compiles a function whole. |
|
||
| Every active data and element segment fits where it is placed. | Instantiation then cannot fail. |
|
||
| No export begins `__wasmq`, and an export a host calls has exactly its type (§5.2, §5.3). | The first are the prepared module's names (§4.4); a host passes the second arguments of those types. |
|
||
|
||
WebAssembly 3.0 defines a deterministic profile: every NaN an instruction makes is
|
||
canonical and positive, and every relaxed vector instruction has a fixed behaviour. Even
|
||
under it, `memory.grow` and `table.grow` remain non-deterministic, to indicate resource
|
||
exhaustion, and the depth of calls an engine allows is an implementation limit. This
|
||
profile takes the deterministic profile's rule for NaNs (§4.5) and closes the others: no
|
||
vector instruction is taken, the table never grows, the memory grows only within the
|
||
maximum the module declares (§4.7), and the stack is weighed (§4.3).
|
||
|
||
The limits of the last rows are each below the limits of the validator and the native
|
||
engine the reference uses, and stay below them once a module is prepared: a test makes a
|
||
module of the most bytes, its largest body all calls, with the most exports, names, table
|
||
entries and memory and as many of each other item as fit beside them, and prepares,
|
||
compiles and runs it on every engine (`wai-wasmq/tests/engines.rs`).
|
||
|
||
### 3.3 Refusal codes
|
||
|
||
| code | the module or envelope |
|
||
|---|---|
|
||
| `W001` | is not a valid core module in Lime1, for a reason no other code names (a component, a malformed module, a body that does not end, an encoding of another feature) |
|
||
| `W002` | imports something |
|
||
| `W003` | has more than one memory, a 64-bit one, one with a page size of its own, one without a maximum or past 4096 pages; or (envelope) has modules with more than 4096 pages among them |
|
||
| `W004` | has a start function |
|
||
| `W005` | has a shared memory, global, table or type, or an atomic instruction |
|
||
| `W006` | has a vector instruction or type, relaxed or not |
|
||
| `W007` | has an instruction or a type of a feature outside Lime1 that no other code names |
|
||
| `W008` | has more than one table, one not of `funcref`, past 2^16 entries, or an instruction that reads or changes it other than `call_indirect` |
|
||
| `W009` | has a function whose parameters, locals and operands can pass 4096 |
|
||
| `W010` | is past a size the last rows of §3.2 give; or (envelope) has modules of more than 512 KiB among them |
|
||
| `W011` | has a data or element segment that does not fit where it is placed |
|
||
| `W012` | exports a name the profile reserves, or an export a host calls with another type than its own |
|
||
| `W013` | (envelope) is not one this profile writes, or is past its size (§2.3) |
|
||
| `W014` | (envelope) has a frame whose digest is not the one its contract pins |
|
||
| `W015` | (envelope) has callouts that are not one for each `extern` in order, a module no callout names, an export of another type than its `extern` lowers to, or a module the host cannot reach as §5.2 asks |
|
||
| `W016` | (envelope) has a program longer than 1 MiB, that the reader refuses, that calls nothing, with a gate outside §5.1's, or past a bound it is read within (§4.8) |
|
||
| `W017` | (envelope) has a run outside the profile's ranges, or past what §4.8 lets a run ask of its host |
|
||
|
||
A module or an envelope that breaks more than one rule is refused with the code of one
|
||
of them. Each refusal vector (§9) breaks exactly one.
|
||
`wai-quantum check-wasmq MODULE.wasm` holds a module to the profile and names the code.
|
||
|
||
## 4. Running a module the same everywhere
|
||
|
||
### 4.1 Instances
|
||
|
||
An instance is made from the module with no imports. Its memory starts at its initial
|
||
size, zeroed, with the active data segments copied in; its table holds the active element
|
||
segments; its globals hold their initial values. A host MUST be able to hold the memory's
|
||
maximum before it calls the instance (§4.7).
|
||
|
||
### 4.2 Fuel
|
||
|
||
Every call a host makes into an instance has a limit of fuel. Fuel is defined on the
|
||
module as written, not on any engine:
|
||
|
||
- A function's body is a sequence of **runs**. A run starts at the function's first
|
||
instruction and right after each `block`, `loop`, `if`, `else`, `end`, `br`, `br_if`,
|
||
`br_table`, `return`, `call`, `call_indirect` and `unreachable`; it ends with the first
|
||
of them that follows, or with the body.
|
||
- The **weight** of an instruction is 0 for `nop`, `drop`, `block`, `loop`,
|
||
`unreachable`, `return`, `else` and `end`, and 1 for every other. Entering a function
|
||
weighs 1.
|
||
- When execution reaches the start of a run, the run's weight (with 1 more for the first
|
||
run of a body) is charged. Before `memory.copy` and `memory.fill` execute, the byte
|
||
count they take is charged; before `memory.grow` executes, 1024 for each page it asks
|
||
for is charged, whether or not the growth succeeds: zeroing a page costs a host what
|
||
1024 instructions do.
|
||
- When a charge takes the fuel below zero, the call stops with `fuel_exhausted`.
|
||
|
||
So a charge happens at the start of a run, before any of the run's instructions, and a
|
||
call that traps in the middle of a run has been charged the whole run. On every path that
|
||
does not stop early, the fuel a call uses is the number of instructions it executed under
|
||
these weights, and one for each function it entered.
|
||
|
||
**Limits.** The contract gives three budgets. A callout's limit is `per_call`, and it
|
||
covers every call the host makes for it (§5.2). The limit of an instance's lifecycle
|
||
calls is `per_instance`, for each of them (§5.3). `per_job` is the run's: when less than
|
||
a call's limit is left of it, what is left is the call's limit. The fuel a call used is
|
||
its limit less what it left, and is taken from what is left of the run's; a call that
|
||
stops with `fuel_exhausted` used its whole limit.
|
||
|
||
The weights are the default operator costs of the native engine the reference uses, with
|
||
one for a byte of `memory.copy` or `memory.fill` and 1024 for a page of `memory.grow`. A
|
||
test holds the profile's fuel equal to that engine's own count, on the module as written,
|
||
over control flow, calls and memory instructions, with one difference it states: for
|
||
`memory.grow` the profile charges 1024 for each page asked for, and that engine's own
|
||
meter one for each page it gets (`wai-wasmq/tests/engines.rs`).
|
||
|
||
Fuel is a count of work, the same on every host. It is not a time: a host that must
|
||
answer within a deadline measures its own instructions per second and sets the budget.
|
||
|
||
### 4.3 The stack
|
||
|
||
Calls are bounded by weight, not by count, so that the bound holds for a frame of any
|
||
size on any engine. The **weight of a function's frame** is
|
||
|
||
```text
|
||
576 + its parameters + its results + its locals + its operands + the bytes of its entry in the code section
|
||
```
|
||
|
||
its parameters and results those of its type, its locals the ones it declares, its
|
||
operands the most values its body holds on the operand stack at once (as a validator
|
||
counts them after each instruction: a call's results among them), and its entry's bytes
|
||
the value of the entry's own size field. The **stack's weight** is zero
|
||
when a host calls an export. Before a `call` or `call_indirect` executes, the weight of
|
||
the function that holds the instruction is added to it; when the callee returns, the
|
||
same weight is taken off. A call that would take the stack's weight past **8 388 608**
|
||
(2^23) stops with `stack_exhausted`, before the instruction does anything else: a
|
||
`call_indirect` that is both past the stack and through an entry it cannot call stops
|
||
for the stack.
|
||
|
||
So the frames suspended at calls never weigh more than 2^23 together, and one more
|
||
frame runs on top of them. An interpreter that keeps a frame's parameters, locals and
|
||
operands in slots needs at most one slot for each unit. The reference's native engine
|
||
needs at most eight bytes for each unit: a test runs modules made to keep as much in a
|
||
frame as the largest body allows (locals held across a call, a new value held at every
|
||
step, the results of calls held on the operand stack across a call, the largest function
|
||
types), each recursing to the limit, on a stack of exactly that size, and shows a smaller
|
||
one fails (`wai-wasmq/tests/engines.rs`). It gives each call nearly twice that.
|
||
|
||
A function of a few instructions recurses some 14 000 deep; a function at the largest
|
||
body the profile takes, some 490.
|
||
|
||
### 4.4 The prepared module
|
||
|
||
A host MAY run a module with an engine that implements §4.2, §4.3 and §4.5 itself (the
|
||
reference interpreter does). The reference's native engine does not, so it runs the
|
||
**prepared module**: the module with every instruction kept byte for byte, and added
|
||
|
||
1. two mutable globals, after the module's own, exported as `__wasmq_fuel` (i64) and
|
||
`__wasmq_stack` (i32);
|
||
2. at the start of each run whose weight is not zero, the charge
|
||
`global.get F; i64.const w; i64.sub; global.set F; global.get F; i64.const 0; i64.lt_s; if; unreachable; end`;
|
||
3. before `memory.copy` and `memory.fill`, the same charge of the byte count, and before
|
||
`memory.grow`, of 1024 times the page count, each taken from the operand with
|
||
`local.tee` of one added `i32` local;
|
||
4. around each `call` and `call_indirect`, with `w` the weight of the function they are
|
||
in, `global.get S; i32.const w; i32.add; global.set S; global.get S; i32.const 8388608; i32.gt_u; if; unreachable; end`
|
||
before it and `global.get S; i32.const w; i32.sub; global.set S` after it;
|
||
5. after each instruction of §4.5, a call of an added function that returns its argument,
|
||
or the canonical NaN if the argument is a NaN;
|
||
|
||
with every custom section dropped. The host sets `__wasmq_fuel` to the call's limit and
|
||
`__wasmq_stack` to 0 before each call. When the prepared module traps at `unreachable`
|
||
with `__wasmq_fuel` below zero, the fault is `fuel_exhausted`; with `__wasmq_stack` past
|
||
2^23, `stack_exhausted`; otherwise the module's own `unreachable`.
|
||
|
||
The prepared module is one way to hold §4.2, §4.3 and §4.5, and its bytes are the
|
||
reference's own: this text does not fix them, and no receipt names them. What a
|
||
conforming runner must reproduce is what those sections define.
|
||
|
||
### 4.5 NaNs
|
||
|
||
The result of `f32` and `f64` `add`, `sub`, `mul`, `div`, `min`, `max`, `sqrt`, `ceil`,
|
||
`floor`, `trunc` and `nearest`, of `f32.demote_f64` and of `f64.promote_f32` is, when it
|
||
is a NaN, the canonical positive NaN (`0x7fc00000`, `0x7ff8000000000000`): WebAssembly
|
||
3.0's deterministic profile. Every other instruction already has one result everywhere.
|
||
A test shows that a module whose result depends on a NaN's bits reads what the host chose
|
||
when neither the preparation nor the engine canonicalises, and the canonical NaN when
|
||
either does (`wai-wasmq/tests/adversarial.rs`).
|
||
|
||
### 4.6 Engines
|
||
|
||
The reference runs every module on two engines and holds them to the same bytes:
|
||
|
||
- **native**: the repository's one pinned release of wasmtime, compiling the prepared
|
||
module with Cranelift, with NaN canonicalisation on, relaxed vector instructions fixed
|
||
and refused, vector instructions, tail calls and every proposal past Lime1 off, its
|
||
own fuel on as a backstop under the profile's, and a run's calls made on one thread with
|
||
a stack of the size §4.3 bounds;
|
||
- **interpreter**: `wai-wasmq`'s own, which runs the module as written and implements
|
||
§4.2, §4.3 and §4.5 itself, and which also runs on wasm32 (where the native engine
|
||
does not run). It also runs the prepared module, with its own metering off, as a check
|
||
of the preparation on the vectors; that use is held to none of §4.8's bounds.
|
||
|
||
`wai-quantum run ENVELOPE.wai --engine native|interpreter` chooses; the receipt is the
|
||
same.
|
||
|
||
### 4.7 Memory
|
||
|
||
`memory.grow` past the module's declared maximum returns −1, on every engine. Within the
|
||
maximum, it succeeds: a host that cannot grow the memory within the maximum (it has run
|
||
out of memory) MUST stop the run and seal nothing (§6.3), because what it would seal
|
||
would be its own condition, not the module's.
|
||
|
||
The modules of a bundle hold at most 4096 pages among them, by the maxima they declare
|
||
(`W003`), so that a 32-bit host holds any bundle's memory: a limit for each module alone
|
||
would leave the number of modules to decide which hosts can run an envelope.
|
||
|
||
### 4.8 What a run asks of its host
|
||
|
||
Fuel is the work of the modules. A run also asks work and memory of its host that no
|
||
fuel counts: reading the program, its steps on the tableau, a fresh instance's memory,
|
||
compiling its modules, the result. So that an envelope that opens on one host opens and
|
||
finishes on every host, a 32-bit one among them, a reader bounds all of it from the
|
||
bundle alone, before anything runs, and refuses past the bounds; nothing here differs by
|
||
engine. A receipt shows part of what these bounds count, and §6.5 holds a job to that part
|
||
(rules 29 to 32).
|
||
|
||
**Reading.** The program is read within these bounds, each checked as it is read, so that
|
||
a short program cannot make a reader hold or do more than a host has before it is refused
|
||
(`W016`):
|
||
|
||
| bound | |
|
||
|---|---|
|
||
| its text | 1 MiB (a reader holds some eighty bytes of tokens for each) |
|
||
| nesting | 64 levels in the text and 256 in all through the bodies calls read (`Q006`): a reader's stack. A statement, each statement that holds it, and each level of its expressions' trees (parentheses, operators, brackets, calls, casts and types) count one; a run of operators of one precedence and an `else if` chain each count one, at any length; an array has at most 7 dimensions |
|
||
| qubits | 32 768 |
|
||
| bits: the program's and its blocks' own together, any one value's, and those of any one array, its elements times each element's | 2^20 |
|
||
| steps, its loops unrolled | 2^21 |
|
||
| the bits its calls take and give, summed over its steps | 2^23 |
|
||
| the work of reading: one for each statement and loop iteration, for each value worked out one and one more for each bit or element it makes, and one for each bit declared | 2^26 |
|
||
|
||
Past one, the reader's code is `Q323`, or `Q111` for the qubits. **A run ends**: the
|
||
reader unrolls every loop, a `while` included, to the bounds it reads or is told
|
||
(`@wai.max_iterations`), so every jump in a program's steps goes forward.
|
||
|
||
**Memory.** A run's shots times the bits a shot holds (all the program's bits, its blocks'
|
||
own among them, though an outcome shows only the program's, §6.1) are at most 2^28
|
||
(`W017`). With the most memory (§4.7), the largest
|
||
envelope (§2.3), the most code (§3.2), the most qubits, bits and steps and the bits of
|
||
calls, these leave a run within a 32-bit host's address space at every limit at once,
|
||
which a test holds the reference to under wasm32 (`wai-wasmq/tests/sweep.rs`).
|
||
|
||
**Work.** A run's work is at most 2^42 operations on a word (`W017`), where, with `n` the
|
||
program's qubits and `r` = ⌈`n`/32⌉ (at least 1) the words of a tableau's run for one
|
||
qubit:
|
||
|
||
| for each shot | operations |
|
||
|---|---|
|
||
| its fresh tableau | 2·`n`·`r` |
|
||
| each gate | `r` |
|
||
| each measurement or reset | 2·`n`·`r` |
|
||
| each callout | 4096 for each call of the module's (its export, `cabi_realloc` for each list argument, and `cabi_post_<export>` after a list result), 4 for each bit it takes and gives, and 1 |
|
||
| each other step | 1 |
|
||
|
||
| for each instance, one for each shot, or one for the run when `state` is `job` | operations |
|
||
|---|---|
|
||
| each module, and each call of `wasmq-seed`, `init`, and two for each prior | 4096 |
|
||
| each byte of each module | 1 |
|
||
| each page of the memory a module starts with | 8192 |
|
||
|
||
| for the run | operations |
|
||
|---|---|
|
||
| each byte of each module, compiled | 131 072 |
|
||
| each unit of the most fuel it may be given, `per_job` | 8 |
|
||
|
||
A random measurement rewrites the tableau's rows that hold an X at its qubit; a determined
|
||
one's outcome is the sign of the product of up to `n` stabilizers, which the reference
|
||
finds a machine word of rows at a time without copying the tableau. Either reads the
|
||
tableau about once, which is the `n`·`r` of its weight. A page a module grows is paid for in fuel (§4.2), as
|
||
a page it starts with is counted here. This is a bound on a host's effort from the bundle
|
||
alone, not a measure of it: fuel is the profile's measure of the modules' work (§4.2).
|
||
The reference was measured at a 64th of the limit, each kind of work alone, on x86_64
|
||
Linux natively and under wasm32: at the limit it would take at most about three hours.
|
||
|
||
## 5. Callouts
|
||
|
||
### 5.1 In the program
|
||
|
||
A program declares each function it calls with `extern`, its arguments and its result
|
||
written as types:
|
||
|
||
```qasm
|
||
extern decode64(bit[4]) -> bit[9];
|
||
bit[4] s;
|
||
bit[9] corr;
|
||
s = measure a;
|
||
corr = decode64(s);
|
||
if (corr[0]) x d[0];
|
||
```
|
||
|
||
An argument is `bit[n]`, `uint[k]` or `int[k]` (k from 1 to 64); the result is `bit[n]`
|
||
or nothing. An argument is a bit register or an integer known on reading (a constant, a
|
||
loop's variable, an `input`); the result goes into a bit register, in a declaration or
|
||
an assignment, and a later condition reads it. Calls inside `for` loops unroll; calls
|
||
under `if` run only on the shots that take the branch.
|
||
|
||
**The gates.** This version runs the Clifford gates that take no angle: `id`, `x`, `y`,
|
||
`z`, `h`, `s`, `sdg`, `sx`, `sxdg`, `cx`, `cy`, `cz` and `swap`, and gates a program
|
||
defines from them, with measurement, reset and conditions on bits, on at most 32 768
|
||
qubits. A program with any other gate is refused (`W016`), whatever its angle: `rz(pi/2)`
|
||
acts as `s`, and is refused all the same, so that no reader decides in floating point
|
||
which gate a program holds. With these gates a state is a stabilizer tableau and every
|
||
step of a run is whole-number arithmetic (§6.1).
|
||
|
||
OpenQASM 3 schedules an `extern` call and lets it run concurrently with other
|
||
operations. This profile fixes one schedule: a call runs when the program reaches it, in
|
||
program order, and completes before the next statement; it is atomic. A program read
|
||
with any other reader of this repository keeps refusing an `extern` call (`Q125`).
|
||
|
||
### 5.2 Lowering
|
||
|
||
A callout has one of two forms, by its widths, as [`quantum-wasmq.wit`](quantum-wasmq.wit)
|
||
declares them:
|
||
|
||
- **scalar**, when every argument and the result have at most 64 bits: each lowers to one
|
||
`i64` (WIT `u64`), bit `i` of the register bit `i` of the integer, an `int[k]`
|
||
sign-extended. The export's type is `(i64, …) -> (i64)`, or `-> ()` with no result.
|
||
- **list**, otherwise: each `bit[n]` argument lowers to a `list<u8>` (an `i32` pointer and
|
||
an `i32` length) and the result to one `i32` that points to a pointer and a length;
|
||
`uint[k]` and `int[k]` arguments stay `i64`. The list's bytes are `ceil(n/8)` long, bit
|
||
`i` in byte `i/8` at bit `i % 8`, the rest of the last byte zero: the Component Model's
|
||
canonical ABI.
|
||
|
||
A host reaches a module's memory only as its export named `memory`. A module with a
|
||
list-form callout exports it, and `cabi_realloc` when the callout has a list argument
|
||
(`W015`). The exports a host calls have exactly these types (`W012`):
|
||
|
||
| export | type |
|
||
|---|---|
|
||
| `cabi_realloc` | `(i32, i32, i32, i32) -> (i32)` |
|
||
| `cabi_post_<export>` | `(i32) -> ()` |
|
||
| `wasmq-seed` | `(i64) -> ()` |
|
||
| `wasmq-prior` | `(i32, i32, i32) -> ()` |
|
||
| `init` | `() -> ()` |
|
||
|
||
A host makes a callout in these steps, each guest call under the callout's one limit,
|
||
its stack's weight starting at zero:
|
||
|
||
1. For each list argument, in order: it calls `cabi_realloc(0, 0, 1, len)`, charges
|
||
`len` fuel, and writes the bytes at the address returned.
|
||
2. It calls the export.
|
||
3. For a scalar result: a bit set at or above the result's width ends the run.
|
||
4. For a list result at address `r`: `r` is a multiple of 4; it reads a pointer and a
|
||
length at `r`; the length is `ceil(n/8)`; it charges that many fuel and reads the
|
||
bytes; no bit past the width is set in the last byte; then it calls
|
||
`cabi_post_<export>(r)` if the module exports it.
|
||
|
||
A step that cannot be done as written (bytes that do not fit in the memory at an
|
||
address, an address not a multiple of 4, another length, a bit past the width) ends the
|
||
run with `bad_result`, with the fuel used up to that step.
|
||
|
||
### 5.3 Lifecycle
|
||
|
||
Instances are made at the start of a shot, before its first step, one for each module in
|
||
the contract's order (each shot when `state` is `shot`, once at the first shot when it is
|
||
`job`); a fault in making one ends the run there, and no later module's is made. When an
|
||
instance is made, the host calls, in this order and each if the module exports it, each
|
||
under the limit `per_instance`:
|
||
|
||
1. `wasmq-seed(seed: u64)`, with the run's seed, or, when `state` is `shot`, the shot's
|
||
seed (§6.1);
|
||
2. `wasmq-prior(index: u32, bytes: list<u8>)` for each of the module's priors, in the
|
||
contract's order, the bytes passed as a list argument is (§5.2, step 1);
|
||
3. `init()`.
|
||
|
||
A module with priors exports `wasmq-prior`, `cabi_realloc` and its memory (`W015`).
|
||
|
||
### 5.4 Priors
|
||
|
||
A prior is a parameter file a module reads: a code's parity checks, a target register, a
|
||
noise model, a calibration. It travels in the envelope and is pinned in the contract, so
|
||
a module is never run against another prior than the one it was packed with (`W014`).
|
||
|
||
## 6. A run and its receipt
|
||
|
||
### 6.1 Shots and outcomes
|
||
|
||
A run goes shot by shot, in order. Each shot starts from `|0…0⟩` with every bit 0, and
|
||
has a **stream**: the outputs of SplitMix64 from the state `s XOR 0x51574d31`, where `s`,
|
||
the shot's seed, is SplitMix64's output for the state
|
||
`seed + (shot + 1) · 0x9e3779b97f4a7c15` (all sums modulo 2^64).
|
||
|
||
A measurement of a qubit in the computational basis has an outcome that is determined by
|
||
the state, or is 0 or 1 with equal chance; with §5.1's gates there is no other case. A
|
||
determined outcome is the outcome, and takes nothing from the stream. A random outcome
|
||
takes the stream's next value `x`, and is 1 when `x < 2^63`. A `reset` measures its qubit
|
||
the same way, without writing a bit, and applies `x` to it when the outcome is 1; a
|
||
reset of a qubit whose outcome is determined takes nothing from the stream.
|
||
|
||
A shot's outcome is its program's bits, `n` of them: every bit it declares at its top
|
||
level, whether or not anything writes it, numbered in the order the program declares them
|
||
(its first declared bit is bit 0, a register's bits in their order) and written bit
|
||
`n − 1` first; the counts sum the shots that read each outcome. A bit that a block or a
|
||
subroutine declares is its own (`Q322`): the steps read and write it, and no outcome holds
|
||
it. Each measurement names the bit it writes: a `measure` with no target bit is refused
|
||
in a program that calls externs (`Q324`), since its outcome is the bits it declares. A
|
||
program that measures nothing has its bits as its outcome all the same, those its calls
|
||
write and 0 for each bit nothing writes: no qubit is read for it.
|
||
|
||
### 6.2 Faults
|
||
|
||
A run stops at the first fault, seals what it ran, and reports:
|
||
|
||
| reason | |
|
||
|---|---|
|
||
| `fuel_exhausted` | a charge took the fuel below zero (§4.2); `budget` says whose limit it was: `call`, `instance`, or `run` when the limit was what was left of the run's |
|
||
| `stack_exhausted` | §4.3 |
|
||
| `unreachable`, `memory_out_of_bounds`, `table_out_of_bounds`, `indirect_call_null`, `indirect_call_type`, `integer_divide_by_zero`, `integer_overflow`, `invalid_conversion_to_integer` | the module trapped (WebAssembly 3.0, §4 Execution) |
|
||
| `bad_result` | §5.2 |
|
||
|
||
A fault has the members `reason`, `shot`, `phase` (`seed`, `prior`, `init` or `call`),
|
||
`module` (its name), `fuel` (what the stopped call used), `callout` (the `extern`'s
|
||
name) when the phase is `call`, and `budget` when the reason is `fuel_exhausted`, and no
|
||
other. The counts are of the shots that finished. A fault receipt is a receipt like any
|
||
other: it verifies, and running the envelope again reproduces it.
|
||
|
||
### 6.3 What is never sealed
|
||
|
||
A host that could not run the bundle seals nothing: a refused envelope (§2.3, §3.3), a
|
||
host that cannot hold the memory's maximum or grow within it (§4.7), an engine that
|
||
fails for its own reasons. These are the host's condition, not the bundle's, and a
|
||
receipt that recorded them would differ by host. Nothing a bundle holds can lead here:
|
||
what a bundle could cause is refused on reading (§2.3, §4.7, §4.8) or sealed as a fault.
|
||
|
||
### 6.4 The result
|
||
|
||
The job body is the engine's (`engine::job`), with exactly four members:
|
||
`{ "status", "method", "request", "result" }`. The request is
|
||
`{ "wasmq": <the contract>, "envelope_blake3" }`. `status` is `done` when the result's
|
||
`status` is `ok` and `fault` otherwise; `method` is the result's. The receipt signs the
|
||
request and the result, as [the provider protocol's §8.2](quantum-providers.md#82-how-it-is-sealed-and-how-a-job-is-read)
|
||
seals every job; `status` and `method` only repeat what is signed. They are the host's
|
||
word, unsealed: a verdict takes nothing from them, and a check notes where they, or any
|
||
other member of the body, differ from what is sealed (§6.5).
|
||
|
||
The result has exactly these members:
|
||
|
||
| member | |
|
||
|---|---|
|
||
| `format` | `"wai.quantum.wasmq.result/1"` |
|
||
| `profile` | `"wai.quantum.wasmq/1"` |
|
||
| `status` | `ok`, or `fault` when the run stopped |
|
||
| `method`, `qubits`, `shots`, `seed` | the contract's method, the program's qubits (§2.2), the contract's shots and seed |
|
||
| `shots_run` | the shots that finished |
|
||
| `counts` | outcome to shots (§6.1) |
|
||
| `callouts` | the callouts made, one that stopped the run among them |
|
||
| `fuel` | `{ "used", "instances", "budget": { "per_call", "per_job", "per_instance" }, "callouts": { name: { "calls", "fuel", "max" } } }`: all the fuel used, the part of it lifecycle calls used, the contract's budgets, and for each `extern` the run called, its calls, their fuel and the most one of them used (an `extern` it did not call has no entry) |
|
||
| `modules` | for each, in the contract's order: `{ "name", "blake3" }` |
|
||
| `transcript_blake3` | BLAKE3 over `wai:wasmq-transcript\x01` and, for each callout that returned, in order: its shot (u64), the index of its `extern` (u32), its input's length (u32) and bytes, its output's length (u32) and bytes, and its fuel (u64), little-endian, the input the arguments' bits in order and the output the result's, packed as a list's bytes are (§5.2); after a fault, the bytes `fault`, the shot (u64), the reason's bytes and the fault's fuel (u64) |
|
||
| `fault` | §6.2, when the run stopped |
|
||
| `accuracy` | `{ "class": "exact" }` |
|
||
| `energy` | `{ "unmetered": "the runner reads no energy meter; fuel is the profile's measure of work" }` |
|
||
| `receipt` | the engine's job receipt (`engine::receipt`): Ed25519 over BLAKE3 of the request and the result in RFC 8785 form |
|
||
|
||
**Result-equivalence.** Every conforming runner, on every host and every engine, gives
|
||
an envelope the same result: the same bytes in its RFC 8785 form, the receipt aside. So
|
||
the receipt's digest is the same everywhere, and under one key the whole job is.
|
||
|
||
### 6.5 Verifying
|
||
|
||
A wasmQ job is a job like any other: every verifier of the engine's job receipts reads it
|
||
and judges it by [the provider protocol's §8.4](quantum-providers.md#84-what-a-verifier-checks),
|
||
and so gives it one verdict, wherever it is checked. `wai-quantum verify` (and its
|
||
`--json` and `--lines`), the job service's `POST /v1/verify`, both MCP servers given the
|
||
job's text, the Python package, the desktop app and a page read the job from its text as
|
||
§8.2 reads every job's, so its seal and its numbers are held as every receipt's are. A
|
||
surface given the job already parsed (a view, an MCP server given its members) cannot see
|
||
what parsing loses, as the provider protocol's §9 says, and gives every other reason; the
|
||
Python package judges a dict as the text it writes for it. A result
|
||
whose `format` is `wai.quantum.wasmq.result/1` is a wasmQ job's, and §8.4 adds one reason
|
||
for it, `wasmq_form`: the job breaks a rule below. Only what is sealed (the request, and
|
||
the result less its receipt) and the form of its seal decide it. A signature shows that a key sealed a job and says
|
||
nothing of what the job holds, so without the envelope to run again these rules are what
|
||
hold a result to its contract and to itself: a key can seal any result of this form, and
|
||
only running the envelope again shows that a result is the one it gives.
|
||
|
||
In these rules a number's value is the double it reads as (§8.2). A *count* is a number
|
||
whose value is a whole number from 0 to 2^53 − 1, however it is written (`1`, `1.0` and
|
||
`1e0` are one count, sealed as one); "at most" and "equals" compare counts, and a rule on
|
||
a count fails when the number is not one. A *digest* is a string of 64 lowercase
|
||
hexadecimal digits. Two values are *equal* as JSON values are, numbers by their value and
|
||
an object's members in any order. "Exactly the members" means every one named and no
|
||
other; a value that is not an object has none. A member is *present* when its object names
|
||
it, whatever its value, `null` among them: "has no `fault`" and "has a `callout`" read
|
||
so. A list or a map named below that is not an
|
||
array or an object is read as an empty one, after its own rule has failed. `C` is the
|
||
request's `wasmq` (the contract), `run` is `C`'s `run`, `r` is the result and `F` is
|
||
`r`'s `fuel`. *The budget* `per_call`, `per_job` or `per_instance` is that member of `run`'s
|
||
`fuel`. The job breaks the form when any of these fails:
|
||
|
||
1. No number in the request, or in the result less its receipt, has a magnitude past
|
||
2^53 − 1.
|
||
2. The request has exactly the members `wasmq` and `envelope_blake3`, and
|
||
`envelope_blake3` is a digest.
|
||
3. `C` has exactly the members §2.2 gives: `profile`, `program`, `modules`, `callouts`
|
||
and `run`; its `program` exactly `blake3`, `bytes` and `inputs`; `run` exactly
|
||
`shots`, `seed`, `method`, `state` and `fuel`; and `run`'s `fuel` exactly `per_call`,
|
||
`per_job` and `per_instance`.
|
||
4. `C`'s `profile` is `wai.quantum.wasmq/1`.
|
||
5. The program's `blake3` is a digest; its `bytes` a count from 1 to 2^20; its `inputs` an
|
||
object each of whose values is a number whose value is whole and at most 2^53 − 1 in
|
||
size.
|
||
6. `C`'s `modules` is an array that is not empty. Each module has exactly the members
|
||
`name`, `blake3`, `bytes` and `priors`: `name` a string, `blake3` a digest, `bytes` a
|
||
count of at least 1, and `priors` an array each of whose priors has exactly the members
|
||
`name` (a string), `blake3` (a digest) and `bytes` (a count). The modules' `bytes` that
|
||
are counts add up to at most 2^19, so each is at most that; rule 31 bounds a prior's.
|
||
7. `C`'s `callouts` is an array that is not empty. Each callout has exactly the members
|
||
`extern`, `module` and `export`: `extern` and `export` strings, and `module` a count
|
||
less than the number of modules. No two callouts have the same `extern`, and each
|
||
module's index is the `module` of a callout.
|
||
8. `run`'s `shots` is a count from 1 to 2^20, its `seed` a count, its `method`
|
||
`stabilizer`, its `state` `shot` or `job`, and each of its fuel's three budgets a count,
|
||
`per_call` and `per_instance` each of at most 2^39 (§4.8; rule 30 holds `per_job` to
|
||
less).
|
||
9. `r` has exactly the members of §6.4 (`format`, `profile`, `status`, `method`,
|
||
`qubits`, `shots`, `shots_run`, `seed`, `counts`, `callouts`, `fuel`, `modules`,
|
||
`transcript_blake3`, `accuracy`, `energy` and `receipt`), and may have `fault`.
|
||
10. The receipt is an object whose `canon` is `rfc8785`: the first form (§8.2) is older than
|
||
this profile, and no host sealed a wasmQ job in it.
|
||
11. `r`'s `format` is `wai.quantum.wasmq.result/1`, its `profile` `wai.quantum.wasmq/1`,
|
||
its `accuracy` `{ "class": "exact" }` and its `energy` the one §6.4 gives; and its
|
||
`method`, `shots` and `seed` equal `run`'s.
|
||
12. `r`'s `qubits` is a count from 1 to 2^15 (a program declares a qubit at least, `Q100`),
|
||
and its `transcript_blake3` a digest.
|
||
13. `F` has exactly the members `used`, `instances`, `budget` and `callouts`; its `budget`
|
||
equals `run`'s `fuel`; and `used` is a count of at most the budget `per_job` (0 when
|
||
that is not a count).
|
||
14. `F`'s `callouts` is an object. Each of its members is named by the `extern` of one of
|
||
`C`'s callouts, and has exactly the members `calls`, `fuel` and `max`: `calls` a count
|
||
of at least 1; `max` a count of at most the budget `per_call` (0 when that is not a
|
||
count) and at most its own `fuel`; and `fuel` a count (rule 20 bounds it by its calls
|
||
and their `max`).
|
||
15. `F`'s `instances` and the `fuel` of each member of `F`'s `callouts`, added, are `F`'s
|
||
`used`; and the `calls` of each member, added, are `r`'s `callouts`. Each of them is a
|
||
count.
|
||
16. `r`'s `modules` has as many entries as `C`'s, each with exactly the members `name`
|
||
and `blake3`, its `blake3` a digest, and its `name` and `blake3` equal to those of
|
||
`C`'s module at its index.
|
||
17. `F`'s `instances` is a count of at most M × S × the budget `per_instance` (0 when that
|
||
is not a count): S is the sum, over `C`'s modules, of 2 and the number of its priors
|
||
(each instance's seed, its init and each prior, §4.2, §5.3); M, the instances made, is 1
|
||
when `run`'s `state` is `job`, whatever the status; for any other `state` it is `r`'s
|
||
`shots_run` (0 when it is not a count), plus 1 when `r`'s `status` is `fault`.
|
||
18. When `r`'s `status` is `ok`: `r` has no `fault`, and its `shots_run` equals its
|
||
`shots`. When it is `fault`: the fault has exactly the members `reason`, `shot`,
|
||
`phase`, `module` and `fuel`, and may have `callout` and `budget`; `reason` is one §6.2
|
||
names; `shots_run` is less than `shots`; `shot` equals `shots_run`; `phase` is `seed`,
|
||
`prior`, `init` or `call`; `module` equals the `name` of one of `C`'s modules; `fuel`
|
||
is a count, and in a phase other than `call` at most the budget `per_instance` (0 when
|
||
that is not a count: a call's is held by its callout's `max`, below, and rule 14; rules
|
||
20 and 25, with rule 15, hold either within `F`'s `used`); it has a
|
||
`callout` exactly when its phase is `call`, and that is the `extern` of one of `C`'s
|
||
callouts that names a member of `F`'s `callouts` whose `max` is at least the fault's
|
||
`fuel` (the call that stopped is one of the calls made, §6.4); and it has
|
||
a `budget` exactly when its reason is `fuel_exhausted`, and that is `run`, or `call` in
|
||
a call and `instance` otherwise. Any other `status` breaks the form.
|
||
19. `r`'s `counts` is an object; each of its names holds only `0` and `1` (the empty name
|
||
among them), all of one length; each of its values is a count of at least 1; and its values, added, are `r`'s
|
||
`shots_run`.
|
||
|
||
Rules 20 to 35 tie members to each other, as the writer does. In them *the fault* is `r`'s
|
||
`fault` when `r`'s `status` is `fault` and that is an object, and there is none
|
||
otherwise; a rule about the fault holds when there is none. A *call's fault* is a fault
|
||
whose `phase` is `call`, and a *lifecycle fault* one that has a `phase` other than `call`.
|
||
|
||
20. For each member of `F`'s `callouts`, let `s` be 1 when the fault is a call's fault
|
||
whose `callout` is that member's name, and 0 otherwise, and `e` the fault's `fuel` when
|
||
`s` is 1 (0 when that is not a count) and 0 otherwise. `T`, its `fuel` less `e`, the fuel
|
||
of its calls that returned, is not less than 0, and is from `k` to `k` × `max`, where `k`
|
||
is its `calls` less `s` (0 when that is less than 0): the call that stopped used the
|
||
fault's fuel, and each call that returned used at least 1 (§4.2: entering a function
|
||
weighs 1) and at most the most one call used. When `max` is more than `e`, the most was a
|
||
call's that returned, and each of the others used at least 1, so `T` is also at least
|
||
`max` + `k` − 1. `calls`, `fuel` and `max` are counts.
|
||
21. `F`'s `instances` is a count of at least `W` × `P` + `E`, the least that making the
|
||
instances took outside a stopped call (§5.3: a shot's instances are made module by
|
||
module in `C`'s order, each one's `wasmq-seed`, then its priors, then its `init`; passing
|
||
a prior calls `cabi_realloc`, at least 2 since it returns a value, then charges the
|
||
prior's bytes, then calls `wasmq-prior`, at least 1, §4.2, §5.2):
|
||
- `P`, what each set of instances took at least: the sum, over every prior of every one
|
||
of `C`'s modules, of 3 and the prior's `bytes` (0 when that is not a count), and 1
|
||
more when the fault is a lifecycle fault in phase `seed` or `init`: its module exports
|
||
that function, and each set called it;
|
||
- `W`, the sets made whole: `r`'s `shots_run` (0 when that is not a count), plus 1 when
|
||
there is a call's fault; when `run`'s `state` is `job` it is that or 1, whichever is
|
||
less;
|
||
- `E`, when the fault is a lifecycle fault, what the set it stopped took at least: the
|
||
same sum, of 3 and its `bytes`, over the priors of each module before the one the fault
|
||
names (the first of `C`'s modules with that `name`), and, when its phase is `init`, over
|
||
that module's own; 0 otherwise, and when no module has that `name`.
|
||
22. A call's fault's `module` equals the `name` of `C`'s module whose index is the
|
||
`module` of each of `C`'s callouts whose `extern` is the fault's `callout` (rule 7
|
||
allows one): a call runs in its callout's module (§2.2, §6.2). It fails when there is
|
||
no such callout, or no such module.
|
||
23. A fault whose `phase` is `prior` has as its `module` the `name` of one of `C`'s
|
||
modules whose `priors` is not empty: a prior is passed only to a module that has one
|
||
(§5.3).
|
||
24. A fault whose `reason` is `bad_result` has the `phase` `call` or `prior`: only these
|
||
pass bytes or take a result (§5.2).
|
||
25. A lifecycle fault's `fuel` is a count, and `F`'s `instances` (a count) is at least it
|
||
added to rule 21's `W` × `P` + `E`: what the stopped lifecycle call used is part of the
|
||
instances' fuel, beside what the calls before it took.
|
||
26. When `run`'s `state` is `job`, a lifecycle fault's `shot` is the count 0: the run's
|
||
one instance of each module is made at the first shot (§5.3).
|
||
27. A lifecycle fault whose `shot` is the count 0 comes with `r`'s `callouts` the count 0:
|
||
the first shot's instances are made before its first step (§5.3). (Rule 32 holds this
|
||
too, so rule 27 is never broken alone.)
|
||
28. A fault whose `reason` is `fuel_exhausted` used its whole limit (§4.2). When its
|
||
`budget` is `call`, its `fuel` is a count equal to the budget `per_call`; when it is
|
||
`instance`, to the budget `per_instance` (each 0 when it is not a count). When it is
|
||
`run`, `F`'s `used` is a count equal to the budget `per_job` (a count), and the fault's
|
||
`fuel` is a count, less than its own limit: the budget `per_call` in a call's fault, and
|
||
the budget `per_instance` in a lifecycle fault, each read as 0 when it is not a count (a
|
||
fault with no `phase` has no limit here, and breaks rule 18): the run's budget was the
|
||
limit only because less of it was left than the call's own.
|
||
29. When `r`'s `counts` has a name, the length of the longest, in characters (Unicode code
|
||
points), is at most 2^20, and `run`'s `shots` (0 when that is not a count) times that
|
||
length at most 2^28: a program holds at most 2^20 bits, and a run's shots times them are
|
||
at most 2^28 (§4.8). (Rule 19 holds each name to `0` and `1`, one byte each; a verifier
|
||
counts a name that breaks it in characters too.)
|
||
30. The part of a run's work that a receipt shows is at most 2^42 (§4.8):
|
||
`S` × 2·`n`·`w` + `I` × Σ (4096 × (1 + 2 × `p`) + `b`) + 131072 × Σ `b` + 4097 × `K` + 8 ×
|
||
the budget `per_job`. `S` is `run`'s `shots`; `n` is `r`'s `qubits`; `w` is ⌈`n`/32⌉, or
|
||
1 when that is 0; `I` is 1 when `run`'s `state` is `job` and `S` otherwise; each sum is
|
||
over `C`'s modules, `p` a module's number of priors and `b` its `bytes`; and `K` is `r`'s
|
||
`callouts`. Every number here that is not a count is read as 0. The host's work is at
|
||
least this: each shot's fresh tableau, each instance's module and its calls of
|
||
`wasmq-prior` and of `cabi_realloc` for it, the run's compiling, each call made (its
|
||
module's part, 4096, and its step, 1: a callout's step is counted in each shot whether or
|
||
not it runs) and the run's fuel.
|
||
31. The envelope the contract names is at most 2^27 bytes (§2.3): 20 bytes of magic and
|
||
lengths, its manifest (§2.1's members, in that order and with no space: 192 bytes and the
|
||
decimal digits of its `qubits`, `r`'s, and its `shots` and `seed`, `run`'s, each read as 0
|
||
when it is not a count), `C` in RFC 8785 form (§2.2; no bytes when the request has no
|
||
`wasmq`), and its frames (the program's `bytes`, each module's `bytes` and each prior's
|
||
`bytes`, each 0 when it is not a count), added, are at most 2^27.
|
||
32. `r`'s `callouts` is at most 2^21 × `H` (each 0 when it is not a count), where `H`, the
|
||
shots that ran steps, is `r`'s `shots_run` and 1 more when there is a call's fault: a
|
||
shot runs each of its steps at most once, every jump going forward, and a program has
|
||
at most 2^21 steps (§4.8), each call one of them.
|
||
33. Each prior passed whole fits within one lifecycle call's limit: 3 and its `bytes` (0 when
|
||
that is not a count) are at most the budget `per_instance` (0 when that is not a count),
|
||
for every prior of every one of `C`'s modules when `W` (rule 21) is at least 1, and for
|
||
each prior `E` counts otherwise. Passing a prior is one stretch under one limit:
|
||
`cabi_realloc`, its bytes and `wasmq-prior` (§5.2, §5.3).
|
||
34. A fault's `fuel`, when it is a count, is at least what its `reason` shows was charged
|
||
(§4.2), when it has one: 1 for every reason but `fuel_exhausted`, one §6.2 does not name
|
||
or `null` among them (a call stops any other way only after its entry was charged); 2
|
||
for `bad_result` (a value came back: the entry, and an instruction that made it); and 788
|
||
for `stack_exhausted`. A frame weighs at most
|
||
576 + 4096 + 2^8 + 2^14 = 21 312 (§3.2, §4.3), so the stack's weight passes 2^23 only at
|
||
a call in the 394th frame entered or deeper, and each frame entered was charged at least
|
||
2: its entry, and the run that holds its call.
|
||
35. When no call returned, the transcript is what §6.4 gives such a run: when `r`'s
|
||
`callouts` is 0, or 1 and the fault is a call's fault (each a count), `r`'s
|
||
`transcript_blake3` is BLAKE3 over the bytes `wai:wasmq-transcript` and the byte 0x01
|
||
and, when there
|
||
is a fault, the bytes `fault`, the fault's `shot` (u64), its `reason`'s UTF-8 bytes and
|
||
its `fuel` (u64), little-endian (`shot` and `fuel` each 0 when it is not a count, and
|
||
`reason` no bytes when it is not a string).
|
||
|
||
**What the rules leave free, and why.** What else the writer fixes, the rules leave free,
|
||
each for the reason given, and only running the envelope again (`--bundle`) holds a job to
|
||
it:
|
||
|
||
- the `transcript_blake3`'s value once a call returned: it digests each call's input and
|
||
output, which no receipt holds (rule 35 holds it when none returned); and each `blake3`
|
||
against the bytes it pins, which no receipt holds;
|
||
- `r`'s `qubits` within rule 12, the outcomes' length within rule 29, and the number of
|
||
callouts within rules 30 and 32, against the program: its text is not sealed, only its
|
||
digest and length, so how many qubits and bits it declares, and how many calls each shot
|
||
makes, are its own;
|
||
- which shot read which outcome, and the counts' spread: only the run shows them;
|
||
- whether a module exports `wasmq-seed` or `init`, but for the one a fault in that phase
|
||
names (rule 21): only its code shows it. So a fault in phase `seed` or `init` may name any
|
||
module;
|
||
- the instances' fuel within rules 17, 21 and 25: what each lifecycle call used past its
|
||
least, and how far the module a fault names got, are its code's;
|
||
- the fuel of each call within rules 14, 18, 20, 28 and 34, and a fault's fuel within rules
|
||
18, 20, 21, 25, 28 and 34: where in its work a call traps, and how many bytes its arguments
|
||
pass, are its module's and its program's;
|
||
- a trap's `reason` against its phase: any guest call can trap any way, and only
|
||
`bad_result` is the host's (rule 24);
|
||
- the rest of §4.8's bounds (the steps of a shot past rule 32's count, the gates and
|
||
measurements, the bits of calls, the pages a module starts with, the program's reading):
|
||
each counts what only the program or the modules hold, so rules 29 to 32 take what a
|
||
receipt shows, and a job a writer can write never breaks them;
|
||
- the least length of a program and of a module, and the spelling of an `extern`: a program
|
||
that packs declares an `extern`, a qubit and a call, a module that packs has a type, a
|
||
function, an export and its code, and an `extern` is a name the reader reads, its letters
|
||
any of Unicode's. So each has a least length, and an `extern` a form, that a rule could
|
||
state; but they follow from the grammar, WebAssembly's binary format and Unicode's tables
|
||
rather than from anything a run does, bound nothing a host does, and a rule at them would
|
||
hold every verifier to those tables. Rules 5, 6 and 7 hold the program's and a module's
|
||
`bytes` to at least 1 and an `extern` to a string, and leave the rest free.
|
||
|
||
Beside §8.4's reasons a verifier of the engine states, for a wasmQ job, `wasmq`:
|
||
`outcome`, the sealed result's `status`; `problems`, what breaks each rule the job breaks,
|
||
in words, each naming its rule first (`rule 19: ...`), the first 64 of them and then how
|
||
many more; and `notes`, where the host's unsealed words
|
||
differ from what is sealed or are missing ("the host's unsealed status says `done`; the
|
||
sealed result says `fault`"). A note is no part of the verdict: the verdict is the sealed
|
||
content's alone, so that every surface that checks a job gives the same one.
|
||
|
||
`wai-quantum verify JOB.json` prints the receipt's check, the outcome and each problem and
|
||
note; `--json` gives the verdict as JSON. `--lines` reads a file of one job a line, each
|
||
line on its own as a job's text (a line that is not UTF-8, or blank, is that line's
|
||
`unreadable`; the newline that ends the file ends its last line), and gives one verdict a
|
||
line, each a job's whatever its kind; a file with no line is refused. A job found
|
||
unreadable, no job, with no receipt, or longer than the surface reads (`too_large`) is
|
||
reported alone (§8.4), whatever kind of job it says it is: `verify` says that its form was
|
||
not judged, and runs no envelope for it. `verify` reads at most 384 MiB and 2^22 values
|
||
(§8.4), more than any job holds, in about 1 GiB of memory at most on every host: a file past the
|
||
bytes is `too_large` by its size, before it is read, and one past the values once read,
|
||
before it is parsed; a file of lines past the bytes is refused, and a line past the values
|
||
is that line's `too_large`. With `--bundle ENVELOPE.wai` it also checks
|
||
that the envelope is the one the receipt binds (its BLAKE3 is the request's
|
||
`envelope_blake3`, and its contract is `C`), and runs it again here: the result must be
|
||
the same, byte for byte, the receipt aside; with `--json` the verdict then also says
|
||
`bundle` and `reproduced`, and is valid only when both hold. A wasmQ job carries no
|
||
history, so `--history` is refused for one.
|
||
|
||
## 7. On hardware
|
||
|
||
**The bring-your-own-quantum boundary is unchanged.** WAI Quantum never pays for or
|
||
resells hardware time; a person's providers speak `wai.quantum.provider/1`
|
||
([quantum-providers](quantum-providers.md)), whose program is a fixed sequence of native
|
||
gates measured at the end. A wasmQ bundle calls its modules between gates, which that
|
||
protocol does not carry, so `run ENVELOPE.wai --provider NAME` is refused. A wasmQ run
|
||
simulates the quantum part on the host that runs it: the person's machine (the command line,
|
||
its MCP server, the Python and npm packages, the Studio and the desktop app), or the job
|
||
service, which runs an envelope as a job (`POST /v1/wasmq`, the MCP tool `run_wasmq`) within
|
||
its own limits, refusing one past them before it runs rather than stopping it part way, and
|
||
seals the job with its key; a second-version run's energy there is what the service read of
|
||
the worker that ran it (§11.5).
|
||
|
||
**Nothing here has run a module beside a quantum processor.** This repository has run
|
||
wasmQ modules only on the hosts its tests name. What holds for a module on a machine
|
||
that runs classical code between gates, and what does not:
|
||
|
||
- A scalar-form module (§5.2) is a function of integers that imports nothing. Whether a
|
||
given machine takes it is that machine's to say.
|
||
- The quantum part's measurements on hardware are not reproducible, so a hardware run
|
||
has no single result to reproduce. Its classical part does: given the inputs each call
|
||
received, a module gives the same outputs and the same fuel on every host, so a
|
||
record of the calls (`run --transcript`) replays through the modules alone and is
|
||
checked call by call (`run ENVELOPE.wai --replay TRANSCRIPT`), each of a shot the run
|
||
has, in the order of its shots. Each call shows only itself; that a record is the whole
|
||
of a run's shows in its digest, which a replay states and the receipt of a run that ended
|
||
`ok` seals as `transcript_blake3`. A machine that ran the module would have to report
|
||
those inputs and outputs for anyone to check them.
|
||
- Fuel is not time (§4.2). A real-time deadline is the machine's; the profile states
|
||
the work.
|
||
|
||
## 8. Reference modules
|
||
|
||
Two modules of record are built from code the engine already has, for
|
||
`wasm32-unknown-unknown` with Lime1 (`wai-wasmq/modules/`, `build.sh`), and committed in
|
||
[`../quantum-conformance/wasmq/modules/`](../quantum-conformance/wasmq/modules/) with
|
||
the digests of the sources that made them. rustc's wasm32 output depends on the host it
|
||
runs on, so they are built on x86_64 Linux, where CI rebuilds them and compares.
|
||
|
||
### 8.1 The qLDPC decoder
|
||
|
||
`quantum_qec`'s decoders: union-find matching for a code whose qubits each lie in at most
|
||
two checks (the surface code), Relay-BP in fixed-point arithmetic otherwise (the
|
||
bivariate-bicycle codes), as the engine itself chooses. Prior 0 is a code's parity
|
||
checks in `CssCode::check_matrix_bytes`'s form. Exports: `decode` (list) and `decode64`
|
||
(scalar). Tests hold every result to the same functions compiled natively, on every
|
||
weight-two error of the distance-3 surface code and on random errors of the [[72,12]]
|
||
bivariate-bicycle code (`CssCode::gross()`), and run memory rounds of the distance-3 and
|
||
distance-5 surface codes and of that code (a 129-qubit circuit) with the decoder called
|
||
between the syndrome and the correction, each shot ending in the code, on every engine
|
||
with the same bytes.
|
||
|
||
### 8.2 The atom rearrangement planner
|
||
|
||
`quantum_atom::solve`, an exact Hungarian assignment of loaded atoms to target sites.
|
||
Prior 0 is the target register in `target_bytes`'s form. Exports: `plan` (list) and
|
||
`plan64` (scalar). A circuit loads each trap with probability ½, measures the occupancy,
|
||
calls the planner, applies its moves and reads the array: every shot whose plan is
|
||
feasible fills the target, and atoms are moved, never made or lost.
|
||
|
||
## 9. Conformance
|
||
|
||
[`../quantum-conformance/wasmq/`](../quantum-conformance/wasmq/) holds:
|
||
|
||
- `envelopes/`: envelopes, each with the result it MUST give on every engine and every
|
||
host (`vectors.json`, `runs`): the status, the fault, the fuel, the transcript's
|
||
digest, the result's digest, the receipt's digest and, under the vectors' key, the
|
||
whole job's digest. Among them are runs that end in every fault reason §6.2 names, in
|
||
every phase and of every budget; callouts of the list form, with a `cabi_post` call and
|
||
with each way a list result is not one; a signed argument; two modules made in order; a
|
||
program that declares no qubit and names physical ones, its bits in two registers; a
|
||
program that measures nothing; one with bits nothing writes, and one whose block holds a
|
||
bit of its own, which pin §6.1's outcome; and a program whose resets and measurements pin
|
||
§6.1's stream;
|
||
- `refuse/`: modules and envelopes a runner MUST refuse, each with its code
|
||
(`vectors.json`, `refuse`), at least one for every code of §3.3, each one defect away
|
||
from a module or an envelope that is accepted; the modules also as WebAssembly text
|
||
where they have one. An envelope past §2.3's size has no vector, for its size: a test
|
||
in `wai-wasmq/tests/sweep.rs` makes one;
|
||
- `modules/`: the modules of record (§8).
|
||
|
||
Six wasmQ jobs the command line sealed are among the job receipts of
|
||
[`../quantum-conformance/receipts/`](../quantum-conformance/receipts/) (`jobs/cli-wasmq-*`):
|
||
a run of the surface-code round, one of a single instance for the whole run, and faults in
|
||
a call, of the run's budget, in an instance's init and in a prior. Beside them are jobs
|
||
crafted from them, sealed again, each of rules 2 to 31 broken alone by one at least, so the
|
||
two verifiers of that corpus, which share no code, are held to each other on `wasmq_form`
|
||
and on the rules each states broken, and on every change of one member of each of the six.
|
||
|
||
`wasmq_vectors verify wai/quantum-conformance/wasmq` checks them on every engine of the
|
||
build and prints a digest of what it computed (each run's job and each refusal's code),
|
||
the same on every host that conforms.
|
||
|
||
## 10. Versioning
|
||
|
||
`wai.quantum.wasmq/1` is this text. A change to anything that moves a result's bytes or
|
||
a module's fuel (the weights, the runs, the stack's weights, the lowering, the stream,
|
||
the transcript, the result's members) is a new profile version, and a receipt names the
|
||
version it was made under.
|
||
|
||
This version leaves out, and a later one may add under a name of its own (the second
|
||
version, §11, adds the first three in part):
|
||
|
||
- gates that take an angle and gates outside the Clifford group, which need a method
|
||
whose arithmetic this text would have to fix bit for bit. The state-vector method,
|
||
which the engine runs in floating point, is not part of this version for that reason:
|
||
`stabilizer` is its one method (§2.2);
|
||
- floating-point arguments and results of a callout;
|
||
- noise, and observables other than the counts.
|
||
|
||
## 11. The second version (`wai.quantum.wasmq/2`)
|
||
|
||
`wai.quantum.wasmq/2` is this text with the changes of this section; what §11 does not
|
||
change holds for it as written. A runner of the second version runs a first-version
|
||
envelope (its contract's `profile` `wai.quantum.wasmq/1`) as the first version runs it, and
|
||
seals the same result: the vectors of §9 hold under it, byte for byte. The reference is
|
||
`wai-wasmq` (`src/exact.rs` beside the files §0 names) and the engine's `engine::wasmq2`.
|
||
|
||
### 11.1 The envelope and the callouts
|
||
|
||
The envelope is §2's. The contract's `profile` is `wai.quantum.wasmq/2`, and its `run`'s
|
||
`method` is `stabilizer` (the tableau of §5.1 and §6.1) or `exact` (§11.3). A writer that
|
||
is given no method writes `stabilizer` when every gate of the program is one of §5.1's, and
|
||
`exact` otherwise; a reader refuses a method the program's gates do not fit (`W016`).
|
||
|
||
A callout's argument may also be `angle[k]`, `k` from 1 to 64: an angle of `m` of `2^k`
|
||
parts of a turn, `2πm/2^k`, as OpenQASM 3's `angle[k]` holds it, its bits those of `m`, the
|
||
lowest first. It lowers as a `uint[k]` does (§5.2): one `i64` that holds `m`, zero-extended,
|
||
in either form, WIT `u64` ([`quantum-wasmq.wit`](quantum-wasmq.wit), `angle-callout`). Its
|
||
value is
|
||
|
||
- a bit register of `k` bits, read as the run writes them: a measurement's, or a callout's
|
||
result; or
|
||
- an angle known on reading: a sized `angle[w]` value by its own bits, or a real value read
|
||
as §11.2 reads an angle, as `m'` of `2^32` parts of a turn.
|
||
|
||
Either way it is a whole number of `2^-k` turns, or the program is refused (`W016`): with
|
||
`w` the bits it is known to (`32` for a real value), `m` is the value shifted up by `k − w`
|
||
when `w ≤ k`, and when `w > k` its `w − k` lowest bits must be zero and `m` is the value
|
||
shifted down by them. Nothing is rounded to fit, and no floating-point value crosses into a
|
||
module. In the transcript (§6.4) an angle argument's `k` bits are the input's bits as any
|
||
argument's are. The first version refuses a program with an angle argument (`W016`).
|
||
|
||
A callout's result is still `bit[n]` or nothing (§5.1). A module that computes a rotation
|
||
returns its angle's numerator as bits, and the program applies it as the engine decomposes
|
||
any dyadic phase, one phase gate under a condition for each bit (`if (th[0]) p(pi / 4) q;`
|
||
for the eighths of a turn, and so on): a run of feedback with non-Clifford steps, and no
|
||
angle read back as a float (vector `v2-feedback-rotation.wai`).
|
||
|
||
### 11.2 Gates and angles
|
||
|
||
The `exact` method runs §5.1's gates and `t`, `tdg`, `p` (`phase`), `rz`, `rx`, `ry`, `ch`,
|
||
`cp` (`cphase`), `crz`, `rzz`, `ccx`, `ccz` and `cswap`, and gates a program defines from
|
||
them, with measurement, reset and conditions on bits, on at most 26 qubits (§11.3). A
|
||
program with any other gate (`u`, `cu`, `crx`, `cry`, `iswap`, `rxx`, `ryy`) is refused
|
||
(`W016`).
|
||
|
||
**An angle.** A gate's angle `θ` is the binary64 value the reader computes for its
|
||
expression: OpenQASM 3's arithmetic in IEEE 754 binary64, each operation rounded to
|
||
nearest with ties to even, and the reader's functions (`sin`, `cos`, `arccos`, `exp`, `ln`
|
||
and the rest) its own implementations, which give the same bits on every host
|
||
(`wai-quantum`, `repro`). It is read as `m` of `2^32` parts of a turn by the engine's rule
|
||
(`qasm::dyadic`), in binary64:
|
||
|
||
1. `t = (θ / τ) mod 1`, `τ` the binary64 nearest 2π and `mod` the Euclidean remainder;
|
||
2. for `j = 0, 1, …, 32`, the first `j` with `|t·2^j − r_j| ≤ 10^-14·2^j`, where `r_j` is
|
||
`t·2^j` rounded to the nearest whole number with halves away from zero, gives
|
||
`m = (r_j mod 2^j)·2^(32−j)`; when none does, the angle is not dyadic;
|
||
3. and the angle is `m` only when it is that angle to within its double's own rounding
|
||
(`qasm::dyadic_exact`): with `a = m/2^32` and `g = min(|t − a|, 1 − |t − a|)`,
|
||
`g ≤ 16·2^-52·|θ/τ|`.
|
||
|
||
An angle that fails 2 or 3 is refused (`W016`): an angle the engine's own exact method would
|
||
move to its grid, or run as no phase, runs on no wasmQ host. Each step is exact or one
|
||
correctly rounded operation, so every host reads the same `m`; the reader's floats end here.
|
||
|
||
**The lowering.** Each gate is the engine's dyadic gates (`qasm::Program::dyadic`, the gate
|
||
set of [`quantum-sim`](quantum-sim.md) §2.2), in this order, with `phase(λ)` on `t` under
|
||
the controls `C` one `P(32 − j)` on `t` under `C` for each set bit `j` of `λ`'s `m`, `j`
|
||
from 0 to 31 in order, and none when `m` is 0:
|
||
|
||
| gate | dyadic gates, in order |
|
||
|---|---|
|
||
| `id` | none |
|
||
| `x`, `y`, `z`, `h`, `s`, `sdg`, `t`, `tdg` on `q` | `X`, `Y`, `Z`, `H`, `S`, `S†`, `T`, `T†` on `q` |
|
||
| `sx`, `sxdg` on `q` | `H`, then `S` (`S†` for `sxdg`), then `H`, on `q` |
|
||
| `p(λ)`, `rz(λ)` on `q` | `phase(λ)` on `q` |
|
||
| `rx(θ)` on `q` | `H`; `phase(θ)`; `H` |
|
||
| `ry(θ)` on `q` | `S†`; `H`; `phase(θ)`; `H`; `S` |
|
||
| `cx`, `cy`, `cz`, `ch` on `c, t` | `X`, `Y`, `Z`, `H` on `t` under `c` |
|
||
| `cp(λ)` on `c, t` | `phase(λ)` on `t` under `c` |
|
||
| `crz(θ)` on `c, t` | `phase(−θ/2)` on `c`; `phase(θ)` on `t` under `c` |
|
||
| `swap` on `a, b` | `X` on `b` under `a`; `X` on `a` under `b`; `X` on `b` under `a` |
|
||
| `rzz(θ)` on `a, b` | `X` on `b` under `a`; `phase(θ)` on `b`; `X` on `b` under `a` |
|
||
| `ccx`, `ccz` on `a, b, c` | `X`, `Z` on `c` under `a, b` |
|
||
| `cswap` on `a, b, c` | `X` on `b` under `c`; `X` on `c` under `a, b`; `X` on `b` under `c` |
|
||
|
||
`−θ/2` is the binary64 the reader's arithmetic gives, read by the same rule. `rz`, `rx`,
|
||
`ry` and `rzz` are their phase forms: each drops the global phase `e^{−iθ/2}` of its matrix,
|
||
which no count shows. A gate a program defines, or writes with a modifier, is the gates the
|
||
reader expands it to.
|
||
|
||
### 11.3 The method `exact`
|
||
|
||
**The state.** `2^n` amplitudes for a program of `n` qubits, each a pair `(re, im)` of
|
||
64-bit whole numbers at `2^-30`, with qubit `q` at bit `q` of an amplitude's index; a shot
|
||
starts from `|0…0⟩`, the amplitude at index 0 `(2^30, 0)` and every other `(0, 0)`. A
|
||
dyadic gate updates it as [`quantum-sim`](quantum-sim.md) §3.0 gives, with the phase table
|
||
of `wai.quantum.circuit2` (§3.2 there, Appendix B.2): the engine's own arithmetic, its
|
||
error bound the one stated there (§5 there, about 1.71 units of `2^-30` a gate).
|
||
|
||
**A measurement** of qubit `q` takes the state's weight `W`, the sum of `re² + im²` over
|
||
every amplitude, and `W1`, the same sum over the amplitudes whose index has bit `q` set,
|
||
each exactly (a 128-bit sum holds them). The outcome is 0 when `W1` is 0 and 1 when `W1` is
|
||
`W`, and neither takes anything from the shot's stream (§6.1). Otherwise it takes the
|
||
stream's next value `x` and is 1 when `⌊x·W / 2^64⌋ < W1`, an exact comparison of whole
|
||
numbers (the product of a 64-bit and a 127-bit number), so that `x/2^64` falls below
|
||
`W1/W`. Then every amplitude of the other outcome becomes `(0, 0)`, and with `K` the weight
|
||
kept (`W1`, or `W − W1`) and `s = ⌊√K⌋`, each component `v` of every amplitude kept becomes
|
||
`⌊(v·2^30 + ⌊s/2⌋) / s⌋`, the floor of the quotient. An outcome the state determines leaves
|
||
the state as it is.
|
||
|
||
**A reset** measures its qubit as a measurement does, without writing a bit, and when it
|
||
read 1 exchanges each pair of amplitudes whose indices differ only in that bit (`X`).
|
||
|
||
Shots, outcomes, counts and the stream are §6.1's. Where a random outcome's two weights
|
||
are equal, `W1` is exactly `W/2` and `⌊x·W/2^64⌋ < W/2` holds exactly when `x < 2^63`: the
|
||
draw is the tableau's, value for value, so a Clifford program whose every random outcome
|
||
is even gives the same counts by either method (vector
|
||
`v2-exact-of-a-clifford-program.wai`, `wai-wasmq/tests/exact.rs`).
|
||
|
||
**Its accuracy.** The counts are those of this arithmetic: the same on every host, and the
|
||
ideal circuit's only to within the fixed point's error, which grows with the gates, as
|
||
quantum-sim §5 states. A result says so by its class, `bit-exact` (§11.4).
|
||
|
||
**The ceiling.** A program the `exact` method runs declares at most **26** qubits
|
||
(`W016` past it), the engine's own exact method's limit. Its state is then 1 GiB, the
|
||
largest state a 32-bit host makes as one allocation: 27 qubits are 2 GiB, past the most one
|
||
object may span there, and the reference's attempt under wasm32 ends with a capacity
|
||
overflow. Measured on the reference, on an arm64 macOS host and an x86_64 Linux host, each
|
||
natively and under wasm32-wasip2 on the pinned wasmtime, both hosts shared with other work
|
||
(a one-minute load of about two a core):
|
||
|
||
- **Memory.** Under wasm32, states of 24, 25 and 26 qubits are made and run beside 512 MiB
|
||
of other memory, and 27 is not (`probe_the_state_past_the_ceiling`). The largest bundle
|
||
the bounds admit, by the `exact` method at 26 qubits (its state, a module's 256 MiB of
|
||
memory, an envelope of 128 MiB and 2^20 bits a shot) runs under wasm32 in 8.4 s, the
|
||
wasmtime process's resident set 2.1 GB (`wai-wasmq/tests/sweep.rs`,
|
||
`the_largest_exact_bundle_fits_a_32_bit_host`, which CI runs under wasm32).
|
||
- **Time,** in nanoseconds an amplitude at 26 qubits, each dyadic gate alone
|
||
(`wai-wasmq/tests/exact.rs`, `measure_the_exact_method`; the same within a few percent at
|
||
20 to 24):
|
||
|
||
| | arm64, native | arm64, wasm32 | x86_64, native | x86_64, wasm32 |
|
||
|---|---|---|---|---|
|
||
| `H` | 2.1 | 11.0 | 2.8 | 20.7 |
|
||
| `T` | 1.1 | 5.3 | 1.5 | 10.7 |
|
||
| `P(k)` | 1.5 | 5.3 | 1.6 | 10.6 |
|
||
| `X` under a control | 0.7 | 2.8 | 0.7 | 1.4 |
|
||
| `X` under two | 0.3 | 0.3 | 0.6 | 0.6 |
|
||
| a measurement | 0.8 | 4.6 | 1.2 | 9.8 |
|
||
|
||
So a gate on 26 qubits takes 0.14 s natively and up to 1.4 s under wasm32, and §11.7
|
||
weighs a gate's amplitude at 8 of §4.8's units: a run at the bound of 2^42 then takes at
|
||
most about three hours on the slowest of these (`H` alone, under wasm32 on x86_64), as a
|
||
first-version run at the bound does (§4.8).
|
||
|
||
### 11.4 The result
|
||
|
||
A second-version result is §6.4's, with these members changed:
|
||
|
||
| member | |
|
||
|---|---|
|
||
| `format` | `"wai.quantum.wasmq.result/2"` |
|
||
| `profile` | `"wai.quantum.wasmq/2"` |
|
||
| `method` | the contract's: `stabilizer` or `exact` |
|
||
| `accuracy` | `{ "class": "exact" }` for `stabilizer`; `{ "class": "bit-exact" }` for `exact` |
|
||
| `energy` | §11.5 |
|
||
|
||
**Result-equivalence** (§6.4) holds for the result less its `energy` and its receipt: every
|
||
conforming runner, on every host and every engine, gives an envelope those same bytes. The
|
||
energy is what the host read of its own run; `verify --bundle` runs the envelope again and
|
||
compares the rest. A runner that reads no meter writes the unmetered form, which is the same
|
||
everywhere, so the vectors (§9, §11.8) pin whole results.
|
||
|
||
### 11.5 Energy
|
||
|
||
`energy` is `{ "run", "callouts" }`. `run` is the run's figure; `callouts` has one member
|
||
for each `extern` the run called, the names of `fuel`'s `callouts`, each that callout's
|
||
figure. A **figure** is either
|
||
|
||
- the **unmetered form**, exactly `{ "unmetered": why }`, `why` a non-empty string; or
|
||
- a **reading**: an object with `joules_micro`, a count of at least 1; `energy_provenance`,
|
||
a reading's class of [energy-measurement](energy-measurement.md) §2
|
||
(`CalibratedInstrument`, `OnChipCounter` or `HwShunt`; an estimate is no reading); and
|
||
`energy_uncertainty`, whose `relative_ppm` and `window_us` are counts of at least 1 and
|
||
whose `relative_ppm` times `joules_micro` is at least 288 676 (§2.3 there: the rounding of
|
||
a reading to whole microjoules). It may say more (what it covers and leaves out, its
|
||
attribution, its source).
|
||
|
||
A figure never reads zero joules: a run that read nothing is unmetered, and says why.
|
||
|
||
**What the reference reads.** `wai-quantum run ENVELOPE.wai` runs a second-version envelope
|
||
in a process of its own and reads it whole once it has exited, on the meter its host has:
|
||
on macOS the process's CPU energy (`proc_pid_rusage`, `ri_energy_nj`), attributed to the
|
||
process, read after it exits and before it is reaped; on Linux the processor packages'
|
||
counters (`/sys/class/powercap`), the whole packages over the run's window, attributed to
|
||
nothing; the figures are the engine's (`engine::energy::figure`, `package_figure`), as the
|
||
job service seals them. Where the host reads no meter (no counter, no privilege to read
|
||
one, a counter that is not live, a wasm32 build), `run` is the unmetered form with that
|
||
reason. A library run (`wai_wasmq::run`) reads no meter: its `run` says
|
||
`the runner read no energy meter for this run`.
|
||
|
||
**Each callout's** figure is the unmetered form, saying that no meter resolves one call: a
|
||
call takes microseconds, and every counter the reference reads is of a whole process or a
|
||
whole processor package, updated in steps far longer than a call. The run's figure covers
|
||
the callouts' work with the rest of the run; fuel (§4.2) is each callout's measure of work,
|
||
on every host. A host with a meter that does resolve a call (a calibrated instrument read
|
||
around it, say) seals its reading in the callout's member.
|
||
|
||
### 11.6 Verifying
|
||
|
||
A second-version job is held to §6.5's rules 1 to 35, each read with the names of this
|
||
version (a verifier of the engine reads it through `engine::wasmq2`, whatever surface it
|
||
is checked on), with these changes, and rule 36:
|
||
|
||
- rule 4: `C`'s `profile` is `wai.quantum.wasmq/2`;
|
||
- rule 8: `run`'s `method` is `stabilizer` or `exact`;
|
||
- rule 11: `r`'s `format` is `wai.quantum.wasmq.result/2`, its `profile`
|
||
`wai.quantum.wasmq/2`, its `accuracy` the one §11.4 gives its method, and its `method`,
|
||
`shots` and `seed` equal `run`'s; rule 11 says nothing of `energy`, which rule 36 holds;
|
||
- rule 12: when the method is `exact`, `r`'s `qubits` are at most 26;
|
||
- rule 30: when the method is `exact`, the first term, a shot's fresh state, is
|
||
`S × 2·2^n` (§11.7), not `S × 2·n·w`;
|
||
- rule 1 holds every number in the energy too;
|
||
- **rule 36**: `r`'s `energy` has exactly the members `run` and `callouts`; `run` is a
|
||
figure; `callouts` is an object whose members are exactly the names of `F`'s `callouts`,
|
||
each a figure (§11.5).
|
||
|
||
What a job's result format is decides which version's rules hold it: a result whose
|
||
`format` is `wai.quantum.wasmq.result/1` is held to §6.5 as written, so a first-version job
|
||
whose contract or result names `wai.quantum.wasmq/2` breaks rule 4 or rule 11.
|
||
|
||
### 11.7 What a run asks of its host
|
||
|
||
§4.8 holds, with the `exact` method's state in place of the tableau: for each shot, its
|
||
fresh state takes `2·2^n` operations, and each dyadic gate a step lowers to (§11.2) and each
|
||
measurement or reset `8·2^n`, where the tableau's take `2·n·r`, `r` and `2·n·r`. A run's
|
||
work is still at most 2^42 (`W017`); the weights are the reference's measured times (§11.3),
|
||
so that the bound holds a run to about three hours on every host measured.
|
||
|
||
### 11.8 Vectors
|
||
|
||
`../quantum-conformance/wasmq/` holds second-version vectors beside the first's, each made
|
||
and checked as §9 says, on every engine and every host:
|
||
|
||
- `envelopes/v2-t-toffoli.wai`: `h`, `t`, `h` and a Toffoli under two set controls, by the
|
||
`exact` method: outcomes in the ratio of sin²(π/8);
|
||
- `envelopes/v2-feedback-rotation.wai`: a callout that turns a measured bit into three
|
||
eighths of a turn, applied as its bits' phases between two Hadamards (§11.1);
|
||
- `envelopes/v2-angle-arguments.wai`: an `angle[8]` known on reading and an `angle[4]` of
|
||
measured bits, given back by the module (§11.1);
|
||
- `envelopes/v2-rotations-and-a-reset.wai`: a random reset and each kind of gate §11.2
|
||
takes;
|
||
- `envelopes/v2-stabilizer.wai` and `envelopes/v2-exact-of-a-clifford-program.wai`: one
|
||
Clifford program by each method, with the same counts (§11.3);
|
||
- `refuse/`: an angle that is not dyadic (`v2-angle-not-dyadic`), one moved past its
|
||
double's rounding (`v2-angle-off-the-double-s-rounding`), a gate the method does not take
|
||
(`v2-gate-outside-the-exact-gates`), a `t` gate by the `stabilizer` method, one qubit past
|
||
the ceiling (`v2-past-the-exact-method-s-qubits`), a run of the state vector past §4.8's
|
||
work, an angle argument off its turns, and an angle argument in a first-version envelope,
|
||
each beside the same bundle without its defect.
|
||
|
||
The job receipts of [`../quantum-conformance/receipts/`](../quantum-conformance/receipts/)
|
||
hold the second version's jobs the command line sealed from these envelopes
|
||
(`jobs/cli-wasmq2-*`), each with the energy its host read, and jobs crafted from them that
|
||
break §11.6's rules each alone; both verifiers of that corpus, which share no code, give every
|
||
one the same verdict and state the same rules broken, and are held to each other at each
|
||
bound of rules 4, 8, 11, 12, 30 and 36 as this section reads them.
|
||
|
||
### 11.9 What the second version leaves out
|
||
|
||
- noise, and observables other than the counts;
|
||
- floating-point arguments and results of a callout: an angle crosses as its numerator, an
|
||
integer as itself, and a module that needs a real number scales one of them itself;
|
||
- the gates of §11.2's refusal, angles off the `2^-32`-turn grid, and more than 26 qubits by
|
||
the `exact` method;
|
||
- a reading of one callout's energy on any host the reference runs on (§11.5);
|
||
- running beside a quantum processor (§7 holds as written). Every surface runs a bundle of
|
||
either version: `wai-quantum run`, both MCP servers (`run_wasmq`), the job service
|
||
(`POST /v1/wasmq`), the Python package (`wasmq`), the npm package (`QuantumEngine.wasmq`,
|
||
on the interpreter alone), the Studio and the desktop app; and every surface of §6.5
|
||
verifies it. Only the command line, its MCP server and the job service read a run's
|
||
energy; the others state it unmetered, with the reason.
|