wai-quantum: methods, passes, noise channels and formats behind traits, every built-in registered through them, a host's own components checked and marked #560

Merged
dcharlot merged 4 commits from wai/quantum-registration into main 2026-11-01 03:37:29 -05:00
42 changed files with 5909 additions and 209 deletions

View file

@ -0,0 +1,169 @@
name: wai-quantum extension points
# The registry (wai/wai-quantum/src/engine/registry/): the traits a simulation
# method, a compiler pass, a noise channel and a program format implement, the
# built-ins registered through them, and a host's own registered beside them.
# The registry's tests hold every built-in to the bytes it gave (the pinned
# job digests, through the built-in registry and through one holding a host's
# components), auto to the order it always tried, the built-in channels and
# passes to what they declare, and a host's method, pass, channel and format
# to what the engine checks of them: each refusal with its code, and its
# identity sealed in the receipt of every result it took part in, marked as
# registered. They run under the full feature set and under the contract
# workflow's, natively on both Linux architectures and under wasm32-wasip2 by
# wasmtime. The example written outside the engine against its public traits
# (wai/wai-quantum/examples/extension_points.rs) is tested and run end to end.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches: [main]
paths:
- 'wai/wai-quantum/**'
- 'rust-toolchain.toml'
- '.github/workflows/wai-quantum-registry.yml'
pull_request:
paths:
- 'wai/wai-quantum/**'
- 'rust-toolchain.toml'
- '.github/workflows/wai-quantum-registry.yml'
jobs:
relevant:
name: does this change touch it
runs-on: linux-arm64
outputs:
run: ${{ steps.gate.outputs.run }}
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- id: gate
run: |
set -euo pipefail
r=$(python3 scripts/pr-touches-paths.py .github/workflows/wai-quantum-registry.yml \
"${{ github.event.pull_request.base.sha }}" \
"${{ github.event.pull_request.head.sha }}")
echo "this change touches the registry: $r"
echo "run=$r" >> "$GITHUB_OUTPUT"
native:
name: the registry and the example · ${{ matrix.runner }}
needs: relevant
if: needs.relevant.outputs.run == 'true'
strategy:
fail-fast: false
matrix:
runner: [linux, linux-arm64]
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
steps:
- uses: actions/checkout@v5
- name: Install Rust 1.98.0
shell: bash
run: |
export PATH="$HOME/.cargo/bin:$PATH"
command -v rustup >/dev/null 2>&1 || \
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.98.0
rustup toolchain install 1.98.0 --profile minimal
rustup default 1.98.0
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
# A module filter is satisfied by any one test of the module, so each
# test is required by name to have run.
- name: The registry's tests, under the full feature set and the contract's
shell: bash
working-directory: wai/wai-quantum
run: |
set -euo pipefail
cargo test --locked --release --features full --lib engine::registry:: 2>&1 | tee "$RUNNER_TEMP/full.log"
cargo test --locked --release --no-default-features --features quantum_engine,quantum_receipt,quantum_qec,quantum_noise --lib engine::registry:: 2>&1 | tee "$RUNNER_TEMP/contract.log"
for log in full contract; do
grep -qE '^test result: ok\. 12 passed; 0 failed' "$RUNNER_TEMP/$log.log" || { echo "::error::the registry's tests did not all pass ($log)"; exit 1; }
for t in every_built_in_registers_through_its_trait_in_a_fixed_order \
auto_tries_the_built_ins_in_the_order_it_did \
the_pinned_jobs_give_their_bytes_through_any_registry \
the_built_in_channels_are_valid_channels \
the_built_in_passes_give_what_they_declare \
a_registered_method_is_planned_run_and_named_in_its_result \
the_receipt_seals_a_registered_component_s_identity \
registration_refuses_what_it_cannot_hold \
a_registered_method_s_result_is_held_to_the_contract \
a_registered_channel_runs_on_the_density_matrix_and_is_refused_elsewhere \
a_registered_pass_is_checked_by_the_engine \
a_registered_front_end_converts_and_feeds_a_job; do
grep -q "^test engine::registry::tests::$t \.\.\. ok$" "$RUNNER_TEMP/$log.log" || { echo "::error::$t did not run ($log)"; exit 1; }
done
done
- name: The built-ins' bytes, the refusal table and the job layer beside them
shell: bash
working-directory: wai/wai-quantum
run: |
set -euo pipefail
cargo test --locked --release --features full --lib -- engine::job:: engine::refusal:: engine::catalog:: engine::compile:: engine::formats:: 2>&1 | tee "$RUNNER_TEMP/engine.log"
grep -qE '^test result: ok\. [1-9][0-9]* passed; 0 failed' "$RUNNER_TEMP/engine.log" || { echo "::error::the engine's tests did not pass"; exit 1; }
for t in engine::job::tests::a_job_that_asks_for_no_verdict_gives_the_bytes_it_gave \
engine::refusal::tests::the_documented_table_is_the_table; do
grep -q "^test $t \.\.\. ok$" "$RUNNER_TEMP/engine.log" || { echo "::error::$t did not run"; exit 1; }
done
- name: The example, tested and run
shell: bash
working-directory: wai/wai-quantum
run: |
set -euo pipefail
cargo test --locked --release --features quantum_engine,quantum_receipt --example extension_points 2>&1 | tee "$RUNNER_TEMP/example.log"
grep -qE '^test result: ok\. 3 passed; 0 failed' "$RUNNER_TEMP/example.log" || { echo "::error::the example's tests did not pass"; exit 1; }
cargo run --locked --release --features quantum_engine,quantum_receipt --example extension_points > "$RUNNER_TEMP/walk.txt"
cat "$RUNNER_TEMP/walk.txt"
for want in 'the job planned on "basis_table"' 'counts {"100":100}' '"origin": "registered"'; do
grep -qF "$want" "$RUNNER_TEMP/walk.txt" || { echo "::error::the example's walk-through does not say: $want"; exit 1; }
done
wasip2:
name: the registry under wasm32-wasip2 (wasmtime)
needs: relevant
if: needs.relevant.outputs.run == 'true'
runs-on: linux
timeout-minutes: 60
steps:
- uses: actions/checkout@v5
- name: Install Rust 1.98.0 with the wasm32-wasip2 target
shell: bash
run: |
export PATH="$HOME/.cargo/bin:$PATH"
command -v rustup >/dev/null 2>&1 || \
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.98.0
rustup toolchain install 1.98.0 --profile minimal
rustup default 1.98.0
rustup target add --toolchain 1.98.0 wasm32-wasip2
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
# The release binary, pinned by version and SHA-256, rather than a build from source.
- name: Install wasmtime 49.0.2
shell: bash
run: |
set -euo pipefail
[ "$(uname -m)" = x86_64 ] || { echo "::error::the pinned wasmtime archive is x86_64; this host is $(uname -m)"; exit 1; }
if ! command -v xz >/dev/null 2>&1; then
SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo"
$SUDO apt-get update -qq
$SUDO apt-get install -y -qq xz-utils
fi
v=49.0.2
f="wasmtime-v$v-x86_64-linux.tar.xz"
curl --proto '=https' --tlsv1.2 -sSfL -o "$RUNNER_TEMP/$f" \
"https://github.com/bytecodealliance/wasmtime/releases/download/v$v/$f"
echo "a4d6e9e3a5a60f527cf7793d674c48930c80c2e8977995b8a275cad3254b9322 $RUNNER_TEMP/$f" | sha256sum -c -
tar -xJf "$RUNNER_TEMP/$f" -C "$RUNNER_TEMP"
echo "$RUNNER_TEMP/wasmtime-v$v-x86_64-linux" >> "$GITHUB_PATH"
# A debug build, so integer overflow panics instead of wrapping.
- name: The registry's tests under wasmtime
shell: bash
working-directory: wai/wai-quantum
env:
CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime
run: |
set -euo pipefail
cargo test --locked --target wasm32-wasip2 --features full --lib engine::registry:: 2>&1 | tee "$RUNNER_TEMP/wasip2.log"
grep -qE '^test result: ok\. 12 passed; 0 failed' "$RUNNER_TEMP/wasip2.log" || { echo "::error::the registry's tests did not all pass under wasip2"; exit 1; }

View file

