open-standards/wai/wai-quantum/tests/cuda_spd.rs
Transaction Science bc2ee2f516 wai-quantum: sparse Pauli dynamics on the GPU, with the CPU's term set, order and bits
The propagation's sum of terms can now live on a device (quantum_spd::device).
The device runs each step's arithmetic on every term at once: one multiply by
sin, one by cos, one addition where a new term meets an old one, and sign
changes, built without fused multiply-add. The host keeps everything that
decides the term set. A rotation's new terms are found in the table, dead
slots included, and the rest are appended in the order of the terms they came
from by an order-preserving prefix sum. The host counts dead and live terms
and compacts exactly when the CPU does. A truncation hands back the
magnitudes it dropped in table order, and the bound and the value are added
on the host in table order (expectation_on over an SpdDevice).

Ground truth first: HostDevice runs that protocol on the CPU, one term after
another, and is the CPU's propagation bit for bit, final table included, over
100 cases at one thread and many. The CUDA member (CudaSpd; kernels
quantum_fabric::spd, implementation cuda-spd) is certified on SPD_DEFAULT
against the CPU. The corpus is 40 cases on one to four words, with term and
weight caps, layered circuits whose dropped strings come back and are revived
in place, and one exact cancellation. Each case's output is every value,
bound, threshold reached and count, and the final table slot for slot, so a
device that keeps the numbers but not the term set or its order is caught: a
mutant that appended a revived string again survived a corpus of numbers
alone. All six mutants are killed. On the laptop GPU: certified, and 24
wider random cases up to two million terms and the 127-qubit kicked-Ising
circuit are the CPU's results bit for bit.

A pauli job asks with fabric (accelerator, or a member's id or key): its sum
of terms runs on that member, with the CPU's answers byte for byte, and the
result's fabric names the member and its certificate (kernel
wai.quantum.spd/propagate). cpu names the CPU, and a split is refused (J013).
auto keeps the method on the CPU, since no member is measured faster for it.
Its members are discovered apart from the state vector's, and wai-quantum
fabric lists them (pauli, optional, in the tools schema). The CUDA leg
certifies the propagation and kills its mutants; the placement legs run a
pauli job on each member, or see accelerator refused; the container legs
name the protocol's tests. Docs, help, both MCP servers' descriptions, the
guide and the changelog say so; agent docs re-blessed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-31 22:05:00 -04:00

104 lines
5.4 KiB
Rust

