Some checks failed
wai-audit collector and player integration / does this change touch it (push) Successful in 23s
wai-quantum command line / does this change touch it (push) Successful in 19s
WAI operation catalogue / does this change touch it (push) Successful in 23s
wai-quantum clippy / does this change touch it (push) Successful in 16s
wai-quantum contract / does this change touch it (push) Successful in 15s
wai-quantum docs, course and locales / does this change touch it (push) Successful in 15s
wai-quantum editors / does this change touch it (push) Successful in 23s
wai-quantum resource estimation / does this change touch it (push) Successful in 20s
wai-quantum other formats / does this change touch it (push) Successful in 18s
wai-quantum for JavaScript / does this change touch it (push) Successful in 22s
wai-quantum-landscape-fpga reference design / does this change touch it (push) Successful in 21s
wai-quantum OpenQASM reader / does this change touch it (push) Successful in 34s
wai-quantum pulse and dynamics / does this change touch it (push) Successful in 31s
wai-quantum qudit jobs / does this change touch it (push) Successful in 26s
wai-quantum error correction and mitigation / does this change touch it (push) Successful in 37s
wai-quantum extension points / does this change touch it (push) Successful in 33s
wai-quantum service / does this change touch it (push) Successful in 46s
wai-quantum studio and desktop / does this change touch it (push) Successful in 46s
wai-quantum for Python / does this change touch it (push) Successful in 2m23s
wai-quantum service / service energy · linux packages (push) Failing after 12s
wai-quantum views in agent hosts / does this change touch it (push) Successful in 51s
wai-quantum on Windows / does this change touch it (push) Successful in 49s
wai-sink-swift native sink package / does this change touch it (push) Successful in 2m59s
wai-quantum variational and algorithm library / does this change touch it (push) Successful in 3m33s
wai-quantum verdicts and repairs / does this change touch it (push) Successful in 3m26s
WAI Studio / does this change touch it (push) Successful in 2m57s
wai-sink-android native sink library / does this change touch it (push) Successful in 3m3s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Has been cancelled
WAI Studio / Studio in Chromium and Firefox · macos (push) Has been cancelled
WAI Studio / Studio in WebKit · linux (push) Has been cancelled
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Has been cancelled
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Has been cancelled
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Has been cancelled
wai-quantum on Windows / engine · windows (push) Has been cancelled
wai-quantum on Windows / command line · windows (push) Has been cancelled
wai-quantum on Windows / job service · windows (push) Has been cancelled
wai-quantum on Windows / python · windows (push) Has been cancelled
wai-quantum wasmQ / does this change touch it (push) Has been cancelled
wai-quantum wasmQ / profile, engines, reference modules, adversarial, vectors · linux (push) Has been cancelled
wai-quantum wasmQ / profile, engines, reference modules, adversarial, vectors · macos (push) Has been cancelled
wai-quantum wasmQ / the reference modules rebuild to their modules of record · linux (push) Has been cancelled
wai-quantum wasmQ / interpreter and vectors under wasm32-wasip2 (wasmtime) (push) Has been cancelled
wai-quantum views in agent hosts / views · the page and its engine (push) Has been cancelled
wai-quantum views in agent hosts / views · a scripted host (push) Has been cancelled
wai-quantum verdicts and repairs / verdicts, repairs and the intent set · linux (push) Has been cancelled
wai-quantum verdicts and repairs / the intent set and the verdict under wasm32-wasip2 (wasmtime) (push) Has been cancelled
wai-quantum variational and algorithm library / parameters, gradients, optimisers, VQE, QAOA and builders · linux, release (push) Has been cancelled
wai-quantum variational and algorithm library / parameters, gradients, optimisers, VQE, QAOA and builders · linux-arm64, release (push) Has been cancelled
wai-quantum studio and desktop / studio · browser (push) Has been cancelled
wai-quantum studio and desktop / desktop app · macos (push) Has been cancelled
wai-quantum studio and desktop / desktop app · linux (push) Has been cancelled
wai-quantum studio and desktop / desktop app · windows (push) Has been cancelled
wai-quantum extension points / the registry and the example · linux (push) Has been cancelled
wai-quantum extension points / the registry and the example · linux-arm64 (push) Has been cancelled
wai-quantum extension points / the registry under wasm32-wasip2 (wasmtime) (push) Has been cancelled
wai-quantum qudit jobs / the module, the job, the conformance set, the referee and the verb · linux (push) Has been cancelled
wai-quantum qudit jobs / the same bits · macos (push) Has been cancelled
wai-quantum qudit jobs / the module and the conformance set under wasm32-wasip2 (wasmtime) (push) Has been cancelled
wai-quantum error correction and mitigation / codes, decoders, texts and mitigation · linux, release (push) Has been cancelled
wai-quantum for Python / python · linux (push) Has been cancelled
wai-quantum for Python / python · macos (push) Has been cancelled
wai-quantum pulse and dynamics / reader, simulator and pulse jobs · linux, release (push) Has been cancelled
wai-quantum pulse and dynamics / the same bits · linux-arm64 (push) Has been cancelled
wai-quantum pulse and dynamics / the same bits · macos (push) Has been cancelled
wai-quantum pulse and dynamics / reader, simulator and pulse jobs under wasm32-wasip2 (wasmtime) (push) Has been cancelled
wai-quantum OpenQASM reader / reader and corpus · linux (push) Has been cancelled
wai-quantum OpenQASM reader / corpus under wasm32-wasip2 (wasmtime) (push) Has been cancelled
wai-quantum energy-landscape jobs / does this change touch it (push) Has been cancelled
wai-quantum energy-landscape jobs / reader, kernels, answers and the stated result · linux, release (push) Has been cancelled
wai-quantum energy-landscape jobs / the same bits · linux-arm64 (push) Has been cancelled
wai-quantum energy-landscape jobs / the same bits · macos (push) Has been cancelled
wai-quantum energy-landscape jobs / the module under wasm32-wasip2 (wasmtime) (push) Has been cancelled
wai-quantum energy-landscape jobs / native against WebAssembly, over many seeds (push) Has been cancelled
wai-quantum landscape stage 0, the sampler against the exact law / does this change touch it (push) Has been cancelled
wai-quantum landscape stage 0, the sampler against the exact law / the small tier, to the byte · linux (push) Has been cancelled
wai-quantum landscape stage 0, the sampler against the exact law / the small tier, to the byte · macos (push) Has been cancelled
wai-quantum landscape energy to solution / does this change touch it (push) Has been cancelled
wai-quantum landscape energy to solution / the small tier, to the byte · linux (push) Has been cancelled
wai-quantum landscape energy to solution / the small tier, to the byte · macos (push) Has been cancelled
wai-quantum-landscape-fpga reference design / the member against the model of the logic, and the logic against the model · linux, release (push) Has been cancelled
wai-quantum-landscape-fpga reference design / the board's code on the board's processor's architecture · linux-arm64, release (push) Has been cancelled
wai-quantum for JavaScript / javascript · linux (push) Has been cancelled
wai-quantum other formats / readers, writers and the set · linux (push) Has been cancelled
wai-quantum other formats / the set under wasm32-wasip2 (wasmtime) (push) Has been cancelled
wai-quantum resource estimation / estimation and calibration · linux, release (push) Has been cancelled
wai-quantum editors / editors · linux (push) Has been cancelled
wai-quantum editors / editors · macos (push) Has been cancelled
wai-quantum docs, course and locales / every command and snippet in the docs · linux (push) Has been cancelled
wai-quantum docs, course and locales / the course and the command line's languages · linux (push) Has been cancelled
wai-quantum docs, course and locales / the Studio's words in every locale · linux (push) Has been cancelled
wai-quantum contract / API compatibility · linux (push) Has been cancelled
wai-quantum contract / schemas and the committed tool lists · linux (push) Has been cancelled
wai-quantum contract / the policy is linked, and the deprecations page reads (push) Has been cancelled
wai-quantum clippy / clippy, warnings denied, every feature set · linux (push) Has been cancelled
wai-quantum command line / command line · linux (push) Has been cancelled
wai-quantum command line / command line · macos (push) Has been cancelled
WAI examples / WAI examples · does this change touch them (push) Has been cancelled
WAI examples / WAI examples · portable · linux (push) Has been cancelled
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (push) Has been cancelled
WAI examples / WAI examples · android-app · linux-arm64 (push) Has been cancelled
WAI operation catalogue / the catalogue and every door · linux (push) Has been cancelled
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Has been cancelled
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Has been cancelled
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Has been cancelled
wai-audit collector and player integration / collector · linux (push) Has been cancelled
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Has been cancelled
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Has been cancelled
wai-quantum service / service · linux (push) Successful in 11m57s
wai-quantum service / service · macos (push) Successful in 7m17s
The published data set of Madsen et al., Nature 606, 75 (2022) (Apache-2.0), all five instances, held to the engine's exact Gaussian-state computations, a signed receipt per run. The module gains what the experiment needs, each tested against exact small cases: - GaussianState::through: squeezed, thermal or squashed inputs through any subunitary transfer matrix (a different loss on every path); an amplifying matrix is refused. - Loops: a time-domain interferometer of fibre loops from its program. It rebuilds every published T and U (16 to 288 modes) to within 2.3e-16 per entry. - photon_moments (exact first- and second-order photon-number cumulants) and photon_number_distribution (the exact total-photon distribution, from the eigenvalues of I - Q^-1 and the generating function on the unit circle; it holds at 576 x 576). In every total, the hafnian probabilities sum to it within 3e-15. - photon_table: what every pattern probability needs, computed once; photon_probability keeps its bits (bit_identity unchanged). examples/gbs_loops.rs reads the pinned data set (wai/tools/quantum-gbs/fetch.sh). tests/gbs_reference.rs holds the receipts to the published values and re-runs the 16-mode comparison up to four photons bit for bit. - 16 modes, 84,096,000 samples: fidelity 99.87-99.66%, total variation distance 3.95-6.51% for three to six photons (paper: >99%, <=6.5%). The sample count alone gives 0.4-3.0%. Thermal, squashed and coherent light are farther off in every total. - The stated means reproduce: 21.120 +- 0.006, 22.416 +- 0.006, 124.35 +- 0.02, max 219. - The ground truth explains the pair cumulants best in all five instances. - Fig. 3b's Bayesian scores for 10-14 photons are above zero against thermal, squashed and coherent light. Co-Authored-By: Claude Opus 5.5 <[email protected]>
558 lines
25 KiB
Rust
558 lines
25 KiB
Rust
//! What the reproductions of the 2022 time-domain Gaussian boson sampler
|
||
//! share (Madsen et al., Nature 606, 75 (2022), doi:10.1038/s41586-022-04725-x):
|
||
//! the pinned data set, a SHA-256 check of every file read, a reader of the
|
||
//! `.npy` arrays it is published in, the instance's loop program, the
|
||
//! photon-number patterns in a fixed order, the energy reading around a run,
|
||
//! and the signed receipt each run writes.
|
||
//!
|
||
//! The data set is published by the paper's authors under the Apache License,
|
||
//! Version 2.0 (see `tests/data/gbs/README.md` for the credit and the licence
|
||
//! copy). Its arrays are fetched by the reader and checked against the hashes
|
||
//! here; only the small fixtures the tests need are kept in this repository.
|
||
|
||
#![allow(dead_code, clippy::needless_range_loop)]
|
||
|
||
use std::path::Path;
|
||
use std::time::Instant;
|
||
|
||
use serde_json::{Value, json};
|
||
use wai_quantum::ed25519_dalek::SigningKey;
|
||
use wai_quantum::quantum_gbs::{C, Loops, Mat};
|
||
|
||
/// The repository that describes the data set, at the commit read, and the
|
||
/// bucket that serves its arrays.
|
||
pub const REPOSITORY: &str = "https://github.com/XanaduAI/xanadu-qca-data"; // allow-name: a repository URL
|
||
pub const COMMIT: &str = "869906e3f4ec2a5c5fd6a78d5d8c8a20371b9e6c";
|
||
pub const BUCKET: &str = "https://qca-data.s3.amazonaws.com/"; // allow-name: the data set's URL
|
||
/// The repository's `LICENSE` at [`COMMIT`]: the Apache License, Version 2.0.
|
||
pub const LICENSE_SHA256: &str = "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4";
|
||
|
||
/// The five instances: the paper's figure each is shown in, and its modes.
|
||
pub const SETS: [(&str, usize); 5] = [("fig2", 16), ("fig3a", 216), ("fig3b", 72), ("fig4", 216), ("figS15", 288)];
|
||
|
||
/// Every object of the data set, by its path in the bucket: its size in bytes
|
||
/// and the SHA-256 of its bytes (the MD5 of each single-part object is the
|
||
/// bucket's ETag, and agreed when fetched).
|
||
pub const SOURCES: &[(&str, u64, &str)] = &[
|
||
("fig2/program_params.json", 4454, "17c2d13824e61307a367f111a6dd4faa5c8f43d249effa00d5f2654d3ce8c0bc"),
|
||
("fig2/U.npy", 4224, "18685d0b53da451ae4cbb91128ab38ef86e88e6bb5db26e01d19496cb1b8bb31"),
|
||
("fig2/T.npy", 4224, "bd2b1805bf3a07734dbb0ebd6bedfe7e60a8fef04c0712f79d72451c3d070e00"),
|
||
("fig2/r.npy", 256, "e5bea96c7a25b9b30dbf1180bacf7165e0c1f92c7f00ebd9d06bf39d245f089b"),
|
||
("fig2/samples.npy", 1345536128, "a24c3500857c89ad7172dfd5eee7d3d961d6d8ec15169b9f11fb7eaf1da9ecac"),
|
||
("fig3a/program_params.json", 45621, "ed584aa6539d8fdca56b5dabc069cfacb809bd1f03d491f8307259f02b224eee"),
|
||
("fig3a/U.npy", 746624, "58ebeb2e1769f9c768eec7672ec322877e43e2c20e2077c8658b1bcfccd4c178"),
|
||
("fig3a/T.npy", 746624, "0f8d001df2a1d86c31b2bea5289b1f7fab9e9df9ec9127de268d2eccc3943091"),
|
||
("fig3a/r.npy", 1856, "599c27dff53d52f831dd99816481c8a2d231a37e2d0f3132c8e77fdfaee1aef0"),
|
||
("fig3a/samples.npy", 225504128, "50b4b3bb37a0e4e2162ff8d188d6386f4dcd0c5b3395828b016f43c19f230725"),
|
||
("fig3b/program_params.json", 21265, "95d723496d328a7116f6b999a1e99e90c3c0d6b6174fca78e082222fbbd6e643"),
|
||
("fig3b/U.npy", 83072, "f7aa1d75e0e5945eadc123b74cd5b5d92ed6e1c01f61cb6354f01c6f738ee393"),
|
||
("fig3b/T.npy", 83072, "c6b796dc2478bc2786ca933255786665b3fd41e73f631ca8269f2b21115ffc54"),
|
||
("fig3b/r.npy", 1856, "b5e5b36bc0bc576f40c9c313afdcc0004a469d8d5acead1228795573d2d71327"),
|
||
("fig3b/samples.npy", 74592128, "37691c55f58549c353d0749aba71bca07bbcb0f322c1355e04b5acf72766ed65"),
|
||
("fig4/program_params.json", 45587, "9d7a9b02cf934ea0913f48a2f68a7285967c555af15b7de9246bec8aedae7070"),
|
||
("fig4/U.npy", 746624, "3654a12fbdb707b92eeda5bafc58f6ef9ca03b8fbb3e59b0ac316870f0c2fd00"),
|
||
("fig4/T.npy", 746624, "c7e5c10b63002ff6e270f9e049aebf36371f62dfc38e73c91725c10c2d0cddbf"),
|
||
("fig4/r.npy", 1856, "f593db9ace468d06fa556f5a074b583756b9e6b4c5264a23e6edcb45fd48c40e"),
|
||
("fig4/samples.npy", 221616128, "20a3a5a334f4d40b6a9de9abb46df3eb5390ce6f660eb15e1a630f406c88165a"),
|
||
("figS15/program_params.json", 59802, "6900e557752c9608d7582ec3920e68faef4d57edc997f3ba999bed3db0acb085"),
|
||
("figS15/U.npy", 1327232, "88338b73614f3af7034498d45c1612c6875c75e31e75fa6481d2f2409256b15a"),
|
||
("figS15/T.npy", 1327232, "fae02f0cd1d26abf6b152ab80386ea2799809ce592194ad1b48309038d1f4c28"),
|
||
("figS15/r.npy", 2432, "9309ea8d12dd68deb7fa677e039efbe52c14ed653633b10fac2569850a985905"),
|
||
("figS15/samples.npy", 320544128, "94e7f0c8ac2280a6cc26028a0ab75afc6bdec2b620cfb626def08a78adb51130"),
|
||
];
|
||
|
||
/// The pinned size and SHA-256 of `path`.
|
||
pub fn pinned(path: &str) -> (u64, &'static str) {
|
||
SOURCES.iter().find(|(p, _, _)| *p == path).map(|(_, n, h)| (*n, *h)).unwrap_or_else(|| panic!("{path} is not a pinned object"))
|
||
}
|
||
|
||
/// The bytes of `path` under the data directory `root` (laid out as the
|
||
/// bucket is: `root/fig2/T.npy`), refused unless their size and SHA-256 are
|
||
/// the pinned ones.
|
||
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 (size, sha) = pinned(path);
|
||
assert_eq!(bytes.len() as u64, size, "{path}: {} bytes, not the pinned {size}", bytes.len());
|
||
let got = hex(&sha256(&bytes));
|
||
assert_eq!(got, sha, "{path}: SHA-256 {got} is not the pinned one; fetch it again from {BUCKET}");
|
||
bytes
|
||
}
|
||
|
||
/// What a receipt names as an input: where it is served, its size and hash.
|
||
pub fn source(path: &str) -> Value {
|
||
let (bytes, sha256) = pinned(path);
|
||
json!({ "url": format!("{BUCKET}{path}"), "bytes": bytes, "sha256": sha256 })
|
||
}
|
||
|
||
pub fn hex(b: &[u8]) -> String {
|
||
b.iter().map(|x| format!("{x:02x}")).collect()
|
||
}
|
||
|
||
/// SHA-256 (FIPS 180-4), over the bytes in place.
|
||
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 block = |h: &mut [u32; 8], block: &[u8]| {
|
||
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 whole = data.len() / 64 * 64;
|
||
for chunk in data[..whole].chunks(64) {
|
||
block(&mut h, chunk);
|
||
}
|
||
let mut tail = data[whole..].to_vec();
|
||
tail.push(0x80);
|
||
while tail.len() % 64 != 56 {
|
||
tail.push(0);
|
||
}
|
||
tail.extend_from_slice(&((data.len() as u64) * 8).to_be_bytes());
|
||
for chunk in tail.chunks(64) {
|
||
block(&mut h, chunk);
|
||
}
|
||
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
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// .npy arrays
|
||
// ---------------------------------------------------------------------------
|
||
|
||
/// A `.npy` array: its element type, memory order, shape and data bytes.
|
||
pub struct Npy<'a> {
|
||
pub descr: String,
|
||
pub fortran: bool,
|
||
pub shape: Vec<usize>,
|
||
pub data: &'a [u8],
|
||
}
|
||
|
||
/// The array in `bytes`; the header is a Python dict literal, read for its
|
||
/// three keys only (nothing in it is evaluated).
|
||
pub fn npy(bytes: &[u8]) -> Npy<'_> {
|
||
assert!(bytes.len() >= 10 && &bytes[..6] == b"\x93NUMPY", "not an .npy array");
|
||
let (len, start) = match bytes[6] {
|
||
1 => (u16::from_le_bytes([bytes[8], bytes[9]]) as usize, 10),
|
||
2 | 3 => (u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]) as usize, 12),
|
||
v => panic!(".npy version {v}"),
|
||
};
|
||
let header = std::str::from_utf8(&bytes[start..start + len]).expect("an ASCII header");
|
||
let value = |key: &str| -> &str {
|
||
let at = header.find(&format!("'{key}':")).unwrap_or_else(|| panic!("no {key} in the header"));
|
||
header[at + key.len() + 3..].trim_start()
|
||
};
|
||
let descr = value("descr").trim_start_matches('\'').split('\'').next().unwrap().to_string();
|
||
let fortran = value("fortran_order").starts_with("True");
|
||
let shape_text = value("shape");
|
||
let shape = shape_text[1..shape_text.find(')').unwrap()].split(',').filter_map(|s| s.trim().parse().ok()).collect::<Vec<usize>>();
|
||
let count: usize = shape.iter().product();
|
||
let width = match descr.as_str() {
|
||
"<c16" => 16,
|
||
"<f8" => 8,
|
||
"|i1" | "|u1" => 1,
|
||
d => panic!("element type {d}"),
|
||
};
|
||
let data = &bytes[start + len..];
|
||
assert_eq!(data.len(), count * width, "the data does not fill the shape");
|
||
Npy { descr, fortran, shape, data }
|
||
}
|
||
|
||
fn f64_at(b: &[u8], i: usize) -> f64 {
|
||
f64::from_le_bytes(b[8 * i..8 * i + 8].try_into().unwrap())
|
||
}
|
||
|
||
/// A square complex matrix (`<c16`), row-major whatever the file's order.
|
||
pub fn complex_matrix(bytes: &[u8]) -> Mat {
|
||
let a = npy(bytes);
|
||
assert!(a.descr == "<c16" && a.shape.len() == 2 && a.shape[0] == a.shape[1], "a square complex matrix");
|
||
let n = a.shape[0];
|
||
let mut m = Mat::zeros(n);
|
||
for i in 0..n {
|
||
for j in 0..n {
|
||
let k = if a.fortran { j * n + i } else { i * n + j };
|
||
m.a[i * n + j] = C { re: f64_at(a.data, 2 * k), im: f64_at(a.data, 2 * k + 1) };
|
||
}
|
||
}
|
||
m
|
||
}
|
||
|
||
/// A real vector (`<f8`).
|
||
pub fn real_vector(bytes: &[u8]) -> Vec<f64> {
|
||
let a = npy(bytes);
|
||
assert!(a.descr == "<f8" && a.shape.len() == 1, "a real vector");
|
||
(0..a.shape[0]).map(|i| f64_at(a.data, i)).collect()
|
||
}
|
||
|
||
/// The samples (`|i1`, shape `shots × 1 × modes`) as one row of `modes` photon
|
||
/// numbers per shot, in shot order, whatever the file's memory order.
|
||
pub fn sample_rows(bytes: &[u8]) -> (usize, usize, std::borrow::Cow<'_, [u8]>) {
|
||
let a = npy(bytes);
|
||
assert!(a.descr == "|i1" && a.shape.len() == 3 && a.shape[1] == 1, "photon-number samples");
|
||
let (shots, modes) = (a.shape[0], a.shape[2]);
|
||
if !a.fortran {
|
||
return (shots, modes, std::borrow::Cow::Borrowed(a.data));
|
||
}
|
||
// Column-major: mode m of shot s at s + shots·m. Transposed in blocks of shots.
|
||
let mut rows = vec![0u8; shots * modes];
|
||
const BLOCK: usize = 4096;
|
||
for s0 in (0..shots).step_by(BLOCK) {
|
||
let s1 = (s0 + BLOCK).min(shots);
|
||
for m in 0..modes {
|
||
let col = &a.data[m * shots + s0..m * shots + s1];
|
||
for (k, &v) in col.iter().enumerate() {
|
||
rows[(s0 + k) * modes + m] = v;
|
||
}
|
||
}
|
||
}
|
||
(shots, modes, std::borrow::Cow::Owned(rows))
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// The instance's program
|
||
// ---------------------------------------------------------------------------
|
||
|
||
/// An instance's program, as `program_params.json` gives it.
|
||
pub struct Program {
|
||
/// Squeezing per input bin, as listed.
|
||
pub r: Vec<f64>,
|
||
pub loops: Loops,
|
||
/// The energy transmittance from the source to the detectors, common to every mode.
|
||
pub common: f64,
|
||
/// The 16 detector channels' relative efficiencies, bin `b` read by channel `b mod 16`.
|
||
pub channels: Vec<f64>,
|
||
/// Bins read before the first pulse arrives, dropped from the samples and matrices.
|
||
pub crop: usize,
|
||
}
|
||
|
||
pub fn program(bytes: &[u8]) -> Program {
|
||
let v: Value = serde_json::from_slice(bytes).expect("the program is JSON");
|
||
let list = |k: &str| -> Vec<f64> { v[k].as_array().unwrap_or_else(|| panic!("{k}")).iter().map(|x| x.as_f64().expect("a number")).collect() };
|
||
Program {
|
||
r: list("r"),
|
||
loops: Loops {
|
||
delays: vec![1, 6, 36],
|
||
phi: vec![list("phi_0"), list("phi_1"), list("phi_2")],
|
||
alpha: vec![list("alpha_0"), list("alpha_1"), list("alpha_2")],
|
||
round_trip_phase: list("loop_phases"),
|
||
round_trip_transmission: list("loop_efficiencies"),
|
||
},
|
||
common: v["common_efficiency"].as_f64().expect("common_efficiency"),
|
||
channels: list("relative_mode_efficiencies"),
|
||
crop: v["crop"].as_u64().expect("crop") as usize,
|
||
}
|
||
}
|
||
|
||
impl Program {
|
||
/// The transfer matrix of the instance's `m` modes: the pulses of input
|
||
/// bins `0..m` to the detected bins `crop..crop + m`, each detected bin `b`
|
||
/// through the common transmittance and channel `b mod 16`'s
|
||
/// (Methods, eq. 10). With `lossless`, the loops' and detectors' losses are
|
||
/// left out: the instance's unitary.
|
||
pub fn transfer(&self, m: usize, lossless: bool) -> Mat {
|
||
let detection: Vec<f64> = (0..m).map(|i| if lossless { 1.0 } else { self.common * self.channels[(i + self.crop) % 16] }).collect();
|
||
let loops = if lossless { Loops { round_trip_transmission: vec![1.0; 3], ..self.loops.clone() } } else { self.loops.clone() };
|
||
loops.transfer(m, self.crop, &detection).expect("the program covers the instance")
|
||
}
|
||
}
|
||
|
||
/// The largest `|a − φ b|` over the entries, for the unit phase `φ ∈ {1, i, −1, −i}`
|
||
/// that makes it smallest, and that phase as a quarter-turn count: a global
|
||
/// phase changes no photon-number statistic.
|
||
pub fn distance_up_to_quarter_phase(a: &Mat, b: &Mat) -> (f64, u32) {
|
||
let phases = [C { re: 1.0, im: 0.0 }, C { re: 0.0, im: 1.0 }, C { re: -1.0, im: 0.0 }, C { re: 0.0, im: -1.0 }];
|
||
let mut best = (f64::INFINITY, 0);
|
||
for (k, p) in phases.iter().enumerate() {
|
||
let d = a
|
||
.a
|
||
.iter()
|
||
.zip(&b.a)
|
||
.map(|(x, y)| {
|
||
let (re, im) = (p.re * y.re - p.im * y.im, p.re * y.im + p.im * y.re);
|
||
((x.re - re) * (x.re - re) + (x.im - im) * (x.im - im)).sqrt()
|
||
})
|
||
.fold(0.0, f64::max);
|
||
if d < best.0 {
|
||
best = (d, k as u32);
|
||
}
|
||
}
|
||
best
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Patterns
|
||
// ---------------------------------------------------------------------------
|
||
|
||
/// Every photon-number pattern of `modes` modes with `total` photons, in a
|
||
/// fixed order: the first mode's count from `total` down to 0, and within it
|
||
/// the rest in the same order.
|
||
pub fn patterns(modes: usize, total: usize) -> Vec<Vec<u8>> {
|
||
fn go(modes: usize, total: usize, prefix: &mut Vec<u8>, out: &mut Vec<Vec<u8>>) {
|
||
if modes == 1 {
|
||
prefix.push(total as u8);
|
||
out.push(prefix.clone());
|
||
prefix.pop();
|
||
return;
|
||
}
|
||
for k in (0..=total).rev() {
|
||
prefix.push(k as u8);
|
||
go(modes - 1, total - k, prefix, out);
|
||
prefix.pop();
|
||
}
|
||
}
|
||
let mut out = Vec::new();
|
||
go(modes, total, &mut Vec::with_capacity(modes), &mut out);
|
||
out
|
||
}
|
||
|
||
/// A pattern of at most 16 modes with at most 15 photons in any mode, packed
|
||
/// four bits a mode.
|
||
pub fn pack(p: &[u8]) -> u64 {
|
||
p.iter().enumerate().fold(0u64, |acc, (i, &c)| acc | (u64::from(c) << (4 * i)))
|
||
}
|
||
|
||
/// The pattern counts file: one line per total photon number `N`, `N` then
|
||
/// the count of each pattern in [`patterns`]' order.
|
||
pub fn counts_text(modes: usize, nmax: usize, count: impl Fn(&[u8]) -> u64) -> String {
|
||
let mut s = String::new();
|
||
for n in 0..=nmax {
|
||
s += &n.to_string();
|
||
for p in patterns(modes, n) {
|
||
s += &format!(" {}", count(&p));
|
||
}
|
||
s.push('\n');
|
||
}
|
||
s
|
||
}
|
||
|
||
/// The counts file read back: per `N`, the counts in [`patterns`]' order.
|
||
pub fn parse_counts(text: &str) -> Vec<Vec<u64>> {
|
||
text.lines()
|
||
.enumerate()
|
||
.map(|(n, line)| {
|
||
let mut it = line.split(' ');
|
||
assert_eq!(it.next().and_then(|x| x.parse::<usize>().ok()), Some(n), "line {n} starts with its N");
|
||
it.map(|x| x.parse().expect("a count")).collect()
|
||
})
|
||
.collect()
|
||
}
|
||
|
||
/// The fidelity `Σ √(pᵢ qᵢ)` and the total variation distance `Σ |pᵢ − qᵢ| / 2`
|
||
/// of two distributions over the same outcomes.
|
||
pub fn fidelity_tvd(p: &[f64], q: &[f64]) -> (f64, f64) {
|
||
let (mut f, mut t) = (0.0, 0.0);
|
||
for (a, b) in p.iter().zip(q) {
|
||
f += (a * b).sqrt();
|
||
t += (a - b).abs();
|
||
}
|
||
(f, t / 2.0)
|
||
}
|
||
|
||
/// A deterministic stream of uniform doubles in (0, 1) (SplitMix64).
|
||
pub struct Rng(pub u64);
|
||
|
||
impl Rng {
|
||
pub fn next(&mut self) -> f64 {
|
||
self.0 = self.0.wrapping_add(0x9e37_79b9_7f4a_7c15);
|
||
let mut z = self.0;
|
||
z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
|
||
z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
|
||
(((z ^ (z >> 31)) >> 11) as f64 + 0.5) / 9_007_199_254_740_992.0
|
||
}
|
||
}
|
||
|
||
/// `shots` draws from the distribution `p` (normalised), as counts.
|
||
pub fn draw(p: &[f64], shots: u64, rng: &mut Rng) -> Vec<u64> {
|
||
let mut cum = Vec::with_capacity(p.len());
|
||
let mut s = 0.0;
|
||
for &x in p {
|
||
s += x;
|
||
cum.push(s);
|
||
}
|
||
let mut counts = vec![0u64; p.len()];
|
||
for _ in 0..shots {
|
||
let u = rng.next() * s;
|
||
let k = cum.partition_point(|&c| c <= u).min(p.len() - 1);
|
||
counts[k] += 1;
|
||
}
|
||
counts
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 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);
|
||
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 `GBS_SIGNING_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("GBS_SIGNING_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 GBS_SIGNING_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"])
|
||
}
|
||
|
||
/// 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())
|
||
}
|