@ -363,6 +363,58 @@ wire format may change and such changes are recorded here.
implementation, `channel_lineup_verify.py`, from the text alone, agrees on every vector, and a
CI step runs both.
### wai-quantum: extension points, registration traits for methods, passes, noise channels and formats
- **`engine::registry`**: four traits a component implements, and one registry the engine goes through. `Method`
(its name, version, accuracy classes and descriptor, whether it models noise, where `auto` tries it, its `plan`
that admits a job or refuses it and estimates its memory and work, and its `run`); `Pass` (the gates it takes and
gives, and its check: `Check::Ladder`, the equivalence ladder, or `Check::Fidelity`, a distance bound on dense
unitaries; never a hash); `Channel` (its parameters and ranges, its arity, its Kraus operators and its Pauli form
where it is one, and how each noisy method takes it); `FrontEnd` (a format read to OpenQASM 3 and written from the
engine's program). `Registry::with_builtins()` holds every built-in, each registered through the same trait: the
twelve methods and the pulse simulator, the eight compile passes, the five noise channels and the three formats.
`register(Component::…)` adds a host's own in its process; nothing is loaded from a file, a path or a request.
- **The engine through the registry.** `catalog::plan`, `catalog::read`, `run::run`, `compile::compile`,
`compile::call`, `formats::convert`, `formats::convert_json`, `formats::expand` and `verdict::of_run` keep their
signatures and run through the built-in registry; `Registry::plan`, `run`, `plan_and_run`, `job`, `compile`,
`compile_call`, `convert_json` and `expand` run through a host's. `auto` tries methods by rank (the built-ins at
10, 20, 30, … in the order they always had), and the methods list is each method's own entry. A compile takes
`passes`, the names of passes to run after its level's and before twirling and decoupling; a job's `noise` takes
a registered channel by its value or its parameters (`{"name": {"p": …}}`), which the density matrix applies as
its Kraus operators after each gate.
- **The engine checks what a registered component gives.** A method's result must carry an accuracy class it
declared and leave the members the engine writes as they were; a pass's output is held to its input on the whole
circuit by the check it declares, within its bound and what is left of the ε budget, or left out (`rejected`) and
the program goes on as it was; a channel's Kraus operators are checked completely positive and trace preserving at
every job's values (`registry::validate`), and its Pauli form a distribution that is its operators' channel; a
format's program must read. The built-in channels and passes are held by tests to what they declare.
- **Identity.** A component's identity is its kind, name, version and the BLAKE3 digest of its descriptor
(`registry::digest_of`). A result made by built-ins alone is the result it always was: the eight pinned job
digests hold through the built-in registry and through one that holds a host's components beside them. A result
a registered component took part in carries `components`, every component that made it (its format, its
compile's passes, its noise's channels, its method), each registered one marked `"origin": "registered"` and
`"certified": false`, sealed by its receipt; so does a compile's answer (and its pass's record, `component`) and a
conversion's. A name a built-in holds is never registered. `components` is in `wai.quantum.job-result/1`,
`wai.quantum.compile-result/1` and the conversion's answer (optional), and `wai.quantum.tools/1` gains the
listing's `components` and `component`.
- **Refusal codes, a decade of their own:** `J060` a pass a request names that this host does not hold, `J061` a
noise channel the method cannot take (a method that models no noise, which was `J011`, and amplitude damping on
the stabilizer's frames among them), `J062` a channel whose operators fail the validity check, `J063` a
registration the registry does not take, `J064` a registered component's answer that breaks the contract, `J065`
an intent on a job whose noise names a registered channel. A method a job names that does not exist is `J010`
whatever else the job asks, and a channel's value that is not a number or an object of its parameters is refused
(`J003`), where it was read as no noise.
- **Surfaces:** `wai-quantum components` (with `--json`, each one's descriptor); the MCP tool `list_components`
on both servers; the job service lists its components in `GET /v1` and `quantum_service_info`, the built-ins
alone.
- **The example** `examples/extension_points.rs`: a method (`basis_table`, for circuits that permute basis states),
a pass (`cancel_pairs`), a channel (`bit_flip`) and a format (`gate_list`), written against the public traits
alone, registered and run end to end, with its tests; `docs/extension-points.md` says how to write one, with a
channel registered and run. CI: `wai-quantum-registry.yml`, on both Linux architectures and under
wasm32-wasip2.
- `engine::fabric_measured`'s test of the measured circuit's job runs where the fabric is built (`quantum_fabric`),
so the contract workflow's feature set passes every test it builds.
### wai-quantum: the state vector resident on the GPU, auto by measurement, and Pauli propagation on the GPU
- **Resident** (`quantum_fabric::sv64`, host layout 2): a member runs a whole plan with the state on the device.

View file

@ -1,6 +1,6 @@
{
"schema": "wai.quantum.docs-manifest/1",
"about": "The documents check_docs.py runs: the WAI Quantum READMEs and docs pages (engine, command line, service, Python, Studio, desktop, editors, providers, the compiler, estimates, verdicts, error correction, other formats, the variational and algorithm library, the feature matrix and the version policy; the service's self-hosting, scheduler and provenance pages; the views' engine), the agent skill, the course's lessons, and their translations.",
"about": "The documents check_docs.py runs: the WAI Quantum READMEs and docs pages (engine, command line, service, Python, Studio, desktop, editors, providers, the compiler, estimates, verdicts, error correction, other formats, the variational and algorithm library, the extension points, the feature matrix and the version policy; the service's self-hosting, scheduler and provenance pages; the views' engine), the agent skill, the course's lessons, and their translations.",
"docs": [
"wai/wai-quantum/README.md",
"wai/wai-quantum/README.zh-CN.md",
@ -10,6 +10,7 @@
"wai/wai-quantum/docs/estimate.md",
"wai/wai-quantum/docs/verdicts.md",
"wai/wai-quantum/docs/job-refusals.md",
"wai/wai-quantum/docs/extension-points.md",
"wai/wai-quantum/docs/formats.md",
"wai/wai-quantum/docs/qasm-diagnostics.md",
"wai/wai-quantum/docs/qasm-feature-matrix.md",

View file

@ -99,7 +99,7 @@ wai-quantum instance ghz-family.qasm --input n=3
| stabilizer_rank | Clifford+T circuits, as a sum of stabilizer states: amplitudes and expectation values, exact, or sampled with a stated bound (`--option terms=N` or `--option epsilon=E`) |
| density_matrix, trajectories | noise |
`wai-quantum methods` lists each method's reach in a job's memory here (`--memory-gb`, default 16).
`wai-quantum methods` lists each method's reach in a job's memory here (`--memory-gb`, default 16). `wai-quantum components` lists every component the engine runs through (the methods, the compiler passes, the noise channels and the formats), each with its version and the digest of its descriptor, the identity a result is sealed with (`--json` gives each one's descriptor). The command line runs the built-ins alone; a host that embeds the engine registers its own ([the extension points](../wai-quantum/docs/extension-points.md)).
`--fabric` says where the `statevector` method's state runs: `auto` (the default: on a member only where the committed measurements show it faster than this host's CPU for the job's size and fusion width, otherwise on the CPU; a job placed that way carries the rule and the measured row it rests on in its receipt), `cpu`, `accelerator` (the first accelerator member certified bit-exact here that holds the state, the next taking over if it fails during the run), or a member's id or key; given more than once, the state is split by its top qubits into one partition on each member named (a power of two of them; `cpu` is a member, and one may be named twice). Every member placed gives the CPU's answers byte for byte, and the result names where its state ran, with the member's certificate, in its receipt. `wai-quantum fabric` lists the members this host can place on, each with the most qubits it holds (and whether its kernels for gates on more than three qubits at once certified), the devices found and not placed, with why, `auto`'s rule, where it places on this host, and every measured row. A placement this host cannot honour is refused (`J013`), never made elsewhere. A `pauli` job takes `--fabric accelerator` or a member's id or key too: its sum of terms then runs on that member (the CUDA member, certified bit-exact against the CPU for it), with the CPU's answers byte for byte; `auto` keeps it on the CPU.
@ -414,6 +414,7 @@ wai-quantum verify sweep.json # the set's receipt, each item's, the r
- `taste_rank` and `taste_next` (your taste, below)
- `history_commit`, `history_changes`, `history_blame` and `history_log` (a program's history, below)
- `list_methods`
- `list_components` (every method, pass, noise channel and format, each with its identity)
- `qasm_diagnostics` (the reader's codes and the job layer's: all of them, or one by `code`)
- `list_targets`, `show_target` and `run_on_target` (device targets, above); `run_quantum_circuit` and `plan_quantum_circuit` also take `target`
- `providers_list`, `provider_estimate` and `run_on_provider` (your own hardware, above): `run_on_provider` says it can spend your money, and refuses a run that is not free without `confirm_cost: true`

View file

@ -102,7 +102,7 @@ wai-quantum instance ghz-family.qasm --input n=3
| stabilizer_rank | Clifford+T 线路,写成稳定子态之和:振幅和期望值,精确,或以给定界抽样(`--option terms=N` 或 `--option epsilon=E`) |
| density_matrix, trajectories | 噪声 |
`wai-quantum methods` 列出每种方法在本机一个作业的内存(`--memory-gb`,默认 16)内能达到的规模。
`wai-quantum methods` 列出每种方法在本机一个作业的内存(`--memory-gb`,默认 16)内能达到的规模。`wai-quantum components` 列出引擎所经过的每个组件(方法、编译遍、噪声信道和格式),各附其版本及其描述符的摘要,即结果封存时所带的身份(`--json` 给出每个组件的描述符)。命令行只运行内置组件;嵌入引擎的宿主可以注册自己的组件([扩展点](../wai-quantum/docs/extension-points.md))。
`--fabric` 指定 `statevector` 方法的态在哪里运行:`auto`(默认:只在已提交的测量结果表明某个成员对该作业的规模和融合宽度比本机 CPU 更快时才放到该成员上,否则放在 CPU 上;这样放置的作业在其收据中带有规则及其所依据的测量行)、`cpu`、`accelerator`(本机第一个经认证逐位一致且能容纳该态的加速器成员;若它在运行中失败,由下一个接替),或成员的 id 或键;多次给出时,态按最高位量子比特划分为若干分区,每个所列成员一个分区(数量为 2 的幂;`cpu` 也是成员,同一成员可列两次)。放置到的每个成员都给出与 CPU 逐字节相同的答案,结果在其收据中指明态在哪里运行以及该成员的证书。`wai-quantum fabric` 列出本机可放置的成员(各附其可容纳的最多量子比特数,以及其一次作用于三个以上量子比特的门的内核是否通过认证)、找到但未放置的设备及原因、`auto` 的规则、它在本机的放置范围以及每个测量行。主机无法兑现的放置会被拒绝(`J013`),绝不改放别处。`pauli` 作业同样可以用 `--fabric accelerator` 或成员的 id 或键:其项之和随后在该成员上运行(CUDA 成员,已对照 CPU 认证为逐位一致),答案与 CPU 逐字节相同;`auto` 把它留在 CPU 上。
@ -410,6 +410,7 @@ wai-quantum verify sweep.json # 程序集的回执、每一项的回
- `taste_rank` 和 `taste_next`(你的品味,见下文)
- `history_commit`、`history_changes`、`history_blame` 和 `history_log`(程序的历史,见下文)
- `list_methods`
- `list_components`(每个方法、编译遍、噪声信道和格式,各附其身份)
- `qasm_diagnostics`(读取器的代码和作业层的代码:全部,或按 `code` 取其中一个)
- `list_targets`、`show_target` 和 `run_on_target`(设备目标,见上文);`run_quantum_circuit` 和 `plan_quantum_circuit` 也接受 `target`
- `providers_list`、`provider_estimate` 和 `run_on_provider`(你自己的硬件,见上文):`run_on_provider` 会声明它可能花你的钱,对不免费的运行,没有 `confirm_cost: true` 时会拒绝

View file

@ -393,6 +393,10 @@ Each simulation method: its accuracy class, the circuits it takes, and the most
The members of the fabric a statevector job can be placed on here (a job's `fabric`): the CPU and each accelerator found and certified bit-exact against the CPU in this process, each with its key, its certificate and the most qubits it holds; the devices found and not placed, and why; and the backends this build reaches.
### `list_components`: List the engine's components
Every component the engine plans, runs, compiles and converts through here: the simulation methods, the compiler passes, the noise channels and the program formats, each with its version, its origin and the BLAKE3 digest of its descriptor, which is the identity a result is sealed with (a result a registered component took part in names each in its `components`). This server runs the built-ins alone.
### `qasm_diagnostics`: The reader's diagnostic codes
Every code the OpenQASM reader gives (each refusal and warning carries one, as `[Q103]` at the end of its message and as `code` in lint's JSON): its outcome (refused, lowered, carried, preserved or warned) and its meaning; and beside them, under job_codes, the codes of what is asked of a program (`[J011]`: the request, the method, an intent, a repair, the host). A code never changes meaning once released. Give `code` for one of either.
@ -775,6 +779,10 @@ Every code the OpenQASM reader gives (each refusal and warning carries one, as `
Parameters:
- `code` (string): One code, as Q103 or J011.
### `list_components`: List the engine's components
Every component the service plans, runs, compiles and converts through: the simulation methods, the compiler passes, the noise channels and the program formats, each with its version, its origin and the BLAKE3 digest of its descriptor, which is the identity a result is sealed with. The service runs the built-ins alone and never loads a component from a request.
### `list_targets`: List the device targets
The device targets the service knows by name: synthetic, made up in the shapes of published formats, each with its qubits, pairs, the gates a program compiles to, the spread of its T1 and readout errors, and its hash. A job may give its own target document instead.
@ -1605,6 +1613,9 @@ Commands:
the CPU and each accelerator this build reaches that is found and certified bit-exact
against the CPU, each with the most qubits it holds; the devices found and not
placed, and why
components Every component the engine plans, runs, compiles and converts through (the simulation
methods, compiler passes, noise channels and program formats), each with its version
and the digest of its descriptor: the identity a result is sealed with
diagnostics The diagnostic codes: the OpenQASM reader's (each with its outcome: refused, lowered,
carried, preserved or warned) and the job layer's (refused or retry), each with its
meaning
@ -3678,6 +3689,38 @@ Options:
Print version
```
### `wai-quantum components`
```text
Every component the engine plans, runs, compiles and converts through (the simulation methods,
compiler passes, noise channels and program formats), each with its version and the digest of its
descriptor: the identity a result is sealed with
Usage: wai-quantum components [OPTIONS]
Options:
--input <NAME=VALUE>
A value for a generator's integer `input`, as n=5 (repeat for each input): the program is
read as its family's circuit at those values
--json
Print the list as JSON, each component with its descriptor
--lang <LOCALE>
The language of the words the command line writes itself, en or zh-CN (by default the one
LANG names); results, codes and the engine's own messages stay in English
--from <FORMAT>
The format of every program the command reads: qasm, qir (LLVM text) or jeff; by default
the file's extension says (.ll or .qir is QIR, .jeff is jeff), and OpenQASM otherwise
-h, --help
Print help
-V, --version
Print version
```
### `wai-quantum diagnostics`
```text

View file

@ -74,6 +74,7 @@ Runs on this machine, signed with the user's key; nothing leaves the machine.
- `history_log`: A program's history
- `list_methods`: List the methods
- `list_fabric`: List the fabric's members
- `list_components`: List the engine's components
- `qasm_diagnostics`: The reader's diagnostic codes
- `list_targets`: List the device targets
- `show_target`: Show a device target
@ -110,6 +111,7 @@ Runs on this machine, signed with the user's key; nothing leaves the machine.
- `repair_qasm`: Repair a quantum program on its flagged lines
- `quantum_service_info`: What the quantum service can do
- `qasm_diagnostics`: The reader's diagnostic codes
- `list_components`: List the engine's components
- `list_targets`: List the device targets
- `show_target`: Show a device target
- `run_on_target`: Run a program as a device would

View file

@ -1383,6 +1383,53 @@
"const": "wai.quantum.job-result/1",
"description": "The schema the result follows."
},
"components": {
"type": "array",
"description": "The identity of every component that made the result, when a registered one took part (engine::registry): its program's format, the passes its compile ran, the noise channels it names, its method; each registered one marked so. Sealed by the receipt with the rest.",
"items": {
"type": "object",
"description": "One component that made the result (engine::registry): its kind, name and version, the BLAKE3 digest of its descriptor, and whether it is built in. A result carries these only when a registered component took part; one made by built-ins alone names them as it always has (its method, its compile's passes, its noise's channels and the engine's version), the digests of that version listed by the components list.",
"required": [
"kind",
"name",
"version",
"digest",
"origin"
],
"properties": {
"kind": {
"enum": [
"method",
"pass",
"channel",
"front_end"
]
},
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"digest": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "BLAKE3 of the component's descriptor in its canonical JSON (engine::registry::digest_of)."
},
"origin": {
"enum": [
"built-in",
"registered"
],
"description": "built-in: the engine's own; registered: a host's own, added in its process, not part of the engine."
},
"certified": {
"const": false,
"description": "Beside a registered component: the engine does not certify it."
}
}
}
},
"method": {
"type": "string",
"description": "The method that ran the job (provider for a run on the person's own provider)."
@ -4964,6 +5011,53 @@
"const": "wai.quantum.job-result/1",
"description": "The schema the result follows."
},
"components": {
"type": "array",
"description": "The identity of every component that made the result, when a registered one took part (engine::registry): its program's format, the passes its compile ran, the noise channels it names, its method; each registered one marked so. Sealed by the receipt with the rest.",
"items": {
"type": "object",
"description": "One component that made the result (engine::registry): its kind, name and version, the BLAKE3 digest of its descriptor, and whether it is built in. A result carries these only when a registered component took part; one made by built-ins alone names them as it always has (its method, its compile's passes, its noise's channels and the engine's version), the digests of that version listed by the components list.",
"required": [
"kind",
"name",
"version",
"digest",
"origin"
],
"properties": {
"kind": {
"enum": [
"method",
"pass",
"channel",
"front_end"
]
},
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"digest": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "BLAKE3 of the component's descriptor in its canonical JSON (engine::registry::digest_of)."
},
"origin": {
"enum": [
"built-in",
"registered"
],
"description": "built-in: the engine's own; registered: a host's own, added in its process, not part of the engine."
},
"certified": {
"const": false,
"description": "Beside a registered component: the engine does not certify it."
}
}
}
},
"method": {
"type": "string",
"description": "The method that ran the job (provider for a run on the person's own provider)."
@ -14423,6 +14517,53 @@
}
],
"description": "The output read back and held to the program read (or, for a program read into OpenQASM, written back to its format and read again), or null when no check was asked."
},
"components": {
"type": "array",
"description": "The identities of the two formats, when a registered front end took part (engine::registry).",
"items": {
"type": "object",
"description": "One component that made the result (engine::registry): its kind, name and version, the BLAKE3 digest of its descriptor, and whether it is built in. A result carries these only when a registered component took part; one made by built-ins alone names them as it always has (its method, its compile's passes, its noise's channels and the engine's version), the digests of that version listed by the components list.",
"required": [
"kind",
"name",
"version",
"digest",
"origin"
],
"properties": {
"kind": {
"enum": [
"method",
"pass",
"channel",
"front_end"
]
},
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"digest": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "BLAKE3 of the component's descriptor in its canonical JSON (engine::registry::digest_of)."
},
"origin": {
"enum": [
"built-in",
"registered"
],
"description": "built-in: the engine's own; registered: a host's own, added in its process, not part of the engine."
},
"certified": {
"const": false,
"description": "Beside a registered component: the engine does not certify it."
}
}
}
}
}
},
@ -17814,6 +17955,49 @@
},
"check": {
"type": "object"
},
"component": {
"type": "object",
"description": "Beside a registered pass's record: its identity. Its check is the engine's, on the whole circuit (by: engine).",
"required": [
"kind",
"name",
"version",
"digest",
"origin"
],
"properties": {
"kind": {
"enum": [
"method",
"pass",
"channel",
"front_end"
]
},
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"digest": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "BLAKE3 of the component's descriptor in its canonical JSON (engine::registry::digest_of)."
},
"origin": {
"enum": [
"built-in",
"registered"
],
"description": "built-in: the engine's own; registered: a host's own, added in its process, not part of the engine."
},
"certified": {
"const": false,
"description": "Beside a registered component: the engine does not certify it."
}
}
}
}
}
@ -17896,6 +18080,53 @@
"description": "The signature, 64 bytes in hex."
}
}
},
"components": {
"type": "array",
"description": "The identity of every component that made the compile, when a registered pass took part (engine::registry): the program's format and each pass, each registered one marked so.",
"items": {
"type": "object",
"description": "One component that made the result (engine::registry): its kind, name and version, the BLAKE3 digest of its descriptor, and whether it is built in. A result carries these only when a registered component took part; one made by built-ins alone names them as it always has (its method, its compile's passes, its noise's channels and the engine's version), the digests of that version listed by the components list.",
"required": [
"kind",
"name",
"version",
"digest",
"origin"
],
"properties": {
"kind": {
"enum": [
"method",
"pass",
"channel",
"front_end"
]
},
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"digest": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "BLAKE3 of the component's descriptor in its canonical JSON (engine::registry::digest_of)."
},
"origin": {
"enum": [
"built-in",
"registered"
],
"description": "built-in: the engine's own; registered: a host's own, added in its process, not part of the engine."
},
"certified": {
"const": false,
"description": "Beside a registered component: the engine does not certify it."
}
}
}
}
}
},
@ -27829,6 +28060,315 @@
]
}
},
{
"name": "list_components",
"title": "List the engine's components",
"description": "Every component the engine plans, runs, compiles and converts through here: the simulation methods, the compiler passes, the noise channels and the program formats, each with its version, its origin and the BLAKE3 digest of its descriptor, which is the identity a result is sealed with (a result a registered component took part in names each in its `components`). This server runs the built-ins alone.",
"inputSchema": {
"type": "object",
"properties": {}
},
"outputSchema": {
"$id": "wai-quantum://schemas/list_components/1",
"type": "object",
"description": "Every component the engine plans, runs, compiles and converts through (engine::registry::Registry::components): the simulation methods, the compiler passes, the noise channels and the program formats, each in the registry's order with its identity and its descriptor. The command line, both MCP servers and the job service serve the built-ins alone.",
"anyOf": [
{
"type": "object",
"description": "Every component the engine plans, runs, compiles and converts through (engine::registry::Registry::components): the simulation methods, the compiler passes, the noise channels and the program formats, each in the registry's order with its identity and its descriptor. The command line, both MCP servers and the job service serve the built-ins alone.",
"required": [
"format",
"engine",
"methods",
"passes",
"channels",
"front_ends",
"registered"
],
"properties": {
"format": {
"const": "wai.quantum.components/1"
},
"engine": {
"type": "object",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
}
}
},
"methods": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"name",
"version",
"digest",
"origin",
"descriptor"
],
"properties": {
"kind": {
"enum": [
"method",
"pass",
"channel",
"front_end"
]
},
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"digest": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "BLAKE3 of the component's descriptor in its canonical JSON (engine::registry::digest_of)."
},
"origin": {
"enum": [
"built-in",
"registered"
],
"description": "built-in: the engine's own; registered: a host's own, added in its process, not part of the engine."
},
"certified": {
"const": false,
"description": "Beside a registered component: the engine does not certify it."
},
"descriptor": {
"type": "object",
"description": "What the component declares of itself, as hashed into its digest: what it declares (declared) and what the engine reads of it (a method's accuracy classes, whether it models noise, where auto tries it; a pass's gates taken and given and its check; a channel's arity, placement and parameters; a format's title, extensions and profiles).",
"required": [
"declared"
]
}
}
}
},
"passes": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"name",
"version",
"digest",
"origin",
"descriptor"
],
"properties": {
"kind": {
"enum": [
"method",
"pass",
"channel",
"front_end"
]
},
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"digest": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "BLAKE3 of the component's descriptor in its canonical JSON (engine::registry::digest_of)."
},
"origin": {
"enum": [
"built-in",
"registered"
],
"description": "built-in: the engine's own; registered: a host's own, added in its process, not part of the engine."
},
"certified": {
"const": false,
"description": "Beside a registered component: the engine does not certify it."
},
"descriptor": {
"type": "object",
"description": "What the component declares of itself, as hashed into its digest: what it declares (declared) and what the engine reads of it (a method's accuracy classes, whether it models noise, where auto tries it; a pass's gates taken and given and its check; a channel's arity, placement and parameters; a format's title, extensions and profiles).",
"required": [
"declared"
]
}
}
}
},
"channels": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"name",
"version",
"digest",
"origin",
"descriptor"
],
"properties": {
"kind": {
"enum": [
"method",
"pass",
"channel",
"front_end"
]
},
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"digest": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "BLAKE3 of the component's descriptor in its canonical JSON (engine::registry::digest_of)."
},
"origin": {
"enum": [
"built-in",
"registered"
],
"description": "built-in: the engine's own; registered: a host's own, added in its process, not part of the engine."
},
"certified": {
"const": false,
"description": "Beside a registered component: the engine does not certify it."
},
"descriptor": {
"type": "object",
"description": "What the component declares of itself, as hashed into its digest: what it declares (declared) and what the engine reads of it (a method's accuracy classes, whether it models noise, where auto tries it; a pass's gates taken and given and its check; a channel's arity, placement and parameters; a format's title, extensions and profiles).",
"required": [
"declared"
]
}
}
}
},
"front_ends": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"name",
"version",
"digest",
"origin",
"descriptor"
],
"properties": {
"kind": {
"enum": [
"method",
"pass",
"channel",
"front_end"
]
},
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"digest": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "BLAKE3 of the component's descriptor in its canonical JSON (engine::registry::digest_of)."
},
"origin": {
"enum": [
"built-in",
"registered"
],
"description": "built-in: the engine's own; registered: a host's own, added in its process, not part of the engine."
},
"certified": {
"const": false,
"description": "Beside a registered component: the engine does not certify it."
},
"descriptor": {
"type": "object",
"description": "What the component declares of itself, as hashed into its digest: what it declares (declared) and what the engine reads of it (a method's accuracy classes, whether it models noise, where auto tries it; a pass's gates taken and given and its check; a channel's arity, placement and parameters; a format's title, extensions and profiles).",
"required": [
"declared"
]
}
}
}
},
"registered": {
"type": "integer",
"minimum": 0,
"description": "How many are registered (a host's own); 0 for the built-ins alone."
}
}
},
{
"type": "object",
"description": "A refusal as data: the message, every code in it (the last is the code), its outcome and meaning as the code's table gives them. A host that refuses for its own reasons gives the error alone.",
"required": [
"error"
],
"properties": {
"error": {
"type": "string"
},
"message": {
"type": "string"
},
"code": {
"type": [
"string",
"null"
]
},
"codes": {
"type": "array",
"items": {
"type": "string"
}
},
"outcome": {
"enum": [
"refused",
"retry",
"failed",
"lowered",
"carried",
"preserved",
"warned",
"dropped",
"defaulted"
]
},
"meaning": {
"type": "string"
}
}
}
]
}
},
{
"name": "qasm_diagnostics",
"title": "The reader's diagnostic codes",
@ -30638,6 +31178,53 @@
"const": "wai.quantum.job-result/1",
"description": "The schema the result follows."
},
"components": {
"type": "array",
"description": "The identity of every component that made the result, when a registered one took part (engine::registry): its program's format, the passes its compile ran, the noise channels it names, its method; each registered one marked so. Sealed by the receipt with the rest.",
"items": {
"type": "object",
"description": "One component that made the result (engine::registry): its kind, name and version, the BLAKE3 digest of its descriptor, and whether it is built in. A result carries these only when a registered component took part; one made by built-ins alone names them as it always has (its method, its compile's passes, its noise's channels and the engine's version), the digests of that version listed by the components list.",
"required": [
"kind",
"name",
"version",
"digest",
"origin"
],
"properties": {
"kind": {
"enum": [
"method",
"pass",
"channel",
"front_end"
]
},
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"digest": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "BLAKE3 of the component's descriptor in its canonical JSON (engine::registry::digest_of)."
},
"origin": {
"enum": [
"built-in",
"registered"
],
"description": "built-in: the engine's own; registered: a host's own, added in its process, not part of the engine."
},
"certified": {
"const": false,
"description": "Beside a registered component: the engine does not certify it."
}
}
}
},
"method": {
"type": "string",
"description": "The method that ran the job (provider for a run on the person's own provider)."
@ -34219,6 +34806,53 @@
"const": "wai.quantum.job-result/1",
"description": "The schema the result follows."
},
"components": {
"type": "array",
"description": "The identity of every component that made the result, when a registered one took part (engine::registry): its program's format, the passes its compile ran, the noise channels it names, its method; each registered one marked so. Sealed by the receipt with the rest.",
"items": {
"type": "object",
"description": "One component that made the result (engine::registry): its kind, name and version, the BLAKE3 digest of its descriptor, and whether it is built in. A result carries these only when a registered component took part; one made by built-ins alone names them as it always has (its method, its compile's passes, its noise's channels and the engine's version), the digests of that version listed by the components list.",
"required": [
"kind",
"name",
"version",
"digest",
"origin"
],
"properties": {
"kind": {
"enum": [
"method",
"pass",
"channel",
"front_end"
]
},
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"digest": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "BLAKE3 of the component's descriptor in its canonical JSON (engine::registry::digest_of)."
},
"origin": {
"enum": [
"built-in",
"registered"
],
"description": "built-in: the engine's own; registered: a host's own, added in its process, not part of the engine."
},
"certified": {
"const": false,
"description": "Beside a registered component: the engine does not certify it."
}
}
}
},
"method": {
"type": "string",
"description": "The method that ran the job (provider for a run on the person's own provider)."
@ -50038,6 +50672,53 @@
"const": "wai.quantum.job-result/1",
"description": "The schema the result follows."
},
"components": {
"type": "array",
"description": "The identity of every component that made the result, when a registered one took part (engine::registry): its program's format, the passes its compile ran, the noise channels it names, its method; each registered one marked so. Sealed by the receipt with the rest.",
"items": {
"type": "object",
"description": "One component that made the result (engine::registry): its kind, name and version, the BLAKE3 digest of its descriptor, and whether it is built in. A result carries these only when a registered component took part; one made by built-ins alone names them as it always has (its method, its compile's passes, its noise's channels and the engine's version), the digests of that version listed by the components list.",
"required": [
"kind",
"name",
"version",
"digest",
"origin"
],
"properties": {
"kind": {
"enum": [
"method",
"pass",
"channel",
"front_end"
]
},
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"digest": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "BLAKE3 of the component's descriptor in its canonical JSON (engine::registry::digest_of)."
},
"origin": {
"enum": [
"built-in",
"registered"
],
"description": "built-in: the engine's own; registered: a host's own, added in its process, not part of the engine."
},
"certified": {
"const": false,
"description": "Beside a registered component: the engine does not certify it."
}
}
}
},
"method": {
"type": "string",
"description": "The method that ran the job (provider for a run on the person's own provider)."
@ -53619,6 +54300,53 @@
"const": "wai.quantum.job-result/1",
"description": "The schema the result follows."
},
"components": {
"type": "array",
"description": "The identity of every component that made the result, when a registered one took part (engine::registry): its program's format, the passes its compile ran, the noise channels it names, its method; each registered one marked so. Sealed by the receipt with the rest.",
"items": {
"type": "object",
"description": "One component that made the result (engine::registry): its kind, name and version, the BLAKE3 digest of its descriptor, and whether it is built in. A result carries these only when a registered component took part; one made by built-ins alone names them as it always has (its method, its compile's passes, its noise's channels and the engine's version), the digests of that version listed by the components list.",
"required": [
"kind",
"name",
"version",
"digest",
"origin"
],
"properties": {
"kind": {
"enum": [
"method",
"pass",
"channel",
"front_end"
]
},
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"digest": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "BLAKE3 of the component's descriptor in its canonical JSON (engine::registry::digest_of)."
},
"origin": {
"enum": [
"built-in",
"registered"
],
"description": "built-in: the engine's own; registered: a host's own, added in its process, not part of the engine."
},
"certified": {
"const": false,
"description": "Beside a registered component: the engine does not certify it."
}
}
}
},
"method": {
"type": "string",
"description": "The method that ran the job (provider for a run on the person's own provider)."

File diff suppressed because it is too large Load diff

View file

@ -63,6 +63,7 @@
"key": { "source": "Show the key this user signs receipts with", "text": "显示本用户为回执签名所用的密钥" },
"methods": { "source": "Each method, what it takes, and how far it reaches in a job's memory here", "text": "每种方法、它接受什么,以及在本机作业内存中它能达到的规模" },
"fabric": { "source": "The members of the fabric a statevector job can be placed on here (`run --fabric`): the CPU and each accelerator this build reaches that is found and certified bit-exact against the CPU, each with the most qubits it holds; the devices found and not placed, and why", "text": "本机上 statevector 作业可放置其上的计算结构成员(`run --fabric`):CPU,以及本构建可达、已找到并经认证与 CPU 逐位一致的每个加速器,各附其可容纳的最多量子比特数;以及找到但未放置的设备及其原因" },
"components": { "source": "Every component the engine plans, runs, compiles and converts through (the simulation methods, compiler passes, noise channels and program formats), each with its version and the digest of its descriptor: the identity a result is sealed with", "text": "引擎规划、运行、编译和转换所经过的每个组件(模拟方法、编译遍、噪声信道和程序格式),各附其版本及其描述符的摘要:结果封存时所带的身份" },
"diagnostics": { "source": "The diagnostic codes: the OpenQASM reader's (each with its outcome: refused, lowered, carried, preserved or warned) and the job layer's (refused or retry), each with its meaning", "text": "诊断代码:OpenQASM 读取器的代码(各附其结果:refused、lowered、carried、preserved 或 warned)和作业层的代码(refused 或 retry),各附其含义" },
"mcp": { "source": "Serve run, plan, lint, critique, the equivalence check, improve, the classical shortcuts, compile, draw, the debugger and verify to an agent over standard input and output (MCP), with resources and prompts; `mcp config` and `mcp install` register it with a client", "text": "通过标准输入输出(MCP)向智能体提供 run、plan、lint、critique、等价检查、improve、经典捷径、compile、draw、调试器和 verify,以及资源和提示词;`mcp config` 和 `mcp install` 把它注册到客户端" },
"fmt": { "source": "Format programs in one layout: a statement to a line, blocks indented two spaces, a space around each operator, comments and blank lines kept; a program whose brackets do not match is left as it is, with its line", "text": "按统一的版式格式化程序:每行一条语句,块缩进两个空格,运算符两侧各留一个空格,保留注释和空行;括号不匹配的程序保持原样,并给出所在的行" },

View file

@ -37,6 +37,7 @@ Generated by `wai-quantum docs skill` from the servers' own lists; `wai-quantum
- `history_log`: A program's history
- `list_methods`: List the methods
- `list_fabric`: List the fabric's members
- `list_components`: List the engine's components
- `qasm_diagnostics`: The reader's diagnostic codes
- `list_targets`: List the device targets
- `show_target`: Show a device target
@ -72,6 +73,7 @@ Generated by `wai-quantum docs skill` from the servers' own lists; `wai-quantum
- `repair_qasm`: Repair a quantum program on its flagged lines
- `quantum_service_info`: What the quantum service can do
- `qasm_diagnostics`: The reader's diagnostic codes
- `list_components`: List the engine's components
- `list_targets`: List the device targets
- `show_target`: Show a device target
- `run_on_target`: Run a program as a device would

View file

@ -44,6 +44,12 @@ has "$tmp/codes" "Q103 refused"
python3 -c 'import json,sys; t=json.load(open(sys.argv[1])); assert any(c["code"]=="Q103" and c["outcome"]=="refused" for c in t["codes"]), t' "$tmp/codes.json"
echo "lint: ok, with codes"
"$W" components > "$tmp/components"
has "$tmp/components" "density_matrix"
"$W" components --json > "$tmp/components.json"
python3 -c 'import json,sys; c=json.load(open(sys.argv[1])); names=lambda k: [x["name"] for x in c[k]]; assert c["format"]=="wai.quantum.components/1" and c["registered"]==0, c; assert names("front_ends")==["qasm","qir","jeff"] and "exact" in names("methods") and "approximate" in names("passes") and names("channels")[0]=="depolarizing_1q", c; assert all(x["origin"]=="built-in" and len(x["digest"])==64 for k in ("methods","passes","channels","front_ends") for x in c[k]), c' "$tmp/components.json"
echo "components: ok, every built-in with its identity"
"$W" plan "$tmp/ghz.qasm" --json > "$tmp/plan.json"
python3 -c 'import json,sys; p=json.load(open(sys.argv[1])); assert p["method"]=="stabilizer" and p["qubits"]==3, p' "$tmp/plan.json"
echo "plan: ok, stabilizer"

View file

@ -339,6 +339,12 @@ enum Command {
#[arg(long, default_value_t = DEFAULT_MEMORY_GB)]
memory_gb: u64,
},
/// Every component the engine plans, runs, compiles and converts through (the simulation methods, compiler passes, noise channels and program formats), each with its version and the digest of its descriptor: the identity a result is sealed with
Components {
/// Print the list as JSON, each component with its descriptor
#[arg(long)]
json: bool,
},
/// The diagnostic codes: the OpenQASM reader's (each with its outcome: refused, lowered, carried, preserved or warned) and the job layer's (refused or retry), each with its meaning
Diagnostics {
/// Print the table as JSON
@ -607,6 +613,19 @@ fn option_arg(s: &str) -> Result<(String, Value), String> {
Ok((k.to_string(), value))
}
/// The components list, one line each: its kind, name, version, where it
/// comes from and its digest.
fn components_text(c: &Value) -> String {
let mut s = format!("engine {} {}\n", c["engine"]["name"].as_str().unwrap_or(""), c["engine"]["version"].as_str().unwrap_or(""));
for (kind, title) in [("methods", "methods"), ("passes", "passes"), ("channels", "noise channels"), ("front_ends", "formats")] {
s.push_str(&format!("\n{title}\n"));
for x in c[kind].as_array().into_iter().flatten() {
s.push_str(&format!(" {:<20} {:<10} {:<10} {}\n", x["name"].as_str().unwrap_or(""), x["version"].as_str().unwrap_or(""), x["origin"].as_str().unwrap_or(""), x["digest"].as_str().unwrap_or("")));
}
}
s
}
/// The fabric's members as `wai-quantum fabric` prints them.
fn fabric_text(f: &Value) -> String {
let s = |v: &Value| v.as_str().unwrap_or("").to_string();
@ -1449,6 +1468,14 @@ fn main() {
print!("{}", fabric_text(&f));
}
}
Command::Components { json } => {
let c = wai_quantum::engine::registry::Registry::builtins().components();
if json {
println!("{c}");
} else {
print!("{}", components_text(&c));
}
}
Command::Diagnostics { json } => {
if json {
println!("{}", job::diagnostic_codes());

View file

@ -73,6 +73,7 @@ fn output_of(tool: &str) -> Option<&'static str> {
"draw_circuit" => "tools.v1.json#/$defs/draw",
"list_methods" => "tools.v1.json#/$defs/methods",
"list_fabric" => "tools.v1.json#/$defs/fabric_members",
"list_components" => "tools.v1.json#/$defs/components",
"rewrite_qasm" => "tools.v1.json#/$defs/rewrite",
"novelty_qasm" => "tools.v1.json#/$defs/novelty",
"discover_evaluate" => "tools.v1.json#/$defs/discover_evaluate",
@ -453,6 +454,12 @@ fn core_tools() -> Value {
"description": "The members of the fabric a statevector job can be placed on here (a job's `fabric`): the CPU and each accelerator found and certified bit-exact against the CPU in this process, each with its key, its certificate and the most qubits it holds; the devices found and not placed, and why; and the backends this build reaches.",
"inputSchema": { "type": "object", "properties": {} },
},
{
"name": "list_components",
"title": "List the engine's components",
"description": "Every component the engine plans, runs, compiles and converts through here: the simulation methods, the compiler passes, the noise channels and the program formats, each with its version, its origin and the BLAKE3 digest of its descriptor, which is the identity a result is sealed with (a result a registered component took part in names each in its `components`). This server runs the built-ins alone.",
"inputSchema": { "type": "object", "properties": {} },
},
{
"name": "qasm_diagnostics",
"title": "The reader's diagnostic codes",
@ -590,6 +597,7 @@ fn call(name: &str, args: &Value, memory_gb: u64, key: &SigningKey, client: &Cli
}
"list_methods" => Ok(json!({ "methods": catalog::describe(memory_gb << 30) })),
"list_fabric" => Ok(wai_quantum::engine::fabric::list(memory_gb << 30)),
"list_components" => Ok(wai_quantum::engine::registry::Registry::builtins().components()),
"qasm_diagnostics" => match raw.get("code").and_then(Value::as_str) {
None => Ok(job::diagnostic_codes()),
Some(c) => job::diagnostic_code(c),
@ -922,7 +930,7 @@ mod tests {
let list = handle(&json!({ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }), 1, &key(), &mut Client::default()).unwrap();
let names: Vec<&str> = list["result"]["tools"].as_array().unwrap().iter().map(|t| t["name"].as_str().unwrap()).collect();
#[allow(unused_mut)]
let mut want = vec!["run_quantum_circuit", "plan_quantum_circuit", "lint_qasm", "repair_qasm", "critique_qasm", "check_equivalent", "convert_program", "improve_qasm", "classical_shortcuts", "compile_qasm", "estimate_resources", "draw_circuit", "debug_step", "debug_branches", "verify_quantum_result", "discover_evaluate", "discover_submit", "discover_archive", "novelty_qasm", "discover_novelty", "rewrite_qasm", "converge_cohort", "converge_history", "study_measure", "study_cohort", "taste_rank", "taste_next", "history_commit", "history_changes", "history_blame", "history_log", "list_methods", "list_fabric", "qasm_diagnostics", "list_targets", "show_target", "run_on_target", "qec_code", "qec_decode", "qec_threshold", "pulse_schedule", "pulse_check", "gradient_qasm", "optimize_qasm", "vqe_ground_state", "qaoa_maxcut", "build_algorithm"];
let mut want = vec!["run_quantum_circuit", "plan_quantum_circuit", "lint_qasm", "repair_qasm", "critique_qasm", "check_equivalent", "convert_program", "improve_qasm", "classical_shortcuts", "compile_qasm", "estimate_resources", "draw_circuit", "debug_step", "debug_branches", "verify_quantum_result", "discover_evaluate", "discover_submit", "discover_archive", "novelty_qasm", "discover_novelty", "rewrite_qasm", "converge_cohort", "converge_history", "study_measure", "study_cohort", "taste_rank", "taste_next", "history_commit", "history_changes", "history_blame", "history_log", "list_methods", "list_fabric", "list_components", "qasm_diagnostics", "list_targets", "show_target", "run_on_target", "qec_code", "qec_decode", "qec_threshold", "pulse_schedule", "pulse_check", "gradient_qasm", "optimize_qasm", "vqe_ground_state", "qaoa_maxcut", "build_algorithm"];
#[cfg(feature = "providers")]
want.extend(["providers_list", "provider_estimate", "run_on_provider"]);
assert_eq!(names, want);
@ -1450,6 +1458,7 @@ mod tests {
call_tool("verify_quantum_result", json!({ "job": job }), false, &mut called, &mut hold);
call_tool("list_methods", json!({}), false, &mut called, &mut hold);
call_tool("list_fabric", json!({}), false, &mut called, &mut hold);
call_tool("list_components", json!({}), false, &mut called, &mut hold);
// A state split over the CPU, as the fabric places it: the answers the
// CPU gives, and where it ran in the result; a member that is not here
// is refused.

View file

@ -206,7 +206,7 @@ The six checks answer at once, in the service's own process. Each counts against
## For agents
`POST /mcp` serves the service to an agent as Model Context Protocol tools: `run_quantum_circuit`, `run_quantum_set` (a set or a sweep), `dry_run_quantum_job` (the plan, running nothing), `get_quantum_job`, `verify_quantum_receipt`, `critique_qasm`, `check_equivalent`, `convert_program`, `improve_qasm`, `classical_shortcuts`, `compile_qasm`, `estimate_resources`, `repair_qasm`, `quantum_service_info`, `qasm_diagnostics`, `list_targets`, `show_target`, `run_on_target`, `quantum_circuit_examples`, `qec_code`, `qec_decode`, `qec_threshold`, `pulse_schedule`, `pulse_check`, `gradient_qasm`, `optimize_qasm`, `vqe_ground_state`, `qaoa_maxcut` and `build_algorithm`. A run it makes asks for its verdict, so the request its receipt seals is the job's own `request`; every answer about a program carries its verdict, every refusal its codes, and every tool a versioned output schema. `verify_quantum_receipt` re-runs an estimate. It also serves the engine's resources and prompts, the same as the command line's own server (`wai-quantum mcp`):
`POST /mcp` serves the service to an agent as Model Context Protocol tools: `run_quantum_circuit`, `run_quantum_set` (a set or a sweep), `dry_run_quantum_job` (the plan, running nothing), `get_quantum_job`, `verify_quantum_receipt`, `critique_qasm`, `check_equivalent`, `convert_program`, `improve_qasm`, `classical_shortcuts`, `compile_qasm`, `estimate_resources`, `repair_qasm`, `quantum_service_info`, `qasm_diagnostics`, `list_components`, `list_targets`, `show_target`, `run_on_target`, `quantum_circuit_examples`, `qec_code`, `qec_decode`, `qec_threshold`, `pulse_schedule`, `pulse_check`, `gradient_qasm`, `optimize_qasm`, `vqe_ground_state`, `qaoa_maxcut` and `build_algorithm`. A run it makes asks for its verdict, so the request its receipt seals is the job's own `request`; every answer about a program carries its verdict, every refusal its codes, and every tool a versioned output schema. `verify_quantum_receipt` re-runs an estimate. It also serves the engine's resources and prompts, the same as the command line's own server (`wai-quantum mcp`):
- resources: the language the engine reads (`wai-quantum://docs/language`), its diagnostics, the methods with their accuracy classes (their reach in a job's memory here), and worked examples, each with what it must do;
- prompts: writing and verifying a circuit for an intent, explaining a result and its receipt, and repairing a program from its diagnostics. Each names this server's own tools.
@ -262,6 +262,8 @@ Where the service cannot read a count (another system, a zone its user may not r
## Versions
`GET /v1` and `quantum_service_info` list the engine's components (`components`, `wai.quantum.components/1`, which `list_components` also gives): every method, compiler pass, noise channel and format the service runs through, each with its version and the digest of its descriptor. The service runs the built-ins alone and never loads a component from a request ([the extension points](../wai-quantum/docs/extension-points.md)).
`GET /v1` names the service's version and gives `versions` (`wai.quantum.versions/1`): the engine's version, the service's, the schema of each document it writes and the MCP protocol versions it speaks. The MCP server gives the same in `initialize` (`_meta["science.transaction.wai/versions"]`) and in `quantum_service_info`. Every result names the engine that made it and its schema (`engine`, `schema`), inside what its receipt seals. Each MCP tool's `outputSchema` is its answer's schema; this server's own are in [`schemas/service-tools.v1.json`](schemas/service-tools.v1.json), the job's and the engine's in [`wai-quantum/schemas/`](../wai-quantum/schemas). What is stable and for how long: [the policy](https://git.transaction.science/Transaction-Science/open-standards/src/branch/main/wai/wai-quantum/VERSIONING.md); what is deprecated: [the deprecations](https://git.transaction.science/Transaction-Science/open-standards/src/branch/main/wai/wai-quantum/DEPRECATIONS.md).
## Running it

View file

@ -181,6 +181,10 @@
"receipt_keys": {
"type": "string",
"description": "Where the service publishes every key it has signed receipts with (the receipt key set, wai.quantum.receipt-keys/1)."
},
"components": {
"$ref": "https://git.transaction.science/Transaction-Science/open-standards/raw/branch/main/wai/wai-quantum/schemas/tools.v1.json#/$defs/components",
"description": "Every component the service runs through (the built-ins alone), each with its identity: what list_components gives."
}
}
},

View file

@ -248,7 +248,7 @@ mcp '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' > "$tmp/mcp-tools.json"
py 'import json,sys
i=json.load(open(sys.argv[1]))["result"]; t=json.load(open(sys.argv[2]))["result"]["tools"]
assert i["protocolVersion"]=="2025-06-18" and "tools" in i["capabilities"], i
names={x["name"] for x in t}; assert {"run_quantum_circuit","run_quantum_set","dry_run_quantum_job","get_quantum_job","verify_quantum_receipt","quantum_service_info","quantum_circuit_examples","critique_qasm","check_equivalent","improve_qasm","classical_shortcuts","compile_qasm","estimate_resources","qec_code","qec_decode","qec_threshold","gradient_qasm","optimize_qasm","vqe_ground_state","qaoa_maxcut","build_algorithm","pulse_schedule","pulse_check"} <= names, names
names={x["name"] for x in t}; assert {"run_quantum_circuit","run_quantum_set","dry_run_quantum_job","get_quantum_job","verify_quantum_receipt","quantum_service_info","quantum_circuit_examples","critique_qasm","check_equivalent","improve_qasm","classical_shortcuts","compile_qasm","estimate_resources","qec_code","qec_decode","qec_threshold","gradient_qasm","optimize_qasm","vqe_ground_state","qaoa_maxcut","build_algorithm","pulse_schedule","pulse_check","list_components"} <= names, names
assert sys.argv[3]=="202", sys.argv[3]
print("mcp: ok, initialized,", len(t), "tools")' "$tmp/mcp-init.json" "$tmp/mcp-tools.json" "$code"
py 'import json,sys

View file

@ -193,6 +193,7 @@ async fn describe(State(app): State<Shared>) -> Response {
"versions": crate::mcp::versions(),
"methods": catalog::describe(c.job_mib << 20),
"accuracy_classes": catalog::accuracy_classes(),
"components": wai_quantum::engine::registry::Registry::builtins().components(),
"limits": {
"body_bytes": MAX_BODY,
"job_memory_bytes": c.job_mib << 20,

View file

@ -75,6 +75,7 @@ fn output_of(tool: &str) -> Option<&'static str> {
"list_targets" => "service-tools.v1.json#/$defs/targets",
"show_target" => "tools.v1.json#/$defs/target_summary",
"qasm_diagnostics" => "service-tools.v1.json#/$defs/diagnostics",
"list_components" => "tools.v1.json#/$defs/components",
"qec_code" | "qec_decode" | "qec_threshold" => "qec.v1.json",
"pulse_schedule" | "pulse_check" => "pulse.v1.json",
"gradient_qasm" | "optimize_qasm" | "vqe_ground_state" | "qaoa_maxcut" | "build_algorithm" => "algorithms.v1.json",
@ -354,6 +355,13 @@ fn tool_list() -> Value {
"inputSchema": { "type": "object", "properties": { "code": { "type": "string", "description": "One code, as Q103 or J011." } } },
"annotations": { "readOnlyHint": true }
},
{
"name": "list_components",
"title": "List the engine's components",
"description": "Every component the service plans, runs, compiles and converts through: the simulation methods, the compiler passes, the noise channels and the program formats, each with its version, its origin and the BLAKE3 digest of its descriptor, which is the identity a result is sealed with. The service runs the built-ins alone and never loads a component from a request.",
"inputSchema": { "type": "object", "properties": {} },
"annotations": { "readOnlyHint": true }
},
{
"name": "list_targets",
"title": "List the device targets",
@ -666,6 +674,7 @@ async fn call(app: &App, who: &str, name: &str, args: Value, base: &str) -> Valu
"receipt_keys": format!("{base}/.well-known/wai-quantum-receipt-keys"),
"api": discovery::index(base, &app.console),
"versions": versions(),
"components": wai_quantum::engine::registry::Registry::builtins().components(),
});
let lines: Vec<String> = info["methods"]
.as_array()
@ -687,6 +696,11 @@ async fn call(app: &App, who: &str, name: &str, args: Value, base: &str) -> Valu
Err(_) => tool_result(format!("{c}: no such code; call qasm_diagnostics with no code for them all"), json!({ "error": "no such code" }), true),
},
},
"list_components" => {
let c = wai_quantum::engine::registry::Registry::builtins().components();
let n = |k: &str| c[k].as_array().map_or(0, Vec::len);
tool_result(format!("{} methods, {} passes, {} noise channels and {} formats, all built in (wai-quantum {}), each with its version and the digest of its descriptor", n("methods"), n("passes"), n("channels"), n("front_ends"), c["engine"]["version"].as_str().unwrap_or("")), c, false)
}
"quantum_circuit_examples" => {
let ex = discovery::examples();
let lines: Vec<String> = ex.as_array().into_iter().flatten().map(|e| format!("{} — {}: {}", e["name"].as_str().unwrap_or(""), e["title"].as_str().unwrap_or(""), e["shows"].as_str().unwrap_or(""))).collect();
@ -832,7 +846,7 @@ mod tests {
#[test]
fn the_tools_are_these() {
let names: Vec<String> = tools().as_array().unwrap().iter().map(|t| t["name"].as_str().unwrap().to_string()).collect();
assert_eq!(names, ["run_quantum_circuit", "run_quantum_set", "dry_run_quantum_job", "get_quantum_job", "verify_quantum_receipt", "critique_qasm", "check_equivalent", "convert_program", "improve_qasm", "classical_shortcuts", "compile_qasm", "estimate_resources", "repair_qasm", "quantum_service_info", "qasm_diagnostics", "list_targets", "show_target", "run_on_target", "quantum_circuit_examples", "qec_code", "qec_decode", "qec_threshold", "pulse_schedule", "pulse_check", "gradient_qasm", "optimize_qasm", "vqe_ground_state", "qaoa_maxcut", "build_algorithm"]);
assert_eq!(names, ["run_quantum_circuit", "run_quantum_set", "dry_run_quantum_job", "get_quantum_job", "verify_quantum_receipt", "critique_qasm", "check_equivalent", "convert_program", "improve_qasm", "classical_shortcuts", "compile_qasm", "estimate_resources", "repair_qasm", "quantum_service_info", "qasm_diagnostics", "list_components", "list_targets", "show_target", "run_on_target", "quantum_circuit_examples", "qec_code", "qec_decode", "qec_threshold", "pulse_schedule", "pulse_check", "gradient_qasm", "optimize_qasm", "vqe_ground_state", "qaoa_maxcut", "build_algorithm"]);
let run = &tools()[0]["inputSchema"]["properties"]["compile"]["properties"];
assert!(run.get("epsilon").is_some() && run.get("basis").is_some() && run.get("target").is_none(), "the compile's gate set is its basis; target is a device: {run}");
}
@ -847,7 +861,7 @@ mod tests {
}
let all = tools();
let names: Vec<&str> = all.as_array().unwrap().iter().map(|t| t["name"].as_str().unwrap()).collect();
assert_eq!(names, ["run_quantum_circuit", "run_quantum_set", "dry_run_quantum_job", "get_quantum_job", "verify_quantum_receipt", "critique_qasm", "check_equivalent", "convert_program", "improve_qasm", "classical_shortcuts", "compile_qasm", "estimate_resources", "repair_qasm", "quantum_service_info", "qasm_diagnostics", "list_targets", "show_target", "run_on_target", "quantum_circuit_examples", "qec_code", "qec_decode", "qec_threshold", "pulse_schedule", "pulse_check", "gradient_qasm", "optimize_qasm", "vqe_ground_state", "qaoa_maxcut", "build_algorithm"]);
assert_eq!(names, ["run_quantum_circuit", "run_quantum_set", "dry_run_quantum_job", "get_quantum_job", "verify_quantum_receipt", "critique_qasm", "check_equivalent", "convert_program", "improve_qasm", "classical_shortcuts", "compile_qasm", "estimate_resources", "repair_qasm", "quantum_service_info", "qasm_diagnostics", "list_components", "list_targets", "show_target", "run_on_target", "quantum_circuit_examples", "qec_code", "qec_decode", "qec_threshold", "pulse_schedule", "pulse_check", "gradient_qasm", "optimize_qasm", "vqe_ground_state", "qaoa_maxcut", "build_algorithm"]);
// Each declares its output, versioned; a tool that reads a program requires its verdict.
for t in all.as_array().unwrap() {
let (name, o) = (t["name"].as_str().unwrap(), &t["outputSchema"]);
@ -995,6 +1009,7 @@ mod tests {
("repair_qasm", json!({ "qasm": "OPENQASM 3.0;\nqubit[1] q;\nfoo q[0];\n", "proposed": "OPENQASM 3.0;\nqubit[2] q;\nh q[0];\n" }), true),
("estimate_resources", json!({ "counts": { "numQubits": 10, "cczCount": 20, "measurementCount": 10 } }), false),
("estimate_resources", json!({ "qasm": "qubit[1] q;\nh q[0];\n", "budget": 3 }), true),
("list_components", json!({}), false),
("list_targets", json!({}), false),
("show_target", json!({ "target": "synthetic-heavy-hex-27", "view": true }), false),
("show_target", json!({ "target": "a-device-elsewhere" }), true),

View file

@ -341,6 +341,11 @@ required-features = ["quantum_engine"]
name = "qec_text_conformance"
required-features = ["quantum_frame", "quantum_ldpc"]
[[example]]
name = "extension_points"
required-features = ["quantum_engine", "quantum_receipt"]
test = true
[[example]]
name = "stablehlo_gate"
required-features = ["quantum_fabric"]

View file

@ -897,6 +897,36 @@ implementation:
and two-mode statistics. Those are computable for any number of modes, and
every sampler of the state must reproduce them.
## Extension points: your own method, pass, noise channel or format
```sh runnable
wai-quantum components # every component the engine runs through here, with its identity
```
`engine::registry` holds every simulation method, compiler pass, noise
channel and program format the engine plans, runs, compiles and converts
through, each behind a trait (`Method`, `Pass`, `Channel`, `FrontEnd`). The
built-ins register through the same traits, and a host that embeds the
engine registers its own beside them with `Registry::with_builtins()` and
`register(...)`, in its own process: nothing is loaded from a file or a
request, and the command line, the MCP servers and the job service run the
built-ins alone. A registered component runs where a built-in runs (a job's
plan and run, `auto`'s choice, a compile's `passes`, a job's `noise`, a
conversion or a job's `source`), and the engine checks what it gives: a
method's result carries a class it declared and leaves the engine's members
alone, a pass is held to its input by the equivalence ladder or a distance
bound on the whole circuit, a channel's Kraus operators are checked
completely positive and trace preserving at each job's values, and a
format's program must read. Each component's identity is its name, its
version and the BLAKE3 digest of its descriptor. A result made by built-ins
alone is the result it always was, byte for byte; one a registered
component took part in carries `components`, every component that made it,
the registered ones marked `registered` and `certified: false`, and its
receipt seals them. [docs/extension-points.md](docs/extension-points.md)
says how to write one, with a channel registered and run, and
[examples/extension_points.rs](examples/extension_points.rs) has a method, a
pass, a channel and a format run end to end.
## Determinism
Simulation is **byte-exact**: amplitudes are dyadic fixed-point, so a circuit

View file

@ -479,6 +479,14 @@ wai-quantum lab gbs --squeezing 0.9,0.7,0.8,0.6 --eta 0.5 --seed 3 --check s.txt
`--check` 用该态精确的单模与双模统计来检验一个观测点击模式文件。这些统计对任意模式数都可以计算,该态的每一个采样器都必须复现它们。
## 扩展点:你自己的方法、编译遍、噪声信道或格式
```sh runnable
wai-quantum components # 引擎在本机所经过的每个组件,及其身份
```
`engine::registry` 持有引擎规划、运行、编译和转换所经过的每个模拟方法、编译遍、噪声信道和程序格式,各在一个 trait 之后(`Method`、`Pass`、`Channel`、`FrontEnd`)。内置组件通过同样的 trait 注册;嵌入引擎的宿主在自己的进程中用 `Registry::with_builtins()` 和 `register(...)` 在它们旁边注册自己的组件:不从文件或请求加载任何东西,命令行、MCP 服务器和作业服务只运行内置组件。注册的组件在内置组件运行的地方运行(作业的规划与运行、`auto` 的选择、编译的 `passes`、作业的 `noise`、转换或作业的 `source`),引擎检查它给出的东西:方法的结果带有它声明的精度等级,并且不改动引擎写入的成员;编译遍在整条线路上按等价阶梯或距离界与其输入对照;信道的 Kraus 算子在每个作业的取值下检查为完全正且保迹;格式读出的程序必须可读。每个组件的身份是它的名称、版本及其描述符的 BLAKE3 摘要。只由内置组件做出的结果与以往逐字节相同;有注册组件参与的结果带有 `components`,即做出它的每个组件,注册的组件标为 `registered` 和 `certified: false`,并由收据封存。[docs/extension-points.md](docs/extension-points.md) 说明如何编写,附一个注册并运行的信道;[examples/extension_points.rs](examples/extension_points.rs) 有一个方法、一个编译遍、一个信道和一个格式的端到端运行。
## 确定性
模拟是**字节级一致**的:振幅是二进定点数,因此一条线路在每台机器、每个目标上都重构出相同的 `statevector_hash`。近似方法(MPS 截断、稀疏 Pauli 动力学、相量/机器学习层)是*可复现的 f64*:严格遵循 IEEE-754,在每个平台上给定相同的输入和种子时结果完全相同。文档如实地这样描述它们,而不声称它们字节级一致。它们的 `sin`、`cos`、`ln` 和 `atan2` 都在 crate 内部由 `+ − × ÷` 计算得出,因为各平台的数学库在最后一位上存在差异——这种差异足以让其中四个层曾经在原生运行和作为 WASI 组件运行时给出不同的位。`tests/bit_identity.rs` 为每个层固定一个结果,因此任何算术不同的平台都会在那里失败。

View file

@ -173,6 +173,7 @@ def main() -> int:
("draw_circuit", {"qasm": bell}),
("verify_quantum_result", {"job": run}),
("list_methods", {}),
("list_components", {}),
("novelty_qasm", {"qasm": bell}),
("rewrite_qasm", {"qasm": bell, "op": "options"}),
("rewrite_qasm", {"qasm": bell, "op": "simplify"}),

View file

@ -0,0 +1,125 @@
# Extension points: methods, passes, noise channels and formats
The engine plans, runs, compiles and converts through one registry
(`engine::registry`). Each simulation method, compiler pass, noise channel
and program format in it is a component behind a trait, and every built-in
is registered through the same trait a host's own implements. A host that
embeds the engine as a Rust library registers its own beside them; they run
where the built-ins run, and the engine checks what they give.
```sh runnable
wai-quantum components # every component here, its version and digest
wai-quantum components --json # with each one's descriptor
```
The command line, both MCP servers (`list_components`) and the job service
(`GET /v1`, `quantum_service_info`) list the built-ins. None of them loads a
component from a file, a path or a request: registration happens in the
process of the host that embeds the engine, in Rust.
## The four traits
| Trait | It declares | The engine calls | The engine checks |
|---|---|---|---|
| `Method` | its name, version, accuracy classes, what circuits and answers it takes (its descriptor), whether it models noise, where `auto` tries it | `plan`: admit the job (or refuse it) and estimate its memory and work; `run`: carry the plan out | a job with noise is refused for a method that models none; every channel the job names must be one it takes; its result must carry an accuracy class it declared and leave the members the engine writes as they are |
| `Pass` | its name, version, the gates it takes and gives, and how its result is checked: by the equivalence ladder or by a distance bound (`Check::Ladder`, `Check::Fidelity`) | `run`, after the level's own passes and before twirling and decoupling, when a compile's `passes` names it | its gates on the program's qubits and reading back; its output held to its input on the whole circuit by the check it declares (never by a hash); a distance within its bound and within what is left of the compile's ε budget |
| `Channel` | its name, version, parameters with their ranges, the qubits it acts on, its Kraus operators and, where it is one, its Pauli form | `kraus` and `pauli` at a job's values; `taken_by`, for how each noisy method takes it | its operators completely positive and trace preserving (their sum of `K†K` within 1e-9 of the identity) at every job's values, and its Pauli form a distribution whose channel is its operators' |
| `FrontEnd` | its name, version, whether it is binary, its file extensions and profiles | `read` (a program, as OpenQASM 3) and `write` | what it reads must read as OpenQASM 3; a conversion holds what it writes to the program by the equivalence ladder, as for the built-in formats |
A registered pass is checked by the engine on the whole circuit. A built-in
pass checks itself where it acts, as its record in the compile certificate
says, and the tests hold every built-in to the gates it declares it gives.
## How a built-in is identified, and a registered one
Every component has an identity: its kind, its name, its version and the
BLAKE3 digest of its descriptor in canonical JSON (keys sorted, no
whitespace), and whether it is built in. A built-in's version is the
engine's.
A result made by built-ins alone is the result it always was, byte for byte:
it names its method, its compile's passes, its noise's channels and the
engine's version, and the receipt seals them. The digests for that version
are the ones `wai-quantum components` lists. A result that a registered
component took part in also carries `components`: the identity of every
component that made it (its program's format, its compile's passes, its
noise channels, its method), each registered one marked `"origin":
"registered"` and `"certified": false`. The receipt seals it with the rest of
the result, so a registered component cannot be passed off as a built-in, and
a name a built-in holds is never registered.
## Writing one
A noise channel, registered and run: `X` with probability `p` after each
gate, which the density matrix takes through its Kraus operators.
```rust runnable features=quantum_engine
use wai_quantum::engine::registry::{Channel, Component, Param, Registry, Value, json};
use wai_quantum::gates::C;
struct BitFlip;
impl Channel for BitFlip {
fn name(&self) -> &str { "bit_flip" }
fn version(&self) -> &str { "1.0.0" }
fn title(&self) -> &str { "bit flip" }
fn descriptor(&self) -> Value { json!({ "does": "X with probability p after each gate" }) }
fn params(&self) -> Vec<Param> { vec![Param::new("p", 0.0, 1.0, "a probability")] }
fn arity(&self) -> u32 { 1 }
fn kraus(&self, v: &[f64]) -> Vec<Vec<C>> {
let (a, b) = ((1.0 - v[0]).sqrt(), v[0].sqrt());
let c = |re| C { re, im: 0.0 };
vec![vec![c(a), c(0.0), c(0.0), c(a)], vec![c(0.0), c(b), c(b), c(0.0)]]
}
fn pauli(&self, v: &[f64]) -> Option<Vec<(f64, String)>> {
Some(vec![(1.0 - v[0], "I".into()), (v[0], "X".into())])
}
}
fn main() {
let mut r = Registry::with_builtins();
let id = r.register(Component::channel(BitFlip)).expect("registers");
let job = json!({ "qasm": "OPENQASM 3.0;\nqubit q;\nx q;\n", "observables": ["Z0"], "noise": { "bit_flip": 0.1 } });
let (plan, result) = r.plan_and_run(&job, 1 << 30).expect("runs");
assert_eq!(plan.method, "density_matrix");
let z = result["expectations"][0]["value"].as_f64().unwrap();
assert!((z - (-0.8)).abs() < 1e-12, "⟨Z⟩ = −1 + 2p");
assert_eq!(result["components"][1]["digest"], json!(id.digest));
println!("{}", result["components"]);
}
```
`examples/extension_points.rs` has one of each, a method (`basis_table`, for
circuits that permute basis states), a pass (`cancel_pairs`), a channel
(`bit_flip`) and a format (`gate_list`), written against the public traits
alone, registered, and run end to end with their tests:
```sh norun=ci:wai-quantum-registry.yml
cargo test --locked --release --features quantum_engine,quantum_receipt --example extension_points
cargo run --locked --release --features quantum_engine,quantum_receipt --example extension_points
```
## What is refused, and how
| Code | When |
|---|---|
| `J060` | a compile's `passes` names a pass this host does not hold |
| `J061` | a noise channel the method cannot take: a method that models no noise, amplitude damping on the stabilizer's Pauli frames, a registered channel on a method that takes the built-in channels alone |
| `J062` | a channel whose operators fail the validity check at the job's values |
| `J063` | a registration the registry does not take: a name its kind holds already (a built-in's among them), a name or version that is not plain text, a descriptor that is not a JSON object, a channel on other than one or two qubits, a pass whose fidelity check states no finite bound |
| `J064` | a registered component's answer that breaks the contract: a method's result without a class it declared, or one that writes a member the engine writes; a pass's gates off the program's qubits or that do not read back |
| `J065` | an intent on a job whose noise names a registered channel: the intent checks model the built-in channels alone |
A method named for a job it cannot take is refused with `J011`, as a built-in
is, and `auto` passes over it to the next. [job-refusals.md](job-refusals.md)
has every code.
## Determinism
Components are kept in the order they are registered, the built-ins first in
a fixed order, and every listing and every choice follows that order: `auto`
tries methods by their rank (the built-ins hold 10, 20, 30, …, in the order
they have always been tried), equal ranks in the order of registration. A
registered component's results are the same bytes for the same job, as the
built-ins' are, when the component itself is deterministic. Device-target
jobs, sets and sweeps run on the built-in components.

View file

@ -62,7 +62,7 @@ browser engine. A test holds this page to the code's table.
| `J008` | refused | The mitigation the job asked for does not read or cannot be done: no noise model, no observables (no shots, for readout), a program that measures in the middle, more qubits or work than it takes, a device target beside it, or options that do not read (the message says which). |
| `J009` | refused | An error-correction request that does not read or cannot be done: an unknown code or decoder, a decoder the code does not admit, a syndrome or an error that does not fit the code, circuit text or a model that does not read, or sizes past the limits (the message says which). |
| `J010` | refused | A method that does not exist: the methods list names them. |
| `J011` | refused | The method named cannot take this job: a gate it does not hold, more qubits, memory or work than it takes here, noise it does not model, or an answer it does not give; the message says which, and `auto` picks a method that can. |
| `J011` | refused | The method named cannot take this job: a gate it does not hold, more qubits, memory or work than it takes here, or an answer it does not give (noise it cannot take is `J061`); the message says which, and `auto` picks a method that can. |
| `J012` | refused | No method takes this job: each method's reason is listed, so the job can be changed to suit one. |
| `J013` | refused | A placement on the fabric (`fabric`) this host cannot honour: no member by that name, no certified accelerator member here, a state or a partition larger than a member holds, a partition count that is not a power of two, or a method or program the fabric's members do not run (the message says which, and what is here). |
| `J020` | refused | An intent that does not read: not one of `distribution`, `equivalent` or `prepares`, a distribution whose outcomes are not the program's bits or whose probabilities do not sum to 1, or a reference program that does not read. |
@ -78,6 +78,12 @@ browser engine. A test holds this page to the code's table.
| `J051` | refused | A gradient that cannot be taken: a differentiation method that does not exist, a parameter the reader cannot carry to the gates (its cause `Q129`), a program that measures in the middle, noise, no Hamiltonian, or more qubits or work than it takes (the message says which). |
| `J052` | refused | An optimisation, a variational eigensolver or a QAOA run that does not read or cannot be done: an unknown optimiser, settings or steps past their limits, a Hamiltonian or a graph that does not read (the message says which). |
| `J053` | refused | An algorithm builder's request that does not read: an unknown algorithm, sizes past its limits, or arguments it does not take (the message says which). |
| `J060` | refused | A compiler pass a request names (`compile.passes`) that this host does not hold: the components list names every method, pass, noise channel and front end here, and no host loads a component from a request. |
| `J061` | refused | A noise channel the method cannot take: a method that models no noise, or a channel the method has no form for (amplitude damping on the stabilizer's Pauli frames, a registered channel on a method that takes only the built-in ones); the message names what does take it. |
| `J062` | refused | A noise channel whose parameters do not read or whose operators fail its validity check: a value outside its range, Kraus operators that are not completely positive and trace preserving (their sum of K†K off the identity), or a Pauli form whose probabilities are negative, do not sum to 1, or are not the channel its operators make (the message says by how much). |
| `J063` | refused | A component the registry does not take: a name its kind holds already (a built-in's among them), a name or version that is empty or not plain text, a descriptor that is not a JSON object, a channel on other than one or two qubits, or a pass whose check states no bound. |
| `J064` | refused | A registered component's answer that breaks the engine's contract: a method's result without an accuracy class it declared, or one that writes a member the engine writes (method, qubits, gates, compiled, repaired, engine, schema, components, receipt, verdict); a pass's gates on qubits the program does not have, or that do not read back; a front end's program that does not read. Nothing it gave is returned. |
| `J065` | refused | An intent on a job whose noise names a registered channel: the intent checks model the built-in channels alone, so the check is refused rather than made against another model. |
| `J100` | dropped | A line of an answer that is not a statement (`name = View(...)`), a program (a fenced `qasm` block with its name), a comment or a marker: left out. |
| `J101` | dropped | A view the answer layer does not have: left out (the views are listed in the layer's specification). |
| `J102` | dropped | An `@` call the engine does not have: the statement is left out. |

View file

@ -0,0 +1,332 @@
//! A host's own components, written outside the engine against its public
//! traits, registered beside the built-ins and run end to end: a simulation
//! method, a compiler pass, a noise channel and a program format.
//!
//! ```sh
//! cargo run --release --example extension_points
//! cargo test --release --example extension_points
//! ```
//!
//! - `basis_table`, a method: a circuit of `x`, `cx`, `ccx` and `swap` gates
//! keeps one basis state, so its answer is that state, followed bit by bit
//! and exact. `auto` tries it first for counts; it refuses every other
//! circuit, and `auto` goes on to the built-ins.
//! - `cancel_pairs`, a pass: adjacent pairs of one self-inverse gate on the
//! same qubits removed. The engine checks its output against its input by
//! the equivalence ladder, as it declares.
//! - `bit_flip`, a noise channel: `X` with probability `p` after each gate,
//! given by its Kraus operators and its Pauli form, which the engine checks
//! at every job's value; the density matrix takes it.
//! - `gate_list`, a format: `qubits N`, then a gate and its qubits a line,
//! and `measure`; read to OpenQASM 3 and written from the engine's program.
//!
//! Every result one of them made names them in `components`, each marked
//! `registered` and `certified: false`, and the receipt seals it.
use serde_json::{Value, json};
use wai_quantum::engine::catalog::Plan;
use wai_quantum::engine::formats::{Exported, Imported};
use wai_quantum::engine::registry::{Channel, Check, Component, FrontEnd, Job, Method, Param, Pass, PassInput, PassOutput, Question, Registry};
use wai_quantum::gates::C;
use wai_quantum::qasm::diag::{Diagnostic, FORMAT_UNREAD, WRITE_CANNOT};
use wai_quantum::qasm::{Kind, Op, Program};
/// A method for circuits that permute basis states.
pub struct BasisTable;
fn permutes(p: &Program) -> bool {
p.ops.iter().all(|o| matches!(o.kind, Kind::X | Kind::Cx | Kind::Ccx | Kind::Swap | Kind::Id))
}
/// The basis state the circuit leaves, qubit by qubit.
fn follow(p: &Program) -> Vec<bool> {
let mut b = vec![false; p.n_qubits as usize];
for o in &p.ops {
let q: Vec<usize> = o.qubits.iter().map(|&q| q as usize).collect();
match o.kind {
Kind::X => b[q[0]] = !b[q[0]],
Kind::Cx if b[q[0]] => b[q[1]] = !b[q[1]],
Kind::Ccx if b[q[0]] && b[q[1]] => b[q[2]] = !b[q[2]],
Kind::Swap => b.swap(q[0], q[1]),
_ => {}
}
}
b
}
impl Method for BasisTable {
fn name(&self) -> &str {
"basis_table"
}
fn version(&self) -> &str {
"1.0.0"
}
fn classes(&self) -> Vec<String> {
vec!["exact".into()]
}
fn descriptor(&self) -> Value {
json!({ "circuits": "x, cx, ccx and swap alone: a permutation of basis states", "answers": ["counts", "amplitudes"] })
}
fn auto_rank(&self, question: Question) -> Option<u32> {
// Before every built-in, for counts alone.
(question == Question::Counts).then_some(5)
}
fn plan(&self, job: &Job<'_>) -> Result<Plan, String> {
let r = job.request()?;
if !permutes(&r.program) {
return Err("basis_table takes x, cx, ccx and swap alone".into());
}
if !r.observables.is_empty() {
return Err("basis_table answers counts and amplitudes".into());
}
let n = r.program.n_qubits;
Ok(Plan { method: "basis_table".into(), why: format!("a permutation of basis states on {n} qubits, followed bit by bit"), memory_bytes: 1 << 20, work: r.program.ops.len() as u64, qubits: n, target: None })
}
fn run(&self, job: &Job<'_>, _: &Plan, out: &mut Value) -> Result<(), String> {
let r = job.request()?;
let b = follow(&r.program);
out["accuracy"] = json!({ "class": "exact", "note": "the one basis state a permutation leaves, followed bit by bit" });
if r.shots > 0 {
let mut counts = serde_json::Map::new();
counts.insert(r.program.bitstring(|q| b[q as usize]), json!(r.shots));
out["counts"] = Value::Object(counts);
}
if !r.amplitudes.is_empty() {
let here: String = (0..b.len()).rev().map(|q| if b[q] { '1' } else { '0' }).collect();
out["amplitudes"] = r.amplitudes.iter().map(|a| json!({ "bits": a, "re": if *a == here { 1.0 } else { 0.0 }, "im": 0.0 })).collect();
}
Ok(())
}
}
/// A pass: adjacent pairs of one self-inverse gate on the same qubits removed.
pub struct CancelPairs;
impl Pass for CancelPairs {
fn name(&self) -> &str {
"cancel_pairs"
}
fn version(&self) -> &str {
"1.0.0"
}
fn descriptor(&self) -> Value {
json!({ "does": "adjacent pairs of one self-inverse gate on the same qubits removed" })
}
fn check(&self) -> Check {
Check::Ladder
}
fn run(&self, input: &PassInput<'_>) -> Result<PassOutput, String> {
let inverse = |k: Kind| matches!(k, Kind::X | Kind::Y | Kind::Z | Kind::H | Kind::Cx | Kind::Cz | Kind::Swap | Kind::Ccx);
let mut out: Vec<Op> = Vec::new();
let mut dropped = 0;
for o in input.ops {
if inverse(o.kind) && out.last().is_some_and(|l| l.kind == o.kind && l.qubits == o.qubits) {
out.pop();
dropped += 1;
} else {
out.push(o.clone());
}
}
Ok(PassOutput::new(out).with_note(json!({ "how": "adjacent pairs dropped", "pairs": dropped })))
}
}
/// A channel: the bit flip.
pub struct BitFlip;
fn re(x: f64) -> C {
C { re: x, im: 0.0 }
}
impl Channel for BitFlip {
fn name(&self) -> &str {
"bit_flip"
}
fn version(&self) -> &str {
"1.0.0"
}
fn title(&self) -> &str {
"bit flip"
}
fn descriptor(&self) -> Value {
json!({ "does": "X with probability p after each gate, on each qubit it touched" })
}
fn params(&self) -> Vec<Param> {
vec![Param::new("p", 0.0, 1.0, "a probability")]
}
fn arity(&self) -> u32 {
1
}
fn kraus(&self, v: &[f64]) -> Vec<Vec<C>> {
let (a, b) = ((1.0 - v[0]).sqrt(), v[0].sqrt());
vec![vec![re(a), re(0.0), re(0.0), re(a)], vec![re(0.0), re(b), re(b), re(0.0)]]
}
fn pauli(&self, v: &[f64]) -> Option<Vec<(f64, String)>> {
Some(vec![(1.0 - v[0], "I".into()), (v[0], "X".into())])
}
}
/// A format: a list of gates.
pub struct GateList;
impl FrontEnd for GateList {
fn name(&self) -> &str {
"gate_list"
}
fn version(&self) -> &str {
"1.0.0"
}
fn title(&self) -> &str {
"a gate list"
}
fn descriptor(&self) -> Value {
json!({ "reads": "qubits N, then a gate and its qubits a line, and measure", "writes": "the same, for gates without parameters" })
}
fn extensions(&self) -> Vec<String> {
vec!["gates".into()]
}
fn read(&self, input: &[u8]) -> Result<Imported, Diagnostic> {
let text = std::str::from_utf8(input).map_err(|_| Diagnostic::new(FORMAT_UNREAD, None, "a gate list is text"))?;
let mut q = String::from("OPENQASM 3.0;\n");
let mut declared = false;
for (i, line) in text.lines().map(str::trim).filter(|l| !l.is_empty()).enumerate() {
let w: Vec<&str> = line.split_whitespace().collect();
match (i, w.as_slice()) {
(0, ["qubits", k]) => {
let k: u32 = k.parse().map_err(|_| Diagnostic::new(FORMAT_UNREAD, None, "the first line is qubits N"))?;
q.push_str(&format!("qubit[{k}] q;\nbit[{k}] c;\n"));
declared = true;
}
(0, _) => return Err(Diagnostic::new(FORMAT_UNREAD, None, "a gate list starts with qubits N")),
(_, ["measure"]) => q.push_str("c = measure q;\n"),
(_, [g, qs @ ..]) => q.push_str(&format!("{g} {};\n", qs.iter().map(|x| format!("q[{x}]")).collect::<Vec<_>>().join(", "))),
_ => {}
}
}
if !declared {
return Err(Diagnostic::new(FORMAT_UNREAD, None, "an empty gate list"));
}
Ok(Imported { qasm: q, notes: Vec::new(), summary: json!({ "format": "gate_list" }) })
}
fn write(&self, p: &Program, _: Option<&str>) -> Result<Exported, Diagnostic> {
let mut s = format!("qubits {}\n", p.n_qubits);
for o in &p.ops {
if !o.params.is_empty() {
return Err(Diagnostic::new(WRITE_CANNOT, None, "a gate list holds gates without parameters"));
}
let qs: Vec<String> = o.qubits.iter().map(u32::to_string).collect();
s.push_str(&format!("{} {}\n", format!("{:?}", o.kind).to_lowercase(), qs.join(" ")));
}
if !p.measured.is_empty() {
s.push_str("measure\n");
}
Ok(Exported { bytes: s.into_bytes(), notes: Vec::new(), summary: json!({ "format": "gate_list" }) })
}
}
/// The built-ins, and the four components above after them.
pub fn registry() -> Registry {
let mut r = Registry::with_builtins();
for c in [Component::method(BasisTable), Component::pass(CancelPairs), Component::channel(BitFlip), Component::front_end(GateList)] {
let id = r.register(c).expect("registers");
assert!(!id.is_builtin());
}
r
}
/// A gate list: `x` on each qubit, the pair on qubit 2 a pair the pass
/// cancels, then `cx` and `swap`.
pub const LIST: &str = "qubits 3\nx 0\nx 1\nx 2\nx 2\ncx 0 1\nswap 0 2\nmeasure\n";
/// The four end to end: a gate list read as a job's source, compiled with
/// the pass, run on the method, and the same program under the channel.
pub fn walk_through(r: &Registry) -> Result<Value, String> {
let list = LIST;
let job = r.expand(&json!({ "source": { "format": "gate_list", "program": list }, "shots": 100, "compile": { "level": 0, "basis": "standard", "passes": ["cancel_pairs"] } }))?.ok_or("a job with a source expands")?;
let (plan, result) = r.plan_and_run(&job, 1 << 30)?;
let noisy = json!({ "qasm": job["qasm"], "observables": ["Z0", "Z2"], "noise": { "bit_flip": 0.05 } });
let (noisy_plan, noisy_result) = r.plan_and_run(&noisy, 1 << 30)?;
let written = r.convert_json(&json!({ "from": "qasm", "to": "gate_list", "program": job["qasm"] }))?;
Ok(json!({ "plan": plan.method, "why": plan.why, "result": result, "noisy_plan": noisy_plan.method, "noisy": noisy_result, "written": written }))
}
fn main() {
let r = registry();
let c = r.components();
println!("{} components, {} registered:", r.identities().len(), c["registered"]);
for id in r.identities().into_iter().filter(|i| !i.is_builtin()) {
println!(" {:<10} {:<14} {:<7} {}", id.kind.as_str(), id.name, id.version, id.digest);
}
match walk_through(&r) {
Ok(w) => {
println!("\nthe job planned on {}: {}", w["plan"], w["why"]);
println!("counts {}", w["result"]["counts"]);
println!("compiled by {}", w["result"]["compiled"]["source"]);
println!("under bit_flip on {}: {}", w["noisy_plan"], w["noisy"]["expectations"]);
println!("written back as a gate list:\n{}", w["written"]["program"].as_str().unwrap_or(""));
println!("made by: {}", serde_json::to_string_pretty(&w["result"]["components"]).unwrap_or_default());
}
Err(e) => {
eprintln!("{e}");
std::process::exit(1);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_four_run_end_to_end_and_are_named_in_what_they_make() {
let r = registry();
let w = walk_through(&r).unwrap();
// x on every qubit, the pair on q[2] cancelled, then cx and swap: q[2] alone reads 1.
assert_eq!(w["plan"], "basis_table", "{}", w["why"]);
assert_eq!(w["result"]["counts"], json!({ "100": 100 }), "{w}");
let names: Vec<(&str, &str)> = w["result"]["components"].as_array().unwrap().iter().map(|c| (c["name"].as_str().unwrap(), c["origin"].as_str().unwrap())).collect();
assert_eq!(names, [("gate_list", "registered"), ("lower", "built-in"), ("cancel_pairs", "registered"), ("basis_table", "registered")]);
assert!(w["result"]["components"].as_array().unwrap().iter().filter(|c| c["origin"] == "registered").all(|c| c["certified"] == false));
// The same counts as the bit-exact method, a built-in, gives.
let mut exact = r.expand(&json!({ "source": { "format": "gate_list", "program": LIST }, "shots": 100 })).unwrap().unwrap();
exact["method"] = json!("exact");
assert_eq!(r.plan_and_run(&exact, 1 << 30).unwrap().1["counts"], w["result"]["counts"]);
// Under the bit flip, on the density matrix: ⟨Z⟩ of a flipped qubit
// after the gates that touch it, each flip with probability 0.05.
assert_eq!(w["noisy_plan"], "density_matrix");
let z: Vec<f64> = w["noisy"]["expectations"].as_array().unwrap().iter().map(|e| e["value"].as_f64().unwrap()).collect();
assert!(z.iter().all(|v| v.abs() < 1.0 && v.abs() > 0.5), "{z:?}");
// Written back as a gate list, read again and held to the program.
assert_eq!(w["written"]["check"]["verdict"]["verdict"], "equivalent", "{}", w["written"]);
}
#[test]
fn the_receipt_seals_them() {
let r = registry();
let key = ed25519_dalek_key();
let job = json!({ "qasm": "OPENQASM 3.0;\nqubit[2] q;\nbit[2] c;\nx q[0];\ncx q[0], q[1];\nc = measure q;\n", "shots": 10 });
let body = r.job(&job, 1 << 30, &key, json!({ "unmetered": "an example" })).unwrap();
assert!(wai_quantum::engine::job::verify(&body).unwrap().0);
let mut forged = body.clone();
forged["result"]["components"][1]["origin"] = json!("built-in");
assert!(!wai_quantum::engine::job::verify(&forged).unwrap().0, "a registered method cannot pass for a built-in");
}
fn ed25519_dalek_key() -> ed25519_dalek::SigningKey {
ed25519_dalek::SigningKey::from_bytes(&[9; 32])
}
#[test]
fn what_they_do_not_take_is_refused_with_its_code() {
let r = registry();
let codes = |e: String| wai_quantum::engine::refusal::codes_in(&e).iter().map(|c| c.to_string()).collect::<Vec<_>>();
let bell = "OPENQASM 3.0;\nqubit[2] q;\nh q[0];\ncx q[0], q[1];\n";
assert_eq!(codes(r.plan(&json!({ "qasm": bell, "shots": 1, "method": "basis_table" }), 1 << 30).unwrap_err()), ["J011"]);
assert_eq!(codes(r.plan(&json!({ "qasm": bell, "shots": 1, "method": "stabilizer", "noise": { "bit_flip": 0.1 } }), 1 << 30).unwrap_err()), ["J061"]);
assert_eq!(codes(r.plan(&json!({ "qasm": bell, "shots": 1, "compile": { "passes": ["no_such_pass"] } }), 1 << 30).unwrap_err()), ["J060"]);
let mut again = registry();
assert!(again.register(Component::method(BasisTable)).unwrap_err().ends_with("[J063]"));
// The built-in registry holds none of them.
assert!(Registry::builtins().plan(&json!({ "qasm": bell, "shots": 1, "method": "basis_table" }), 1 << 30).unwrap_err().ends_with("[J010]"));
}
}

View file

@ -289,6 +289,13 @@
},
"receipt": {
"$ref": "receipt.v2.json"
},
"components": {
"type": "array",
"description": "The identity of every component that made the compile, when a registered pass took part (engine::registry): the program's format and each pass, each registered one marked so.",
"items": {
"$ref": "#/$defs/component"
}
}
}
},
@ -382,6 +389,10 @@
},
"check": {
"type": "object"
},
"component": {
"$ref": "#/$defs/component",
"description": "Beside a registered pass's record: its identity. Its check is the engine's, on the whole circuit (by: engine)."
}
}
}
@ -406,6 +417,49 @@
"description": "What the certificate says of the output, in words."
}
}
},
"component": {
"type": "object",
"description": "One component that made the result (engine::registry): its kind, name and version, the BLAKE3 digest of its descriptor, and whether it is built in. A result carries these only when a registered component took part; one made by built-ins alone names them as it always has (its method, its compile's passes, its noise's channels and the engine's version), the digests of that version listed by the components list.",
"required": [
"kind",
"name",
"version",
"digest",
"origin"
],
"properties": {
"kind": {
"enum": [
"method",
"pass",
"channel",
"front_end"
]
},
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"digest": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "BLAKE3 of the component's descriptor in its canonical JSON (engine::registry::digest_of)."
},
"origin": {
"enum": [
"built-in",
"registered"
],
"description": "built-in: the engine's own; registered: a host's own, added in its process, not part of the engine."
},
"certified": {
"const": false,
"description": "Beside a registered component: the engine does not certify it."
}
}
}
}
}

View file

@ -32,6 +32,13 @@
"const": "wai.quantum.job-result/1",
"description": "The schema the result follows."
},
"components": {
"type": "array",
"description": "The identity of every component that made the result, when a registered one took part (engine::registry): its program's format, the passes its compile ran, the noise channels it names, its method; each registered one marked so. Sealed by the receipt with the rest.",
"items": {
"$ref": "#/$defs/component"
}
},
"method": {
"type": "string",
"description": "The method that ran the job (provider for a run on the person's own provider)."
@ -1362,6 +1369,49 @@
"type": "string"
}
}
},
"component": {
"type": "object",
"description": "One component that made the result (engine::registry): its kind, name and version, the BLAKE3 digest of its descriptor, and whether it is built in. A result carries these only when a registered component took part; one made by built-ins alone names them as it always has (its method, its compile's passes, its noise's channels and the engine's version), the digests of that version listed by the components list.",
"required": [
"kind",
"name",
"version",
"digest",
"origin"
],
"properties": {
"kind": {
"enum": [
"method",
"pass",
"channel",
"front_end"
]
},
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"digest": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "BLAKE3 of the component's descriptor in its canonical JSON (engine::registry::digest_of)."
},
"origin": {
"enum": [
"built-in",
"registered"
],
"description": "built-in: the engine's own; registered: a host's own, added in its process, not part of the engine."
},
"certified": {
"const": false,
"description": "Beside a registered component: the engine does not certify it."
}
}
}
}
}

View file

@ -2225,6 +2225,13 @@
}
],
"description": "The output read back and held to the program read (or, for a program read into OpenQASM, written back to its format and read again), or null when no check was asked."
},
"components": {
"type": "array",
"description": "The identities of the two formats, when a registered front end took part (engine::registry).",
"items": {
"$ref": "#/$defs/component"
}
}
}
},
@ -2579,6 +2586,161 @@
}
}
}
},
"component": {
"type": "object",
"description": "One component that made the result (engine::registry): its kind, name and version, the BLAKE3 digest of its descriptor, and whether it is built in. A result carries these only when a registered component took part; one made by built-ins alone names them as it always has (its method, its compile's passes, its noise's channels and the engine's version), the digests of that version listed by the components list.",
"required": [
"kind",
"name",
"version",
"digest",
"origin"
],
"properties": {
"kind": {
"enum": [
"method",
"pass",
"channel",
"front_end"
]
},
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"digest": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "BLAKE3 of the component's descriptor in its canonical JSON (engine::registry::digest_of)."
},
"origin": {
"enum": [
"built-in",
"registered"
],
"description": "built-in: the engine's own; registered: a host's own, added in its process, not part of the engine."
},
"certified": {
"const": false,
"description": "Beside a registered component: the engine does not certify it."
}
}
},
"component_entry": {
"type": "object",
"required": [
"kind",
"name",
"version",
"digest",
"origin",
"descriptor"
],
"properties": {
"kind": {
"enum": [
"method",
"pass",
"channel",
"front_end"
]
},
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"digest": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "BLAKE3 of the component's descriptor in its canonical JSON (engine::registry::digest_of)."
},
"origin": {
"enum": [
"built-in",
"registered"
],
"description": "built-in: the engine's own; registered: a host's own, added in its process, not part of the engine."
},
"certified": {
"const": false,
"description": "Beside a registered component: the engine does not certify it."
},
"descriptor": {
"type": "object",
"description": "What the component declares of itself, as hashed into its digest: what it declares (declared) and what the engine reads of it (a method's accuracy classes, whether it models noise, where auto tries it; a pass's gates taken and given and its check; a channel's arity, placement and parameters; a format's title, extensions and profiles).",
"required": [
"declared"
]
}
}
},
"components": {
"type": "object",
"description": "Every component the engine plans, runs, compiles and converts through (engine::registry::Registry::components): the simulation methods, the compiler passes, the noise channels and the program formats, each in the registry's order with its identity and its descriptor. The command line, both MCP servers and the job service serve the built-ins alone.",
"required": [
"format",
"engine",
"methods",
"passes",
"channels",
"front_ends",
"registered"
],
"properties": {
"format": {
"const": "wai.quantum.components/1"
},
"engine": {
"type": "object",
"required": [
"name",
"version"
],
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
}
}
},
"methods": {
"type": "array",
"items": {
"$ref": "#/$defs/component_entry"
}
},
"passes": {
"type": "array",
"items": {
"$ref": "#/$defs/component_entry"
}
},
"channels": {
"type": "array",
"items": {
"$ref": "#/$defs/component_entry"
}
},
"front_ends": {
"type": "array",
"items": {
"$ref": "#/$defs/component_entry"
}
},
"registered": {
"type": "integer",
"minimum": 0,
"description": "How many are registered (a host's own); 0 for the built-ins alone."
}
}
}
}
}

