Some checks failed
WAI operation catalogue / does this change touch it (push) Successful in 15s
wai-quantum docs, course and locales / does this change touch it (push) Successful in 26s
wai-quantum editors / does this change touch it (push) Successful in 28s
wai-quantum clippy / does this change touch it (push) Successful in 32s
wai-quantum pulse and dynamics / does this change touch it (push) Successful in 14s
wai-quantum for Python / does this change touch it (push) Successful in 21s
wai-quantum error correction and mitigation / does this change touch it (push) Successful in 18s
wai-quantum qudit jobs / does this change touch it (push) Successful in 17s
wai-quantum extension points / does this change touch it (push) Successful in 45s
wai-quantum service / does this change touch it (push) Successful in 40s
wai-quantum variational and algorithm library / does this change touch it (push) Successful in 1m51s
wai-quantum verdicts and repairs / does this change touch it (push) Successful in 1m28s
wai-quantum views in agent hosts / does this change touch it (push) Successful in 1m22s
wai-quantum studio and desktop / does this change touch it (push) Successful in 1m53s
wai-quantum on Windows / does this change touch it (push) Successful in 1m9s
WAI Studio / does this change touch it (push) Successful in 1m6s
wai-sink-android native sink library / does this change touch it (push) Successful in 1m11s
wai-sink-swift native sink package / does this change touch it (push) Successful in 1m9s
wai-quantum service / service energy · linux packages (push) Successful in 4m27s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 35s
WAI examples / WAI examples · does this change touch them (push) Successful in 25s
wai-quantum landscape energy to solution / does this change touch it (push) Successful in 25s
wai-quantum landscape stage 0, the sampler against the exact law / does this change touch it (push) Successful in 27s
wai-quantum energy-landscape jobs / does this change touch it (push) Successful in 31s
wai-quantum OpenQASM reader / does this change touch it (push) Successful in 9m22s
wai-quantum-landscape-fpga reference design / does this change touch it (push) Successful in 9m52s
wai-quantum resource estimation / does this change touch it (push) Successful in 9m59s
wai-quantum command line / does this change touch it (push) Successful in 10m12s
wai-quantum for JavaScript / does this change touch it (push) Successful in 9m56s
wai-quantum other formats / does this change touch it (push) Successful in 9m56s
wai-audit collector and player integration / does this change touch it (push) Successful in 10m27s
wai-quantum contract / does this change touch it (push) Successful in 10m11s
wai-quantum docs, course and locales / the Studio's words in every locale · linux (push) Successful in 28s
wai-quantum docs, course and locales / the course and the command line's languages · linux (push) Successful in 4m52s
wai-quantum editors / editors · linux (push) Successful in 5m58s
wai-quantum pulse and dynamics / reader, simulator and pulse jobs under wasm32-wasip2 (wasmtime) (push) Successful in 2m17s
wai-quantum wasmQ / does this change touch it (push) Successful in 47s
wai-quantum pulse and dynamics / reader, simulator and pulse jobs · linux, release (push) Successful in 4m10s
wai-quantum clippy / clippy, warnings denied, every feature set · linux (push) Successful in 6m4s
wai-quantum qudit jobs / the module and the conformance set under wasm32-wasip2 (wasmtime) (push) Successful in 2m25s
wai-quantum for Python / python · linux (push) Successful in 8m49s
wai-quantum extension points / the registry under wasm32-wasip2 (wasmtime) (push) Successful in 3m53s
wai-quantum qudit jobs / the module, the job, the conformance set, the referee and the verb · linux (push) Successful in 6m58s
wai-quantum error correction and mitigation / codes, decoders, texts and mitigation · linux, release (push) Successful in 5m56s
WAI operation catalogue / the catalogue and every door · linux (push) Successful in 17m40s
wai-quantum verdicts and repairs / verdicts, repairs and the intent set · linux (push) Successful in 6m11s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 1m52s
wai-quantum extension points / the registry and the example · linux (push) Successful in 8m25s
wai-quantum verdicts and repairs / the intent set and the verdict under wasm32-wasip2 (wasmtime) (push) Successful in 4m36s
wai-quantum landscape stage 0, the sampler against the exact law / the small tier, to the byte · linux (push) Successful in 1m1s
wai-quantum variational and algorithm library / parameters, gradients, optimisers, VQE, QAOA and builders · linux, release (push) Successful in 8m27s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 4m4s
wai-quantum landscape energy to solution / the small tier, to the byte · linux (push) Successful in 2m49s
wai-quantum OpenQASM reader / corpus under wasm32-wasip2 (wasmtime) (push) Successful in 1m33s
wai-quantum views in agent hosts / views · a scripted host (push) Successful in 7m11s
wai-quantum service / service · linux (push) Failing after 12m32s
wai-quantum OpenQASM reader / reader and corpus · linux (push) Successful in 4m45s
wai-quantum command line / command line · linux (push) Failing after 3m28s
wai-quantum views in agent hosts / views · the page and its engine (push) Successful in 8m38s
wai-quantum studio and desktop / studio · browser (push) Successful in 8m56s
wai-quantum contract / the policy is linked, and the deprecations page reads (push) Successful in 10s
wai-quantum other formats / the set under wasm32-wasip2 (wasmtime) (push) Successful in 2m25s
wai-quantum docs, course and locales / every command and snippet in the docs · linux (push) Successful in 27m38s
wai-quantum studio and desktop / desktop app · linux (push) Successful in 11m41s
wai-audit collector and player integration / collector · linux (push) Successful in 4m9s
wai-quantum energy-landscape jobs / native against WebAssembly, over many seeds (push) Successful in 9m39s
wai-quantum other formats / readers, writers and the set · linux (push) Successful in 7m39s
wai-quantum contract / schemas and the committed tool lists · linux (push) Failing after 5m48s
wai-quantum resource estimation / estimation and calibration · linux, release (push) Successful in 11m42s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 16m48s
wai-quantum contract / API compatibility · linux (push) Successful in 10m28s
wai-quantum for JavaScript / javascript · linux (push) Successful in 12m12s
wai-quantum wasmQ / the reference modules rebuild to their modules of record · linux (push) Successful in 40s
wai-quantum energy-landscape jobs / reader, kernels, answers and the stated result · linux, release (push) Successful in 21m18s
wai-quantum energy-landscape jobs / the module under wasm32-wasip2 (wasmtime) (push) Successful in 21m30s
WAI examples / WAI examples · portable · linux (push) Successful in 23m6s
wai-quantum wasmQ / interpreter and vectors under wasm32-wasip2 (wasmtime) (push) Successful in 5m9s
wai-quantum wasmQ / profile, engines, reference modules, adversarial, vectors · linux (push) Successful in 8m23s
WAI Studio / Studio in WebKit · linux (push) Successful in 29m12s
wai-quantum-landscape-fpga reference design / the member against the model of the logic, and the logic against the model · linux, release (push) Successful in 43m29s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Failing after 9m43s
wai-quantum pulse and dynamics / the same bits · linux-arm64 (push) Successful in 10m28s
wai-quantum-landscape-fpga reference design / the board's code on the board's processor's architecture · linux-arm64, release (push) Successful in 2m11s
wai-quantum variational and algorithm library / parameters, gradients, optimisers, VQE, QAOA and builders · linux-arm64, release (push) Successful in 5m37s
wai-quantum extension points / the registry and the example · linux-arm64 (push) Successful in 11m39s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Successful in 9m40s
WAI examples / WAI examples · android-app · linux-arm64 (push) Successful in 14m47s
wai-quantum energy-landscape jobs / the same bits · linux-arm64 (push) Successful in 14m50s
wai-quantum editors / editors · macos (push) Successful in 1m57s
wai-quantum pulse and dynamics / the same bits · macos (push) Successful in 2m0s
wai-quantum for Python / python · macos (push) Successful in 3m37s
wai-quantum qudit jobs / the same bits · macos (push) Successful in 3m3s
wai-quantum service / service · macos (push) Failing after 5m34s
wai-quantum studio and desktop / desktop app · macos (push) Successful in 4m7s
wai-quantum landscape stage 0, the sampler against the exact law / the small tier, to the byte · macos (push) Successful in 26s
wai-quantum landscape energy to solution / the small tier, to the byte · macos (push) Successful in 1m37s
wai-quantum command line / command line · macos (push) Failing after 1m14s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Successful in 2m11s
wai-quantum energy-landscape jobs / the same bits · macos (push) Successful in 6m52s
WAI Studio / Studio in Chromium and Firefox · macos (push) Successful in 11m32s
wai-quantum wasmQ / profile, engines, reference modules, adversarial, vectors · macos (push) Successful in 2m10s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Successful in 19m56s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Successful in 25m32s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (push) Successful in 27m5s
wai-quantum studio and desktop / desktop app · windows (push) Successful in 14m37s
wai-quantum on Windows / engine · windows (push) Failing after 17m32s
wai-quantum on Windows / command line · windows (push) Failing after 5m35s
wai-quantum on Windows / job service · windows (push) Failing after 4m45s
wai-quantum on Windows / python · windows (push) Failing after 9m47s
The tracker's all-to-all peaked circuit P9_Hqap_56x1917 is read: its peak is the string the published solution (arXiv:2604.21908) checks its samples against, found in 139 s with 16 threads on a shared 32-core CPU host. The published run took an hour on a GPU and 734 s on a laptop CPU. It needed a dense SVD fast at bonds in the hundreds; the tensor networks' was scalar. quantum_svd (new module, with quantum_bptn): complex SVD, thin QR and product for matrices a few thousand on a side, our own code. Householder reduction to a real bidiagonal (a step's two updates sharing two passes over the trailing matrix; a matrix twice as tall as wide to its triangle first), the implicit-shift QR iteration of Demmel and Kahan with its rotations recorded and applied in panels of eight columns, the reflections back onto the kept vectors 32 at a time as I - V T V^H. Keep::Truncate forms only the kept vectors, Keep::Values the values alone. Every number is formed by one worker in an order the shape fixes, row sums go in blocks of 64 rows added in block order, no FMA, no library call: the same bits at any thread count and on arm64 and x86_64. examples/svd_bench.rs: one thread of a shared x86_64 host, 1.5 ms at 64^2, 59 ms at 256^2, 0.40 s at 512^2 (the Jacobi rotations 22 ms, 0.79 s, 5.8 s), 3.2 s at 1024^2, 28 s at 2048^2; the twelve matrices' hashes equal at 1, 4 and 16 threads and on macOS arm64 (tests/data/svd, tests/svd_bench.rs). quantum_peaked::chain (new): the published method in our own code. The blocks cut at half, those before on the operator's columns, those after on its rows, the side with fewer gates going next; each side routed onto a chain online by a lookahead heuristic; every two-site operation a TEBD update of the operator as a state of local dimension 4, truncated at a relative cutoff and a cap, the norm in an exact scale; past 10^6 numbers a SWAP of both indices, the columns or the rows tried on every bond and kept as a relabelling where it shrinks the bond. A layer's operations and a pass's trials run side by side on disjoint pairs, committed in order. route_both routes by whole sites, a qubit's row and column together: the published routing moves one side's index, and a row moved from its column costs a bond. Peak by exact conditional decoding, exact sampling, any string's amplitude. 56x1917 (tracker_peaked chain route=both): at most bond 512 and 975,352 numbers, 17,341 routed SWAPs, 813 kept by unswapping of 9,033 tried, 2.3e-4 discarded, 388 MB. Peak 01101110111001100000100000001010011100101101010111110111 (qubit 0 first), the published string, |<s|C|0>|^2 = 0.0999, 103 of 1000 exact samples (the published run: about 10.6%). chainverify at cutoffs 1e-3 and 5e-4: 0.09999. Routed as published: the same peak and 0.09996 in 762 s. Linux arm64 and macOS arm64 give every number of the x86_64 receipt, bit for bit. Tests: SVD reconstruction and orthonormality on full-rank, rank-deficient, graded, zero and extreme-scale matrices, the same bits across thread counts; chain amplitudes exact untruncated from any cut, a planted peak, a hidden permutation stripped by unswapping; tests/tracker_peaked.rs re-runs the 56x1917 operator's first 62 layers to the bits its receipt logs and checks the receipts; the P9 circuit kept as a fixture (unmodified, Apache-2.0). Co-Authored-By: Claude Opus 5.5 <[email protected]>
528 lines
23 KiB
Rust
528 lines
23 KiB
Rust
//! What the published-instance reproductions share: the pinned sources, a
|
||
//! SHA-256 check of every input read, the compaction of a device-wide register
|
||
//! to the qubits a circuit touches, the energy reading around a run, and the
|
||
//! signed receipt each run writes.
|
||
//!
|
||
//! The instances come from a public tracker of quantum-advantage candidates,
|
||
//! licensed Apache-2.0 (see `tracker/NOTICE` beside this file). Its circuits and
|
||
//! Hamiltonians are fetched by the reader at the commit below and checked
|
||
//! against the hashes here; only the small fixtures the tests need are kept in
|
||
//! this repository, under `tests/data/tracker/`, with the licence notice.
|
||
|
||
#![allow(dead_code)]
|
||
|
||
use std::path::Path;
|
||
use std::time::Instant;
|
||
|
||
use serde_json::{Value, json};
|
||
use wai_quantum::ed25519_dalek::SigningKey;
|
||
use wai_quantum::quantum_spd::{Op, Pauli, PauliString, RotCircuit};
|
||
|
||
/// The tracker's repository and the commit every input is read at.
|
||
pub const REPOSITORY: &str = "https://github.com/quantum-advantage-tracker/quantum-advantage-tracker.github.io"; // allow-name: a repository URL
|
||
pub const COMMIT: &str = "4d27f882d55028162206c4f12b5b6fc6a63868e7";
|
||
|
||
/// Every input a reproduction reads, by its path in the repository at
|
||
/// [`COMMIT`], and the SHA-256 of its bytes there.
|
||
pub const SOURCES: &[(&str, &str)] = &[
|
||
("LICENSE", "6a50c5923c736b5d160cc6dc8737ef6bb998f885bedfa2c036f6c0b0e6117848"),
|
||
("data/observable-estimations/submissions.json", "82258f5ee279eea6ba487df1f014bf3a3e3dcd2e0888cc5bc5a24e846a09f61c"),
|
||
("data/classically-verifiable-problems/submissions.json", "055846dbca163b516eb4a4e53b7d0a1d6f56d9bad5abf067bdef1f4752046992"),
|
||
("data/variational-problems/submissions.json", "639e7beade574e5e9a8ad259a93f30775021cb0950dd921c9b1d37199f837bcc"),
|
||
(
|
||
"data/observable-estimations/circuit-models/operator_loschmidt_echo/49Q_OLE_circuit_L_3_b_0.25_delta0.15.qasm",
|
||
"4aae5089337253921cd70df470dc52b5243e0c8410b36a6bcbb43497db48db29",
|
||
),
|
||
(
|
||
"data/observable-estimations/circuit-models/operator_loschmidt_echo/49Q_OLE_circuit_L_6_b_0.25_delta0.15.qasm",
|
||
"3748e2c026c118f9d6c7499093ea43e41a45251b6bf8d3adb6fb056f718f6cc0",
|
||
),
|
||
(
|
||
"data/observable-estimations/circuit-models/operator_loschmidt_echo/70Q_OLE_circuit_L_6_b_0.25_delta0.15.qasm",
|
||
"7bb359cd6a6270856819780f410336ecefe52f082cd14af2d95539a681bf9843",
|
||
),
|
||
("data/observable-estimations/circuit-models/su2_hadron_dynamics_lsh/x_100_meson.qasm", "28b3d82a2572dac60110c94a0f58a945a117d414e94446866e0d87cc5121b4f9"),
|
||
("data/observable-estimations/circuit-models/su2_hadron_dynamics_lsh/x_100_SCV.qasm", "af269dd7aeacfe6c9fef00bdab3bd1d0a641f5ee99be67c56ebf73a15791b6da"),
|
||
(
|
||
"data/classically-verifiable-problems/circuit-models/peaked_circuit/peaked_circuit_heavy_hex_49x4020.qasm",
|
||
"ef92424a8e365905037e2a610a50ec8cd46b032a0fb01fd12b80d2d8ed0f112f",
|
||
),
|
||
(
|
||
"data/classically-verifiable-problems/circuit-models/peaked_circuit/peaked_circuit_heavy_hex_49x5072.qasm",
|
||
"a9c2409bad096273b64c722a29cf493f0dd621a80760e87f4b778f0bbb70ddc9",
|
||
),
|
||
(
|
||
"data/classically-verifiable-problems/circuit-models/peaked_circuit/peaked_circuit_P9_Hqap_56x1917.qasm",
|
||
"f043c6cdf9e1a1ba9b3e68ea3b893277e8acacab80319d6a8524cc022886a3f9",
|
||
),
|
||
(
|
||
"data/variational-problems/hamiltonians/guided_sparse_ground_state_problem/49Q_v1/49Q_v1_hamiltonian.npy",
|
||
"c15ad8d74b5a41508a7cf99c389ad8a45ac50206a0eb7a992a72a3e11bf07566",
|
||
),
|
||
(
|
||
"data/variational-problems/hamiltonians/guided_sparse_ground_state_problem/49Q_v1/49Q_v1_initial_state_index.npy",
|
||
"dcdb99c48ad035351f3ba0361d02e3fc8c6f6a02b47200796b293c47f1f3ad4b",
|
||
),
|
||
];
|
||
|
||
/// The pinned SHA-256 of `path`, as hex.
|
||
pub fn pinned(path: &str) -> &'static str {
|
||
SOURCES.iter().find(|(p, _)| *p == path).map(|(_, h)| *h).unwrap_or_else(|| panic!("{path} is not a pinned source"))
|
||
}
|
||
|
||
/// The bytes of `path` under the checkout `root`, refused unless their SHA-256
|
||
/// is the pinned one.
|
||
pub fn read_pinned(root: &Path, path: &str) -> Vec<u8> {
|
||
let bytes = std::fs::read(root.join(path)).unwrap_or_else(|e| panic!("reading {path} under {}: {e}", root.display()));
|
||
let got = hex(&sha256(&bytes));
|
||
assert_eq!(got, pinned(path), "{path}: SHA-256 {got} is not the pinned one; fetch the tracker at {COMMIT}");
|
||
bytes
|
||
}
|
||
|
||
/// The checkout of the tracker: the first argument, or `TRACKER_ROOT`.
|
||
pub fn tracker_root(arg: Option<String>) -> std::path::PathBuf {
|
||
arg.or_else(|| std::env::var("TRACKER_ROOT").ok())
|
||
.unwrap_or_else(|| panic!("give the tracker checkout ({REPOSITORY} at {COMMIT}) as the first argument or TRACKER_ROOT"))
|
||
.into()
|
||
}
|
||
|
||
/// What a receipt names as its input: the repository, the commit, the path
|
||
/// and its hash.
|
||
pub fn source(path: &str) -> Value {
|
||
json!({ "repository": REPOSITORY, "commit": COMMIT, "path": path, "sha256": pinned(path) })
|
||
}
|
||
|
||
pub fn hex(b: &[u8]) -> String {
|
||
b.iter().map(|x| format!("{x:02x}")).collect()
|
||
}
|
||
|
||
/// SHA-256 (FIPS 180-4).
|
||
#[allow(clippy::needless_range_loop)]
|
||
pub fn sha256(data: &[u8]) -> [u8; 32] {
|
||
const K: [u32; 64] = [
|
||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be,
|
||
0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa,
|
||
0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85,
|
||
0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
|
||
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f,
|
||
0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
||
];
|
||
let mut h: [u32; 8] = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];
|
||
let mut msg = data.to_vec();
|
||
msg.push(0x80);
|
||
while msg.len() % 64 != 56 {
|
||
msg.push(0);
|
||
}
|
||
msg.extend_from_slice(&((data.len() as u64) * 8).to_be_bytes());
|
||
for block in msg.chunks(64) {
|
||
let mut w = [0u32; 64];
|
||
for i in 0..16 {
|
||
w[i] = u32::from_be_bytes([block[4 * i], block[4 * i + 1], block[4 * i + 2], block[4 * i + 3]]);
|
||
}
|
||
for i in 16..64 {
|
||
let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3);
|
||
let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10);
|
||
w[i] = w[i - 16].wrapping_add(s0).wrapping_add(w[i - 7]).wrapping_add(s1);
|
||
}
|
||
let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut hh] = h;
|
||
for i in 0..64 {
|
||
let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
|
||
let ch = (e & f) ^ (!e & g);
|
||
let t1 = hh.wrapping_add(s1).wrapping_add(ch).wrapping_add(K[i]).wrapping_add(w[i]);
|
||
let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
|
||
let maj = (a & b) ^ (a & c) ^ (b & c);
|
||
let t2 = s0.wrapping_add(maj);
|
||
hh = g;
|
||
g = f;
|
||
f = e;
|
||
e = d.wrapping_add(t1);
|
||
d = c;
|
||
c = b;
|
||
b = a;
|
||
a = t1.wrapping_add(t2);
|
||
}
|
||
for (x, y) in h.iter_mut().zip([a, b, c, d, e, f, g, hh]) {
|
||
*x = x.wrapping_add(y);
|
||
}
|
||
}
|
||
let mut out = [0u8; 32];
|
||
for (i, x) in h.iter().enumerate() {
|
||
out[4 * i..4 * i + 4].copy_from_slice(&x.to_be_bytes());
|
||
}
|
||
out
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Circuits
|
||
// ---------------------------------------------------------------------------
|
||
|
||
/// The qubits a gate acts on.
|
||
fn qubits(op: &Op) -> Vec<u32> {
|
||
match op {
|
||
Op::H(q) | Op::S(q) | Op::Sdg(q) | Op::SX(q) | Op::SXdg(q) | Op::X(q) | Op::Y(q) | Op::Z(q) => vec![*q],
|
||
Op::CX(a, b) | Op::CZ(a, b) | Op::Swap(a, b) => vec![*a, *b],
|
||
Op::Rot { axis, .. } | Op::QuarterRot { axis, .. } => axis.iter().map(|(q, _)| *q).collect(),
|
||
}
|
||
}
|
||
|
||
/// `op` with its qubits renamed by `f`.
|
||
fn renamed(op: &Op, f: &impl Fn(u32) -> u32) -> Op {
|
||
let s = |axis: &PauliString| axis.iter().map(|(q, p)| (f(*q), *p)).collect::<PauliString>();
|
||
match op {
|
||
Op::H(q) => Op::H(f(*q)),
|
||
Op::S(q) => Op::S(f(*q)),
|
||
Op::Sdg(q) => Op::Sdg(f(*q)),
|
||
Op::SX(q) => Op::SX(f(*q)),
|
||
Op::SXdg(q) => Op::SXdg(f(*q)),
|
||
Op::X(q) => Op::X(f(*q)),
|
||
Op::Y(q) => Op::Y(f(*q)),
|
||
Op::Z(q) => Op::Z(f(*q)),
|
||
Op::CX(a, b) => Op::CX(f(*a), f(*b)),
|
||
Op::CZ(a, b) => Op::CZ(f(*a), f(*b)),
|
||
Op::Swap(a, b) => Op::Swap(f(*a), f(*b)),
|
||
Op::Rot { axis, theta } => Op::Rot { axis: s(axis), theta: *theta },
|
||
Op::QuarterRot { axis, k } => Op::QuarterRot { axis: s(axis), k: *k },
|
||
}
|
||
}
|
||
|
||
/// A rotation through a whole number of quarter turns, as written (`pi/2`,
|
||
/// `-pi/2`, `pi`, read as the doubles nearest them), held as the exact
|
||
/// quarter-turn gate it means; every other angle is kept as read.
|
||
fn exact_quarters(op: Op) -> Op {
|
||
use std::f64::consts::{FRAC_PI_2, PI};
|
||
match op {
|
||
Op::Rot { axis, theta } => {
|
||
let k = [(FRAC_PI_2, 1u8), (PI, 2), (-FRAC_PI_2, 3), (-PI, 2)].iter().find(|(a, _)| a.to_bits() == theta.to_bits()).map(|(_, k)| *k);
|
||
match k {
|
||
Some(k) => Op::QuarterRot { axis, k },
|
||
None => Op::Rot { axis, theta },
|
||
}
|
||
}
|
||
other => other,
|
||
}
|
||
}
|
||
|
||
/// The circuit on the qubits it touches, renumbered in increasing order, with
|
||
/// each quarter turn held exactly; and the device qubit of each new index.
|
||
pub fn compact(c: &RotCircuit) -> (RotCircuit, Vec<u32>) {
|
||
let mut used: Vec<u32> = c.ops.iter().flat_map(qubits).collect();
|
||
used.sort_unstable();
|
||
used.dedup();
|
||
let at = |q: u32| used.binary_search(&q).expect("a qubit the circuit uses") as u32;
|
||
let ops = c.ops.iter().map(|op| exact_quarters(renamed(op, &at))).collect();
|
||
(RotCircuit { n: used.len() as u32, ops }, used)
|
||
}
|
||
|
||
/// A product of `Z`s on device qubits, on the compacted register.
|
||
pub fn z_string(device: &[u32], map: &[u32]) -> PauliString {
|
||
device.iter().map(|q| (map.binary_search(q).expect("a qubit the circuit uses") as u32, Pauli::Z)).collect()
|
||
}
|
||
|
||
/// The pairs the circuit's two-qubit gates act on, each once, smaller first,
|
||
/// in the order they first appear.
|
||
pub fn coupling(c: &RotCircuit) -> Vec<(u32, u32)> {
|
||
let mut out: Vec<(u32, u32)> = Vec::new();
|
||
for op in &c.ops {
|
||
if let [a, b] = qubits(op)[..] {
|
||
let e = (a.min(b), a.max(b));
|
||
if !out.contains(&e) {
|
||
out.push(e);
|
||
}
|
||
}
|
||
}
|
||
out
|
||
}
|
||
|
||
/// The echo `2⁻ⁿ Tr(O · W† O W)` of a circuit `W` on `n` qubits as one state
|
||
/// on `2n`: each qubit `q` paired with an ancilla `n + q` in a Bell state (the
|
||
/// purification of the maximally mixed state), `W` on the qubits, and, for `O`
|
||
/// the `Z`s on `sites`, each ancilla's `Z` folded onto its qubit by a `CX`.
|
||
/// Then `2⁻ⁿ Tr(O W† O W) = ⟨Π_{q ∈ sites} Z_q⟩` in that state, since
|
||
/// `(I ⊗ O)` on the ancillas commutes with `W` and `O` is diagonal. Returns the
|
||
/// circuit and its coupling graph: the qubits' own, and an edge to each ancilla.
|
||
pub fn purified_echo(w: &RotCircuit, sites: &[u32]) -> (RotCircuit, Vec<(u32, u32)>) {
|
||
let n = w.n;
|
||
let mut edges = coupling(w);
|
||
edges.extend((0..n).map(|q| (q, n + q)));
|
||
let mut c = RotCircuit::new(2 * n);
|
||
for q in 0..n {
|
||
c.push(Op::H(q)).push(Op::CX(q, n + q));
|
||
}
|
||
c.ops.extend(w.ops.iter().cloned());
|
||
for &q in sites {
|
||
c.push(Op::CX(n + q, q));
|
||
}
|
||
(c, edges)
|
||
}
|
||
|
||
/// The loop through `sites` on the graph `edges`: the shortest path from each
|
||
/// site to the next (breadth first, neighbours in increasing order), joined;
|
||
/// `None` unless the joined paths make one simple cycle.
|
||
pub fn loop_through(edges: &[(u32, u32)], sites: &[u32]) -> Option<Vec<u32>> {
|
||
let n = edges.iter().map(|&(a, b)| a.max(b) + 1).max()? as usize;
|
||
let mut adj = vec![Vec::new(); n];
|
||
for &(a, b) in edges {
|
||
adj[a as usize].push(b);
|
||
adj[b as usize].push(a);
|
||
}
|
||
for a in &mut adj {
|
||
a.sort_unstable();
|
||
}
|
||
let path = |s: u32, t: u32| -> Option<Vec<u32>> {
|
||
let mut prev = vec![u32::MAX; n];
|
||
prev[s as usize] = s;
|
||
let mut queue = std::collections::VecDeque::from([s]);
|
||
while let Some(v) = queue.pop_front() {
|
||
if v == t {
|
||
break;
|
||
}
|
||
for &w in &adj[v as usize] {
|
||
if prev[w as usize] == u32::MAX {
|
||
prev[w as usize] = v;
|
||
queue.push_back(w);
|
||
}
|
||
}
|
||
}
|
||
if prev[t as usize] == u32::MAX {
|
||
return None;
|
||
}
|
||
let mut p = vec![t];
|
||
while *p.last()? != s {
|
||
p.push(prev[*p.last()? as usize]);
|
||
}
|
||
p.reverse();
|
||
Some(p)
|
||
};
|
||
let mut ring = Vec::new();
|
||
for i in 0..sites.len() {
|
||
let p = path(sites[i], sites[(i + 1) % sites.len()])?;
|
||
ring.extend_from_slice(&p[..p.len() - 1]);
|
||
}
|
||
let mut seen = ring.clone();
|
||
seen.sort_unstable();
|
||
seen.dedup();
|
||
(seen.len() == ring.len() && ring.len() >= 3).then_some(ring)
|
||
}
|
||
|
||
/// The qubits in breadth-first order through the coupling graph `edges`, from
|
||
/// qubit 0, each vertex's neighbours in increasing order: a chain on which
|
||
/// coupled qubits sit near each other.
|
||
pub fn bfs_chain(n: u32, edges: &[(u32, u32)]) -> Vec<u32> {
|
||
let mut adj = vec![Vec::new(); n as usize];
|
||
for &(a, b) in edges {
|
||
adj[a as usize].push(b);
|
||
adj[b as usize].push(a);
|
||
}
|
||
for a in &mut adj {
|
||
a.sort_unstable();
|
||
a.dedup();
|
||
}
|
||
let mut seen = vec![false; n as usize];
|
||
let mut order = Vec::with_capacity(n as usize);
|
||
for start in 0..n {
|
||
if seen[start as usize] {
|
||
continue;
|
||
}
|
||
seen[start as usize] = true;
|
||
let mut queue = std::collections::VecDeque::from([start]);
|
||
while let Some(v) = queue.pop_front() {
|
||
order.push(v);
|
||
for &w in &adj[v as usize] {
|
||
if !seen[w as usize] {
|
||
seen[w as usize] = true;
|
||
queue.push_back(w);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
order
|
||
}
|
||
|
||
/// The circuit a QASM text describes, as Pauli rotations and Clifford gates.
|
||
pub fn rotations(src: &str) -> RotCircuit {
|
||
wai_quantum::qasm::parse(src).unwrap_or_else(|e| panic!("the circuit does not read: {e}")).rotations()
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Energy, time and receipts
|
||
// ---------------------------------------------------------------------------
|
||
|
||
/// Run `f`, timing it and reading an energy counter around it: the result, the
|
||
/// wall time in seconds, and the energy figure (or why there is none). On macOS
|
||
/// the counter is this process's own CPU energy; on Linux, the processor
|
||
/// packages' counters, which count every process on the host (the figure says
|
||
/// so, and gives the host's load when the run began).
|
||
pub fn metered<T>(f: impl FnOnce() -> T) -> (T, f64, Value) {
|
||
if cfg!(target_os = "linux") {
|
||
return metered_packages(f);
|
||
}
|
||
metered_process(f)
|
||
}
|
||
|
||
fn metered_packages<T>(f: impl FnOnce() -> T) -> (T, f64, Value) {
|
||
use std::sync::Mutex;
|
||
use std::sync::atomic::{AtomicBool, Ordering};
|
||
use wai_quantum::engine::energy::{Accumulated, Packages, package_figure};
|
||
let load = std::fs::read_to_string("/proc/loadavg").ok().and_then(|s| s.split_whitespace().next().map(str::to_string));
|
||
let packages = match Packages::open() {
|
||
Ok(p) => p,
|
||
Err(why) => {
|
||
let started = Instant::now();
|
||
let out = f();
|
||
return (out, started.elapsed().as_secs_f64(), json!({ "unmetered": why }));
|
||
}
|
||
};
|
||
let Some(first) = packages.read() else {
|
||
let started = Instant::now();
|
||
let out = f();
|
||
return (out, started.elapsed().as_secs_f64(), json!({ "unmetered": "a package counter did not read" }));
|
||
};
|
||
let acc = Mutex::new(Accumulated::new(&packages, first));
|
||
let done = AtomicBool::new(false);
|
||
let started = Instant::now();
|
||
let (out, ok) = std::thread::scope(|s| {
|
||
// Read every 250 ms, so that no counter wraps between two reads.
|
||
s.spawn(|| {
|
||
while !done.load(Ordering::Relaxed) {
|
||
std::thread::sleep(std::time::Duration::from_millis(250));
|
||
let _ = acc.lock().unwrap().read();
|
||
}
|
||
});
|
||
let out = f();
|
||
done.store(true, Ordering::Relaxed);
|
||
let ok = acc.lock().unwrap().read().is_some();
|
||
(out, ok)
|
||
});
|
||
let secs = started.elapsed().as_secs_f64();
|
||
let mut figure = if ok {
|
||
package_figure(Some(&acc.lock().unwrap().reading(0)), "")
|
||
} else {
|
||
json!({ "unmetered": "a package counter did not read at the end of the run" })
|
||
};
|
||
if figure.get("joules_micro").is_some() {
|
||
figure["covers"] = json!("the energy of the host's processor packages over the run: every process on the host in that window, this one among them, and the packages' idle draw");
|
||
figure["source"] = json!("powercap energy_uj of every package zone, read before the run, every 250 ms during it and after it, each wrap corrected at the zone's max_energy_range_uj");
|
||
figure["host_load_1m_at_start"] = json!(load);
|
||
// Other work on the host is not counted in jobs here: the load says it.
|
||
if let Some(m) = figure.as_object_mut() {
|
||
m.remove("concurrent_jobs");
|
||
}
|
||
}
|
||
(out, secs, figure)
|
||
}
|
||
|
||
fn metered_process<T>(f: impl FnOnce() -> T) -> (T, f64, Value) {
|
||
use wai_quantum::engine::energy::own;
|
||
let before = own();
|
||
let started = Instant::now();
|
||
let out = f();
|
||
let secs = started.elapsed().as_secs_f64();
|
||
let after = own();
|
||
let figure = match (before, after) {
|
||
(Some(a), Some(b)) => {
|
||
let nj = |r: &wai_quantum::engine::energy::Reading| r.energy_nj.saturating_add(r.billed_nj).saturating_sub(r.serviced_nj);
|
||
let delta = nj(&b).saturating_sub(nj(&a));
|
||
let joules_micro = delta.saturating_add(500) / 1000;
|
||
if joules_micro == 0 {
|
||
json!({ "unmetered": "the process's energy counter did not advance over the run" })
|
||
} else {
|
||
json!({
|
||
"joules_micro": joules_micro,
|
||
"energy_provenance": "OnChipCounter",
|
||
"energy_counter_filtering": "Unknown",
|
||
"attribution": "process",
|
||
"coverage": "partial",
|
||
"covers": "the CPU energy of this process's own threads over the run, with what system services billed to it",
|
||
"excludes": ["memory", "the host's idle draw", "energy of threads still on a core at the second read"],
|
||
"cpu_ms": b.cpu_ns.saturating_sub(a.cpu_ns) / 1_000_000,
|
||
"source": "proc_pid_rusage ri_energy_nj + ri_billed_energy − ri_serviced_energy of this process, read before and after the run",
|
||
})
|
||
}
|
||
}
|
||
_ => json!({ "unmetered": "this platform gives no per-process energy counter here" }),
|
||
};
|
||
(out, secs, figure)
|
||
}
|
||
|
||
/// The signing key: 64 hex digits in the file `WAI_TRACKER_KEY` names, or, with
|
||
/// none, a key every reader knows, which the receipt then says it was sealed
|
||
/// with.
|
||
pub fn signing_key() -> (SigningKey, &'static str) {
|
||
match std::env::var("WAI_TRACKER_KEY") {
|
||
Ok(path) => {
|
||
let text = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("reading the key {path}: {e}"));
|
||
let text = text.trim();
|
||
assert_eq!(text.len(), 64, "the key file holds 64 hex digits");
|
||
let mut b = [0u8; 32];
|
||
for (i, x) in b.iter_mut().enumerate() {
|
||
*x = u8::from_str_radix(&text[2 * i..2 * i + 2], 16).expect("hex digits");
|
||
}
|
||
(SigningKey::from_bytes(&b), "the operator's key")
|
||
}
|
||
Err(_) => (SigningKey::from_bytes(&[42u8; 32]), "a demonstration key every reader knows (set WAI_TRACKER_KEY to sign with your own)"),
|
||
}
|
||
}
|
||
|
||
/// Seal `result` of `request` and write the three, as one JSON document, to
|
||
/// `out` (when given). Returns the document.
|
||
pub fn seal(request: Value, mut result: Value, out: Option<&Path>) -> Value {
|
||
let (key, which) = signing_key();
|
||
result["signed_with"] = json!(which);
|
||
let receipt = wai_quantum::engine::receipt::seal(&key, &request, &result);
|
||
let doc = json!({ "request": request, "result": result, "receipt": receipt });
|
||
if let Some(path) = out {
|
||
if let Some(dir) = path.parent() {
|
||
std::fs::create_dir_all(dir).unwrap_or_else(|e| panic!("creating {}: {e}", dir.display()));
|
||
}
|
||
std::fs::write(path, serde_json::to_string_pretty(&doc).unwrap() + "\n").unwrap_or_else(|e| panic!("writing {}: {e}", path.display()));
|
||
}
|
||
doc
|
||
}
|
||
|
||
/// Whether a written receipt document's seal holds over its request and result.
|
||
pub fn verifies(doc: &Value) -> bool {
|
||
wai_quantum::engine::receipt::verify(&doc["request"], &doc["result"], &doc["receipt"])
|
||
}
|
||
|
||
/// Where a run's receipt goes: `RECEIPTS_DIR` (default `receipts`) / `name`.
|
||
pub fn receipt_path(name: &str) -> std::path::PathBuf {
|
||
Path::new(&std::env::var("RECEIPTS_DIR").unwrap_or_else(|_| "receipts".into())).join(name)
|
||
}
|
||
|
||
/// The number as its exact bits, for a reader that must get the same double back.
|
||
pub fn bits(x: f64) -> String {
|
||
format!("{:016x}", x.to_bits())
|
||
}
|
||
|
||
/// The process's peak resident memory so far, in bytes: `VmHWM` on Linux,
|
||
/// `ru_maxrss` (bytes there) on macOS; `None` elsewhere or if it does not read.
|
||
pub fn peak_rss_bytes() -> Option<u64> {
|
||
#[cfg(target_os = "linux")]
|
||
{
|
||
let s = std::fs::read_to_string("/proc/self/status").ok()?;
|
||
let kb: u64 = s.lines().find(|l| l.starts_with("VmHWM:"))?.split_whitespace().nth(1)?.parse().ok()?;
|
||
Some(kb * 1024)
|
||
}
|
||
#[cfg(target_os = "macos")]
|
||
{
|
||
// `struct rusage` from `<sys/resource.h>`: two `timeval`s (16 bytes
|
||
// each), then `ru_maxrss` and thirteen more `long`s.
|
||
#[repr(C)]
|
||
struct Rusage {
|
||
times: [i64; 4],
|
||
maxrss: i64,
|
||
rest: [i64; 13],
|
||
}
|
||
unsafe extern "C" {
|
||
fn getrusage(who: i32, usage: *mut Rusage) -> i32;
|
||
}
|
||
let mut r = Rusage { times: [0; 4], maxrss: 0, rest: [0; 13] };
|
||
// SAFETY: `r` is a writable `struct rusage` for the call; RUSAGE_SELF is 0.
|
||
let rc = unsafe { getrusage(0, &mut r) };
|
||
(rc == 0 && r.maxrss > 0).then_some(r.maxrss as u64)
|
||
}
|
||
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
||
{
|
||
None
|
||
}
|
||
}
|