//! Sparse Pauli dynamics on the CUDA devices of the machine that runs this:
//! the CPU's result bit for bit (value, bound, threshold reached, peak, final
//! terms, term updates), or the certificate says not.
//!
//! The machine declares its devices in `WAI_QUANTUM_CUDA_EXPECT` (comma-
//! separated keys, or `none`), as for `cuda_member`; `WAI_QUANTUM_CUDA_REPORT`
//! names a file the established lines are appended to.
#![cfg(not(target_arch = "wasm32"))]
use std::io::Write;
use wai_quantum::quantum_fabric::spd::{MUTANTS, mutate};
use wai_quantum::quantum_fabric::{Certification, Parity};
use wai_quantum::quantum_fabric_cuda::{CudaMember, discover};
use wai_quantum::quantum_spd::device::{KERNEL_SPD, SPD_DEFAULT, SPD_DEFAULT_PIN, SpdCorpus, expectation_on};
use wai_quantum::quantum_spd::{SpdConfig, expectation_capped, heavy_hex_127, kicked_ising};
fn report(line: &str) {
println!("{line}");
if let Some(path) = std::env::var_os("WAI_QUANTUM_CUDA_REPORT") {
let mut f = std::fs::OpenOptions::new().create(true).append(true).open(&path).expect("the report file opens");
writeln!(f, "{line}").expect("the report line is written");
}
}
fn members() -> Vec<CudaMember> {
let d = discover();
let mut found: Vec<String> = d.found.iter().map(|m| m.key().to_string()).collect();
found.sort();
let found_list = if found.is_empty() { "none".to_string() } else { found.join(",") };
let expect = std::env::var("WAI_QUANTUM_CUDA_EXPECT").unwrap_or_else(|_| panic!("WAI_QUANTUM_CUDA_EXPECT is not set; found {found_list}"));
assert_eq!(found_list, expect, "the devices found are not the declared ones");
d.found
}
fn hex(b: &[u8]) -> String {
b.iter().map(|x| format!("{x:02x}")).collect()
}
/// The CPU certifying itself on `corpus`: the digest a bit-exact device
/// reproduces.
fn cpu_digest(corpus: SpdCorpus) -> String {
let mut cert = Certification::new();
for case in corpus.cases() {
let r = case.reference().unwrap();
cert.add(&case.bytes(), &r, &r);
}
hex(&cert.finish(KERNEL_SPD, "cpu", "cpu", &corpus.generator()).corpus)
}
#[test]
fn d0_the_propagation_is_the_cpus_bit_for_bit() {
let cpu = cpu_digest(SPD_DEFAULT);
assert_eq!(cpu, SPD_DEFAULT_PIN, "this machine's CPU does not reproduce the pin");
for m in members() {
let spd = m.spd().unwrap_or_else(|e| panic!("{}: {e}", m.key()));
let c = spd.certify(SPD_DEFAULT).unwrap_or_else(|e| panic!("{}: {e}", m.key()));
assert_eq!(c.parity, Parity::BitExact, "{}", m.key());
assert_eq!(hex(&c.corpus), cpu, "{}: the digest is not the CPU's", m.key());
report(&format!("cuda spd certified: key={} member={} kernel={} implementation={} parity=bit-exact cases={} digest={}", m.key(), m.id(), c.kernel, c.implementation, c.cases, hex(&c.corpus)));
}
}
/// Beyond the corpus: wider random cases, and the 127-qubit kicked-Ising
/// circuit on the heavy-hex map, whose sum grows past a hundred thousand
/// terms; each the CPU's result bit for bit.
#[test]
fn d1_large_sums_match_the_cpu() {
for m in members() {
let spd = m.spd().unwrap();
let mut largest = 0;
for case in (SpdCorpus { seed: 77, cases: 24, max_qubits: 20, depth: 160 }).cases() {
let want = expectation_capped(&case.circuit, &case.observable, &case.ones, &case.cfg, 8, case.max_terms).unwrap();
let got = expectation_on(&mut spd.run(), &case.circuit, &case.observable, &case.ones, &case.cfg, case.max_terms).unwrap();
assert_eq!((got.0.value.to_bits(), got.0.truncation_bound.to_bits(), got.0.peak_terms, got.0.final_terms, got.0.term_updates, got.1.to_bits()), (want.0.value.to_bits(), want.0.truncation_bound.to_bits(), want.0.peak_terms, want.0.final_terms, want.0.term_updates, want.1.to_bits()), "{}", m.key());
largest = largest.max(want.0.peak_terms);
}
let c = kicked_ising(127, &heavy_hex_127(), 0.6, 5);
let obs = vec![(1.0, vec![(62u32, wai_quantum::quantum_spd::Pauli::Z)])];
let cfg = SpdConfig { threshold: 1e-4, max_weight: None };
let want = expectation_capped(&c, &obs, &[], &cfg, 8, usize::MAX).unwrap();
let got = expectation_on(&mut spd.run(), &c, &obs, &[], &cfg, usize::MAX).unwrap();
assert_eq!((got.0.value.to_bits(), got.0.truncation_bound.to_bits(), got.0.peak_terms, got.0.final_terms, got.0.term_updates), (want.0.value.to_bits(), want.0.truncation_bound.to_bits(), want.0.peak_terms, want.0.final_terms, want.0.term_updates), "{}: kicked Ising", m.key());
report(&format!("cuda spd large: key={} 24 random cases (peak {largest} terms) and kicked Ising on 127 qubits (peak {} terms) match the CPU bit for bit", m.key(), want.0.peak_terms));
}
}
#[test]
fn d2_mutants_are_caught() {
let small = SPD_DEFAULT;
let cpu = cpu_digest(small);
for m in members() {
let mut killed = 0;
for mutant in &MUTANTS {
let spd = m.spd_with_source(&mutate(mutant)).unwrap_or_else(|e| panic!("mutant {}: {e}", mutant.name));
match spd.certify(small) {
Ok(c) => assert!(c.parity != Parity::BitExact || hex(&c.corpus) != cpu, "{}: mutant {} survived", m.key(), mutant.name),
// A mutant that makes the device fail is caught too.
Err(_) => {}
}
killed += 1;
}
report(&format!("cuda spd mutants: key={} killed {killed} of {}", m.key(), MUTANTS.len()));
}
}