View file

@ -36,6 +36,7 @@
//! the counts and values carry.
use super::refusal::{self, tag};
use super::registry::{Job, Method, Question, Registry};
use crate::qasm::{self, Program};
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
@ -149,23 +150,70 @@ pub fn accuracy_classes() -> Value {
}
/// The methods, for the service description. `job_bytes` is the memory one
/// job may take.
/// job may take. Each method's entry is its own ([`super::registry::Method::describe`]),
/// in the order the built-in registry holds them.
pub fn describe(job_bytes: u64) -> Value {
json!([
{ "method": "exact", "accuracy": "bit-exact", "circuits": "every angle a dyadic multiple of 2π (2πm/2^k, k ≤ 32)", "max_qubits": EXACT_MAX_QUBITS, "answers": ["counts", "expectations", "amplitudes", "state_hash"] },
{ "method": "statevector", "accuracy": "exact", "circuits": "any", "max_qubits": statevector_max_qubits(job_bytes), "answers": ["counts", "expectations", "amplitudes"] },
{ "method": "statevector_single", "accuracy": "single", "circuits": "any, gates on at most four qubits", "max_qubits": gpu_max_qubits().map(|m| m.min(single_max_qubits(job_bytes))).unwrap_or(0), "available": gpu_max_qubits().is_ok(), "answers": ["counts", "expectations", "amplitudes"], "options": { "fusion": "the widest fused gate (default 3, at most 3)" } },
{ "method": "stabilizer", "accuracy": "exact; with Pauli noise, counts are noisy samples and expectations estimates", "circuits": "Clifford (rotations by whole quarter turns, controlled phases by whole half turns), optionally with depolarizing, dephasing and readout noise", "max_qubits": STABILIZER_MAX_QUBITS, "answers": ["counts", "expectations"], "options": { "trajectories": "with noise: the shots expectation values are estimated from (default: the shots, at least 10000)" } },
{ "method": "pauli", "accuracy": "bounded", "circuits": "any", "max_qubits": PAULI_MAX_QUBITS, "answers": ["expectations"], "options": { "threshold": "coefficients below it are dropped (default 1e-5); the bound covers what was dropped", "max_weight": "Pauli strings heavier than this are dropped", "ladder": "also report the value at up to three thresholds a decade apart above it, to show it settling (default true)", "max_terms": "the most Pauli terms to hold (default: what the job's memory holds); past it the threshold rises, and the bound covers what drops" } },
{ "method": "mps", "accuracy": "exact when nothing is truncated, otherwise heuristic", "circuits": "any; best on low-entanglement circuits", "max_qubits": MPS_MAX_QUBITS, "answers": ["counts", "expectations", "amplitudes"], "options": { "max_bond": "the largest bond dimension kept (default 64, at most 256)", "cutoff": "singular values whose squared share is below it are dropped (default 1e-12)" } },
{ "method": "stabilizer_rank", "accuracy": "exact; with terms or epsilon, an estimate with its bound", "circuits": "Clifford+T (t, tdg, and p, rz, rx, ry at odd multiples of π/4, cp at odd multiples of π/2, ccz and ccx), each T gate doubling the sum's terms: the state as a sum of stabilizer states", "max_qubits": STABILIZER_RANK_MAX_QUBITS, "answers": ["amplitudes", "expectations"], "options": { "terms": "the sampled mode: this many branches drawn, seeded, the result an estimate with the bound on its distance from the state that holds with the confidence stated", "epsilon": "the sampled mode with as many branches as that bound needs", "confidence": "the probability the sampled bound holds (default 0.95)" } },
{ "method": "tensor_network", "accuracy": "exact", "circuits": "any", "max_qubits": qasm::MAX_QUBITS, "answers": ["amplitudes"], "options": { "path_restarts": "independent contraction-path searches, the cheapest kept (default 8; 1 in a browser tab)" } },
{ "method": "cutrank", "accuracy": "exact; single when only a single-precision table fits", "circuits": "a Clifford circuit, then one layer of one-qubit gates at any angles: each qubit's gates from its first non-Clifford one on act on that qubit alone; the cost is set by the state's cut-rank along an order of the qubits, not by their number", "max_qubits": CUTRANK_MAX_QUBITS, "answers": ["counts", "amplitudes"] },
{ "method": "cutrank_single", "accuracy": "single", "circuits": "as for cutrank; the table on a GPU, its arithmetic in single precision", "max_qubits": CUTRANK_MAX_QUBITS, "max_cut": gpu_cutrank_max_cut().unwrap_or(0), "available": gpu_cutrank_max_cut().is_ok(), "answers": ["counts", "amplitudes"] },
{ "method": "density_matrix", "accuracy": "exact", "circuits": "any, with noise", "max_qubits": density_max_qubits(job_bytes), "answers": ["counts", "expectations"] },
{ "method": "trajectories", "accuracy": "estimate", "circuits": "any, with noise", "max_qubits": TRAJECTORY_MAX_QUBITS.min(statevector_max_qubits(job_bytes)), "answers": ["counts", "expectations"], "options": { "trajectories": "how many (default: the shots, or 1000 for expectations alone); shots that take the same errors share one state" } },
super::pulse::describe(),
])
super::registry::Registry::builtins().describe_methods(job_bytes)
}
/// A built-in method's words: its accuracy, the circuits and the answers it
/// takes, and its options, which depend on nothing of the host (what is
/// hashed into its identity).
pub(crate) fn method_text(method: &str) -> Value {
match method {
"exact" => json!({ "accuracy": "bit-exact", "circuits": "every angle a dyadic multiple of 2π (2πm/2^k, k ≤ 32)", "answers": ["counts", "expectations", "amplitudes", "state_hash"] }),
"statevector" => json!({ "accuracy": "exact", "circuits": "any", "answers": ["counts", "expectations", "amplitudes"] }),
"statevector_single" => json!({ "accuracy": "single", "circuits": "any, gates on at most four qubits", "answers": ["counts", "expectations", "amplitudes"], "options": { "fusion": "the widest fused gate (default 3, at most 3)" } }),
"stabilizer" => json!({ "accuracy": "exact; with Pauli noise, counts are noisy samples and expectations estimates", "circuits": "Clifford (rotations by whole quarter turns, controlled phases by whole half turns), optionally with depolarizing, dephasing and readout noise", "answers": ["counts", "expectations"], "options": { "trajectories": "with noise: the shots expectation values are estimated from (default: the shots, at least 10000)" } }),
"pauli" => json!({ "accuracy": "bounded", "circuits": "any", "answers": ["expectations"], "options": { "threshold": "coefficients below it are dropped (default 1e-5); the bound covers what was dropped", "max_weight": "Pauli strings heavier than this are dropped", "ladder": "also report the value at up to three thresholds a decade apart above it, to show it settling (default true)", "max_terms": "the most Pauli terms to hold (default: what the job's memory holds); past it the threshold rises, and the bound covers what drops" } }),
"mps" => json!({ "accuracy": "exact when nothing is truncated, otherwise heuristic", "circuits": "any; best on low-entanglement circuits", "answers": ["counts", "expectations", "amplitudes"], "options": { "max_bond": "the largest bond dimension kept (default 64, at most 256)", "cutoff": "singular values whose squared share is below it are dropped (default 1e-12)" } }),
"stabilizer_rank" => json!({ "accuracy": "exact; with terms or epsilon, an estimate with its bound", "circuits": "Clifford+T (t, tdg, and p, rz, rx, ry at odd multiples of π/4, cp at odd multiples of π/2, ccz and ccx), each T gate doubling the sum's terms: the state as a sum of stabilizer states", "answers": ["amplitudes", "expectations"], "options": { "terms": "the sampled mode: this many branches drawn, seeded, the result an estimate with the bound on its distance from the state that holds with the confidence stated", "epsilon": "the sampled mode with as many branches as that bound needs", "confidence": "the probability the sampled bound holds (default 0.95)" } }),
"tensor_network" => json!({ "accuracy": "exact", "circuits": "any", "answers": ["amplitudes"], "options": { "path_restarts": "independent contraction-path searches, the cheapest kept (default 8; 1 in a browser tab)" } }),
"cutrank" => json!({ "accuracy": "exact; single when only a single-precision table fits", "circuits": "a Clifford circuit, then one layer of one-qubit gates at any angles: each qubit's gates from its first non-Clifford one on act on that qubit alone; the cost is set by the state's cut-rank along an order of the qubits, not by their number", "answers": ["counts", "amplitudes"] }),
"cutrank_single" => json!({ "accuracy": "single", "circuits": "as for cutrank; the table on a GPU, its arithmetic in single precision", "answers": ["counts", "amplitudes"] }),
"density_matrix" => json!({ "accuracy": "exact", "circuits": "any, with noise", "answers": ["counts", "expectations"] }),
"trajectories" => json!({ "accuracy": "estimate", "circuits": "any, with noise", "answers": ["counts", "expectations"], "options": { "trajectories": "how many (default: the shots, or 1000 for expectations alone); shots that take the same errors share one state" } }),
_ => json!({}),
}
}
/// What a built-in method holds on this host in `job_bytes` of memory: its
/// most qubits, and for the methods on a GPU, whether there is one.
fn method_limits(method: &str, job_bytes: u64) -> Value {
match method {
"exact" => json!({ "max_qubits": EXACT_MAX_QUBITS }),
"statevector" => json!({ "max_qubits": statevector_max_qubits(job_bytes) }),
"statevector_single" => json!({ "max_qubits": gpu_max_qubits().map(|m| m.min(single_max_qubits(job_bytes))).unwrap_or(0), "available": gpu_max_qubits().is_ok() }),
"stabilizer" => json!({ "max_qubits": STABILIZER_MAX_QUBITS }),
"pauli" => json!({ "max_qubits": PAULI_MAX_QUBITS }),
"mps" => json!({ "max_qubits": MPS_MAX_QUBITS }),
"stabilizer_rank" => json!({ "max_qubits": STABILIZER_RANK_MAX_QUBITS }),
"tensor_network" => json!({ "max_qubits": qasm::MAX_QUBITS }),
"cutrank" => json!({ "max_qubits": CUTRANK_MAX_QUBITS }),
"cutrank_single" => json!({ "max_qubits": CUTRANK_MAX_QUBITS, "max_cut": gpu_cutrank_max_cut().unwrap_or(0), "available": gpu_cutrank_max_cut().is_ok() }),
"density_matrix" => json!({ "max_qubits": density_max_qubits(job_bytes) }),
"trajectories" => json!({ "max_qubits": TRAJECTORY_MAX_QUBITS.min(statevector_max_qubits(job_bytes)) }),
_ => json!({}),
}
}
/// A built-in method's entry in the methods list: its name, accuracy and
/// circuits, what it holds here, its answers and its options.
pub(crate) fn describe_one(method: &str, job_bytes: u64) -> Value {
let text = method_text(method);
let mut o = serde_json::Map::new();
o.insert("method".into(), json!(method));
for k in ["accuracy", "circuits"] {
o.insert(k.into(), text[k].clone());
}
if let Some(l) = method_limits(method, job_bytes).as_object() {
o.extend(l.clone());
}
o.insert("answers".into(), text["answers"].clone());
if let Some(x) = text.get("options") {
o.insert("options".into(), x.clone());
}
Value::Object(o)
}
/// The memory a dense state vector of `n` qubits takes with room to run.
@ -316,33 +364,60 @@ pub(crate) fn read_observable(v: &Value, n: u32) -> Result<Observable, String> {
observable(v, n)
}
/// The noise channels a job names with a value other than zero: each by its
/// place in the registry, with its values.
pub(crate) type Uses = Vec<(usize, Vec<f64>)>;
/// The noise channels a job may name.
pub const NOISE_CHANNELS: [&str; 5] = ["depolarizing_1q", "depolarizing_2q", "amplitude_damping", "dephasing", "readout"];
fn noise(v: &Value) -> Result<crate::quantum_noisy::NoiseModel, String> {
/// The noise a job's `noise` object names, read with `reg`'s channels: the
/// built-in channels' values as the noise model the built-in methods take,
/// and every channel named with a value other than zero, each checked
/// ([`super::registry::validate`]), by its place in the registry.
fn noise(reg: &Registry, v: &Value) -> Result<(crate::quantum_noisy::NoiseModel, Uses), String> {
// A channel the model does not know is refused: read as no noise, a
// misspelt one would run the circuit noiseless.
if let Some(k) = v.as_object().and_then(|o| o.keys().find(|k| !NOISE_CHANNELS.contains(&k.as_str()))) {
return Err(format!("noise {k}: no such channel; the channels are {}", NOISE_CHANNELS.join(", ")));
let names: Vec<&str> = reg.channels().map(|c| c.name()).collect();
if let Some(k) = v.as_object().and_then(|o| o.keys().find(|k| !names.contains(&k.as_str()))) {
return Err(format!("noise {k}: no such channel; the channels are {}", names.join(", ")));
}
let f = |k: &str| -> Result<f64, String> {
let x = v.get(k).and_then(Value::as_f64).unwrap_or(0.0);
if (0.0..=1.0).contains(&x) { Ok(x) } else { Err(format!("noise {k} is a probability, 0 to 1")) }
};
Ok(crate::quantum_noisy::NoiseModel {
depol1: f("depolarizing_1q")?,
depol2: f("depolarizing_2q")?,
damping: f("amplitude_damping")?,
dephasing: f("dephasing")?,
readout: f("readout")?,
})
let mut model = crate::quantum_noisy::NoiseModel::default();
let mut uses = Vec::new();
for (i, c) in reg.channels().enumerate() {
let values = super::registry::channel_values(c, c.name(), v.get(c.name()).unwrap_or(&Value::Null))?;
if values.iter().all(|&x| x == 0.0) {
continue;
}
super::registry::validate(c, &values)?;
if reg.channel_at(i).1.is_builtin() {
let x = values[0];
match c.name() {
"depolarizing_1q" => model.depol1 = x,
"depolarizing_2q" => model.depol2 = x,
"amplitude_damping" => model.damping = x,
"dephasing" => model.dephasing = x,
"readout" => model.readout = x,
_ => {}
}
}
uses.push((i, values));
}
Ok((model, uses))
}
/// The noise a job's JSON asks for, read as [`read`] reads it.
pub fn noise_of(v: &Value) -> Result<Option<crate::quantum_noisy::NoiseModel>, String> {
noise_in(Registry::builtins(), v).map(|(m, _)| m)
}
/// [`noise_of`] with `reg`'s channels, and each channel named with a value
/// other than zero.
pub(crate) fn noise_in(reg: &Registry, v: &Value) -> Result<(Option<crate::quantum_noisy::NoiseModel>, Uses), String> {
let field = |e: String| if refusal::codes_in(&e).is_empty() { tag(refusal::BAD_FIELD, e) } else { e };
match v.get("noise") {
None | Some(Value::Null) => Ok(None),
Some(o @ Value::Object(_)) => noise(o).map(Some).map_err(|e| tag(refusal::BAD_FIELD, e)),
None | Some(Value::Null) => Ok((None, Vec::new())),
Some(o @ Value::Object(_)) => noise(reg, o).map(|(m, u)| (Some(m), u)).map_err(field),
Some(_) => Err(tag(refusal::BAD_FIELD, "\"noise\" is an object")),
}
}
@ -401,6 +476,13 @@ pub fn bind_params(src: &str, params: Option<&Value>) -> Result<String, String>
/// Reads a job's JSON. A refusal ends with its code ([`super::refusal`]).
pub fn read(v: &Value) -> Result<Request, String> {
read_in(Registry::builtins(), v).map(|(r, _)| r)
}
/// [`read`] with `reg`'s noise channels and passes: the request, and each
/// channel its noise names with a value other than zero, by its place in
/// the registry with its values.
pub(crate) fn read_in(reg: &Registry, v: &Value) -> Result<(Request, Uses), String> {
// A set runs as a set, item by item; read as one job it would run one
// program in place of all of them.
if is_set(v) {
@ -444,7 +526,8 @@ pub fn read(v: &Value) -> Result<Request, String> {
None | Some(Value::Null) => (program, None, None),
Some(c) => {
let o = super::compile::Options::read(c).map_err(|e| tag(refusal::COMPILE, format!("compile: {e}")))?;
let out = super::compile::compile(&program, &o).map_err(|e| tag(refusal::COMPILE, format!("compile: {e}")))?;
let extra = super::compile::passes_of(reg, c.get("passes"))?;
let out = super::compile::compile_in(reg, &program, &o, &extra).map_err(|e| tag(refusal::COMPILE, format!("compile: {e}")))?;
let summary = super::compile::summary(&out);
let passes: Vec<Value> = out.certificate["passes"].as_array().into_iter().flatten().map(|r| json!({ "pass": r["pass"], "verdict": r["check"]["verdict"] })).collect();
(out.program, Some(summary), Some(Value::Array(passes)))
@ -481,7 +564,15 @@ pub fn read(v: &Value) -> Result<Request, String> {
if shots == 0 && observables.is_empty() && amplitudes.is_empty() {
return Err(tag(refusal::ASKS_NOTHING, "the job asks for nothing: give shots, observables or amplitudes"));
}
let noise = noise_of(v)?;
let (noise, uses) = noise_in(reg, v)?;
let registered = uses.iter().any(|(i, _)| !reg.channel_at(*i).1.is_builtin());
// The intent checks and the mitigation model the built-in channels alone.
if registered && v.get("intent").is_some_and(|i| !i.is_null()) {
return Err(tag(refusal::REGISTERED_INTENT, "intent: this job's noise names a registered channel, and the intent checks model the built-in channels alone; run it without the intent, or with the built-in channels"));
}
if registered && v.get("mitigate").is_some_and(|m| !m.is_null()) {
return Err(tag(refusal::MITIGATE, "mitigate: the mitigation models the built-in channels alone, and this job's noise names a registered channel"));
}
let seed = v.get("seed").and_then(Value::as_u64).unwrap_or(1);
// "mitigate": a method and its choices (super::mitigate), checked here so
// a plan refuses what the run could not do.
@ -504,7 +595,7 @@ pub fn read(v: &Value) -> Result<Request, String> {
}
}
};
Ok(Request {
Ok((Request {
program,
diagnostics,
method: v.get("method").and_then(Value::as_str).unwrap_or("auto").to_string(),
@ -518,7 +609,7 @@ pub fn read(v: &Value) -> Result<Request, String> {
compile_passes,
repaired,
mitigate,
})
}, uses))
}
/// The option `key` in GiB as bytes, within `cap`.
@ -783,16 +874,17 @@ pub fn stabilizer_rank_work(c: &crate::quantum_stabrank::CliffordT, amplitude_te
a.saturating_add(e)
}
fn try_method(method: &str, r: &Request, dyadic: &Result<crate::quantum::Circuit, String>, clifford: &Result<Vec<crate::quantum_tableau::Clifford>, String>, cap: u64) -> Result<Plan, String> {
/// A built-in method's plan for `r` (its registry entry's,
/// [`super::registry::Method::plan`]): whether `method` can take the job,
/// and the plan if so; otherwise why not. A job with noise reaches only the
/// methods that model it (the registry refuses it for the others).
pub(crate) fn try_method(method: &str, r: &Request, dyadic: &Result<crate::quantum::Circuit, String>, clifford: &Result<Vec<crate::quantum_tableau::Clifford>, String>, cap: u64) -> Result<Plan, String> {
let n = r.program.n_qubits;
let gates = r.program.ops.len() as u64;
let plan = |memory_bytes: u64, work: u64, why: String| Plan { method: method.into(), why, memory_bytes, work, qubits: n, target: None };
let counts = r.shots > 0;
let wants_amplitudes = !r.amplitudes.is_empty();
let wants_expectations = !r.observables.is_empty();
if r.noise.is_some() && !matches!(method, "density_matrix" | "trajectories" | "stabilizer") {
return Err(format!("{method} does not model noise"));
}
if let Some(steps) = &r.program.dynamic {
return try_dynamic(method, r, clifford, cap, steps);
}
@ -836,7 +928,7 @@ fn try_method(method: &str, r: &Request, dyadic: &Result<crate::quantum::Circuit
return Err("stabilizer answers counts and expectations".into());
}
if r.noise.is_some_and(|z| z.damping > 0.0) {
return Err("stabilizer models Pauli noise (depolarizing, dephasing, readout), not amplitude damping".into());
return Err(tag(refusal::CHANNEL_REFUSED, super::registry::STABILIZER_NOT_DAMPING));
}
let (nn, measured) = (n as u64, r.program.measured.len() as u64);
let words = nn.div_ceil(64);
@ -1033,11 +1125,19 @@ pub fn trajectory_count(r: &Request) -> u64 {
/// The plan for a job's JSON, `job_bytes` the memory one job may take.
pub fn plan(v: &Value, job_bytes: u64) -> Result<Plan, String> {
// A pulse run is read by the pulse reader and integrated by the pulse
// simulator, a device target giving its qubits' parameters
// (engine::pulse).
if super::pulse::names_pulse(v) {
return super::pulse::plan(v, job_bytes);
plan_in(Registry::builtins(), v, job_bytes)
}
/// The method `v` names, when it reads its job's program itself (a pulse
/// run: the pulse reader reads it, and the pulse simulator integrates it).
pub(crate) fn reads_own<'a>(reg: &'a Registry, v: &Value) -> Option<&'a dyn Method> {
v.get("method").and_then(Value::as_str).and_then(|n| reg.method(n)).filter(|m| m.reads_program())
}
/// [`plan`] through `reg`: its methods, channels and passes.
pub(crate) fn plan_in(reg: &Registry, v: &Value, job_bytes: u64) -> Result<Plan, String> {
if let Some(m) = reads_own(reg, v) {
return m.plan(&Job::new(reg, v, job_bytes, None, &[]));
}
// A job that names a device target is compiled, placed and simulated as
// the device would run it (engine::targets::job).
@ -1047,67 +1147,83 @@ pub fn plan(v: &Value, job_bytes: u64) -> Result<Plan, String> {
return Err(tag(refusal::FABRIC, "fabric: a job on a device target runs on the CPU, as the device's simulation; give fabric cpu, or leave it out"));
}
let p = crate::engine::targets::job::plan(v, job_bytes)?;
return with_intent(v, &read(v)?, p, job_bytes);
return with_intent(reg, v, &read_in(reg, v)?.0, p, job_bytes);
}
#[cfg(not(feature = "quantum_receipt"))]
if v.get("target").is_some_and(|t| !t.is_null()) {
return Err("this build has no device targets (the engine's quantum_receipt feature)".into());
}
let r = read(v)?;
let mut p = choose_placed(v, &r, job_bytes)?;
let (r, uses) = read_in(reg, v)?;
let job = Job::new(reg, v, job_bytes, Some(&r), &uses);
let mut p = choose_placed(&job)?;
super::fabric::admit(v, &r, &mut p)?;
with_intent(v, &r, p, job_bytes)
with_intent(reg, v, &r, p, job_bytes)
}
/// [`choose`], except that a job asking for a member of the fabric
/// (`accelerator`, a member, or partitions) under `auto` takes the one method
/// the fabric's members run, `statevector`.
fn choose_placed(v: &Value, r: &Request, job_bytes: u64) -> Result<Plan, String> {
fn choose_placed(job: &Job<'_>) -> Result<Plan, String> {
use super::fabric::Ask;
let ask = super::fabric::asked(v)?;
let r = job.request()?;
let ask = super::fabric::asked(job.json())?;
if r.method != "auto" || matches!(ask, Ask::Auto | Ask::Cpu) {
return choose(r, job_bytes);
return choose(job);
}
let (dyadic, clifford) = (r.program.dyadic(), r.program.clifford());
try_method("statevector", r, &dyadic, &clifford, job_bytes).map_err(|e| tag(refusal::FABRIC, format!("fabric: only the statevector method is placed on the fabric's members, and it cannot take this job: {e}")))
let sv = job.registry().method("statevector").ok_or("internal: no statevector method")?;
plan_one(sv, job).map_err(|e| tag(refusal::FABRIC, format!("fabric: only the statevector method is placed on the fabric's members, and it cannot take this job: {e}")))
}
/// `p` for the job `v`, read as `r`, admitting the memory its intents' check
/// takes beside the run, where it fits (super::verdict).
fn with_intent(v: &Value, r: &Request, mut p: Plan, job_bytes: u64) -> Result<Plan, String> {
fn with_intent(reg: &Registry, v: &Value, r: &Request, mut p: Plan, job_bytes: u64) -> Result<Plan, String> {
if let Some(i) = v.get("intent").filter(|i| !i.is_null()) {
let need = super::verdict::intent_memory(v, &super::verdict::intents(i)?, r.program.n_qubits, r.noise.is_some(), job_bytes);
let need = super::verdict::intent_memory_in(reg, v, &super::verdict::intents(i)?, r.program.n_qubits, r.noise.is_some(), job_bytes);
p.memory_bytes = p.memory_bytes.max(need);
}
Ok(p)
}
/// The method for `r`: the one it names, or the first `auto` finds.
fn choose(r: &Request, job_bytes: u64) -> Result<Plan, String> {
let dyadic = r.program.dyadic();
let clifford = r.program.clifford();
/// The method `m`'s plan for `job`: refused for a method that models no
/// noise when the job has a noise model, then the method's own plan, then
/// every channel the job names held to what the method takes (each refusal
/// of a channel `J061`).
pub(crate) fn plan_one(m: &dyn Method, job: &Job<'_>) -> Result<Plan, String> {
let r = job.request()?;
if r.noise.is_some() && !m.models_noise() {
return Err(tag(refusal::CHANNEL_REFUSED, format!("{} does not model noise", m.name())));
}
let p = m.plan(job)?;
if p.method != m.name() {
return Err(tag(refusal::CONTRACT, format!("the method {} planned the job for another method, {}", m.name(), p.method)));
}
for c in job.channels() {
m.takes(c.channel).map_err(|e| tag(refusal::CHANNEL_REFUSED, e))?;
}
Ok(p)
}
/// The method for the job: the one it names, or the first `auto` finds, in
/// the registry's order for what the job asks ([`Question`]).
fn choose(job: &Job<'_>) -> Result<Plan, String> {
let r = job.request()?;
let reg = job.registry();
if r.method != "auto" {
return try_method(&r.method, r, &dyadic, &clifford, job_bytes).map_err(|e| if refusal::codes_in(&e).last().is_some_and(|c| c.starts_with('J')) { e } else { tag(refusal::METHOD_REFUSES, e) });
let Some(m) = reg.method(&r.method) else {
return Err(tag(refusal::UNKNOWN_METHOD, format!("unknown method {}; GET /v1/methods lists them", r.method)));
};
return plan_one(m, job).map_err(|e| if refusal::codes_in(&e).last().is_some_and(|c| c.starts_with('J')) { e } else { tag(refusal::METHOD_REFUSES, e) });
}
let order: &[&str] = if r.noise.is_some() {
&["density_matrix", "stabilizer", "trajectories"]
} else if r.shots > 0 {
&["stabilizer", "exact", "statevector", "statevector_single", "cutrank", "mps"]
} else if !r.observables.is_empty() && r.amplitudes.is_empty() {
&["stabilizer", "exact", "statevector", "statevector_single", "stabilizer_rank", "pauli", "mps"]
} else {
&["exact", "statevector", "statevector_single", "cutrank", "stabilizer_rank", "tensor_network", "mps"]
};
let mut refusals = Vec::new();
for m in order {
match try_method(m, r, &dyadic, &clifford, job_bytes) {
for m in reg.auto_order(Question::of(r)) {
match plan_one(m, job) {
Ok(mut p) => {
if !refusals.is_empty() {
p.why = format!("{}; not {}", p.why, refusals.join("; "));
}
return Ok(p);
}
Err(e) => refusals.push(format!("{m}: {e}")),
Err(e) => refusals.push(format!("{}: {e}", m.name())),
}
}
Err(tag(refusal::NO_METHOD, format!("no method takes this job — {}", refusals.join("; "))))

View file

@ -57,6 +57,7 @@
//! `epsilon.budget`. Up to [`WHOLE_MAX_QUBITS`] qubits the whole output is
//! also measured against the whole program on their dense unitaries.
use super::registry::{Check, Kind as ComponentKind, Pass, PassInput, PassOutput, Registry};
use super::rewrite;
use super::synth::{euler, kak, lower, mat, phase, ring, rz};
use crate::qasm::{Kind, Op, Program};
@ -798,10 +799,143 @@ pub fn hash_of_text(text: &str) -> String {
hash_hex(text.as_bytes())
}
/// A built-in pass's run (its registry entry's,
/// [`super::registry::Pass::run`]): the pass named `name` on `input`, with
/// its own check where it acts.
pub(crate) fn builtin_pass(name: &str, input: &PassInput<'_>) -> Result<PassOutput, String> {
let (n, ops, o) = (input.qubits, input.ops, input.options);
let out = match name {
"lower" => pass_lower(ops)?,
"simplify" => pass_simplify(n, ops),
"phase_fold" => pass_phase_fold(n, ops),
"resynth_1q" => pass_resynth_1q(n, ops, o.basis),
"resynth_2q" => pass_resynth_2q(n, ops, o.basis),
"twirl" => pass_twirl(n, ops, o.twirl.unwrap_or(1)),
"dd" => pass_dd(n, ops, o.dd.unwrap_or(Dd::Xx)),
"approximate" => {
let m = rotations(ops);
let left = o.epsilon - input.spent;
let each = if m == 0 { o.epsilon.min(rz::MAX_EPSILON) } else { (left / m as f64).min(rz::MAX_EPSILON) };
if m > 0 && each < rz::MIN_EPSILON {
return Err(format!("ε = {:e} shared by {m} rotations leaves {each:e} each, below the {:e} a rotation is approximated to; give a larger ε or fewer rotations", o.epsilon, rz::MIN_EPSILON));
}
let (out, detail) = pass_approximate(ops, each)?;
return Ok(PassOutput { ops: out.ops, check: out.check, distance: out.distance, epsilon: Some((m, each)), rotations: Some(detail) });
}
other => return Err(format!("no pass {other}")),
};
Ok(PassOutput { ops: out.ops, check: out.check, distance: out.distance, epsilon: None, rotations: None })
}
/// The pass names a compile's `passes` gives, each one `reg` holds: run
/// after the level's own and before twirling and decoupling. Refused: a name
/// no pass here has (`J060`), or `passes` that is not a list of names.
pub(crate) fn passes_of<'a>(reg: &Registry, v: Option<&'a Value>) -> Result<Vec<&'a str>, String> {
use super::refusal::{COMPILE, UNKNOWN_COMPONENT, tag};
let Some(v) = v.filter(|v| !v.is_null()) else { return Ok(Vec::new()) };
let names = v.as_array().and_then(|a| a.iter().map(Value::as_str).collect::<Option<Vec<&str>>>()).ok_or_else(|| tag(COMPILE, "compile: \"passes\" is a list of the names of passes to run after the level's"))?;
for name in &names {
if reg.pass(name).is_none() {
let here: Vec<&str> = reg.passes().map(|p| p.name()).collect();
return Err(tag(UNKNOWN_COMPONENT, format!("compile: no pass {name} here; the passes here are {}", here.join(", "))));
}
}
Ok(names)
}
/// A registered pass on `input`, checked by the engine on the whole circuit
/// by the check it declares ([`Check`]): passed over when the circuit holds
/// gates it does not take, and rejected (left out, the program going on as
/// it was) when its gates are not ones it declared it gives or its check does
/// not hold. Refused (`J064`): gates on qubits the program does not have, or
/// that do not read back.
fn registered_pass(pass: &dyn Pass, input: &PassInput<'_>) -> Result<PassOutput, String> {
use super::refusal::{CONTRACT, tag};
let (n, before, o) = (input.qubits, input.ops, input.options);
let name = pass.name();
let keep = |check: Value| PassOutput { ops: before.to_vec(), check, distance: 0.0, epsilon: None, rotations: None };
if !pass.takes().holds(before) {
return Ok(keep(json!({ "how": "none", "verdict": "skipped", "why": format!("the circuit holds gates outside what the pass takes ({})", pass.takes().as_str()) })));
}
let given = pass.run(input)?;
for g in &given.ops {
if let Some(&q) = g.qubits.iter().find(|&&q| q >= n) {
return Err(tag(CONTRACT, format!("the pass {name} gave a gate on qubit {q} of a {n}-qubit program")));
}
if (1..g.qubits.len()).any(|i| g.qubits[..i].contains(&g.qubits[i])) {
return Err(tag(CONTRACT, format!("the pass {name} gave a {:?} on one qubit twice", g.kind)));
}
}
let text = rewrite::render(&given.ops, n, &[], 0);
match crate::qasm::parse(&text) {
Ok(back) if back.ops.len() == given.ops.len() => {}
_ => return Err(tag(CONTRACT, format!("the pass {name} gave gates that do not read back as the same circuit"))),
}
let said = given.check.clone();
if !pass.gives().holds(&given.ops) {
return Ok(keep(json!({ "how": "the gates it gives, against what it declares", "verdict": "rejected", "why": format!("its output holds gates outside what it declares it gives ({})", pass.gives().as_str()), "by": "engine" })));
}
let (a, b) = (program_of(n, before.to_vec()), program_of(n, given.ops.clone()));
let (mut check, distance) = match pass.check() {
Check::Ladder => {
let v = super::equiv::equivalent_programs(&a, &b);
let how = v["how"].as_str().unwrap_or("none").to_string();
let verdict = match (v["verdict"].as_str(), how.as_str()) {
(Some("equivalent"), "tableau" | "rewrites") => "proof",
(Some("equivalent"), "exact") => "exact",
_ => "rejected",
};
let mut c = json!({ "how": format!("the whole circuit against the circuit before, by the equivalence ladder ({how})"), "verdict": verdict, "by": "engine", "ladder": v });
if verdict == "rejected" {
c["why"] = json!(match v["verdict"].as_str() {
Some("equivalent") => "the ladder found the two equal on random inputs alone, which is evidence, not a check a compile certifies".to_string(),
Some("different") => "the output is not the circuit before".to_string(),
_ => format!("the ladder does not decide it: {}", v["why"].as_str().unwrap_or("")),
});
}
(c, 0.0)
}
Check::Fidelity { max_distance } => {
let total = (before.len() + given.ops.len()) as f64;
if n > WHOLE_MAX_QUBITS || total * (1u64 << (2 * n)) as f64 > WHOLE_MAX_WORK {
(json!({ "how": "the dense unitaries of the circuit before and after", "verdict": "rejected", "why": format!("{n} qubits and {} gates: wider than the dense check runs ({WHOLE_MAX_QUBITS} qubits)", before.len() + given.ops.len()), "by": "engine" }), 0.0)
} else {
let d = mat::distance(&mat::Dense::of(n, &given.ops), &mat::Dense::of(n, before));
let left = o.epsilon - input.spent;
let mut c = json!({ "how": "the dense unitaries of the circuit before and after, their operator-norm distance up to a global phase", "by": "engine", "distance": d, "max_distance": max_distance });
if d > max_distance {
c["verdict"] = json!("rejected");
c["why"] = json!(format!("the output is {d:e} from the circuit before, more than the pass's bound {max_distance:e}"));
(c, 0.0)
} else if d > left {
c["verdict"] = json!("rejected");
c["why"] = json!(format!("the output is {d:e} from the circuit before, more than the {left:e} left of the compile's budget"));
(c, 0.0)
} else {
c["verdict"] = json!(if d <= 1e-12 { "exact" } else { "approximate" });
(c, d)
}
}
}
};
if !said.is_null() {
check["said"] = said;
}
let applied = check["verdict"] != "rejected";
Ok(PassOutput { ops: if applied { given.ops } else { before.to_vec() }, check, distance, epsilon: None, rotations: None })
}
/// `p` compiled under `o`; refused for a program that measures in the
/// middle, one larger than [`MAX_GATES`], or a budget the rotations cannot
/// share.
pub fn compile(p: &Program, o: &Options) -> Result<Compiled, String> {
compile_in(Registry::builtins(), p, o, &[])
}
/// [`compile`] through `reg`, with `extra` (passes `reg` holds) run after
/// the level's own and before twirling and decoupling. A registered pass's
/// record names it (`component`), and its check is the engine's.
pub(crate) fn compile_in(reg: &Registry, p: &Program, o: &Options, extra: &[&str]) -> Result<Compiled, String> {
if p.dynamic.is_some() {
return Err("the program measures in the middle, resets after a gate or acts on what it measured; compiling takes programs whose measurements all come at the end".into());
}
@ -815,37 +949,37 @@ pub fn compile(p: &Program, o: &Options) -> Result<Compiled, String> {
let mut epsilon = json!({ "budget": o.epsilon });
let mut rotation_detail = Vec::new();
let mut stages = Vec::new();
for &name in &pipeline(o) {
let mut names = base_pipeline(o);
names.extend(extra.iter().copied());
if o.twirl.is_some() {
names.push("twirl");
}
if o.dd.is_some() {
names.push("dd");
}
for &name in &names {
let before = counts(n, &ops);
let out = match name {
"lower" => pass_lower(&ops)?,
"simplify" => pass_simplify(n, &ops),
"phase_fold" => pass_phase_fold(n, &ops),
"resynth_1q" => pass_resynth_1q(n, &ops, o.basis),
"resynth_2q" => pass_resynth_2q(n, &ops, o.basis),
"twirl" => pass_twirl(n, &ops, o.twirl.unwrap_or(1)),
"dd" => pass_dd(n, &ops, o.dd.unwrap_or(Dd::Xx)),
"approximate" => {
let m = rotations(&ops);
let left = o.epsilon - bound;
let each = if m == 0 { o.epsilon.min(rz::MAX_EPSILON) } else { (left / m as f64).min(rz::MAX_EPSILON) };
if m > 0 && each < rz::MIN_EPSILON {
return Err(format!("ε = {:e} shared by {m} rotations leaves {each:e} each, below the {:e} a rotation is approximated to; give a larger ε or fewer rotations", o.epsilon, rz::MIN_EPSILON));
}
epsilon["rotations"] = json!(m);
epsilon["each"] = json!(each);
let (out, detail) = pass_approximate(&ops, each)?;
rotation_detail = detail;
out
}
other => return Err(format!("no pass {other}")),
};
let pass = reg.pass(name).ok_or_else(|| super::refusal::tag(super::refusal::UNKNOWN_COMPONENT, format!("no pass {name} here")))?;
let id = reg.identity(ComponentKind::Pass, name).ok_or("internal: a pass without an identity")?;
let input = PassInput { qubits: n, ops: &ops, options: o, spent: bound };
let out = if id.is_builtin() { pass.run(&input)? } else { registered_pass(pass, &input)? };
if let Some((m, each)) = out.epsilon {
epsilon["rotations"] = json!(m);
epsilon["each"] = json!(each);
}
if let Some(detail) = out.rotations {
rotation_detail = detail;
}
let applied = out.check["verdict"] != "rejected" && out.check["verdict"] != "skipped";
if applied {
bound += out.distance;
ops = out.ops;
}
records.push(json!({ "pass": name, "before": before, "after": counts(n, &ops), "check": out.check }));
let mut record = json!({ "pass": name, "before": before, "after": counts(n, &ops), "check": out.check });
if !id.is_builtin() {
record["component"] = id.to_json();
}
records.push(record);
if o.stages {
stages.push((name.to_string(), rewrite::render(&ops, n, &p.measured, p.n_bits)));
}
@ -939,10 +1073,18 @@ pub fn certificate_hash(_: &Value) -> Value {
/// in, `{"ok": true, "options", "qasm", "certificate"}` out (with `stages`,
/// also each pass's circuit), not yet sealed; or the reason it is refused.
pub fn call(request: &Value) -> Result<Value, String> {
call_in(Registry::builtins(), request)
}
/// [`call`] through `reg`, with the request's `passes` (passes `reg` holds)
/// run after the level's own. An answer a registered pass took part in
/// carries `components`, the identity of every component that made it.
pub(crate) fn call_in(reg: &Registry, request: &Value) -> Result<Value, String> {
let src = request.get("qasm").and_then(Value::as_str).ok_or("give \"qasm\": the program")?;
let p = crate::qasm::parse(src)?;
let o = Options::read(request)?;
let c = compile(&p, &o)?;
let extra = passes_of(reg, request.get("passes"))?;
let c = compile_in(reg, &p, &o, &extra)?;
let mut out = json!({ "ok": true, "options": o.to_json(), "qasm": c.qasm, "certificate": c.certificate });
if o.stages {
out["stages"] = c.stages.iter().map(|(pass, qasm)| json!({ "pass": pass, "qasm": qasm })).collect();
@ -950,6 +1092,17 @@ pub fn call(request: &Value) -> Result<Value, String> {
// The engine that compiled it, and the schema it follows, inside what the
// receipt seals.
super::schema::stamp_as(&mut out, super::schema::COMPILE_RESULT);
let mut used: Vec<&super::registry::Identity> = reg.identity(ComponentKind::FrontEnd, "qasm").into_iter().collect();
for r in c.certificate["passes"].as_array().into_iter().flatten() {
if let Some(i) = r["pass"].as_str().and_then(|n| reg.identity(ComponentKind::Pass, n))
&& !used.contains(&i)
{
used.push(i);
}
}
if used.iter().any(|i| !i.is_builtin()) {
out["components"] = used.iter().map(|i| i.to_json()).collect();
}
Ok(out)
}

View file

@ -393,6 +393,9 @@ mod tests {
assert_eq!(tail(&[row(18, 10.0, 5.0, 50), row(20, 40.0, 30.0, 55), row(22, 160.0, 170.0, 60)]), None);
}
/// A job that names its placement plans only where the fabric is built
/// (a build without it honours `auto` alone).
#[cfg(feature = "quantum_fabric")]
#[test]
fn the_measured_circuit_reads_and_its_job_plans() {
for n in [2u32, 9, 18] {

View file

@ -33,6 +33,7 @@ mod emit;
pub use qir::Profile;
use crate::engine::registry::{FrontEnd, Registry};
use crate::qasm::diag::{self, Diagnostic};
use crate::qasm::Program;
use serde_json::{Value, json};
@ -137,25 +138,22 @@ pub struct Exported {
/// A program read from `input` in `format`, as OpenQASM 3 that the engine's
/// reader reads.
pub fn read(format: Format, input: &[u8]) -> Result<Imported, Diagnostic> {
let text = || std::str::from_utf8(input).map_err(|_| Diagnostic::new(diag::FORMAT_UNREAD, None, format!("{} is text, and this is not UTF-8", format.name())));
let imported = match format {
Format::Qasm => {
let t = text()?;
crate::qasm::read(t)?;
return Ok(Imported { qasm: t.to_string(), notes: Vec::new(), summary: json!({ "format": "qasm" }) });
}
Format::Qir => {
let r = qir::read(text()?)?;
Imported { qasm: r.qasm, notes: r.notes, summary: r.summary }
}
Format::Jeff => {
let r = jeff::read(input)?;
Imported { qasm: r.qasm, notes: r.notes, summary: r.summary }
}
};
read_fe(builtin(format), input)
}
/// The built-in front end of `format`.
fn builtin(format: Format) -> &'static dyn FrontEnd {
Registry::builtins().front_end(format.name()).expect("every format has its built-in front end")
}
/// A program read by the front end `fe`, as OpenQASM 3 that the engine's
/// reader reads: what the front end gives is read again here, and a
/// program that does not read is refused rather than passed on.
pub(crate) fn read_fe(fe: &dyn FrontEnd, input: &[u8]) -> Result<Imported, Diagnostic> {
let imported = fe.read(input)?;
// The text a reader writes reads; if it did not, that is this reader's
// fault, and it is refused rather than passed on.
crate::qasm::read(&imported.qasm).map_err(|d| Diagnostic::new(d.code, None, format!("the program read from {} does not read as OpenQASM: {}", format.name(), d.message)))?;
crate::qasm::read(&imported.qasm).map_err(|d| Diagnostic::new(d.code, None, format!("the program read from {} does not read as OpenQASM: {}", fe.name(), d.message)))?;
Ok(imported)
}
@ -209,28 +207,52 @@ pub struct Converted {
/// read into OpenQASM is written back to its own format and read again, and
/// the two readings held to each other.
pub fn convert(from: Format, to: Format, input: &[u8], profile: Option<Profile>, check: bool) -> Result<Converted, Diagnostic> {
if profile.is_some() && to != Format::Qir {
return Err(Diagnostic::new(diag::WRITE_CANNOT, None, format!("a profile is QIR's; {} takes none", to.name())));
let c = convert_fe(Registry::builtins(), builtin(from), builtin(to), input, profile.map(Profile::name), check)?;
Ok(Converted { from, to, output: c.output, qasm: c.qasm, notes: c.notes, read: c.read, written: c.written, check: c.check })
}
/// A conversion between two front ends, as [`convert`] gives it.
pub(crate) struct ConvertedFe {
pub output: Vec<u8>,
pub qasm: String,
pub notes: Vec<Diagnostic>,
pub read: Value,
pub written: Value,
pub check: Value,
}
/// [`convert`] between the front ends `from` and `to` of `reg`.
pub(crate) fn convert_fe(reg: &Registry, from: &dyn FrontEnd, to: &dyn FrontEnd, input: &[u8], profile: Option<&str>, check: bool) -> Result<ConvertedFe, Diagnostic> {
if let Some(p) = profile {
let profiles = to.profiles();
if profiles.is_empty() {
let owners: Vec<&str> = reg.front_ends().filter(|f| !f.profiles().is_empty()).map(|f| f.title()).collect();
return Err(Diagnostic::new(diag::WRITE_CANNOT, None, format!("a profile is {}'s; {} takes none", owners.join(" or "), to.name())));
}
if !profiles.iter().any(|x| x == p) {
return Err(Diagnostic::new(diag::WRITE_CANNOT, None, format!("profile: {p} is not a {} profile; the profiles are {}", to.title(), profiles.join(" and "))));
}
}
let imported = read(from, input)?;
let imported = read_fe(from, input)?;
let program = crate::qasm::read(&imported.qasm)?.program;
let mut notes = imported.notes.clone();
let (output, written) = if to == Format::Qasm {
let to_qasm = to.name() == Format::Qasm.name();
let (output, written) = if to_qasm {
(imported.qasm.clone().into_bytes(), json!({ "format": "qasm" }))
} else {
// (The same format as read is written afresh from the program read.)
let e = write(to, &program, profile)?;
let e = to.write(&program, profile)?;
notes.extend(e.notes);
(e.bytes, e.summary)
};
let check = if !check {
Value::Null
} else if to != Format::Qasm {
let back = read(to, &output)?;
} else if !to_qasm {
let back = read_fe(to, &output)?;
let p2 = crate::qasm::read(&back.qasm)?.program;
json!({ "what": format!("the {} written, read back, against the program read", to.name()), "verdict": held(&program, &imported.qasm, &p2, &back.qasm) })
} else if from != Format::Qasm {
let again = write(from, &program, None).and_then(|e| read(from, &e.bytes));
} else if from.name() != Format::Qasm.name() {
let again = from.write(&program, None).and_then(|e| read_fe(from, &e.bytes));
match again {
Ok(a) => {
let p2 = crate::qasm::read(&a.qasm)?.program;
@ -241,7 +263,7 @@ pub fn convert(from: Format, to: Format, input: &[u8], profile: Option<Profile>,
} else {
json!({ "what": "OpenQASM to OpenQASM: the text is the program", "verdict": { "verdict": "equivalent", "how": "identity", "why": "the program is passed through as it is" } })
};
Ok(Converted { from, to, output, qasm: imported.qasm, notes, read: imported.summary, written, check })
Ok(ConvertedFe { output, qasm: imported.qasm, notes, read: imported.summary, written, check })
}
/// The ladder's verdict on two programs. Programs that measure in the middle
@ -392,16 +414,21 @@ pub fn decode_base64(text: &str) -> Result<Vec<u8>, String> {
Ok(out)
}
fn format_field(v: &Value, key: &str) -> Result<Option<Format>, String> {
fn format_field<'a>(reg: &'a Registry, v: &Value, key: &str) -> Result<Option<&'a dyn FrontEnd>, String> {
match v.get(key) {
None | Some(Value::Null) => Ok(None),
Some(Value::String(s)) => Format::parse(s).map(Some).ok_or_else(|| crate::engine::refusal::tag(crate::engine::refusal::BAD_FIELD, format!("{key}: {s} is not a format; the formats are {}", names()))),
Some(_) => Err(crate::engine::refusal::tag(crate::engine::refusal::BAD_FIELD, format!("{key} is a format's name: {}", names()))),
Some(Value::String(s)) => reg.front_end(s).map(Some).ok_or_else(|| crate::engine::refusal::tag(crate::engine::refusal::BAD_FIELD, format!("{key}: {s} is not a format; the formats are {}", names(reg)))),
Some(_) => Err(crate::engine::refusal::tag(crate::engine::refusal::BAD_FIELD, format!("{key} is a format's name: {}", names(reg)))),
}
}
fn names() -> String {
Format::all().iter().map(|f| f.name()).collect::<Vec<_>>().join(", ")
fn names(reg: &Registry) -> String {
reg.front_ends().map(|f| f.name()).collect::<Vec<_>>().join(", ")
}
/// The front end whose format `bytes` look like ([`Format::sniff`]).
fn sniffed(bytes: &[u8]) -> &'static dyn FrontEnd {
builtin(Format::sniff(bytes))
}
/// The program a request carries: `program` (text) or `program_base64`
@ -428,16 +455,22 @@ fn notes_json(notes: &[Diagnostic]) -> Value {
/// "written", "check"}`. A refusal is the reader's or writer's diagnostic, with its code
/// last (`… [Q704]`), or the job layer's for a request that does not read.
pub fn convert_json(req: &Value) -> Result<Value, String> {
convert_json_in(Registry::builtins(), req)
}
/// [`convert_json`] with `reg`'s front ends. A conversion a registered front
/// end took part in carries `components`, the identities of the two.
pub(crate) fn convert_json_in(reg: &Registry, req: &Value) -> Result<Value, String> {
use crate::engine::refusal::{BAD_FIELD, tag};
if !req.is_object() {
return Err(tag(BAD_FIELD, "a conversion is a JSON object: {\"to\", \"program\" or \"program_base64\", \"from\", \"profile\", \"check\"}"));
}
let bytes = program_bytes(req)?;
let from = format_field(req, "from")?.unwrap_or_else(|| Format::sniff(&bytes));
let to = format_field(req, "to")?.ok_or_else(|| tag(BAD_FIELD, format!("\"to\": the format to write, one of {}", names())))?;
let from = format_field(reg, req, "from")?.unwrap_or_else(|| sniffed(&bytes));
let to = format_field(reg, req, "to")?.ok_or_else(|| tag(BAD_FIELD, format!("\"to\": the format to write, one of {}", names(reg))))?;
let profile = match req.get("profile") {
None | Some(Value::Null) => None,
Some(Value::String(s)) => Some(Profile::parse(s).ok_or_else(|| tag(BAD_FIELD, format!("profile: {s} is not a QIR profile; the profiles are base and adaptive")))?),
Some(Value::String(s)) => Some(Profile::parse(s).map(Profile::name).or_else(|| to.profiles().into_iter().find(|p| p == s).map(|_| s.as_str())).ok_or_else(|| tag(BAD_FIELD, format!("profile: {s} is not a QIR profile; the profiles are base and adaptive")))?),
Some(_) => return Err(tag(BAD_FIELD, "profile is base or adaptive")),
};
let check = match req.get("check") {
@ -445,22 +478,26 @@ pub fn convert_json(req: &Value) -> Result<Value, String> {
Some(Value::Bool(b)) => *b,
Some(_) => return Err(tag(BAD_FIELD, "check is true or false")),
};
let c = convert(from, to, &bytes, profile, check).map_err(String::from)?;
let c = convert_fe(reg, from, to, &bytes, profile, check).map_err(String::from)?;
let mut out = json!({
"ok": true,
"format": FORMAT,
"from": c.from.name(),
"to": c.to.name(),
"from": from.name(),
"to": to.name(),
"qasm": c.qasm,
"notes": notes_json(&c.notes),
"read": c.read,
"written": c.written,
"check": c.check,
});
if c.to.is_binary() {
if to.binary() {
out["program_base64"] = json!(encode_base64(&c.output));
} else {
out["program"] = json!(String::from_utf8(c.output).expect("a text format writes text"));
out["program"] = json!(String::from_utf8(c.output).map_err(|_| crate::engine::refusal::tag(crate::engine::refusal::CONTRACT, format!("the front end {} is text, and wrote bytes that are not UTF-8", to.name())))?);
}
let used: Vec<&crate::engine::registry::Identity> = [from.name(), to.name()].iter().filter_map(|n| reg.identity(crate::engine::registry::Kind::FrontEnd, n)).collect();
if used.iter().any(|i| !i.is_builtin()) {
out["components"] = used.iter().map(|i| i.to_json()).collect();
}
Ok(out)
}
@ -513,6 +550,11 @@ pub fn program_required() -> Value {
/// whose `qasm` is what its `source` reads to (read already); one whose
/// `qasm` is something else is refused, as is a source that does not read.
pub fn expand(req: &Value) -> Result<Option<Value>, String> {
expand_in(Registry::builtins(), req)
}
/// [`expand`] with `reg`'s front ends.
pub(crate) fn expand_in(reg: &Registry, req: &Value) -> Result<Option<Value>, String> {
use crate::engine::refusal::{BAD_FIELD, NOT_A_JOB, tag};
let Some(src) = req.get("source").filter(|s| !s.is_null()) else { return Ok(None) };
let given = req.get("qasm").filter(|q| !q.is_null());
@ -520,8 +562,8 @@ pub fn expand(req: &Value) -> Result<Option<Value>, String> {
return Err(tag(BAD_FIELD, "\"source\" is {\"format\", \"program\" or \"program_base64\"}"));
}
let bytes = program_bytes(src)?;
let format = format_field(src, "format")?.unwrap_or_else(|| Format::sniff(&bytes));
let imported = read(format, &bytes).map_err(|d| tag(NOT_A_JOB, format!("the source does not read as {}: {d}", format.name())))?;
let format = format_field(reg, src, "format")?.unwrap_or_else(|| sniffed(&bytes));
let imported = read_fe(format, &bytes).map_err(|d| tag(NOT_A_JOB, format!("the source does not read as {}: {d}", format.name())))?;
// A request read already (one a receipt sealed) holds both, alike.
if let Some(q) = given {
if q.as_str() == Some(imported.qasm.as_str()) {

View file

@ -145,7 +145,7 @@ pub fn answer(request: &Value, outcome: Result<Value, String>) -> Result<Value,
}
#[cfg(test)]
mod tests {
pub(crate) mod tests {
use super::*;
#[test]
@ -207,8 +207,13 @@ mod tests {
];
pub(crate) fn pinned_digest(request: &str) -> String {
pinned_digest_in(crate::engine::registry::Registry::builtins(), request)
}
/// [`pinned_digest`] for the job planned and run through `reg`.
pub(crate) fn pinned_digest_in(reg: &crate::engine::registry::Registry, request: &str) -> String {
let req: Value = serde_json::from_str(request).unwrap();
let (plan, mut result) = plan_and_run(&req, 1 << 30).unwrap();
let (plan, mut result) = reg.plan_and_run(&req, 1 << 30).unwrap();
// The wall clock, and the engine and schema every result now names
// (engine::schema::stamp), which those bytes predate: the rest is what
// the pins hold.

View file

@ -140,6 +140,14 @@
//! calibrations, the device and target snapshot, and the integrator and
//! its step; its schedule as a view; and a pulse job checked by running
//! it again.
//! - [`registry`]: the extension points. Each simulation method, compiler
//! pass, noise channel and program format is a component behind a trait
//! ([`registry::Method`], [`registry::Pass`], [`registry::Channel`],
//! [`registry::FrontEnd`]), held in one registry the planner, the run, the
//! compile and the conversions go through; every built-in registers through
//! the same trait, and a host registers its own
//! ([`registry::Registry::register`]), its identity sealed in every result
//! it takes part in and marked as registered, never as built in.
//! - [`refusal`]: the job layer's refusal codes (`J…`), beside the reader's
//! (`Q…`): the request, the method, an intent, a repair and the host.
//! - [`energy`]: what the CPU cores spent for one process, as the kernel counts
@ -222,6 +230,7 @@ pub mod provider;
pub mod receipt;
pub mod pulse;
pub mod refusal;
pub mod registry;
pub mod repair;
#[cfg(feature = "quantum_receipt")]
pub mod sets;

View file

@ -85,7 +85,7 @@ table! {
// The method.
UNKNOWN_METHOD = "J010", Refused, "A method that does not exist: the methods list names them.";
METHOD_REFUSES = "J011", Refused, "The method named cannot take this job: a gate it does not hold, more qubits, memory or work than it takes here, noise it does not model, or an answer it does not give; the message says which, and `auto` picks a method that can.";
METHOD_REFUSES = "J011", Refused, "The method named cannot take this job: a gate it does not hold, more qubits, memory or work than it takes here, or an answer it does not give (noise it cannot take is `J061`); the message says which, and `auto` picks a method that can.";
NO_METHOD = "J012", Refused, "No method takes this job: each method's reason is listed, so the job can be changed to suit one.";
FABRIC = "J013", Refused, "A placement on the fabric (`fabric`) this host cannot honour: no member by that name, no certified accelerator member here, a state or a partition larger than a member holds, a partition count that is not a power of two, or a method or program the fabric's members do not run (the message says which, and what is here).";
@ -109,6 +109,14 @@ table! {
OPTIMIZE = "J052", Refused, "An optimisation, a variational eigensolver or a QAOA run that does not read or cannot be done: an unknown optimiser, settings or steps past their limits, a Hamiltonian or a graph that does not read (the message says which).";
BUILD = "J053", Refused, "An algorithm builder's request that does not read: an unknown algorithm, sizes past its limits, or arguments it does not take (the message says which).";
// Registered components (engine::registry).
UNKNOWN_COMPONENT = "J060", Refused, "A compiler pass a request names (`compile.passes`) that this host does not hold: the components list names every method, pass, noise channel and front end here, and no host loads a component from a request.";
CHANNEL_REFUSED = "J061", Refused, "A noise channel the method cannot take: a method that models no noise, or a channel the method has no form for (amplitude damping on the stabilizer's Pauli frames, a registered channel on a method that takes only the built-in ones); the message names what does take it.";
CHANNEL_INVALID = "J062", Refused, "A noise channel whose parameters do not read or whose operators fail its validity check: a value outside its range, Kraus operators that are not completely positive and trace preserving (their sum of K†K off the identity), or a Pauli form whose probabilities are negative, do not sum to 1, or are not the channel its operators make (the message says by how much).";
REGISTER = "J063", Refused, "A component the registry does not take: a name its kind holds already (a built-in's among them), a name or version that is empty or not plain text, a descriptor that is not a JSON object, a channel on other than one or two qubits, or a pass whose check states no bound.";
CONTRACT = "J064", Refused, "A registered component's answer that breaks the engine's contract: a method's result without an accuracy class it declared, or one that writes a member the engine writes (method, qubits, gates, compiled, repaired, engine, schema, components, receipt, verdict); a pass's gates on qubits the program does not have, or that do not read back; a front end's program that does not read. Nothing it gave is returned.";
REGISTERED_INTENT = "J065", Refused, "An intent on a job whose noise names a registered channel: the intent checks model the built-in channels alone, so the check is refused rather than made against another model.";
// An answer's view statements (the answer view layer).
ANSWER_PARSE = "J100", Dropped, "A line of an answer that is not a statement (`name = View(...)`), a program (a fenced `qasm` block with its name), a comment or a marker: left out.";
ANSWER_VIEW = "J101", Dropped, "A view the answer layer does not have: left out (the views are listed in the layer's specification).";

View file

@ -0,0 +1,394 @@
//! The built-in components, each registered through the trait a host's own
//! implements: the methods (their plans and runs the engine's own, in
//! `catalog` and `run`), the compile's passes (`compile`), the five noise
//! channels and the three formats (`formats`).
use super::{Channel, Check, Component, FrontEnd, GateSet, Job, Method, Param, Pass, PassInput, PassOutput, Placement, Question};
use crate::engine::catalog::{self, Plan};
use crate::engine::formats::{self, Exported, Format, Imported};
use crate::engine::schema::ENGINE_VERSION;
use crate::gates::C;
use crate::qasm::Program;
use crate::qasm::diag::{self, Diagnostic};
use serde_json::{Value, json};
/// Every built-in, in the order the registry holds them: the methods as the
/// methods list gives them, then the passes, the channels (as a job's
/// `noise` lists them) and the formats.
pub(super) fn components() -> Vec<Component> {
let mut v: Vec<Component> = METHODS.iter().map(|m| Component::method(*m)).collect();
v.push(Component::method(PulseMethod));
v.extend(PASSES.iter().map(|p| Component::pass(*p)));
v.extend(CHANNELS.iter().map(|c| Component::channel(*c)));
v.extend([Component::front_end(Fe(Format::Qasm)), Component::front_end(Fe(Format::Qir)), Component::front_end(Fe(Format::Jeff))]);
v
}
// ---------------------------------------------------------------------------
// Methods
// ---------------------------------------------------------------------------
/// A built-in simulation method: its plan is the catalog's, its run the
/// worker's, its words the methods list's.
#[derive(Clone, Copy)]
struct BuiltinMethod {
name: &'static str,
classes: &'static [&'static str],
noise: bool,
/// Its rank for each question: noise, counts, expectations, amplitudes.
ranks: [Option<u32>; 4],
}
const fn m(name: &'static str, classes: &'static [&'static str], noise: bool, ranks: [Option<u32>; 4]) -> BuiltinMethod {
BuiltinMethod { name, classes, noise, ranks }
}
/// The built-in methods, as the methods list gives them, each with where
/// `auto` tries it: with noise, `density_matrix`, `stabilizer`,
/// `trajectories`; for counts, `stabilizer`, `exact`, `statevector`,
/// `statevector_single`, `cutrank`, `mps`; for expectation values alone,
/// `stabilizer`, `exact`, `statevector`, `statevector_single`,
/// `stabilizer_rank`, `pauli`, `mps`; for amplitudes, `exact`,
/// `statevector`, `statevector_single`, `cutrank`, `stabilizer_rank`,
/// `tensor_network`, `mps`.
const METHODS: [BuiltinMethod; 12] = [
m("exact", &["bit-exact"], false, [None, Some(20), Some(20), Some(10)]),
m("statevector", &["exact"], false, [None, Some(30), Some(30), Some(20)]),
m("statevector_single", &["single"], false, [None, Some(40), Some(40), Some(30)]),
m("stabilizer", &["exact", "estimate"], true, [Some(20), Some(10), Some(10), None]),
m("pauli", &["bounded"], false, [None, None, Some(60), None]),
m("mps", &["exact", "heuristic"], false, [None, Some(60), Some(70), Some(70)]),
m("stabilizer_rank", &["exact", "estimate"], false, [None, None, Some(50), Some(50)]),
m("tensor_network", &["exact"], false, [None, None, None, Some(60)]),
m("cutrank", &["exact", "single"], false, [None, Some(50), None, Some(40)]),
m("cutrank_single", &["single"], false, [None, None, None, None]),
m("density_matrix", &["exact"], true, [Some(10), None, None, None]),
m("trajectories", &["estimate"], true, [Some(30), None, None, None]),
];
fn question_index(q: Question) -> usize {
match q {
Question::Noise => 0,
Question::Counts => 1,
Question::Expectations => 2,
Question::Amplitudes => 3,
}
}
impl Method for BuiltinMethod {
fn name(&self) -> &str {
self.name
}
fn version(&self) -> &str {
ENGINE_VERSION
}
fn classes(&self) -> Vec<String> {
self.classes.iter().map(|c| c.to_string()).collect()
}
fn descriptor(&self) -> Value {
catalog::method_text(self.name)
}
fn describe(&self, job_bytes: u64) -> Value {
catalog::describe_one(self.name, job_bytes)
}
fn models_noise(&self) -> bool {
self.noise
}
fn auto_rank(&self, question: Question) -> Option<u32> {
self.ranks[question_index(question)]
}
fn plan(&self, job: &Job<'_>) -> Result<Plan, String> {
catalog::try_method(self.name, job.request()?, job.dyadic(), job.clifford(), job.job_bytes())
}
fn run(&self, job: &Job<'_>, plan: &Plan, out: &mut Value) -> Result<(), String> {
crate::engine::run::run_builtin(self.name, job, plan, out)
}
}
/// The pulse simulator, as the method `pulse`: it reads its job's pulse
/// program itself (`engine::pulse`).
#[derive(Clone, Copy)]
struct PulseMethod;
impl Method for PulseMethod {
fn name(&self) -> &str {
crate::engine::pulse::METHOD
}
fn version(&self) -> &str {
ENGINE_VERSION
}
fn classes(&self) -> Vec<String> {
vec!["bounded".into()]
}
fn descriptor(&self) -> Value {
let mut d = crate::engine::pulse::describe();
if let Some(o) = d.as_object_mut() {
o.remove("method");
o.remove("max_qubits");
}
d
}
fn describe(&self, _: u64) -> Value {
crate::engine::pulse::describe()
}
fn reads_program(&self) -> bool {
true
}
fn plan(&self, job: &Job<'_>) -> Result<Plan, String> {
crate::engine::pulse::plan(job.json(), job.job_bytes())
}
fn run(&self, job: &Job<'_>, plan: &Plan, out: &mut Value) -> Result<(), String> {
*out = crate::engine::pulse::run(job.json(), plan)?;
Ok(())
}
}
// ---------------------------------------------------------------------------
// Passes
// ---------------------------------------------------------------------------
/// A built-in compiler pass: its run is the compile's own, and it checks
/// itself where it acts, as its record says.
#[derive(Clone, Copy)]
struct BuiltinPass {
name: &'static str,
takes: GateSet,
gives: GateSet,
check: Check,
}
const PASSES: [BuiltinPass; 8] = [
BuiltinPass { name: "lower", takes: GateSet::Any, gives: GateSet::Basis, check: Check::Fidelity { max_distance: 1e-12 } },
BuiltinPass { name: "simplify", takes: GateSet::Any, gives: GateSet::Any, check: Check::Ladder },
BuiltinPass { name: "phase_fold", takes: GateSet::Any, gives: GateSet::Any, check: Check::Ladder },
BuiltinPass { name: "resynth_1q", takes: GateSet::Any, gives: GateSet::Any, check: Check::Fidelity { max_distance: 1e-12 } },
BuiltinPass { name: "resynth_2q", takes: GateSet::Any, gives: GateSet::Any, check: Check::Fidelity { max_distance: 1e-9 } },
BuiltinPass { name: "approximate", takes: GateSet::Basis, gives: GateSet::CliffordT, check: Check::Fidelity { max_distance: crate::engine::synth::rz::MAX_EPSILON } },
BuiltinPass { name: "twirl", takes: GateSet::Any, gives: GateSet::Any, check: Check::Ladder },
BuiltinPass { name: "dd", takes: GateSet::Any, gives: GateSet::Any, check: Check::Ladder },
];
impl Pass for BuiltinPass {
fn name(&self) -> &str {
self.name
}
fn version(&self) -> &str {
ENGINE_VERSION
}
fn descriptor(&self) -> Value {
let does = crate::engine::compile::PASSES.iter().find(|(n, _)| *n == self.name).map(|(_, d)| *d).unwrap_or("");
json!({ "does": does })
}
fn takes(&self) -> GateSet {
self.takes
}
fn gives(&self) -> GateSet {
self.gives
}
fn check(&self) -> Check {
self.check
}
fn run(&self, input: &PassInput<'_>) -> Result<PassOutput, String> {
crate::engine::compile::builtin_pass(self.name, input)
}
}
// ---------------------------------------------------------------------------
// Noise channels
// ---------------------------------------------------------------------------
/// A built-in noise channel: one probability, applied by the noisy methods
/// as their noise model holds it.
#[derive(Clone, Copy)]
struct BuiltinChannel {
name: &'static str,
title: &'static str,
does: &'static str,
}
const CHANNELS: [BuiltinChannel; 5] = [
BuiltinChannel { name: "depolarizing_1q", title: "one-qubit depolarizing noise", does: "X, Y and Z each with probability p/3 after each gate on other than two qubits, on each qubit it touched" },
BuiltinChannel { name: "depolarizing_2q", title: "two-qubit depolarizing noise", does: "each of the 15 non-identity two-qubit Paulis with probability p/15 after each two-qubit gate" },
BuiltinChannel { name: "amplitude_damping", title: "amplitude damping", does: "amplitude damping of strength p after each gate, on each qubit it touched" },
BuiltinChannel { name: "dephasing", title: "dephasing", does: "Z with probability p after each gate, on each qubit it touched" },
BuiltinChannel { name: "readout", title: "readout error", does: "each measured bit read flipped with probability p" },
];
/// What `stabilizer` says of amplitude damping, which has no Pauli form.
pub(crate) const STABILIZER_NOT_DAMPING: &str = "stabilizer models Pauli noise (depolarizing, dephasing, readout), not amplitude damping";
fn c(re: f64) -> C {
C { re, im: 0.0 }
}
const PAULIS: [char; 4] = ['I', 'X', 'Y', 'Z'];
fn pauli_one(p: char) -> [C; 4] {
let (z, o) = (c(0.0), c(1.0));
match p {
'X' => [z, o, o, z],
'Y' => [z, C { re: 0.0, im: -1.0 }, C { re: 0.0, im: 1.0 }, z],
'Z' => [o, z, z, c(-1.0)],
_ => [o, z, z, o],
}
}
/// `w · (a ⊗ b)`, `a` on the most significant qubit.
fn kron_scaled(w: f64, a: &[C; 4], b: &[C; 4]) -> Vec<C> {
let mut m = vec![c(0.0); 16];
for r in 0..4 {
for col in 0..4 {
let (x, y) = (a[(r >> 1) * 2 + (col >> 1)], b[(r & 1) * 2 + (col & 1)]);
m[r * 4 + col] = C { re: w * (x.re * y.re - x.im * y.im), im: w * (x.re * y.im + x.im * y.re) };
}
}
m
}
impl BuiltinChannel {
/// Its Pauli form at probability `p`, where it has one.
fn form(&self, p: f64) -> Option<Vec<(f64, String)>> {
Some(match self.name {
"depolarizing_1q" => PAULIS.iter().enumerate().map(|(i, &q)| (if i == 0 { 1.0 - p } else { p / 3.0 }, q.to_string())).collect(),
"depolarizing_2q" => (0..16).map(|k| (if k == 0 { 1.0 - p } else { p / 15.0 }, format!("{}{}", PAULIS[k >> 2], PAULIS[k & 3]))).collect(),
"dephasing" => vec![(1.0 - p, "I".into()), (p, "Z".into())],
"readout" => vec![(1.0 - p, "I".into()), (p, "X".into())],
_ => return None,
})
}
}
impl Channel for BuiltinChannel {
fn name(&self) -> &str {
self.name
}
fn version(&self) -> &str {
ENGINE_VERSION
}
fn title(&self) -> &str {
self.title
}
fn descriptor(&self) -> Value {
json!({ "does": self.does })
}
fn params(&self) -> Vec<Param> {
vec![Param::new("p", 0.0, 1.0, "a probability")]
}
fn arity(&self) -> u32 {
if self.name == "depolarizing_2q" { 2 } else { 1 }
}
fn placement(&self) -> Placement {
if self.name == "readout" { Placement::OnReadout } else { Placement::AfterGate }
}
fn kraus(&self, values: &[f64]) -> Vec<Vec<C>> {
let p = values.first().copied().unwrap_or(0.0);
if self.name == "amplitude_damping" {
let (z, o) = (c(0.0), c(1.0));
return vec![vec![o, z, z, c((1.0 - p).sqrt())], vec![z, c(p.sqrt()), z, z]];
}
let form = self.form(p).unwrap_or_default();
form.iter()
.filter(|(w, _)| *w > 0.0)
.map(|(w, s)| {
let ps: Vec<char> = s.chars().collect();
if ps.len() == 2 {
kron_scaled(w.sqrt(), &pauli_one(ps[0]), &pauli_one(ps[1]))
} else {
pauli_one(ps[0]).iter().map(|x| C { re: w.sqrt() * x.re, im: w.sqrt() * x.im }).collect()
}
})
.collect()
}
fn pauli(&self, values: &[f64]) -> Option<Vec<(f64, String)>> {
self.form(values.first().copied().unwrap_or(0.0))
}
fn taken_by(&self, method: &str) -> Result<String, String> {
match method {
"density_matrix" if self.name == "readout" => Ok("exactly: each measured bit's flip applied to the measured distribution".into()),
"density_matrix" => Ok("exactly: its Kraus sum applied to the density matrix as one superoperator with each gate".into()),
"stabilizer" if self.name == "amplitude_damping" => Err(STABILIZER_NOT_DAMPING.into()),
"stabilizer" if self.name == "readout" => Ok("each measured bit flipped in the Pauli frames' samples".into()),
"stabilizer" => Ok("as Pauli frames against a noiseless stabilizer tableau, each frame drawing its Paulis".into()),
"trajectories" if self.name == "amplitude_damping" => Ok("as a jump drawn with the state's own probability, the state renormalised".into()),
"trajectories" if self.name == "readout" => Ok("each measured bit flipped in each trajectory's sample".into()),
"trajectories" => Ok("a Pauli drawn in each trajectory, shots that draw the same sharing a state".into()),
m => Err(format!("{m} does not model noise")),
}
}
}
// ---------------------------------------------------------------------------
// Front ends
// ---------------------------------------------------------------------------
/// A built-in format, read and written by `engine::formats`.
#[derive(Clone, Copy)]
struct Fe(Format);
fn text(format: Format, input: &[u8]) -> Result<&str, Diagnostic> {
std::str::from_utf8(input).map_err(|_| Diagnostic::new(diag::FORMAT_UNREAD, None, format!("{} is text, and this is not UTF-8", format.name())))
}
impl FrontEnd for Fe {
fn name(&self) -> &str {
self.0.name()
}
fn version(&self) -> &str {
ENGINE_VERSION
}
fn title(&self) -> &str {
match self.0 {
Format::Qir => "QIR",
Format::Jeff => "jeff",
_ => "OpenQASM 3",
}
}
fn descriptor(&self) -> Value {
match self.0 {
Format::Qir => json!({ "reads": "QIR as LLVM text, under its base and adaptive profiles", "writes": "QIR 2.0 under the base or the adaptive profile" }),
Format::Jeff => json!({ "reads": "jeff, its Cap'n Proto message", "writes": "jeff, its Cap'n Proto message" }),
_ => json!({ "reads": "OpenQASM 3, and the OpenQASM 2 forms in common use", "writes": "OpenQASM 3" }),
}
}
fn binary(&self) -> bool {
self.0.is_binary()
}
fn extensions(&self) -> Vec<String> {
match self.0 {
Format::Qir => vec!["ll".into(), "qir".into()],
Format::Jeff => vec!["jeff".into()],
_ => vec!["qasm".into(), "inc".into()],
}
}
fn profiles(&self) -> Vec<String> {
match self.0 {
Format::Qir => vec!["base".into(), "adaptive".into()],
_ => Vec::new(),
}
}
fn read(&self, input: &[u8]) -> Result<Imported, Diagnostic> {
match self.0 {
Format::Qir => {
let r = formats::qir::read(text(self.0, input)?)?;
Ok(Imported { qasm: r.qasm, notes: r.notes, summary: r.summary })
}
Format::Jeff => {
let r = formats::jeff::read(input)?;
Ok(Imported { qasm: r.qasm, notes: r.notes, summary: r.summary })
}
_ => {
let t = text(self.0, input)?;
crate::qasm::read(t)?;
Ok(Imported { qasm: t.to_string(), notes: Vec::new(), summary: json!({ "format": "qasm" }) })
}
}
}
fn write(&self, program: &Program, profile: Option<&str>) -> Result<Exported, Diagnostic> {
let profile = match profile {
None => None,
Some(p) if self.0 == Format::Qir => Some(formats::Profile::parse(p).ok_or_else(|| Diagnostic::new(diag::WRITE_CANNOT, None, format!("profile: {p} is not a QIR profile; the profiles are base and adaptive")))?),
Some(_) => return Err(Diagnostic::new(diag::WRITE_CANNOT, None, format!("a profile is QIR's; {} takes none", self.0.name()))),
};
formats::write(self.0, program, profile)
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,673 @@
//! The registry: ground truth that every built-in gives the bytes it gave,
//! through the registry and beside a host's own components; and a host's
//! method, pass, channel and front end, registered, run, refused with their
//! codes, and named in what they make.
use super::*;
use crate::engine::refusal::codes_in;
use crate::qasm::Kind as Gate;
use serde_json::json;
const CAP: u64 = 1 << 30;
// ---------------------------------------------------------------------------
// A host's own components, as a test writes them
// ---------------------------------------------------------------------------
/// A method for circuits of `x`, `cx`, `ccx` and `swap` alone: the state
/// stays one basis state, followed bit by bit.
struct BasisTable;
fn permutes(p: &Program) -> bool {
p.ops.iter().all(|o| matches!(o.kind, Gate::X | Gate::Cx | Gate::Ccx | Gate::Swap | Gate::Id))
}
fn follow(p: &Program) -> Vec<bool> {
let mut b = vec![false; p.n_qubits as usize];
for o in &p.ops {
let q: Vec<usize> = o.qubits.iter().map(|&q| q as usize).collect();
match o.kind {
Gate::X => b[q[0]] = !b[q[0]],
Gate::Cx if b[q[0]] => b[q[1]] = !b[q[1]],
Gate::Ccx if b[q[0]] && b[q[1]] => b[q[2]] = !b[q[2]],
Gate::Swap => b.swap(q[0], q[1]),
_ => {}
}
}
b
}
impl Method for BasisTable {
fn name(&self) -> &str {
"basis_table"
}
fn version(&self) -> &str {
"1.0.0"
}
fn classes(&self) -> Vec<String> {
vec!["exact".into()]
}
fn descriptor(&self) -> Value {
json!({ "circuits": "x, cx, ccx and swap alone", "answers": ["counts", "amplitudes"] })
}
fn auto_rank(&self, question: Question) -> Option<u32> {
(question == Question::Counts).then_some(5)
}
fn plan(&self, job: &Job<'_>) -> Result<Plan, String> {
let r = job.request()?;
if !permutes(&r.program) {
return Err("basis_table takes x, cx, ccx and swap alone".into());
}
if !r.observables.is_empty() {
return Err("basis_table answers counts and amplitudes".into());
}
Ok(Plan { method: "basis_table".into(), why: format!("a permutation of basis states on {} qubits, followed bit by bit", r.program.n_qubits), memory_bytes: 1 << 20, work: r.program.ops.len() as u64, qubits: r.program.n_qubits, target: None })
}
fn run(&self, job: &Job<'_>, _: &Plan, out: &mut Value) -> Result<(), String> {
let r = job.request()?;
let b = follow(&r.program);
out["accuracy"] = json!({ "class": "exact" });
if r.shots > 0 {
let mut counts = serde_json::Map::new();
counts.insert(r.program.bitstring(|q| b[q as usize]), json!(r.shots));
out["counts"] = Value::Object(counts);
}
if !r.amplitudes.is_empty() {
let n = r.program.n_qubits as usize;
let here: String = (0..n).rev().map(|q| if b[q] { '1' } else { '0' }).collect();
out["amplitudes"] = r.amplitudes.iter().map(|a| json!({ "bits": a, "re": if *a == here { 1.0 } else { 0.0 }, "im": 0.0 })).collect();
}
Ok(())
}
}
/// A method that misbehaves in one of the ways the contract names.
struct Rogue(&'static str);
impl Method for Rogue {
fn name(&self) -> &str {
self.0
}
fn version(&self) -> &str {
"0.1"
}
fn classes(&self) -> Vec<String> {
vec!["exact".into()]
}
fn descriptor(&self) -> Value {
json!({})
}
fn plan(&self, job: &Job<'_>) -> Result<Plan, String> {
let method = if self.0 == "rogue_plan" { "exact".to_string() } else { self.0.to_string() };
Ok(Plan { method, why: "rogue".into(), memory_bytes: 1, work: 1, qubits: job.request()?.program.n_qubits, target: None })
}
fn run(&self, _: &Job<'_>, _: &Plan, out: &mut Value) -> Result<(), String> {
match self.0 {
"rogue_method" => {
out["accuracy"] = json!({ "class": "exact" });
out["method"] = json!("exact");
}
"rogue_class" => out["accuracy"] = json!({ "class": "bit-exact" }),
_ => out["accuracy"] = json!({ "class": "exact" }),
}
Ok(())
}
}
/// A pass that drops adjacent pairs of the same self-inverse gate on the
/// same qubits.
struct CancelPairs;
fn self_inverse(k: Gate) -> bool {
matches!(k, Gate::X | Gate::Y | Gate::Z | Gate::H | Gate::Cx | Gate::Cz | Gate::Swap | Gate::Ccx)
}
impl Pass for CancelPairs {
fn name(&self) -> &str {
"cancel_pairs"
}
fn version(&self) -> &str {
"1.0.0"
}
fn descriptor(&self) -> Value {
json!({ "does": "adjacent pairs of one self-inverse gate on the same qubits removed" })
}
fn check(&self) -> Check {
Check::Ladder
}
fn run(&self, input: &PassInput<'_>) -> Result<PassOutput, String> {
let mut out: Vec<Op> = Vec::new();
for o in input.ops {
if self_inverse(o.kind) && out.last().is_some_and(|l| l.kind == o.kind && l.qubits == o.qubits) {
out.pop();
} else {
out.push(o.clone());
}
}
Ok(PassOutput::new(out).with_note(json!({ "how": "pairs dropped" })))
}
}
/// A pass that is wrong: it drops the last gate.
struct DropLast(Check);
impl Pass for DropLast {
fn name(&self) -> &str {
if self.0 == Check::Ladder { "drop_last" } else { "drop_last_fidelity" }
}
fn version(&self) -> &str {
"0.0.1"
}
fn descriptor(&self) -> Value {
json!({ "does": "drops the last gate" })
}
fn check(&self) -> Check {
self.0
}
fn run(&self, input: &PassInput<'_>) -> Result<PassOutput, String> {
let mut ops = input.ops.to_vec();
ops.pop();
Ok(PassOutput::new(ops))
}
}
/// A channel: the bit flip, X with probability p.
struct BitFlip;
fn one(re: f64) -> C {
C { re, im: 0.0 }
}
impl Channel for BitFlip {
fn name(&self) -> &str {
"bit_flip"
}
fn version(&self) -> &str {
"1.0.0"
}
fn title(&self) -> &str {
"bit flip"
}
fn descriptor(&self) -> Value {
json!({ "does": "X with probability p after each gate, on each qubit it touched" })
}
fn params(&self) -> Vec<Param> {
vec![Param::new("p", 0.0, 1.0, "a probability")]
}
fn arity(&self) -> u32 {
1
}
fn kraus(&self, v: &[f64]) -> Vec<Vec<C>> {
let (a, b) = ((1.0 - v[0]).sqrt(), v[0].sqrt());
vec![vec![one(a), one(0.0), one(0.0), one(a)], vec![one(0.0), one(b), one(b), one(0.0)]]
}
fn pauli(&self, v: &[f64]) -> Option<Vec<(f64, String)>> {
Some(vec![(1.0 - v[0], "I".into()), (v[0], "X".into())])
}
}
/// A channel of two parameters: Z with probability `p` and X with
/// probability `q`, whose operators are wrong when `broken`.
struct TwoParam {
broken: bool,
}
impl Channel for TwoParam {
fn name(&self) -> &str {
if self.broken { "leaky" } else { "zx" }
}
fn version(&self) -> &str {
"1"
}
fn title(&self) -> &str {
"Z and X flips"
}
fn descriptor(&self) -> Value {
json!({})
}
fn params(&self) -> Vec<Param> {
vec![Param::new("p", 0.0, 0.5, "a probability"), Param::new("q", 0.0, 0.5, "a probability")]
}
fn arity(&self) -> u32 {
1
}
fn kraus(&self, v: &[f64]) -> Vec<Vec<C>> {
let (p, q) = (v[0], v[1]);
let keep = if self.broken { 1.0 } else { (1.0 - p - q).sqrt() };
vec![vec![one(keep), one(0.0), one(0.0), one(keep)], vec![one(p.sqrt()), one(0.0), one(0.0), one(-p.sqrt())], vec![one(0.0), one(q.sqrt()), one(q.sqrt()), one(0.0)]]
}
}
/// A front end: a list of gates, one a line (`qubits 2`, `h 0`, `cx 0 1`,
/// `measure`).
struct GateList;
impl FrontEnd for GateList {
fn name(&self) -> &str {
"gate_list"
}
fn version(&self) -> &str {
"1.0.0"
}
fn title(&self) -> &str {
"a gate list"
}
fn descriptor(&self) -> Value {
json!({ "reads": "qubits N, then a gate and its qubits a line, and measure", "writes": "the same, gates without parameters" })
}
fn extensions(&self) -> Vec<String> {
vec!["gates".into()]
}
fn read(&self, input: &[u8]) -> Result<crate::engine::formats::Imported, Diagnostic> {
use crate::qasm::diag::FORMAT_UNREAD;
let text = std::str::from_utf8(input).map_err(|_| Diagnostic::new(FORMAT_UNREAD, None, "a gate list is text"))?;
let mut q = String::from("OPENQASM 3.0;\n");
let mut n = None;
for (i, line) in text.lines().map(str::trim).filter(|l| !l.is_empty()).enumerate() {
let w: Vec<&str> = line.split_whitespace().collect();
match (i, w.as_slice()) {
(0, ["qubits", k]) => {
let k: u32 = k.parse().map_err(|_| Diagnostic::new(FORMAT_UNREAD, None, "qubits N"))?;
q.push_str(&format!("qubit[{k}] q;\nbit[{k}] c;\n"));
n = Some(k);
}
(0, _) => return Err(Diagnostic::new(FORMAT_UNREAD, None, "a gate list starts with qubits N")),
(_, ["measure"]) => q.push_str("c = measure q;\n"),
(_, [g, qs @ ..]) => q.push_str(&format!("{g} {};\n", qs.iter().map(|x| format!("q[{x}]")).collect::<Vec<_>>().join(", "))),
_ => {}
}
}
n.ok_or_else(|| Diagnostic::new(FORMAT_UNREAD, None, "an empty gate list"))?;
Ok(crate::engine::formats::Imported { qasm: q, notes: Vec::new(), summary: json!({ "format": "gate_list" }) })
}
fn write(&self, p: &Program, _: Option<&str>) -> Result<crate::engine::formats::Exported, Diagnostic> {
let mut s = format!("qubits {}\n", p.n_qubits);
for o in &p.ops {
if !o.params.is_empty() {
return Err(Diagnostic::new(crate::qasm::diag::WRITE_CANNOT, None, "a gate list holds gates without parameters"));
}
s.push_str(&format!("{} {}\n", format!("{:?}", o.kind).to_lowercase(), o.qubits.iter().map(u32::to_string).collect::<Vec<_>>().join(" ")));
}
if !p.measured.is_empty() {
s.push_str("measure\n");
}
Ok(crate::engine::formats::Exported { bytes: s.into_bytes(), notes: Vec::new(), summary: json!({ "format": "gate_list" }) })
}
}
/// The built-ins and every component above.
fn host() -> Registry {
let mut r = Registry::with_builtins();
r.register(Component::method(BasisTable)).unwrap();
r.register(Component::pass(CancelPairs)).unwrap();
r.register(Component::channel(BitFlip)).unwrap();
r.register(Component::channel(TwoParam { broken: false })).unwrap();
r.register(Component::front_end(GateList)).unwrap();
r
}
// ---------------------------------------------------------------------------
// The built-ins
// ---------------------------------------------------------------------------
#[test]
fn every_built_in_registers_through_its_trait_in_a_fixed_order() {
let r = Registry::with_builtins();
let names = |k: Kind| r.identities().into_iter().filter(|i| i.kind == k).map(|i| i.name.clone()).collect::<Vec<_>>();
assert_eq!(names(Kind::Method), ["exact", "statevector", "statevector_single", "stabilizer", "pauli", "mps", "stabilizer_rank", "tensor_network", "cutrank", "cutrank_single", "density_matrix", "trajectories", "pulse"]);
assert_eq!(names(Kind::Pass), crate::engine::compile::PASSES.iter().map(|(n, _)| n.to_string()).collect::<Vec<_>>());
assert_eq!(names(Kind::Channel), crate::engine::catalog::NOISE_CHANNELS);
assert_eq!(names(Kind::FrontEnd), ["qasm", "qir", "jeff"]);
for i in r.identities() {
assert!(i.is_builtin() && i.version == crate::engine::schema::ENGINE_VERSION, "{i:?}");
assert!(i.digest.len() == 64 && i.digest.bytes().all(|b| b.is_ascii_hexdigit()), "{i:?}");
assert!(i.to_json().get("certified").is_none(), "a built-in is not marked as registered: {i:?}");
}
// Made twice, the same bytes; the shared one is the same registry.
assert_eq!(r.components().to_string(), Registry::with_builtins().components().to_string());
assert_eq!(r.components(), Registry::builtins().components());
assert_eq!(r.components()["registered"], 0);
// The methods list is the registry's, and the words hashed hold nothing of the host.
assert_eq!(crate::engine::catalog::describe(CAP), r.describe_methods(CAP));
for m in r.methods() {
let d = m.descriptor();
assert!(d.get("max_qubits").is_none() && d.get("available").is_none() && d.get("max_cut").is_none() && d.get("method").is_none(), "{}: {d}", m.name());
let entry = m.describe(CAP);
assert_eq!(entry["method"], m.name());
for (k, v) in d.as_object().unwrap() {
assert_eq!(&entry[k], v, "{}: {k}", m.name());
}
}
}
/// `auto` tries the methods in the order it always has.
#[test]
fn auto_tries_the_built_ins_in_the_order_it_did() {
let r = Registry::with_builtins();
let order = |q| r.auto_order(q).into_iter().map(|m| m.name().to_string()).collect::<Vec<_>>();
assert_eq!(order(Question::Noise), ["density_matrix", "stabilizer", "trajectories"]);
assert_eq!(order(Question::Counts), ["stabilizer", "exact", "statevector", "statevector_single", "cutrank", "mps"]);
assert_eq!(order(Question::Expectations), ["stabilizer", "exact", "statevector", "statevector_single", "stabilizer_rank", "pauli", "mps"]);
assert_eq!(order(Question::Amplitudes), ["exact", "statevector", "statevector_single", "cutrank", "stabilizer_rank", "tensor_network", "mps"]);
// A host's method joins where its rank puts it, the built-ins kept in their order.
let h = host();
assert_eq!(h.auto_order(Question::Counts).iter().map(|m| m.name()).collect::<Vec<_>>(), ["basis_table", "stabilizer", "exact", "statevector", "statevector_single", "cutrank", "mps"]);
}
/// Ground truth: the pinned jobs give the bytes they gave, through the
/// built-in registry and through one that holds a host's components beside
/// the built-ins (none of which these jobs name, and no method `auto` tries:
/// one it tries is named in the account of what `auto` passed over).
#[cfg(feature = "quantum_receipt")]
#[test]
fn the_pinned_jobs_give_their_bytes_through_any_registry() {
use crate::engine::job::tests::{PINNED, pinned_digest_in};
let mut h = Registry::with_builtins();
h.register(Component::method(Rogue("quiet"))).unwrap();
h.register(Component::pass(CancelPairs)).unwrap();
h.register(Component::channel(BitFlip)).unwrap();
h.register(Component::front_end(GateList)).unwrap();
for (req, want) in PINNED {
assert_eq!(pinned_digest_in(Registry::builtins(), req), want, "{req}");
assert_eq!(pinned_digest_in(&h, req), want, "{req}, beside a host's components");
}
}
/// The built-in channels pass their own validity check at every strength,
/// and each Pauli form is its operators' channel.
#[test]
fn the_built_in_channels_are_valid_channels() {
let r = Registry::with_builtins();
for c in r.channels() {
for p in [0.0, 1e-9, 0.01, 0.3, 0.75, 1.0] {
let v = validate(c, &[p]).unwrap_or_else(|e| panic!("{} at {p}: {e}", c.name()));
assert_eq!(v["pauli"], c.name() != "amplitude_damping", "{}", c.name());
}
}
assert!(validate(&TwoParam { broken: true }, &[0.1, 0.1]).unwrap_err().ends_with("[J062]"));
}
/// Every built-in pass gives the gates it declares it gives, on compiles
/// that use each at every level.
#[test]
fn the_built_in_passes_give_what_they_declare() {
use crate::engine::compile::{Basis, Dd, Options, pipeline};
let r = Registry::with_builtins();
let src = "OPENQASM 3.0;\nqubit[3] q;\nh q;\nrz(0.3) q[0];\ncx q[0], q[1];\nry(1.1) q[1];\ncp(0.7) q[1], q[2];\nccx q[0], q[1], q[2];\nt q[0];\nt q[0];\nswap q[1], q[2];\n";
let p = crate::qasm::parse(src).unwrap();
for basis in [Basis::CliffordT, Basis::Standard] {
for level in 0..=crate::engine::compile::MAX_LEVEL {
let o = Options { epsilon: 1e-3, level, basis, twirl: Some(2), dd: Some(Dd::Xy4), ..Default::default() };
let mut ops = p.ops.clone();
let mut spent = 0.0;
for name in pipeline(&o) {
let pass = r.pass(name).unwrap();
assert!(pass.takes().holds(&ops), "{name} is given gates it takes");
let out = pass.run(&PassInput { qubits: 3, ops: &ops, options: &o, spent }).unwrap();
if out.check["verdict"] != "rejected" && out.check["verdict"] != "skipped" {
assert!(pass.gives().holds(&out.ops), "{basis:?} level {level}: {name} gives what it declares");
spent += out.distance;
ops = out.ops;
}
}
// The registry's compile is the compile.
let a = crate::engine::compile::compile(&p, &o).unwrap();
assert_eq!(r.compile(&p, &o, &[]).unwrap().qasm, a.qasm);
assert_eq!(a.program.ops, ops, "{basis:?} level {level}: the passes run one by one through their trait are the compile");
}
}
}
// ---------------------------------------------------------------------------
// A host's components
// ---------------------------------------------------------------------------
/// A host's method runs where the built-ins run, named or chosen by `auto`,
/// gives the bit-exact method's answer, and the result names every
/// component that made it, the method marked as registered.
#[test]
fn a_registered_method_is_planned_run_and_named_in_its_result() {
let h = host();
let toffoli = "OPENQASM 3.0;\nqubit[3] q;\nbit[3] c;\nx q[0];\nx q[1];\nccx q[0], q[1], q[2];\nswap q[0], q[2];\nc = measure q;\n";
let (plan, out) = h.plan_and_run(&json!({ "qasm": toffoli, "shots": 50 }), CAP).unwrap();
assert_eq!(plan.method, "basis_table", "auto takes it by its rank: {}", plan.why);
assert_eq!(out["counts"], json!({ "111": 50 }), "{out}");
let (_, exact) = h.plan_and_run(&json!({ "qasm": toffoli, "shots": 50, "method": "exact" }), CAP).unwrap();
assert_eq!(exact["counts"], out["counts"]);
assert!(exact.get("components").is_none(), "a built-in's result is as it was: {exact}");
let c = out["components"].as_array().unwrap();
assert_eq!(c.iter().map(|x| (x["kind"].as_str().unwrap(), x["name"].as_str().unwrap(), x["origin"].as_str().unwrap())).collect::<Vec<_>>(), [("front_end", "qasm", "built-in"), ("method", "basis_table", "registered")]);
assert_eq!(c[1]["certified"], false);
assert_eq!(c[1]["digest"], h.identity(Kind::Method, "basis_table").unwrap().digest);
assert_eq!(h.components_of(&json!({}), &out), c.clone());
// The same job, the same bytes, less the wall clock.
let again = h.plan_and_run(&json!({ "qasm": toffoli, "shots": 50 }), CAP).unwrap().1;
let strip = |mut v: Value| {
v.as_object_mut().unwrap().remove("elapsed_ms");
v.to_string()
};
assert_eq!(strip(again), strip(out));
// What it does not take, it refuses, and auto passes over it.
let bell = json!({ "qasm": "OPENQASM 3.0;\nqubit[2] q;\nh q[0];\ncx q[0], q[1];\n", "shots": 10, "method": "basis_table" });
assert_eq!(codes_in(&h.plan(&bell, CAP).unwrap_err()), ["J011"]);
let mut auto = bell.clone();
auto["method"] = json!("auto");
let p = h.plan(&auto, CAP).unwrap();
assert!(p.method == "stabilizer" && p.why.contains("not basis_table: basis_table takes"), "{}", p.why);
// Not in the built-in registry.
assert_eq!(codes_in(&Registry::builtins().plan(&json!({ "qasm": toffoli, "shots": 1, "method": "basis_table" }), CAP).unwrap_err()), ["J010"]);
// A built-in's identity, read from a result that names only built-ins.
let ids = h.components_of(&json!({ "qasm": toffoli }), &exact);
assert_eq!(ids.iter().map(|x| x["name"].as_str().unwrap()).collect::<Vec<_>>(), ["qasm", "exact"]);
}
/// The receipt seals a registered component's identity, which cannot be
/// changed or dropped (it cannot pass for a built-in).
#[cfg(feature = "quantum_receipt")]
#[test]
fn the_receipt_seals_a_registered_component_s_identity() {
let h = host();
let key = ed25519_dalek::SigningKey::from_bytes(&[5; 32]);
let req = json!({ "qasm": "OPENQASM 3.0;\nqubit[2] q;\nbit[2] c;\nx q[1];\ncx q[1], q[0];\nc = measure q;\n", "shots": 8, "method": "basis_table" });
let body = h.job(&req, CAP, &key, json!({ "unmetered": "a test" })).unwrap();
assert!(crate::engine::job::verify(&body).unwrap().0);
assert_eq!(body["result"]["components"][1]["origin"], "registered");
for (path, v) in [("/result/components/1/origin", json!("built-in")), ("/result/components/1/digest", json!("0".repeat(64))), ("/result/components/1/certified", json!(true))] {
let mut forged = body.clone();
*forged.pointer_mut(path).unwrap() = v;
assert!(!crate::engine::job::verify(&forged).unwrap().0, "{path}");
}
let mut dropped = body.clone();
dropped["result"].as_object_mut().unwrap().remove("components");
assert!(!crate::engine::job::verify(&dropped).unwrap().0);
// It follows the result's schema.
let errs = crate::engine::schema::Registry::engine().check("job.v1.json", &body);
assert!(errs.is_empty(), "{errs:?}");
}
/// Registration is refused with its code, and a built-in's name is never
/// taken.
#[test]
fn registration_refuses_what_it_cannot_hold() {
let mut h = host();
for (c, needle) in [
(Component::method(BasisTable), "a registered method holds that name"),
(Component::method(Rogue("exact")), "a built-in method holds that name"),
(Component::method(Rogue("auto")), "auto is the engine's choice"),
(Component::method(Rogue("bad name")), "a name is 1 to 64"),
(Component::pass(DropLast(Check::Fidelity { max_distance: f64::NAN })), "a finite distance"),
(Component::front_end(GateList), "a registered front end holds that name"),
(Component::channel(BitFlip), "a registered channel holds that name"),
] {
let e = h.register(c).unwrap_err();
assert!(e.ends_with("[J063]") && e.contains(needle), "{e}");
}
assert_eq!(h.components()["registered"], 5);
let names: Vec<&str> = h.identities().iter().filter(|i| !i.is_builtin()).map(|i| i.name.as_str()).collect();
assert_eq!(names, ["basis_table", "cancel_pairs", "bit_flip", "zx", "gate_list"], "in the order registered, by kind");
}
/// A registered method that breaks the result's contract is refused, and
/// nothing it gave is returned.
#[test]
fn a_registered_method_s_result_is_held_to_the_contract() {
let mut h = Registry::with_builtins();
for n in ["rogue_method", "rogue_class", "rogue_plan"] {
h.register(Component::method(Rogue(n))).unwrap();
}
let job = |m: &str| json!({ "qasm": "OPENQASM 3.0;\nqubit[1] q;\nx q[0];\n", "shots": 1, "method": m });
for m in ["rogue_method", "rogue_class", "rogue_plan"] {
let e = h.plan_and_run(&job(m), CAP).unwrap_err();
assert_eq!(codes_in(&e).last().copied(), Some("J064"), "{m}: {e}");
}
}
/// A registered channel: its values read and checked, taken by the density
/// matrix through its Kraus operators, refused by the methods that take the
/// built-ins alone, and named in the result.
#[test]
fn a_registered_channel_runs_on_the_density_matrix_and_is_refused_elsewhere() {
let h = host();
// X then a bit flip: the qubit reads 0 with probability p.
let x = "OPENQASM 3.0;\nqubit q;\nx q;\n";
let (plan, out) = h.plan_and_run(&json!({ "qasm": x, "observables": ["Z0"], "noise": { "bit_flip": 0.2 } }), CAP).unwrap();
assert_eq!(plan.method, "density_matrix");
assert!((out["expectations"][0]["value"].as_f64().unwrap() - (-1.0 + 2.0 * 0.2)).abs() < 1e-12, "{out}");
let names: Vec<(&str, &str)> = out["components"].as_array().unwrap().iter().map(|c| (c["name"].as_str().unwrap(), c["origin"].as_str().unwrap())).collect();
assert_eq!(names, [("qasm", "built-in"), ("bit_flip", "registered"), ("density_matrix", "built-in")]);
// A channel of two parameters, given by name, against its closed form,
// beside a built-in channel: ⟨Z⟩ after x then Z and X flips and dephasing.
let (_, two) = h.plan_and_run(&json!({ "qasm": x, "observables": ["Z0"], "noise": { "zx": { "p": 0.1, "q": 0.15 }, "dephasing": 0.05 } }), CAP).unwrap();
assert!((two["expectations"][0]["value"].as_f64().unwrap() - (-1.0 + 2.0 * 0.15)).abs() < 1e-12, "{two}");
// The same channel as a built-in, registered under another name: the same numbers.
let mut d = host();
struct Dephase;
impl Channel for Dephase {
fn name(&self) -> &str {
"my_dephasing"
}
fn version(&self) -> &str {
"1"
}
fn title(&self) -> &str {
"dephasing"
}
fn descriptor(&self) -> Value {
json!({})
}
fn params(&self) -> Vec<Param> {
vec![Param::new("p", 0.0, 1.0, "a probability")]
}
fn arity(&self) -> u32 {
1
}
fn kraus(&self, v: &[f64]) -> Vec<Vec<C>> {
let (a, b) = ((1.0 - v[0]).sqrt(), v[0].sqrt());
vec![vec![one(a), one(0.0), one(0.0), one(a)], vec![one(b), one(0.0), one(0.0), one(-b)]]
}
}
d.register(Component::channel(Dephase)).unwrap();
let ghz = "OPENQASM 3.0;\nqubit[3] q;\nbit[3] c;\nh q[0];\ncx q[0], q[1];\nry(0.4) q[2];\ncx q[1], q[2];\nc = measure q;\n";
let obs = json!(["X0 X1", "Z1 Z2", "Y0 Y1 X2"]);
let mine = d.plan_and_run(&json!({ "qasm": ghz, "observables": obs, "noise": { "my_dephasing": 0.07 } }), CAP).unwrap().1;
let built = d.plan_and_run(&json!({ "qasm": ghz, "observables": obs, "noise": { "dephasing": 0.07 } }), CAP).unwrap().1;
for k in 0..3 {
let (a, b) = (mine["expectations"][k]["value"].as_f64().unwrap(), built["expectations"][k]["value"].as_f64().unwrap());
assert!((a - b).abs() < 1e-14, "{k}: {a} against {b}");
}
// Refused, with J061, by the methods that take the built-in channels alone.
for m in ["stabilizer", "trajectories"] {
let e = h.plan(&json!({ "qasm": x, "shots": 10, "method": m, "noise": { "bit_flip": 0.1 } }), CAP).unwrap_err();
assert!(e.ends_with("[J061]") && e.contains("taken by density_matrix"), "{m}: {e}");
}
let e = h.plan(&json!({ "qasm": x, "shots": 10, "method": "exact", "noise": { "bit_flip": 0.1 } }), CAP).unwrap_err();
assert!(e.ends_with("[J061]") && e.contains("exact does not model noise"), "{e}");
// Its values read: a value outside its range, a parameter it lacks or
// does not have, a value of another kind (J003); and an intent beside it
// (J065), which the intent checks do not model.
for (noise, code) in [(json!({ "bit_flip": 1.5 }), "J003"), (json!({ "zx": { "p": 0.1 } }), "J003"), (json!({ "zx": { "p": 0.1, "q": 0.1, "r": 0.0 } }), "J003"), (json!({ "bit_flip": "high" }), "J003")] {
let e = h.plan(&json!({ "qasm": x, "shots": 10, "noise": noise }), CAP).unwrap_err();
assert_eq!(codes_in(&e).last().copied(), Some(code), "{noise}: {e}");
}
let measured = "OPENQASM 3.0;\nqubit q;\nbit c;\nx q;\nc = measure q;\n";
let e = h.plan(&json!({ "qasm": measured, "shots": 10, "noise": { "bit_flip": 0.1 }, "intent": { "distribution": { "0": 0.1, "1": 0.9 } } }), CAP).unwrap_err();
assert!(e.ends_with("[J065]"), "{e}");
// A channel whose operators are not a channel is refused at the job's values.
let mut l = Registry::with_builtins();
l.register(Component::channel(TwoParam { broken: true })).unwrap();
let e = l.plan(&json!({ "qasm": x, "shots": 10, "noise": { "leaky": { "p": 0.1, "q": 0.1 } } }), CAP).unwrap_err();
assert!(e.ends_with("[J062]") && e.contains("not trace preserving"), "{e}");
// And it is unknown where it is not registered.
let e = Registry::builtins().plan(&json!({ "qasm": x, "shots": 10, "noise": { "bit_flip": 0.1 } }), CAP).unwrap_err();
assert!(e.ends_with("[J003]") && e.contains("no such channel"), "{e}");
// Amplitude damping on the stabilizer's frames is refused with the same code.
let e = Registry::builtins().plan(&json!({ "qasm": "OPENQASM 3.0;\nqubit q;\nh q;\n", "shots": 10, "method": "stabilizer", "noise": { "amplitude_damping": 0.1 } }), CAP).unwrap_err();
assert!(e.ends_with("[J061]") && e.contains("not amplitude damping"), "{e}");
}
/// A registered pass runs where the built-ins run, after the level's own,
/// checked by the engine by the check it declares; a wrong one is rejected
/// and the program goes on as it was.
#[test]
fn a_registered_pass_is_checked_by_the_engine() {
let mut h = host();
h.register(Component::pass(DropLast(Check::Ladder))).unwrap();
h.register(Component::pass(DropLast(Check::Fidelity { max_distance: 1e-3 }))).unwrap();
let src = "OPENQASM 3.0;\nqubit[2] q;\nh q[0];\nx q[1];\nx q[1];\ncx q[0], q[1];\nt q[1];\n";
let req = |passes: Value| json!({ "qasm": src, "level": 0, "basis": "standard", "passes": passes });
let good = h.compile_call(&req(json!(["cancel_pairs"]))).unwrap();
let records = good["certificate"]["passes"].as_array().unwrap();
let mine = records.iter().find(|r| r["pass"] == "cancel_pairs").unwrap();
assert_eq!((mine["check"]["verdict"].as_str(), mine["check"]["by"].as_str(), mine["component"]["origin"].as_str()), (Some("exact"), Some("engine"), Some("registered")), "every basis state: {mine}");
assert_eq!(mine["check"]["said"]["how"], "pairs dropped");
assert!(records.iter().filter(|r| r["pass"] != "cancel_pairs").all(|r| r.get("component").is_none()), "a built-in's record is as it was");
assert!(!good["qasm"].as_str().unwrap().contains("x q[1]"), "{}", good["qasm"]);
assert_eq!(good["components"].as_array().unwrap().iter().map(|c| c["name"].as_str().unwrap()).collect::<Vec<_>>(), ["qasm", "lower", "cancel_pairs"]);
// Wrong, and caught by the ladder or by its distance: left out.
for name in ["drop_last", "drop_last_fidelity"] {
let bad = h.compile_call(&req(json!([name]))).unwrap();
let r = bad["certificate"]["passes"].as_array().unwrap().iter().find(|r| r["pass"] == name).unwrap().clone();
assert_eq!(r["check"]["verdict"], "rejected", "{name}: {r}");
assert!(bad["qasm"].as_str().unwrap().contains("t q[1]"), "{name}: the program went on as it was");
}
// The same passes as a job's compile, the result naming them.
let (_, out) = h.plan_and_run(&json!({ "qasm": src, "shots": 20, "compile": { "level": 0, "basis": "standard", "passes": ["cancel_pairs"] } }), CAP).unwrap();
assert!(out["components"].as_array().unwrap().iter().any(|c| c["name"] == "cancel_pairs" && c["origin"] == "registered"), "{out}");
// A pass this host does not hold is refused, here and in the built-ins.
let e = h.compile_call(&req(json!(["no_such_pass"]))).unwrap_err();
assert!(e.ends_with("[J060]") && e.contains("the passes here are"), "{e}");
let e = Registry::builtins().plan(&json!({ "qasm": src, "shots": 1, "compile": { "passes": ["cancel_pairs"] } }), CAP).unwrap_err();
assert!(e.ends_with("[J060]"), "{e}");
// A built-in pass named in `passes` runs through the same path.
let extra = Registry::builtins().compile_call(&json!({ "qasm": src, "level": 0, "basis": "standard", "passes": ["simplify"] })).unwrap();
assert!(extra.get("components").is_none() && extra["certificate"]["passes"].as_array().unwrap().iter().any(|r| r["pass"] == "simplify"));
}
/// A registered front end reads and writes through the conversions and a
/// job's source, each checked, and is named in what it made.
#[test]
fn a_registered_front_end_converts_and_feeds_a_job() {
let h = host();
let list = "qubits 2\nh 0\ncx 0 1\nmeasure\n";
let to_qir = h.convert_json(&json!({ "from": "gate_list", "to": "qir", "program": list })).unwrap();
assert_eq!(to_qir["check"]["verdict"]["verdict"], "equivalent", "{to_qir}");
assert_eq!(to_qir["components"].as_array().unwrap().iter().map(|c| c["origin"].as_str().unwrap()).collect::<Vec<_>>(), ["registered", "built-in"]);
let back = h.convert_json(&json!({ "from": "qasm", "to": "gate_list", "program": to_qir["qasm"] })).unwrap();
assert_eq!(back["program"], list, "{back}");
assert_eq!(back["check"]["verdict"]["verdict"], "equivalent", "{back}");
let to_qasm = h.convert_json(&json!({ "from": "gate_list", "to": "qasm", "program": list })).unwrap();
assert_eq!(to_qasm["check"]["verdict"]["verdict"], "equivalent", "written back as a gate list and read again: {to_qasm}");
// A job whose source is a gate list.
let job = h.expand(&json!({ "source": { "format": "gate_list", "program": list }, "shots": 100 })).unwrap().unwrap();
let (_, out) = h.plan_and_run(&job, CAP).unwrap();
assert!(out["counts"].as_object().unwrap().keys().all(|k| k == "00" || k == "11"), "{out}");
assert_eq!(out["components"][0]["name"], "gate_list");
// Refused: a program it cannot write, and the format where it is not registered.
let e = h.convert_json(&json!({ "from": "qasm", "to": "gate_list", "program": "OPENQASM 3.0;\nqubit q;\nrx(0.3) q;\n" })).unwrap_err();
assert!(e.ends_with("[Q720]"), "{e}");
let e = Registry::builtins().convert_json(&json!({ "from": "gate_list", "to": "qasm", "program": list })).unwrap_err();
assert!(e.ends_with("[J003]") && e.contains("the formats are qasm, qir, jeff"), "{e}");
// A profile is QIR's, as it always said.
let e = h.convert_json(&json!({ "from": "qasm", "to": "gate_list", "profile": "base", "program": "OPENQASM 3.0;\nqubit q;\nh q;\n" })).unwrap_err();
assert!(e.contains("a profile is QIR's; gate_list takes none"), "{e}");
}

View file

@ -3,6 +3,7 @@
//! first; an amplitude's bit string names every qubit, qubit `n − 1` first.
use crate::engine::catalog::{self, MAX_OUTCOMES, Plan, Request};
use crate::engine::registry::{Job, Kind, Method, Registry};
use serde_json::{Value, json};
use std::collections::HashMap;
@ -116,16 +117,27 @@ impl Started {
/// it always gave. The result names the engine that made it and the schema
/// it follows ([`crate::engine::schema::stamp`]).
pub fn run(request: &Value, plan: &Plan) -> Result<Value, String> {
run_noting(request, plan, &mut None)
run_in(Registry::builtins(), request, plan)
}
fn run_noting(request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<Value, String> {
let mut out = run_job(request, plan, passes)?;
/// [`run`] through `reg`: its methods, channels, passes and front ends. A
/// result a registered component took part in carries `components`, the
/// identity of every component that made it.
pub(crate) fn run_in(reg: &Registry, request: &Value, plan: &Plan) -> Result<Value, String> {
run_noting(reg, request, plan, &mut None)
}
fn run_noting(reg: &Registry, request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<Value, String> {
let mut out = run_job(reg, request, plan, passes)?;
// A pulse run carries its own verdict (engine::pulse).
if crate::engine::verdict::asked(request) && !crate::engine::pulse::names_pulse(request) {
out["verdict"] = crate::engine::verdict::of_run(request, &out, plan.memory_bytes)?;
if crate::engine::verdict::asked(request) && catalog::reads_own(reg, request).is_none() {
out["verdict"] = crate::engine::verdict::of_run_in(reg, request, &out, plan.memory_bytes)?;
}
crate::engine::schema::stamp(&mut out);
let used = reg.used(request, &out);
if used.iter().any(|i| !i.is_builtin()) {
out["components"] = used.iter().map(|i| i.to_json()).collect();
}
Ok(out)
}
@ -150,8 +162,13 @@ pub const PROVENANCE: &str = "wai.quantum.provenance/1";
/// identifier and times are the host's, outside the result);
/// - `software`: the engine's name and version, and the host's.
pub fn run_with_provenance(request: &Value, plan: &Plan, host: &str) -> Result<Value, String> {
run_with_provenance_in(Registry::builtins(), request, plan, host)
}
/// [`run_with_provenance`] through `reg`.
pub(crate) fn run_with_provenance_in(reg: &Registry, request: &Value, plan: &Plan, host: &str) -> Result<Value, String> {
let mut passes = None;
let mut out = run_noting(request, plan, &mut passes)?;
let mut out = run_noting(reg, request, plan, &mut passes)?;
out["provenance"] = provenance(request, plan, &out, passes, host);
Ok(out)
}
@ -207,10 +224,13 @@ pub fn provenance(request: &Value, plan: &Plan, result: &Value, passes: Option<V
})
}
fn run_job(request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<Value, String> {
// A pulse run is integrated by the pulse simulator (engine::pulse).
if crate::engine::pulse::names_pulse(request) {
return crate::engine::pulse::run(request, plan);
fn run_job(reg: &Registry, request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<Value, String> {
// A method that reads its job's program itself: a pulse run is integrated
// by the pulse simulator (engine::pulse).
if let Some(m) = catalog::reads_own(reg, request) {
let mut out = Value::Null;
m.run(&Job::new(reg, request, plan.memory_bytes, None, &[]), plan, &mut out)?;
return Ok(out);
}
// A job that names a device target runs as the device would run it.
#[cfg(feature = "quantum_receipt")]
@ -224,7 +244,7 @@ fn run_job(request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<V
if request.get("target").is_some_and(|t| !t.is_null()) {
return Err("this build has no device targets (the engine's quantum_receipt feature)".into());
}
let r = catalog::read(request)?;
let (r, uses) = catalog::read_in(reg, request)?;
passes.clone_from(&r.compile_passes);
let started = Started::now();
let n = r.program.n_qubits;
@ -239,13 +259,62 @@ fn run_job(request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<V
if let Some(x) = &r.repaired {
out["repaired"] = x.clone();
}
if let Some(steps) = &r.program.dynamic {
run_dynamic(&r, plan, steps, &mut out)?;
carried(&r, plan, &mut out);
started.stamp(&mut out);
return Ok(out);
let m = reg.method(&plan.method).ok_or_else(|| format!("unknown method {}", plan.method))?;
let job = Job::new(reg, request, plan.memory_bytes, Some(&r), &uses);
// A registered method's result is held to the contract: what the engine
// wrote stays as it wrote it, and its accuracy class is one it declared.
let registered = reg.identity(Kind::Method, m.name()).is_some_and(|i| !i.is_builtin());
let before = registered.then(|| out.clone());
m.run(&job, plan, &mut out)?;
if let Some(b) = before {
contract(m, &b, &out)?;
}
match plan.method.as_str() {
if r.program.dynamic.is_none() {
#[cfg(feature = "quantum_noise")]
if let Some(mit) = &r.mitigate {
let o = crate::engine::mitigate::Options::read(mit, r.seed)?;
let noise = r.noise.ok_or("mitigation needs the job's noise")?;
out["mitigation"] = crate::engine::mitigate::run(&r.program, &noise, &r.observables, &o, out.get("counts")).map_err(|e| crate::engine::refusal::tag(crate::engine::refusal::MITIGATE, e))?;
}
}
carried(&r, plan, &mut out);
started.stamp(&mut out);
Ok(out)
}
/// A registered method's result held to the engine's contract (`J064`): the
/// members the engine writes as it wrote them, and an accuracy class the
/// method declared.
fn contract(m: &dyn Method, before: &Value, out: &Value) -> Result<(), String> {
use crate::engine::refusal::{CONTRACT, tag};
let name = m.name();
if !out.is_object() {
return Err(tag(CONTRACT, format!("the method {name} gave a result that is not an object")));
}
for k in crate::engine::registry::ENGINE_MEMBERS {
if before.get(k) != out.get(k) {
return Err(tag(CONTRACT, format!("the method {name} wrote `{k}`, which the engine writes")));
}
}
let class = out.pointer("/accuracy/class").and_then(Value::as_str).unwrap_or("");
if !m.classes().iter().any(|c| c == class) {
return Err(tag(CONTRACT, format!("the method {name} gave the accuracy class {class:?}; it declared {:?}", m.classes())));
}
Ok(())
}
/// A built-in method's run (its registry entry's,
/// [`crate::engine::registry::Method::run`]): `method` carried out for the
/// job, its answers written into `out`. A program that measures in the
/// middle runs branch by branch.
pub(crate) fn run_builtin(method: &str, job: &Job<'_>, plan: &Plan, out: &mut Value) -> Result<(), String> {
let r = job.request()?;
let request = job.json();
if let Some(steps) = &r.program.dynamic {
return run_dynamic(r, plan, steps, out);
}
let n = r.program.n_qubits;
match method {
"exact" => {
let c = r.program.dyadic()?;
let sv = c.simulate().map_err(|e| e.to_string())?;
@ -259,10 +328,10 @@ fn run_job(request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<V
*map.entry(r.program.bitstring(|q| x >> q & 1 == 1)).or_insert(0) += k;
}
}
merge(&mut out, counts(map));
merge(out, counts(map));
}
if !r.observables.is_empty() {
out["expectations"] = expectation_entries(&r, |s| expect_fixed(&sv.amps, s));
out["expectations"] = expectation_entries(r, |s| expect_fixed(&sv.amps, s));
}
if !r.amplitudes.is_empty() {
let scale = (1u64 << 30) as f64;
@ -293,10 +362,10 @@ fn run_job(request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<V
for x in sv.sample(r.shots as usize, r.seed) {
*map.entry(r.program.bitstring(|q| x >> (n - 1 - q) & 1 == 1)).or_insert(0) += 1;
}
merge(&mut out, counts(map));
merge(out, counts(map));
}
if !r.observables.is_empty() {
out["expectations"] = expectation_entries(&r, |s| sv.expectation(&[(1.0, s.to_vec())]));
out["expectations"] = expectation_entries(r, |s| sv.expectation(&[(1.0, s.to_vec())]));
}
if !r.amplitudes.is_empty() {
out["amplitudes"] = Value::Array(
@ -315,7 +384,7 @@ fn run_job(request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<V
let width = r.options.get("fusion").and_then(Value::as_u64).unwrap_or(3).clamp(1, 5) as usize;
// On the CPU, or on the fabric's member the job's placement
// names: the CPU's bits either way.
let (sv, placed) = crate::engine::fabric::statevector(request, n, &gates, width, crate::engine::fabric::widest_of(&r))?;
let (sv, placed) = crate::engine::fabric::statevector(request, n, &gates, width, crate::engine::fabric::widest_of(r))?;
out["accuracy"] = json!({ "class": "exact", "fusion": width });
if let Some(f) = placed {
out["fabric"] = f;
@ -325,10 +394,10 @@ fn run_job(request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<V
for x in sv.sample(r.shots as usize, r.seed) {
*map.entry(r.program.bitstring(|q| x >> (n - 1 - q) & 1 == 1)).or_insert(0) += 1;
}
merge(&mut out, counts(map));
merge(out, counts(map));
}
if !r.observables.is_empty() {
out["expectations"] = expectation_entries(&r, |s| sv.expectation(&[(1.0, s.to_vec())]));
out["expectations"] = expectation_entries(r, |s| sv.expectation(&[(1.0, s.to_vec())]));
}
if !r.amplitudes.is_empty() {
out["amplitudes"] = Value::Array(
@ -363,7 +432,7 @@ fn run_job(request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<V
}
}
let threads = std::thread::available_parallelism().map_or(1, |t| t.get()).min(8);
let frame_shots = catalog::frame_expectation_shots(&r);
let frame_shots = catalog::frame_expectation_shots(r);
out["accuracy"] = json!({
"class": "estimate",
"shots": frame_shots,
@ -382,7 +451,7 @@ fn run_job(request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<V
}
*map.entry(String::from_utf8(s).expect("ASCII")).or_insert(0) += k;
}
merge(&mut out, counts(map));
merge(out, counts(map));
}
if !r.observables.is_empty() {
let strings: Vec<Vec<(u32, char)>> = r.observables.iter().map(|(_, _, s)| s.clone()).collect();
@ -415,7 +484,7 @@ fn run_job(request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<V
}
*map.entry(String::from_utf8(s).expect("ASCII")).or_insert(0) += k;
}
merge(&mut out, counts(map));
merge(out, counts(map));
out["outcome_dimension"] = json!(support.dimension());
}
if !r.observables.is_empty() {
@ -485,7 +554,7 @@ fn run_job(request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<V
}
"mps" => {
use crate::quantum_mpsim::{Chain, MpsConfig};
let chi = catalog::mps_bond(&r);
let chi = catalog::mps_bond(r);
let cutoff = r.options.get("cutoff").and_then(Value::as_f64).unwrap_or(1e-12).clamp(0.0, 1e-2);
let mut chain = Chain::run(n, &r.program.local_matrices(), MpsConfig { max_bond: chi, cutoff }).map_err(|e| e.to_string())?;
let rep = chain.report();
@ -502,7 +571,7 @@ fn run_job(request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<V
for bits in chain.sample(r.shots, r.seed) {
*map.entry(r.program.bitstring(|q| bits[q as usize] == 1)).or_insert(0) += 1;
}
merge(&mut out, counts(map));
merge(out, counts(map));
}
if !r.observables.is_empty() {
let mut entries = Vec::new();
@ -527,7 +596,7 @@ fn run_job(request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<V
#[cfg(feature = "quantum_cutrank_metal")]
"cutrank_single" => {
use crate::quantum_cutrank_metal::GpuCutRank;
let mut pm = catalog::product_measured(&r, true)?;
let mut pm = catalog::product_measured(r, true)?;
let cut = pm.max_cut();
// The batch the plan's memory holds, the first table aside.
let one = catalog::cutrank_bytes(&pm, crate::quantum_cutrank::Precision::Single);
@ -546,10 +615,10 @@ fn run_job(request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<V
for (bits, _) in gpu.sample(&pm, r.shots as usize, r.seed).map_err(|e| e.to_string())? {
*map.entry(r.program.bitstring(|q| bits[q as usize])).or_insert(0) += 1;
}
merge(&mut out, counts(map));
merge(out, counts(map));
}
if !r.amplitudes.is_empty() {
let fixed = catalog::cutrank_phase_with(&r, &pm, true, &mut |p, x| gpu.amplitude(p, x).map_err(|e| e.to_string()))?;
let fixed = catalog::cutrank_phase_with(r, &pm, true, &mut |p, x| gpu.amplitude(p, x).map_err(|e| e.to_string()))?;
if let Some(kappa) = fixed {
pm.scale(kappa);
}
@ -569,7 +638,7 @@ fn run_job(request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<V
use crate::quantum_cutrank::Precision;
// An order at least as cheap as the planner's (searched longer, from
// several seeds), and the precision the admitted memory allows.
let pm = catalog::product_measured(&r, true)?;
let pm = catalog::product_measured(r, true)?;
let precision = if plan.memory_bytes >= catalog::cutrank_bytes(&pm, Precision::Double) { Precision::Double } else { Precision::Single };
let cut = pm.max_cut();
out["accuracy"] = json!({
@ -583,14 +652,14 @@ fn run_job(request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<V
for (bits, _) in pm.sample_in(r.shots as usize, r.seed, cut, precision).map_err(|e| e.to_string())? {
*map.entry(r.program.bitstring(|q| bits[q as usize])).or_insert(0) += 1;
}
merge(&mut out, counts(map));
merge(out, counts(map));
}
if !r.amplitudes.is_empty() {
// The circuit's global phase: the lowering's own, times the
// ratio of one exactly tracked amplitude of the Clifford part
// to the graph form's at the same basis state.
let mut pm = pm;
let fixed = catalog::cutrank_phase(&r, &pm, cut, precision)?;
let fixed = catalog::cutrank_phase(r, &pm, cut, precision)?;
if let Some(kappa) = fixed {
pm.scale(kappa);
}
@ -610,7 +679,7 @@ fn run_job(request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<V
use crate::quantum_stabrank::{amplitudes as exact_sums, exact_scale, sampled, sampled_bound, sampled_scale};
let c = r.program.clifford_t()?;
let t = c.t_count();
let mode = catalog::stabilizer_rank_mode(&r, t)?;
let mode = catalog::stabilizer_rank_mode(r, t)?;
let threads = if cfg!(target_arch = "wasm32") { 1 } else { std::thread::available_parallelism().map_or(1, |t| t.get()).min(16) };
// Every branch, or `terms` drawn: the sums and the scale they are read at.
let sums = |c: &crate::quantum_stabrank::CliffordT, xs: &[Vec<bool>], seed: u64| -> Result<(Vec<(f64, f64)>, Option<f64>), String> {
@ -689,7 +758,14 @@ fn run_job(request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<V
"density_matrix" => {
use crate::quantum_noisy::DensityMatrix;
let noise = r.noise.ok_or("no noise model")?;
let rho = DensityMatrix::evolve(&r.program.matrices(), n, &noise).map_err(|e| format!("{e:?}"))?;
// A registered channel joins the built-ins' after each gate, as its
// Kraus operators; without one, the noise model's own passes.
let registered: Vec<_> = job.registered_channels().collect();
let rho = if registered.is_empty() {
DensityMatrix::evolve(&r.program.matrices(), n, &noise).map_err(|e| format!("{e:?}"))?
} else {
DensityMatrix::evolve_steps(n, &noisy_steps(&r.program.matrices(), &noise, &registered)).map_err(|e| format!("{e:?}"))?
};
out["accuracy"] = json!({ "class": "exact", "note": "the noisy state itself, under the model; counts are seeded samples of its distribution" });
if r.shots > 0 {
let p = rho.probabilities(noise.readout);
@ -697,16 +773,16 @@ fn run_job(request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<V
for x in sample_distribution(&p, r.shots, r.seed) {
*map.entry(r.program.bitstring(|q| x >> (n - 1 - q) & 1 == 1)).or_insert(0) += 1;
}
merge(&mut out, counts(map));
merge(out, counts(map));
}
if !r.observables.is_empty() {
out["expectations"] = expectation_entries(&r, |s| rho.expectation(&[(1.0, s.to_vec())]));
out["expectations"] = expectation_entries(r, |s| rho.expectation(&[(1.0, s.to_vec())]));
}
}
"trajectories" => {
use crate::quantum_noisy::branching_trajectories;
let noise = r.noise.ok_or("no noise model")?;
let count = catalog::trajectory_count(&r);
let count = catalog::trajectory_count(r);
// The plan reserved memory for this many states.
let states = ((plan.memory_bytes.saturating_sub(256 << 20)) / (catalog::amplitude_bytes(n) * if n < 16 { 8 } else { 1 }).max(1)).clamp(1, 16) as usize;
let threads = std::thread::available_parallelism().map_or(1, |t| t.get()).min(8);
@ -733,21 +809,13 @@ fn run_job(request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<V
for &(x, k) in &run.counts {
*map.entry(r.program.bitstring(|q| x >> (n - 1 - q) & 1 == 1)).or_insert(0) += k;
}
merge(&mut out, counts(map));
merge(out, counts(map));
out["shots"] = json!(count);
}
}
other => return Err(format!("unknown method {other}")),
}
#[cfg(feature = "quantum_noise")]
if let Some(m) = &r.mitigate {
let o = crate::engine::mitigate::Options::read(m, r.seed)?;
let noise = r.noise.ok_or("mitigation needs the job's noise")?;
out["mitigation"] = crate::engine::mitigate::run(&r.program, &noise, &r.observables, &o, out.get("counts")).map_err(|e| crate::engine::refusal::tag(crate::engine::refusal::MITIGATE, e))?;
}
carried(&r, plan, &mut out);
started.stamp(&mut out);
Ok(out)
Ok(())
}
/// What the program carries beyond its gates, in the result: its `output`s
@ -832,6 +900,49 @@ fn run_dynamic(r: &catalog::Request, plan: &Plan, steps: &[crate::qasm::Instr],
Ok(())
}
/// The steps of `gates` under `noise` and the registered channels
/// `registered`: each gate, then the built-in channels in the order the
/// density matrix applies them ([`crate::quantum_noisy::DensityMatrix::evolve`]),
/// then each registered channel, a one-qubit one on each qubit the gate
/// touched and a two-qubit one on a two-qubit gate's pair.
fn noisy_steps(gates: &[crate::gates::Gate], noise: &crate::quantum_noisy::NoiseModel, registered: &[&crate::engine::registry::ChannelUse<'_>]) -> Vec<crate::quantum_noisy::Step> {
use crate::gates::Gate;
use crate::quantum_noisy::{Step, damping_kraus, pauli_gate, pauli_pair};
let mut steps = Vec::with_capacity(gates.len() * 4);
for g in gates {
steps.push(Step::Gate(g.clone()));
let k = g.qubits.len();
if k == 2 && noise.depol2 > 0.0 {
let (a, b) = (g.qubits[0], g.qubits[1]);
steps.push(Step::Channel((0..16u8).map(|j| (if j == 0 { 1.0 - noise.depol2 } else { noise.depol2 / 15.0 }, pauli_pair(a, b, j))).collect()));
}
for &q in &g.qubits {
if k != 2 && noise.depol1 > 0.0 {
steps.push(Step::Channel((0..4u8).map(|j| (if j == 0 { 1.0 - noise.depol1 } else { noise.depol1 / 3.0 }, pauli_gate(q, j))).collect()));
}
if noise.dephasing > 0.0 {
steps.push(Step::Channel(vec![(1.0 - noise.dephasing, pauli_gate(q, 0)), (noise.dephasing, pauli_gate(q, 3))]));
}
if noise.damping > 0.0 {
let [k0, k1] = damping_kraus(q, noise.damping);
steps.push(Step::Channel(vec![(1.0, k0), (1.0, k1)]));
}
}
for u in registered {
let places: Vec<Vec<u32>> = match u.channel.arity() {
1 => g.qubits.iter().map(|&q| vec![q]).collect(),
_ if k == 2 => vec![g.qubits.clone()],
_ => Vec::new(),
};
let ks = u.channel.kraus(&u.values);
for at in places {
steps.push(Step::Channel(ks.iter().map(|m| (1.0, Gate { qubits: at.clone(), matrix: m.clone() })).collect()));
}
}
}
steps
}
pub(crate) fn merge(out: &mut Value, extra: Value) {
if let (Some(o), Some(e)) = (out.as_object_mut(), extra.as_object()) {
for (k, v) in e {

View file

@ -291,12 +291,17 @@ pub fn fit(intents: &[Value], program: &Program) -> Result<(), String> {
/// (`n` qubits) takes, when each fits in `job_bytes`: what a plan admits
/// beside the method's own. A sampled intent's run is planned as the job's.
pub fn intent_memory(request: &Value, intents: &[Value], n: u32, noisy: bool, job_bytes: u64) -> u64 {
intent_memory_in(super::registry::Registry::builtins(), request, intents, n, noisy, job_bytes)
}
/// [`intent_memory`], a sampled intent's run planned through `reg`.
pub(crate) fn intent_memory_in(reg: &super::registry::Registry, request: &Value, intents: &[Value], n: u32, noisy: bool, job_bytes: u64) -> u64 {
intents
.iter()
.map(|i| {
let need = if i.get("sampled").is_some() {
let src = request.get("qasm").and_then(Value::as_str).unwrap_or("");
catalog::plan(&sampled_request(i, request, src), job_bytes).map(|p| p.memory_bytes).unwrap_or(0)
reg.plan(&sampled_request(i, request, src), job_bytes).map(|p| p.memory_bytes).unwrap_or(0)
} else if i.get("distribution").is_some() {
if noisy { catalog::statevector_bytes(2 * n) } else if n <= DISTRIBUTION_MAX_QUBITS { catalog::statevector_bytes(n) } else { 0 }
} else {
@ -338,9 +343,14 @@ fn sampled_request(intent: &Value, job: &Value, src: &str) -> Value {
/// "alpha", "fit"}`, with the counts (up to [`MAX_LISTED`] outcomes). A
/// statistical test is evidence, never proof.
pub fn check_intent_for(intent: &Value, job: &Value, program: &Program, cap: u64) -> Result<Value, String> {
check_intent_for_in(super::registry::Registry::builtins(), intent, job, program, cap)
}
/// [`check_intent_for`], a sampled intent's run planned and run through `reg`.
pub(crate) fn check_intent_for_in(reg: &super::registry::Registry, intent: &Value, job: &Value, program: &Program, cap: u64) -> Result<Value, String> {
let kind = shape(intent).map_err(bad)?;
if kind != "sampled" {
let noise = catalog::noise_of(job)?;
let noise = catalog::noise_in(reg, job)?.0;
return check_intent(intent, program, noise.as_ref(), cap);
}
fit(std::slice::from_ref(intent), program)?;
@ -358,7 +368,7 @@ pub fn check_intent_for(intent: &Value, job: &Value, program: &Program, cap: u64
out["why"] = json!(why);
Ok(out)
};
let plan = match catalog::plan(&req, cap) {
let plan = match reg.plan(&req, cap) {
Ok(p) => p,
Err(e) => return unchecked(out, format!("the run the test samples does not run in this job: {e}")),
};
@ -366,7 +376,7 @@ pub fn check_intent_for(intent: &Value, job: &Value, program: &Program, cap: u64
if plan.memory_bytes > cap {
return unchecked(out, format!("the run the test samples needs about {}, more than this job was admitted with ({})", mib(plan.memory_bytes), mib(cap)));
}
let result = super::run::run(&req, &plan)?;
let result = reg.run(&req, &plan)?;
if result["omitted_shots"].as_u64().unwrap_or(0) > 0 {
return unchecked(out, "the run has more distinct outcomes than its counts list, so the test cannot see them all".into());
}
@ -645,6 +655,11 @@ fn base(parsed: Value) -> Value {
/// request's intents checked against the program it ran (the repaired one,
/// before any compile) within `cap` bytes, the job's admitted memory.
pub fn of_run(request: &Value, result: &Value, cap: u64) -> Result<Value, String> {
of_run_in(super::registry::Registry::builtins(), request, result, cap)
}
/// [`of_run`] for a job run through `reg`.
pub(crate) fn of_run_in(reg: &super::registry::Registry, request: &Value, result: &Value, cap: u64) -> Result<Value, String> {
let given = request.get("qasm").and_then(Value::as_str).ok_or_else(|| refusal::tag(refusal::NOT_A_JOB, "a job needs \"qasm\""))?;
// The program as it ran: its parameters bound (catalog::bind_params),
// then repaired when the job asked (the repair is of the bound text).
@ -665,7 +680,7 @@ pub fn of_run(request: &Value, result: &Value, cap: u64) -> Result<Value, String
if let Some(o) = ran.as_object_mut() {
o.remove("params");
}
v["intent"] = intents.iter().map(|i| check_intent_for(i, &ran, &reading.program, cap)).collect::<Result<Vec<_>, _>>()?.into();
v["intent"] = intents.iter().map(|i| check_intent_for_in(reg, i, &ran, &reading.program, cap)).collect::<Result<Vec<_>, _>>()?.into();
v["critique"] = smells(src);
if let Some(r) = result.pointer("/repaired/repairs") {
v["repairs"] = r.clone();

View file

@ -246,7 +246,7 @@ fn pauli(q: u32, p: u8) -> Gate {
}
/// The two Kraus operators of amplitude damping.
fn damping_kraus(q: u32, gamma: f64) -> [Gate; 2] {
pub(crate) fn damping_kraus(q: u32, gamma: f64) -> [Gate; 2] {
let (z, o) = (complex(0.0, 0.0), complex(1.0, 0.0));
[
Gate { qubits: vec![q], matrix: vec![o, z, z, complex((1.0 - gamma).sqrt(), 0.0)] },