open-standards/wai/wai-quantum/examples/gbs_loops.rs
Transaction Science a4efd4e634
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
quantum_gbs: a 2022 time-domain photonic boson sampler's samples against their exact ground truth
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]>
2026-11-12 02:44:00 -05:00

608 lines
34 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! The published samples of the 2022 time-domain Gaussian boson sampler
//! (Madsen et al., Nature 606, 75 (2022)) held to their ground truth, computed
//! exactly, with a signed receipt per run.
//!
//! ```text
//! cargo run --release --example gbs_loops --features quantum_gbs,quantum_engine,quantum_receipt -- DATA SET [options]
//! ```
//!
//! `DATA` holds the data set as its bucket lays it out (`DATA/fig2/T.npy`, …);
//! every file read is held to its pinned SHA-256 (`gbs_loops/common.rs`). `SET`
//! is one of `fig2` (16 modes), `fig3a` (216), `fig3b` (72), `fig4` (216) and
//! `figS15` (288). Options:
//!
//! - `--receipt PATH`: write the signed receipt there;
//! - `--counts PATH`: with `fig2`, write the observed count of every pattern
//! of up to four photons (the fixture the tests re-run);
//! - `--max-n N`: with `fig2`, compare every pattern of up to `N` photons (6,
//! as the paper does);
//! - `--scores A-B`, `--per-n K`: the paper's per-`N` scores for `N` in `A..=B`
//! over the first `K` samples with `N` photons: the cross entropy against
//! the ground truth (`fig3a`, as its Fig. 3a; 10,000 samples a point there)
//! or the Bayesian log-average against each classical hypothesis (`fig3b`,
//! as its Fig. 3b; 2,000 there);
//! - `--replicas R` (20), `--batches B` (100), `--threads T` (all).
//!
//! What it does, every step exact or counted:
//!
//! 1. rebuilds the instance's transfer matrix and unitary from its loop
//! program (`quantum_gbs::Loops`, the paper's Methods eq. 10) and measures
//! them against the published `T` and `U`;
//! 2. builds the ground truth, squeezed vacua `r` through `T`
//! (`GaussianState::through`), and four classical hypotheses with the same
//! mean photon number in every mode: thermal and squashed light through `T`,
//! coherent light, and distinguishable squeezed light;
//! 3. counts the samples: the total photon number, every mode's mean, every
//! pair's covariance (with standard errors from `B` batches), and, for the
//! 16-mode set, every pattern;
//! 4. compares: the total photon-number distribution with each hypothesis's,
//! exact (`photon_number_distribution`); the first- and second-order
//! cumulants with each hypothesis's, exact (`photon_moments`); for the
//! 16-mode set, every pattern's frequency with its exact probability
//! (`PhotonTable`, a hafnian each), as fidelity and total variation
//! distance within each photon number; and, for each, what the sample count
//! alone would give, from `R` sets of draws from the exact distribution.
#![allow(clippy::needless_range_loop)]
#[path = "gbs_loops/common.rs"]
mod common;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::time::Instant;
use serde_json::{Value, json};
use wai_quantum::quantum_gbs::{GaussianState, Input, Mat, PhotonTable};
struct Args {
data: PathBuf,
set: String,
receipt: Option<PathBuf>,
counts: Option<PathBuf>,
max_n: usize,
scores: Option<(usize, usize)>,
per_n: usize,
replicas: usize,
batches: usize,
threads: usize,
}
fn main() {
let a: Vec<String> = std::env::args().skip(1).collect();
let usage = "usage: gbs_loops DATA SET [--receipt PATH] [--counts PATH] [--max-n N] [--scores A-B] [--per-n K] [--replicas R] [--batches B] [--threads T]";
let opt = |k: &str| a.iter().position(|x| x == k).map(|i| a.get(i + 1).cloned().unwrap_or_else(|| panic!("{k} needs a value")));
let num = |k: &str, d: usize| opt(k).map_or(d, |v| v.parse().unwrap_or_else(|_| panic!("{k}: a number")));
let set = a.get(1).expect(usage).clone();
let modes = common::SETS.iter().find(|(s, _)| *s == set).unwrap_or_else(|| panic!("SET is one of fig2, fig3a, fig3b, fig4, figS15")).1;
let args = Args {
data: PathBuf::from(a.first().expect(usage)),
receipt: opt("--receipt").map(PathBuf::from),
counts: opt("--counts").map(PathBuf::from),
max_n: num("--max-n", 6).min(15),
scores: opt("--scores").map(|s| {
let (x, y) = s.split_once('-').expect("--scores A-B");
(x.parse().expect("A"), y.parse().expect("B"))
}),
per_n: num("--per-n", if set == "fig3b" { 2000 } else { 10_000 }),
replicas: num("--replicas", 20),
batches: num("--batches", 100),
threads: num("--threads", std::thread::available_parallelism().map_or(1, |t| t.get())),
set,
};
let ((request, result), secs, energy) = common::metered(|| run(&args, modes));
let mut result = result;
result["wall_seconds"] = json!(secs);
result["energy_figure"] = energy;
let doc = common::seal(request, result, args.receipt.as_deref());
println!("wall {secs:.1} s · energy {}", doc["result"]["energy_figure"].get("joules_micro").map_or("unmetered".to_string(), |j| format!("{:.1} J", j.as_u64().unwrap_or(0) as f64 / 1e6)));
if let Some(p) = &args.receipt {
println!("receipt {} (seal verifies: {})", p.display(), common::verifies(&doc));
}
}
/// `sinh² r`, the mean photon number of squeezed vacuum, from the engine.
fn nbar(r: f64) -> f64 {
GaussianState::through(&[Input::Squeezed(r)], &Mat::identity(1)).expect("a squeezed mode").photon_moments().0[0]
}
/// Run `f(i)` for `i in 0..n` on `threads` threads; results in index order.
fn parallel<T: Send>(n: usize, threads: usize, f: impl Fn(usize) -> T + Sync) -> Vec<T> {
let mut slots: Vec<Option<T>> = (0..n).map(|_| None).collect();
let threads = threads.clamp(1, n.max(1));
let chunk = n.div_ceil(threads).max(1);
std::thread::scope(|s| {
for (t, part) in slots.chunks_mut(chunk).enumerate() {
let f = &f;
s.spawn(move || {
for (k, slot) in part.iter_mut().enumerate() {
*slot = Some(f(t * chunk + k));
}
});
}
});
slots.into_iter().map(|x| x.expect("every slot filled")).collect()
}
/// One batch of samples, counted.
struct Batch {
shots: u64,
hist: Vec<u64>,
s1: Vec<u64>,
s2: Vec<u64>,
max_count: Vec<u8>,
negative: u64,
patterns: HashMap<u64, u64>,
}
fn count_batch(rows: &[u8], modes: usize, max_n: Option<usize>) -> Batch {
let mut b = Batch { shots: 0, hist: Vec::new(), s1: vec![0; modes], s2: vec![0; modes * modes], max_count: vec![0; modes], negative: 0, patterns: HashMap::new() };
let mut nz: Vec<(usize, u64)> = Vec::with_capacity(modes);
for row in rows.chunks(modes) {
b.shots += 1;
nz.clear();
let mut total = 0usize;
let mut clean = true;
for (i, &v) in row.iter().enumerate() {
if v > 127 {
// A negative entry: counted, and the sample kept out of the pattern table.
b.negative += 1;
clean = false;
continue;
}
if v > 0 {
nz.push((i, u64::from(v)));
total += v as usize;
b.max_count[i] = b.max_count[i].max(v);
}
}
if b.hist.len() <= total {
b.hist.resize(total + 1, 0);
}
b.hist[total] += 1;
for (x, &(i, ni)) in nz.iter().enumerate() {
b.s1[i] += ni;
for &(j, nj) in &nz[x..] {
b.s2[i * modes + j] += ni * nj;
}
}
if let Some(n) = max_n
&& total <= n
&& clean
{
*b.patterns.entry(common::pack(row)).or_insert(0) += 1;
}
}
b
}
/// The mean and standard error of a statistic across batches.
fn mean_se(xs: &[f64]) -> (f64, f64) {
let k = xs.len() as f64;
let m = xs.iter().sum::<f64>() / k;
let v = xs.iter().map(|x| (x - m) * (x - m)).sum::<f64>() / (k - 1.0);
(m, (v / k).sqrt())
}
/// How the observed pair cumulants compare with a hypothesis's: the root mean
/// square and the share within 3 of the standardised differences, the
/// correlation, and the least-squares slope of observed on predicted.
fn cumulant_agreement(obs: &[f64], se: &[f64], hyp: &[f64]) -> Value {
let k = obs.len() as f64;
let z: Vec<f64> = obs.iter().zip(se).zip(hyp).map(|((o, s), h)| (o - h) / s).collect();
let rms = (z.iter().map(|x| x * x).sum::<f64>() / k).sqrt();
let within3 = z.iter().filter(|x| x.abs() <= 3.0).count() as f64 / k;
let (mo, mh) = (obs.iter().sum::<f64>() / k, hyp.iter().sum::<f64>() / k);
let (mut sxy, mut sxx, mut syy, mut soh, mut shh) = (0.0, 0.0, 0.0, 0.0, 0.0);
for (o, h) in obs.iter().zip(hyp) {
sxy += (o - mo) * (h - mh);
sxx += (o - mo) * (o - mo);
syy += (h - mh) * (h - mh);
soh += o * h;
shh += h * h;
}
let corr = if syy > 0.0 { Some(sxy / (sxx * syy).sqrt()) } else { None };
let slope = if shh > 0.0 { Some(soh / shh) } else { None };
json!({ "rms_z": rms, "share_within_3": within3, "correlation": corr, "slope": slope, "mean_abs_diff": obs.iter().zip(hyp).map(|(o, h)| (o - h).abs()).sum::<f64>() / k })
}
fn tvd(p: &[f64], q: &[f64]) -> f64 {
let n = p.len().max(q.len());
(0..n).map(|i| (p.get(i).copied().unwrap_or(0.0) - q.get(i).copied().unwrap_or(0.0)).abs()).sum::<f64>() / 2.0
}
/// The coherent hypothesis's total: Poisson of mean `mu`, normalised over
/// `0..=nmax` (with `nmax` far past the mean, what is cut is below rounding).
fn poisson(mu: f64, nmax: usize) -> Vec<f64> {
let mut p = vec![1.0];
for n in 1..=nmax {
let last = p[n - 1];
p.push(last * mu / n as f64);
}
let s: f64 = p.iter().sum();
p.iter().map(|x| x / s).collect()
}
/// Convolution of distributions over photon numbers, cut at `nmax`.
fn convolve(a: &[f64], b: &[f64], nmax: usize) -> Vec<f64> {
let mut out = vec![0.0; nmax + 1];
for (i, x) in a.iter().enumerate() {
for (j, y) in b.iter().enumerate() {
if i + j <= nmax {
out[i + j] += x * y;
}
}
}
out
}
#[allow(clippy::too_many_lines)]
fn run(args: &Args, modes: usize) -> (Value, Value) {
let set = args.set.as_str();
let path = |f: &str| format!("{set}/{f}");
let root: &Path = &args.data;
let t0 = Instant::now();
let params = common::read_pinned(root, &path("program_params.json"));
let t_pub = common::complex_matrix(&common::read_pinned(root, &path("T.npy")));
let u_pub = common::complex_matrix(&common::read_pinned(root, &path("U.npy")));
let r_npy = common::real_vector(&common::read_pinned(root, &path("r.npy")));
let sample_bytes = common::read_pinned(root, &path("samples.npy"));
let read_secs = t0.elapsed().as_secs_f64();
assert_eq!(t_pub.n, modes, "the transfer matrix has the set's modes");
let m = modes;
println!("{set}: {m} modes; files read and checked against their pins in {read_secs:.1} s");
// 1. The transfer matrix and unitary, rebuilt from the program.
let prog = common::program(&params);
let (dt, qt) = common::distance_up_to_quarter_phase(&prog.transfer(m, false), &t_pub);
let (du, qu) = common::distance_up_to_quarter_phase(&prog.transfer(m, true), &u_pub);
println!("rebuilt from the loop program: max |T − published| {dt:.2e}, max |U − published| {du:.2e} (global phase i^{qt}, i^{qu})");
// The squeezing the published ground truth pairs with T's columns, and where it sits in
// the program's list (the two agree to the last bit or two, not bit for bit).
let r_gt: Vec<f64> = r_npy[..m].to_vec();
let offset = (0..=prog.r.len() - m).find(|&o| (0..m).all(|i| (prog.r[o + i] - r_gt[i]).abs() < 1e-12));
// 2. The hypotheses.
let nb: Vec<f64> = r_gt.iter().map(|&r| nbar(r)).collect();
let gt = GaussianState::through(&r_gt.iter().map(|&r| Input::Squeezed(r)).collect::<Vec<_>>(), &t_pub).expect("the ground truth");
let thermal = GaussianState::through(&nb.iter().map(|&n| Input::Thermal(n)).collect::<Vec<_>>(), &t_pub).expect("thermal");
let squashed = GaussianState::through(&nb.iter().map(|&n| Input::Squashed(n)).collect::<Vec<_>>(), &t_pub).expect("squashed");
// The ground truth with the program's first m squeezing values, where they differ from r.npy's.
let alt = (offset != Some(0)).then(|| {
let r0: Vec<f64> = prog.r[..m].to_vec();
(r0.clone(), GaussianState::through(&r0.iter().map(|&r| Input::Squeezed(r)).collect::<Vec<_>>(), &t_pub).expect("the alternative"))
});
// 3. The samples, counted in batches.
let t1 = Instant::now();
let (shots, sm, rows) = common::sample_rows(&sample_bytes);
assert_eq!(sm, m, "the samples have the set's modes");
let per = shots.div_ceil(args.batches);
let pattern_cap = (m <= 16).then_some(args.max_n);
let batches: Vec<Batch> = parallel(args.batches, args.threads, |b| {
let lo = (b * per).min(shots);
let hi = ((b + 1) * per).min(shots);
count_batch(&rows[lo * m..hi * m], m, pattern_cap)
});
let count_secs = t1.elapsed().as_secs_f64();
let k = batches.iter().map(|b| b.shots).sum::<u64>();
assert_eq!(k as usize, shots);
let negative: u64 = batches.iter().map(|b| b.negative).sum();
let nmax_obs = batches.iter().map(|b| b.hist.len()).max().unwrap() - 1;
let mut hist = vec![0u64; nmax_obs + 1];
let mut s1 = vec![0u64; m];
let mut s2 = vec![0u64; m * m];
let mut max_count = vec![0u8; m];
for b in &batches {
for (i, x) in b.hist.iter().enumerate() {
hist[i] += x;
}
for i in 0..m {
s1[i] += b.s1[i];
max_count[i] = max_count[i].max(b.max_count[i]);
}
for (x, y) in s2.iter_mut().zip(&b.s2) {
*x += y;
}
}
let kf = k as f64;
println!("{k} samples counted in {count_secs:.1} s; most photons in one sample {nmax_obs}; most in one detector {}; negative entries {negative}", max_count.iter().max().unwrap());
// 4a. The total photon number.
// Far enough past the largest observed total that the exact tail beyond is below rounding.
let nmax = nmax_obs + nmax_obs / 2 + 60;
let t2 = Instant::now();
let p_gt = gt.photon_number_distribution(nmax).expect("the ground truth's total");
let p_th = thermal.photon_number_distribution(nmax).expect("thermal total");
let p_sq = squashed.photon_number_distribution(nmax).expect("squashed total");
let (mean_gt, cov_gt) = gt.photon_moments();
let (_, cov_th) = thermal.photon_moments();
let (_, cov_sq) = squashed.photon_moments();
let mu_gt: f64 = mean_gt.iter().sum();
let var_gt: f64 = cov_gt.iter().sum();
let p_coh = poisson(mu_gt, nmax);
// Distinguishable squeezed light: each input on its own, its total that of
// one squeezed mode through its column's transmittance.
let mut p_di = vec![1.0];
let mut cov_di = vec![0.0; m * m];
for col in 0..m {
let eta: f64 = (0..m).map(|i| t_pub.at(i, col).re * t_pub.at(i, col).re + t_pub.at(i, col).im * t_pub.at(i, col).im).sum();
let one = GaussianState::through(&[Input::Squeezed(r_gt[col])], &Mat { n: 1, a: vec![wai_quantum::quantum_gbs::C { re: eta.sqrt(), im: 0.0 }] }).expect("one input");
p_di = convolve(&p_di, &one.photon_number_distribution(nmax).expect("one input's total"), nmax);
// Its pair cumulants: |T_ik T_jk|² (s⁴ + s² c²), with s² = n̄ and c² = n̄ + 1.
let s2k = nb[col];
for i in 0..m {
for j in 0..m {
let (a, b) = (t_pub.at(i, col), t_pub.at(j, col));
let w = (a.re * a.re + a.im * a.im) * (b.re * b.re + b.im * b.im);
cov_di[i * m + j] += if i == j { 0.0 } else { w * (s2k * s2k + s2k * (s2k + 1.0)) };
}
}
}
let exact_secs = t2.elapsed().as_secs_f64();
let h_obs: Vec<f64> = hist.iter().map(|&x| x as f64 / kf).collect();
let sum1: f64 = p_gt.iter().sum();
let pm1: f64 = p_gt.iter().enumerate().map(|(n, x)| n as f64 * x).sum();
let pm2: f64 = p_gt.iter().enumerate().map(|(n, x)| (n * n) as f64 * x).sum();
let obs_mean = hist.iter().enumerate().map(|(n, &c)| n as f64 * c as f64).sum::<f64>() / kf;
let obs_var = hist.iter().enumerate().map(|(n, &c)| (n as f64 - obs_mean) * (n as f64 - obs_mean) * c as f64).sum::<f64>() / (kf - 1.0);
let mode_obs = (0..hist.len()).max_by_key(|&n| (hist[n], std::cmp::Reverse(n))).unwrap();
// What K draws from the exact total alone give.
let floor: Vec<f64> = parallel(args.replicas, args.threads, |rep| {
let mut rng = common::Rng(0x5eed_0000 + rep as u64);
let c = common::draw(&p_gt, k, &mut rng);
tvd(&c.iter().map(|&x| x as f64 / kf).collect::<Vec<_>>(), &p_gt)
});
let (floor_mean, floor_se) = mean_se(&floor);
let floor_sd = floor_se * (args.replicas as f64).sqrt();
let totals = json!({
"observed": { "samples": k, "histogram": hist, "mean": obs_mean, "mean_se": (obs_var / kf).sqrt(), "variance": obs_var, "most_frequent": mode_obs, "max": nmax_obs },
"ground_truth": { "mean": mu_gt, "variance": var_gt, "most_probable": (0..=nmax).max_by(|&a, &b| p_gt[a].total_cmp(&p_gt[b])).unwrap(),
"distribution": p_gt, "check": { "sum": sum1, "mean_from_distribution": pm1, "variance_from_distribution": pm2 - pm1 * pm1 } },
"tvd": { "ground_truth": tvd(&h_obs, &p_gt), "thermal": tvd(&h_obs, &p_th), "squashed": tvd(&h_obs, &p_sq), "coherent": tvd(&h_obs, &p_coh), "distinguishable": tvd(&h_obs, &p_di) },
"tvd_from_sampling_alone": { "replicas": args.replicas, "mean": floor_mean, "sd": floor_sd },
});
println!("total photons: observed mean {obs_mean:.4} ± {:.4}, exact {mu_gt:.4}; TVD to exact {:.4} (sampling alone {floor_mean:.4} ± {floor_sd:.4}); thermal {:.4}, squashed {:.4}, coherent {:.4}, distinguishable {:.4}",
(obs_var / kf).sqrt(), totals["tvd"]["ground_truth"].as_f64().unwrap(), totals["tvd"]["thermal"].as_f64().unwrap(), totals["tvd"]["squashed"].as_f64().unwrap(), totals["tvd"]["coherent"].as_f64().unwrap(), totals["tvd"]["distinguishable"].as_f64().unwrap());
// 4b. First-order cumulants: every mode's mean.
let obs_means: Vec<f64> = s1.iter().map(|&x| x as f64 / kf).collect();
let z1: Vec<f64> = (0..m).map(|i| (obs_means[i] - mean_gt[i]) / (cov_gt[i * m + i] / kf).sqrt()).collect();
let rel1: Vec<f64> = (0..m).map(|i| obs_means[i] / mean_gt[i] - 1.0).collect();
let firsts = json!({
"observed": obs_means, "ground_truth": mean_gt,
"max_abs_z": z1.iter().fold(0.0f64, |a, z| a.max(z.abs())),
"rms_z": (z1.iter().map(|z| z * z).sum::<f64>() / m as f64).sqrt(),
"mean_relative_deviation": rel1.iter().sum::<f64>() / m as f64,
"max_abs_relative_deviation": rel1.iter().fold(0.0f64, |a, x| a.max(x.abs())),
});
println!("mode means: rms z {:.1}, max |z| {:.1}, mean relative deviation {:+.4}, largest {:.4}", firsts["rms_z"].as_f64().unwrap(), firsts["max_abs_z"].as_f64().unwrap(), firsts["mean_relative_deviation"].as_f64().unwrap(), firsts["max_abs_relative_deviation"].as_f64().unwrap());
// 4c. Second-order cumulants of every pair, with standard errors from the batches.
let pairs: Vec<(usize, usize)> = (0..m).flat_map(|i| (i + 1..m).map(move |j| (i, j))).collect();
let obs_c: Vec<f64> = pairs.iter().map(|&(i, j)| s2[i * m + j] as f64 / kf - (s1[i] as f64 / kf) * (s1[j] as f64 / kf)).collect();
let se_c: Vec<f64> = pairs
.iter()
.map(|&(i, j)| {
let per_batch: Vec<f64> = batches.iter().map(|b| {
let n = b.shots as f64;
b.s2[i * m + j] as f64 / n - (b.s1[i] as f64 / n) * (b.s1[j] as f64 / n)
}).collect();
mean_se(&per_batch).1
})
.collect();
let hyp = |c: &[f64]| -> Vec<f64> { pairs.iter().map(|&(i, j)| c[i * m + j]).collect() };
let zeros = vec![0.0; pairs.len()];
let median_se = {
let mut s = se_c.clone();
s.sort_by(f64::total_cmp);
s[s.len() / 2]
};
let seconds = json!({
"pairs": pairs.len(), "batches": args.batches,
"ground_truth": cumulant_agreement(&obs_c, &se_c, &hyp(&cov_gt)),
"thermal": cumulant_agreement(&obs_c, &se_c, &hyp(&cov_th)),
"squashed": cumulant_agreement(&obs_c, &se_c, &hyp(&cov_sq)),
"coherent": cumulant_agreement(&obs_c, &se_c, &zeros),
"distinguishable": cumulant_agreement(&obs_c, &se_c, &hyp(&cov_di)),
"median_se": median_se,
});
for h in ["ground_truth", "thermal", "squashed", "coherent", "distinguishable"] {
let x = &seconds[h];
println!("pair cumulants vs {h:15}: rms z {:8.2}, within 3σ {:.3}, correlation {}, slope {}", x["rms_z"].as_f64().unwrap(), x["share_within_3"].as_f64().unwrap(), x["correlation"].as_f64().map_or("—".into(), |v| format!("{v:.4}")), x["slope"].as_f64().map_or("—".into(), |v| format!("{v:.4}")));
}
let mut result = json!({
"set": set, "modes": m,
"rebuilt_from_program": { "max_abs_diff_T": dt, "max_abs_diff_U": du, "global_phase_quarter_turns_T": qt, "global_phase_quarter_turns_U": qu },
"squeezing": { "r_npy_length": r_npy.len(), "offset_in_program_list": offset, "program_list_length": prog.r.len() },
"totals": totals, "first_order": firsts, "second_order": seconds,
"phases_seconds": { "read_and_hash": read_secs, "count_samples": count_secs, "exact_totals_and_cumulants": exact_secs },
});
if let Some((r0, st)) = &alt {
let (mean_alt, _) = st.photon_moments();
let z: Vec<f64> = (0..m).map(|i| (obs_means[i] - mean_alt[i]) / (cov_gt[i * m + i] / kf).sqrt()).collect();
result["alternative_squeezing"] = json!({
"why": "the program's first m squeezing values, which the other four sets pair with their inputs, where r.npy holds others",
"r": r0,
"first_order_rms_z": (z.iter().map(|x| x * x).sum::<f64>() / m as f64).sqrt(),
});
}
// 4d. The 16-mode set: every pattern of up to max_n photons.
if m <= 16 {
let t3 = Instant::now();
let mut merged: HashMap<u64, u64> = HashMap::new();
for b in &batches {
for (key, c) in &b.patterns {
*merged.entry(*key).or_insert(0) += c;
}
}
let tables: Vec<(&str, PhotonTable)> = {
let mut v = vec![("ground_truth", gt.photon_table().unwrap()), ("thermal", thermal.photon_table().unwrap()), ("squashed", squashed.photon_table().unwrap())];
if let Some((_, st)) = &alt {
v.push(("alternative_squeezing", st.photon_table().unwrap()));
}
v
};
let mut sectors = Vec::new();
for n in 0..=args.max_n {
let pats = common::patterns(m, n);
let observed: Vec<u64> = pats.iter().map(|p| merged.get(&common::pack(p)).copied().unwrap_or(0)).collect();
let kn: u64 = observed.iter().sum();
let q: Vec<f64> = observed.iter().map(|&c| c as f64 / kn.max(1) as f64).collect();
let mut row = json!({ "n": n, "patterns": pats.len(), "samples": kn, "patterns_seen": observed.iter().filter(|&&c| c > 0).count() });
let mut p_ground = Vec::new();
for (name, table) in &tables {
let p: Vec<f64> = parallel(pats.len(), args.threads, |i| table.probability(&pats[i].iter().map(|&x| x as usize).collect::<Vec<_>>()).expect("a pattern"));
let s: f64 = p.iter().sum();
let pn: Vec<f64> = p.iter().map(|x| x / s).collect();
let (f, t) = common::fidelity_tvd(&pn, &q);
row[*name] = json!({ "fidelity": f, "tvd": t, "sector_probability": s, "fidelity_bits": common::bits(f), "tvd_bits": common::bits(t) });
if *name == "ground_truth" {
row["exact_sector_probability"] = json!(p_gt[n]);
p_ground = pn;
}
}
// Coherent light with the same mode means: a multinomial within the sector.
let mut pc: Vec<f64> = pats.iter().map(|p| p.iter().enumerate().fold(1.0, |acc, (i, &c)| {
let mut v = acc;
for d in 1..=c as usize {
v *= mean_gt[i] / d as f64;
}
v
})).collect();
let s: f64 = pc.iter().sum();
pc.iter_mut().for_each(|x| *x /= s);
let (f, t) = common::fidelity_tvd(&pc, &q);
row["coherent"] = json!({ "fidelity": f, "tvd": t });
// What kn draws from the exact distribution alone give.
let reps: Vec<(f64, f64)> = parallel(args.replicas, args.threads, |rep| {
let mut rng = common::Rng(0x0b5e_0000 + 1000 * n as u64 + rep as u64);
let c = common::draw(&p_ground, kn, &mut rng);
common::fidelity_tvd(&p_ground, &c.iter().map(|&x| x as f64 / kn.max(1) as f64).collect::<Vec<_>>())
});
let (fm, fse) = mean_se(&reps.iter().map(|x| x.0).collect::<Vec<_>>());
let (tm, tse) = mean_se(&reps.iter().map(|x| x.1).collect::<Vec<_>>());
let sd = (args.replicas as f64).sqrt();
row["from_sampling_alone"] = json!({ "replicas": args.replicas, "fidelity_mean": fm, "fidelity_sd": fse * sd, "tvd_mean": tm, "tvd_sd": tse * sd });
println!(
"N = {n}: {kn:>9} samples over {:>6} patterns · ground truth F {:.4} TVD {:.4} (sampling alone F {fm:.4} TVD {tm:.4} ± {:.4}) · thermal TVD {:.4} · squashed {:.4} · coherent {:.4}{}",
pats.len(),
row["ground_truth"]["fidelity"].as_f64().unwrap(),
row["ground_truth"]["tvd"].as_f64().unwrap(),
tse * sd,
row["thermal"]["tvd"].as_f64().unwrap(),
row["squashed"]["tvd"].as_f64().unwrap(),
row["coherent"]["tvd"].as_f64().unwrap(),
row.get("alternative_squeezing").map_or(String::new(), |a| format!(" · alternative squeezing TVD {:.4}", a["tvd"].as_f64().unwrap()))
);
sectors.push(row);
}
result["patterns"] = json!({ "sectors": sectors, "seconds": t3.elapsed().as_secs_f64() });
if let Some(path) = &args.counts {
let text = common::counts_text(m, 4.min(args.max_n), |p| merged.get(&common::pack(p)).copied().unwrap_or(0));
std::fs::write(path, &text).unwrap_or_else(|e| panic!("writing {}: {e}", path.display()));
result["pattern_counts_file"] = json!({ "max_n": 4.min(args.max_n), "sha256": common::hex(&common::sha256(text.as_bytes())) });
println!("pattern counts for N ≤ 4 written to {}", path.display());
}
}
// 4e. The paper's per-N scores, over the first K samples with N photons.
if let Some((lo, hi)) = args.scores {
let t4 = Instant::now();
let mut by_n: Vec<Vec<Vec<usize>>> = vec![Vec::new(); hi + 1];
for row in rows.chunks(m) {
let n: usize = row.iter().map(|&v| v as usize).sum();
if (lo..=hi).contains(&n) && by_n[n].len() < args.per_n {
by_n[n].push(row.iter().map(|&v| v as usize).collect());
}
}
let t_gt = gt.photon_table().unwrap();
let t_th = thermal.photon_table().unwrap();
let t_sq = squashed.photon_table().unwrap();
let ln_binom = |n: usize, k: usize| -> f64 { (0..k).map(|i| ((n - i) as f64).ln() - ((i + 1) as f64).ln()).sum() };
let mut rows_out = Vec::new();
for n in lo..=hi {
let samples = &by_n[n];
if samples.is_empty() {
continue;
}
// From N = 12 each hafnian runs on every core itself.
let outer = if n >= 12 { 1 } else { args.threads };
let probs: Vec<(f64, f64, f64)> = parallel(samples.len(), outer, |i| {
(t_gt.probability(&samples[i]).unwrap(), t_th.probability(&samples[i]).unwrap(), t_sq.probability(&samples[i]).unwrap())
});
// Cross entropy: ln(Pr(S) / (Pr(N) / C(N+M−1, N))).
let ln_uniform = p_gt[n].ln() - ln_binom(n + m - 1, n);
let xe: Vec<f64> = probs.iter().map(|p| p.0.ln() - ln_uniform).collect();
// Bayesian log-average against each hypothesis: ln(Pr₀(S) Pr_I(N) / (Pr_I(S) Pr₀(N))).
let coh = |s: &[usize]| -> f64 {
// Multinomial ln: ln N! − Σ ln nᵢ! + Σ nᵢ ln(μᵢ/μ).
let mut v: f64 = (1..=n).map(|d| (d as f64).ln()).sum();
for (i, &c) in s.iter().enumerate() {
v -= (1..=c).map(|d| (d as f64).ln()).sum::<f64>();
v += c as f64 * (mean_gt[i] / mu_gt).ln();
}
v
};
let b_th: Vec<f64> = probs.iter().map(|p| (p.0 / p_gt[n]).ln() - (p.1 / p_th[n]).ln()).collect();
let b_sq: Vec<f64> = probs.iter().map(|p| (p.0 / p_gt[n]).ln() - (p.2 / p_sq[n]).ln()).collect();
let b_co: Vec<f64> = probs.iter().zip(samples).map(|(p, s)| (p.0 / p_gt[n]).ln() - coh(s)).collect();
let ms = |v: &[f64]| -> Value {
let (mean, se) = if v.len() > 1 { let k = v.len() as f64; let mu = v.iter().sum::<f64>() / k; (mu, (v.iter().map(|x| (x - mu) * (x - mu)).sum::<f64>() / (k - 1.0) / k).sqrt()) } else { (v[0], f64::NAN) };
json!({ "mean": mean, "se": se })
};
let row = json!({ "n": n, "samples": samples.len(), "cross_entropy": ms(&xe), "bayes_vs_thermal": ms(&b_th), "bayes_vs_squashed": ms(&b_sq), "bayes_vs_coherent": ms(&b_co) });
println!(
"N = {n}: {} samples · cross entropy {:.4} ± {:.4} · Bayes vs thermal {:.3} ± {:.3}, squashed {:.3} ± {:.3}, coherent {:.3} ± {:.3}",
samples.len(),
row["cross_entropy"]["mean"].as_f64().unwrap(), row["cross_entropy"]["se"].as_f64().unwrap_or(f64::NAN),
row["bayes_vs_thermal"]["mean"].as_f64().unwrap(), row["bayes_vs_thermal"]["se"].as_f64().unwrap_or(f64::NAN),
row["bayes_vs_squashed"]["mean"].as_f64().unwrap(), row["bayes_vs_squashed"]["se"].as_f64().unwrap_or(f64::NAN),
row["bayes_vs_coherent"]["mean"].as_f64().unwrap(), row["bayes_vs_coherent"]["se"].as_f64().unwrap_or(f64::NAN),
);
rows_out.push(row);
}
result["scores"] = json!({ "per_n_cap": args.per_n, "selection": "the first samples in file order with each N", "rows": rows_out, "seconds": t4.elapsed().as_secs_f64(),
"logarithms": "the platform's f64 ln: these scores are not bit-identical across platforms, unlike the rest of the result" });
}
let mut sources: Vec<Value> = ["program_params.json", "T.npy", "U.npy", "r.npy", "samples.npy"].iter().map(|f| common::source(&path(f))).collect();
sources.push(json!({ "repository": common::REPOSITORY, "commit": common::COMMIT, "path": "LICENSE", "sha256": common::LICENSE_SHA256, "licence": "Apache-2.0" }));
let request = json!({
"experiment": "time-domain photonic Gaussian boson sampling: Madsen et al., Nature 606, 75 (2022), doi:10.1038/s41586-022-04725-x",
"set": set,
"source": sources,
"ground_truth": "squeezed vacua with r.npy's first m values through the published T (the paper's (r, T))",
"hypotheses": {
"thermal": "thermal states with the same mean photon number per input, through T",
"squashed": "squashed states (vacuum noise in one quadrature) with the same mean photon number per input, through T",
"coherent": "coherent light with the ground truth's mean in every mode: independent Poisson counts",
"distinguishable": "each squeezed input on its own through T, the outputs added",
},
"method": {
"transfer": "quantum_gbs::Loops from program_params.json (delays 1, 6, 36; detection common × channel (bin mod 16))",
"patterns": "quantum_gbs::PhotonTable: Haf(A_n)/(Π nᵢ! √det Q) for every pattern of up to max_n photons",
"totals": "quantum_gbs::GaussianState::photon_number_distribution",
"cumulants": "quantum_gbs::GaussianState::photon_moments",
"standard_errors": "spread of the statistic across contiguous batches of samples",
"sampling_alone": "draws of the observed sample count from the exact distribution, SplitMix64 seeds 0x0b5e0000 + 1000 N + replica (patterns) and 0x5eed0000 + replica (totals)",
},
"settings": { "max_n": args.max_n, "replicas": args.replicas, "batches": args.batches, "scores": args.scores.map(|(a, b)| json!([a, b])), "per_n": args.per_n },
"published": published(set),
});
(request, result)
}
/// What the paper states for each set, as its text gives it.
fn published(set: &str) -> Value {
match set {
"fig2" => json!({ "samples": "84.1 × 10⁶", "photon_numbers_compared": "3, 4, 5 and 6", "claim": "fidelities in excess of 99% and TVDs below or equal to 6.5% for the total photon-number sectors considered", "normalisation": "pᵢ and qᵢ normalised by the probability of their total photon number" }),
"fig3a" => json!({ "mean_photons": "21.120 ± 0.006", "modes": 216, "scores": "cross entropy over 10,000 samples per N, N = 10…26, above every classical spoofer" }),
"fig3b" => json!({ "mean_photons": "22.416 ± 0.006", "modes": 72, "scores": "Bayesian log-average over 2,000 samples per N, N = 10…26, above zero against squashed, thermal, coherent and distinguishable squeezed hypotheses" }),
"fig4" => json!({ "samples": "10⁶", "mean_photons": "centred at 125 (every hypothesis has the ground truth's mode means)", "most_probable_observed": "N = 124.35 ± 0.02", "max_observed": 219, "claim": "the ground truth is the only hypothesis that explains the pair cumulants well" }),
"figS15" => json!({ "modes": 288, "note": "shown in the Supplementary Information; no value is stated in the main text" }),
_ => Value::Null,
}
}