All checks were successful
wai-quantum CUDA member / member · CUDA (jetson-hub) (pull_request) Successful in 16s
wai-quantum OpenCL member / member · OpenCL GPU and CPU (jetson-hub) (pull_request) Successful in 37s
wai-quantum WebGPU member / member · discrete GPU, Vulkan (jetson-hub) (pull_request) Successful in 12m24s
spec citations resolve / Code cites no section its SPEC lacks (pull_request) Successful in 5s
standards workspaces build + test / arl-rs (pull_request) Successful in 1m12s
standards workspaces build + test / eoc-rs (pull_request) Successful in 2m4s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m38s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 1m29s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 1m19s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m59s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m39s
standards workspaces build + test / sense-rs (pull_request) Successful in 1m8s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 1m27s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 4m13s
lockfiles / every Cargo.lock is current (pull_request) Successful in 32s
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Successful in 4s
no-vanity-metrics / WAI public text names no comparison (pull_request) Successful in 4s
no-vanity-metrics / WAI public text names no company or product (pull_request) Successful in 4s
no-vanity-metrics / No new orphan source files (pull_request) Successful in 6s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 2m17s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 14s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 6m31s
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 3m44s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 9m40s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 1m37s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 9s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 28s
standards workspaces build + test / aac-rs (pull_request) Successful in 2m58s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 58s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 50s
standards workspaces build + test / map-rs (pull_request) Successful in 58s
standards workspaces build + test / mesh-rs (pull_request) Successful in 59s
tests in CI / every test file is run by a job or listed with its reason (pull_request) Successful in 5s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 2m7s
WAI HTTP delivery / WAI HTTP · does this change touch it (pull_request) Successful in 5s
WAI HTTP delivery / HTTP corpus · falsify mutations 0/3 (pull_request) Has been skipped
WAI HTTP delivery / HTTP corpus · falsify mutations 1/3 (pull_request) Has been skipped
WAI HTTP delivery / HTTP corpus · falsify mutations 2/3 (pull_request) Has been skipped
WAI HTTP delivery / HTTP corpus · falsify patches 0/3 (pull_request) Has been skipped
WAI HTTP delivery / HTTP corpus · falsify patches 1/3 (pull_request) Has been skipped
WAI HTTP delivery / HTTP corpus · falsify patches 2/3 (pull_request) Has been skipped
WAI HTTP delivery / HTTP corpus · every patch and mutation in exactly one shard (pull_request) Has been skipped
WAI HTTP delivery / HTTP corpus · verified both ways, and the differential (pull_request) Has been skipped
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Has been skipped
standards workspaces build + test / openpay (pull_request) Successful in 3m0s
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Successful in 1m25s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 26s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 7m48s
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Successful in 1m15s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 5s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 21s
wai-quantum CUDA member / member · CUDA absent (linux) (pull_request) Successful in 20s
wai-quantum FPGA member / member · FPGA simulation absent (linux) (pull_request) Successful in 17s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 43s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 2m10s
workflow hygiene / every workflow supersedes its own queue (pull_request) Successful in 5s
wai-quantum OpenCL member / member · OpenCL absent (linux) (pull_request) Successful in 20s
wai-quantum-qpu / clients against their contracts (linux) (pull_request) Successful in 19s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 1m42s
wai-quantum FPGA member / member · FPGA datapath, simulated (macos) (pull_request) Successful in 1m11s
wai-quantum WebGPU member / shader checks · browser build · wasip2 inertness · MSRV (pull_request) Successful in 2m43s
wai-quantum OpenCL member / member · OpenCL GPU (macos) (pull_request) Successful in 21s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 9m10s
wai-quantum WebGPU member / member · software Vulkan (linux) (pull_request) Successful in 1m57s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 17m29s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 5s
wai-quantum WebGPU member / member · Metal (macos) (pull_request) Successful in 6m8s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 3m22s
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Successful in 5m25s
wai-quantum WebGPU member / member · browser WebGPU (macos) (pull_request) Successful in 3m46s
wai-pack / wai-pack · linux (pull_request) Successful in 1m40s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 33s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 2m23s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 1m34s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 4m47s
wai-pack / wai-pack · macos (pull_request) Successful in 3m30s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m57s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 5m3s
Breaking: Certificate gains a public field, provenance; a silicon certificate hashes exactly as before, and every pinned digest is unchanged. quantum_fabric now includes quantum_toolchain. - Provenance (silicon / emulator / simulator) inside the certificate hash; placement ranks silicon first. - Sampling members: the sample kernels, the Sampler trait, statistical certification on the qpu-v1 corpus, a virtual QPU, and a virtual device that compiles each circuit for its coupling map and native basis. - quantum_toolchain::device: OpenQASM 2/3 in standard gates only, multi- controlled gates lowered exactly, every qubit measured; counts read back. quantum_toolchain::native: rz/sx/x/cz, prx/cz and gpi/gpi2/zz (turns), routed over a coupling map and verified against the circuit by simulation. from_qasm reads ch. - The OpenCL member (quantum_fabric_opencl), bit-exact on GPUs and CPUs. - The FPGA member, simulated (quantum_fabric_fpga): a Verilog datapath compiled by Verilator at run time; certificates say simulator. - The dense f64 state vector on accelerators (quantum_fabric::sv64): the CPU's own launches with no fused multiply-add, on CUDA and on OpenCL devices with binary64, certified against a pinned digest. - wai-quantum-qpu 0.1.0: clients for three quantum processing protocols, the wai-qpu command, curl transport with secrets off command lines. - CI: OpenCL, FPGA and QPU-client workflows; the CUDA and OpenCL hardware jobs certify the f64 state vector; the hardware host declares its devices. Co-Authored-By: Claude Opus 5.5 <[email protected]>
501 lines
28 KiB
Rust
501 lines
28 KiB
Rust
//! The clients against services that follow each provider's published
|
||
//! contract: each fake parses the submitted program in that provider's own
|
||
//! format, runs it on the crate's simulator, and answers in that provider's
|
||
//! result shape and bit order. A client whose request, units or bit order
|
||
//! departs from the contract reads back the wrong distribution, and these
|
||
//! tests fail.
|
||
//!
|
||
//! The fakes encode the contracts as published; the first job on each real
|
||
//! service is still [`probe_circuit`], which settles its bit order there.
|
||
|
||
use serde_json::{Value, json};
|
||
use std::collections::HashMap;
|
||
use std::f64::consts::PI;
|
||
use std::sync::Mutex;
|
||
use wai_quantum::quantum::{Circuit, Gateset};
|
||
use wai_quantum::quantum_fabric::sampling::{QPU_DEFAULT, reference_distribution, sample_kernel};
|
||
use wai_quantum::quantum_fabric::{FabricClass, Parity, Provenance};
|
||
use wai_quantum::quantum_toolchain::from_qasm;
|
||
use wai_quantum_qpu::sampler_v2::SamplerV2;
|
||
use wai_quantum_qpu::native_turns::NativeTurns;
|
||
use wai_quantum_qpu::prx_circuits::PrxCircuits;
|
||
use wai_quantum_qpu::transport::{Method, Request, Response, Scripted, Transport};
|
||
use wai_quantum_qpu::{Provider, QpuError, QpuSampler, Secret, Wait, check_probe, counts_from_probabilities, probe_circuit};
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Running what a fake receives
|
||
// ---------------------------------------------------------------------------
|
||
|
||
/// A radian angle that is a dyadic multiple of π, as the reader writes it.
|
||
fn angle_text(rad: f64) -> String {
|
||
if rad == 0.0 {
|
||
return "0".into();
|
||
}
|
||
for j in 0..=40 {
|
||
let m = rad / PI * 2f64.powi(j);
|
||
if (m - m.round()).abs() < 1e-6 {
|
||
let m = m.round() as i64;
|
||
return if j == 0 { format!("{m}*pi") } else { format!("{m}*pi/{}", 1u64 << j) };
|
||
}
|
||
}
|
||
panic!("the fake received an angle that is no dyadic multiple of π: {rad}")
|
||
}
|
||
|
||
/// The probabilities of `width` physical qubits after `body`, a program in the
|
||
/// gates the reader takes on `q[0..width]`.
|
||
fn physical_distribution(width: usize, body: &str) -> Vec<f64> {
|
||
let text = format!("OPENQASM 3.0;\nqubit[{width}] q;\n{body}");
|
||
let c = from_qasm(&text).unwrap_or_else(|e| panic!("the fake cannot read the program: {e}\n{text}"));
|
||
reference_distribution(&c).expect("the fake runs the program")
|
||
}
|
||
|
||
/// Exact-as-possible counts over the classical register: physical outcome `x`
|
||
/// gives `c[i] = bit measured[i] of x`.
|
||
fn register_counts(p: &[f64], measured: &[usize], shots: u64) -> HashMap<u64, u64> {
|
||
let mut reg = vec![0.0; 1 << measured.len()];
|
||
for (x, &px) in p.iter().enumerate() {
|
||
let r = measured.iter().enumerate().fold(0usize, |r, (i, &q)| r | (((x >> q) & 1) << i));
|
||
reg[r] += px;
|
||
}
|
||
counts_from_probabilities(®, shots).into_iter().enumerate().filter(|&(_, k)| k > 0).map(|(r, k)| (r as u64, k)).collect()
|
||
}
|
||
|
||
fn not_found(req: &Request) -> Response {
|
||
Response::json(404, &json!({"error": format!("no route {} {}", req.method.as_str(), req.url)}))
|
||
}
|
||
|
||
fn body_json(req: &Request) -> Value {
|
||
serde_json::from_slice(req.body.as_deref().expect("a body")).expect("a JSON body")
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Sampler V2: an IAM token, then OpenQASM 3 in rz/sx/x/cz on `$p`
|
||
// ---------------------------------------------------------------------------
|
||
|
||
const SAMPLER_EDGES: [[u16; 2]; 7] = [[0, 1], [1, 2], [3, 4], [4, 5], [0, 3], [1, 4], [2, 5]];
|
||
|
||
struct SamplerV2Service {
|
||
jobs: Mutex<HashMap<String, (u32, Vec<String>)>>,
|
||
/// Write `c[0]` as the most significant bit: the wrong convention, for the
|
||
/// probe's test.
|
||
flip_bit_order: bool,
|
||
fail_jobs: bool,
|
||
}
|
||
|
||
impl SamplerV2Service {
|
||
fn new() -> Self {
|
||
SamplerV2Service { jobs: Mutex::new(HashMap::new()), flip_bit_order: false, fail_jobs: false }
|
||
}
|
||
|
||
fn answer(&self, req: &Request) -> Response {
|
||
if req.url == "https://iam.test/identity/token" {
|
||
let body = String::from_utf8(req.body.clone().unwrap()).unwrap();
|
||
assert!(body.starts_with("grant_type=urn%3Aibm%3Aparams%3Aoauth%3Agrant-type%3Aapikey&apikey="), "{body}"); // allow-name: a protocol grant type
|
||
return Response::json(200, &json!({"access_token": "tok-1", "token_type": "Bearer", "expires_in": 3600, "expiration": 4_000_000_000u64}));
|
||
}
|
||
let authed = req.header_value("Authorization") == Some("Bearer tok-1") && req.header_value("Service-CRN") == Some("crn:test") && req.header_value(wai_quantum_qpu::sampler_v2::VERSION_HEADER) == Some("2026-04-15");
|
||
if !authed {
|
||
return Response::json(401, &json!({"error": "unauthorised"}));
|
||
}
|
||
let path = req.url.strip_prefix("https://sampler.test/api/v1").unwrap_or("");
|
||
match (req.method, path) {
|
||
(Method::Get, "/backends/test_device/configuration") => {
|
||
Response::json(200, &json!({"backend_name": "test_device", "n_qubits": 6, "basis_gates": ["cz", "id", "rz", "sx", "x"], "coupling_map": [[0, 1]], "gates": [{"name": "cz", "coupling_map": SAMPLER_EDGES.iter().flat_map(|e| [[e[0], e[1]], [e[1], e[0]]]).collect::<Vec<_>>()}]}))
|
||
}
|
||
(Method::Post, "/jobs") => {
|
||
let b = body_json(req);
|
||
assert_eq!(b["program_id"], "sampler");
|
||
assert_eq!(b["params"]["version"], 2);
|
||
let pub0 = &b["params"]["pubs"][0];
|
||
let (qasm, shots) = (pub0[0].as_str().unwrap(), pub0[2].as_u64().unwrap());
|
||
assert!(pub0[1].is_null());
|
||
let mut body = String::new();
|
||
let mut measured: Vec<(usize, usize)> = Vec::new();
|
||
for line in qasm.lines() {
|
||
let line = line.trim();
|
||
if line.starts_with("OPENQASM") || line.starts_with("include") || line.starts_with("bit[") || line.is_empty() {
|
||
continue;
|
||
}
|
||
assert!(!line.starts_with("gate "), "an ISA circuit defines no gate: {line}");
|
||
if let Some(rest) = line.strip_prefix("c[") {
|
||
let (i, p) = rest.split_once("] = measure $").expect("c[i] = measure $p;");
|
||
measured.push((i.parse().unwrap(), p.trim_end_matches(';').parse().unwrap()));
|
||
continue;
|
||
}
|
||
let name: String = line.chars().take_while(|c| c.is_ascii_alphanumeric()).collect();
|
||
assert!(["rz", "sx", "x", "cz"].contains(&name.as_str()), "only basis gates: {line}");
|
||
if name == "cz" {
|
||
let qs: Vec<u16> = line[2..].trim_end_matches(';').split(',').map(|q| q.trim().trim_start_matches('$').parse().unwrap()).collect();
|
||
assert!(SAMPLER_EDGES.iter().any(|e| (e[0], e[1]) == (qs[0], qs[1]) || (e[1], e[0]) == (qs[0], qs[1])), "cz on an unconnected pair: {line}");
|
||
}
|
||
body.push_str(&line.replace('$', "q[").replace(';', "];").replace(", q[", "],q["));
|
||
body.push('\n');
|
||
}
|
||
measured.sort();
|
||
let order: Vec<usize> = measured.iter().map(|&(_, p)| p).collect();
|
||
let p = physical_distribution(6, &body);
|
||
let n = order.len();
|
||
let mut samples = Vec::new();
|
||
for (reg, k) in register_counts(&p, &order, shots) {
|
||
let v = if self.flip_bit_order { (0..n).fold(0u64, |v, i| v | (((reg >> i) & 1) << (n - 1 - i))) } else { reg };
|
||
for _ in 0..k {
|
||
samples.push(format!("0x{v:x}"));
|
||
}
|
||
}
|
||
let id = format!("job-{}", self.jobs.lock().unwrap().len());
|
||
self.jobs.lock().unwrap().insert(id.clone(), (0, samples));
|
||
Response::json(200, &json!({"id": id, "backend": "test_device"}))
|
||
}
|
||
(Method::Get, p) if p.starts_with("/jobs/") && p.ends_with("/results") => {
|
||
let id = &p["/jobs/".len()..p.len() - "/results".len()];
|
||
let jobs = self.jobs.lock().unwrap();
|
||
Response::json(200, &json!({"results": [{"data": {"c": {"samples": jobs[id].1}}, "metadata": {}}], "metadata": {"version": 2}}))
|
||
}
|
||
(Method::Get, p) if p.starts_with("/jobs/") => {
|
||
let id = p["/jobs/".len()..].split('?').next().unwrap();
|
||
let mut jobs = self.jobs.lock().unwrap();
|
||
let job = jobs.get_mut(id).unwrap();
|
||
job.0 += 1;
|
||
let status = if self.fail_jobs { "Failed" } else if job.0 < 3 { "Queued" } else { "Completed" };
|
||
Response::json(200, &json!({"id": id, "state": {"status": status, "reason": "the fake failed it"}}))
|
||
}
|
||
_ => not_found(req),
|
||
}
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Phased-X circuits: prx/cz in radians on named qubits, one measurement key per bit
|
||
// ---------------------------------------------------------------------------
|
||
|
||
struct PrxService {
|
||
jobs: Mutex<HashMap<String, (u32, Value)>>,
|
||
}
|
||
|
||
const PRX_LOCI: [[&str; 2]; 5] = [["QB1", "QB2"], ["QB2", "QB3"], ["QB3", "QB4"], ["QB4", "QB5"], ["QB2", "QB5"]];
|
||
|
||
impl PrxService {
|
||
fn answer(&self, req: &Request) -> Response {
|
||
if req.header_value("Authorization") != Some("Bearer prx-token") {
|
||
return Response::json(401, &json!({"detail": "unauthorised"}));
|
||
}
|
||
let path = req.url.strip_prefix("https://prx.test/api/v1").unwrap_or("");
|
||
match (req.method, path) {
|
||
(Method::Get, "/calibration-sets/test-qc/default/dynamic-quantum-architecture") => Response::json(
|
||
200,
|
||
&json!({"calibration_set_id": "00000000-0000-0000-0000-000000000000", "qubits": ["QB1", "QB2", "QB3", "QB4", "QB5"], "computational_resonators": [],
|
||
"gates": {"prx": {"implementations": {"drag_gaussian": {"loci": [["QB1"], ["QB2"], ["QB3"], ["QB4"], ["QB5"]]}}, "default_implementation": "drag_gaussian"},
|
||
"cz": {"implementations": {"tgss": {"loci": PRX_LOCI}}, "default_implementation": "tgss", "override_default_implementation": {}},
|
||
"measure": {"implementations": {"constant": {"loci": [["QB1"], ["QB2"], ["QB3"], ["QB4"], ["QB5"]]}}}}}),
|
||
),
|
||
(Method::Post, "/jobs/test-qc/circuit") => {
|
||
let b = body_json(req);
|
||
let keys: Vec<&str> = b.as_object().unwrap().keys().map(|k| k.as_str()).collect();
|
||
assert!(keys.iter().all(|k| ["circuits", "shots"].contains(k)), "no field the contract does not name: {keys:?}");
|
||
let shots = b["shots"].as_u64().unwrap();
|
||
let qb = |name: &Value| -> usize { name.as_str().unwrap().strip_prefix("QB").unwrap().parse::<usize>().unwrap() - 1 };
|
||
let mut body = String::new();
|
||
let mut measured: Vec<(usize, usize)> = Vec::new();
|
||
for ins in b["circuits"][0]["instructions"].as_array().unwrap() {
|
||
let locus = ins["locus"].as_array().unwrap();
|
||
match ins["name"].as_str().unwrap() {
|
||
"prx" => {
|
||
let (theta, phi, q) = (ins["args"]["angle"].as_f64().unwrap(), ins["args"]["phase"].as_f64().unwrap(), qb(&locus[0]));
|
||
// PRX(θ, φ) = RZ(φ)·RX(θ)·RZ(−φ).
|
||
body.push_str(&format!("rz({}) q[{q}];\nrx({}) q[{q}];\nrz({}) q[{q}];\n", angle_text(-phi), angle_text(theta), angle_text(phi)));
|
||
}
|
||
"cz" => {
|
||
let (a, c) = (locus[0].as_str().unwrap(), locus[1].as_str().unwrap());
|
||
assert!(PRX_LOCI.iter().any(|l| (l[0], l[1]) == (a, c) || (l[1], l[0]) == (a, c)), "cz on an uncalibrated pair {a}, {c}");
|
||
body.push_str(&format!("cz q[{}],q[{}];\n", qb(&locus[0]), qb(&locus[1])));
|
||
}
|
||
"measure" => {
|
||
let key = ins["args"]["key"].as_str().unwrap();
|
||
measured.push((key.strip_prefix('c').unwrap().parse().unwrap(), qb(&locus[0])));
|
||
}
|
||
other => panic!("not one of the service's instructions: {other}"),
|
||
}
|
||
}
|
||
measured.sort();
|
||
let order: Vec<usize> = measured.iter().map(|&(_, q)| q).collect();
|
||
let n = order.len();
|
||
let p = physical_distribution(5, &body);
|
||
// Keys listed last bit first, so a client must use the order given.
|
||
let keys: Vec<String> = (0..n).rev().map(|i| format!("c{i}")).collect();
|
||
let mut counts = serde_json::Map::new();
|
||
for (reg, k) in register_counts(&p, &order, shots) {
|
||
let bits: String = (0..n).rev().map(|i| if (reg >> i) & 1 == 1 { '1' } else { '0' }).collect();
|
||
counts.insert(bits, json!(k));
|
||
}
|
||
let id = format!("prx-{}", self.jobs.lock().unwrap().len());
|
||
self.jobs.lock().unwrap().insert(id.clone(), (0, json!([{"measurement_keys": keys, "counts": counts}])));
|
||
Response::json(200, &json!({"id": id, "type": "circuit", "status": "waiting"}))
|
||
}
|
||
(Method::Get, p) if p.ends_with("/artifacts/measurement_counts") => {
|
||
let id = &p["/jobs/".len()..p.len() - "/artifacts/measurement_counts".len()];
|
||
Response::json(200, &self.jobs.lock().unwrap()[id].1)
|
||
}
|
||
(Method::Get, p) if p.starts_with("/jobs/") => {
|
||
let id = &p["/jobs/".len()..];
|
||
let mut jobs = self.jobs.lock().unwrap();
|
||
let job = jobs.get_mut(id).unwrap();
|
||
job.0 += 1;
|
||
Response::json(200, &json!({"id": id, "status": if job.0 < 2 { "processing" } else { "completed" }, "new_field": "ignored"}))
|
||
}
|
||
_ => not_found(req),
|
||
}
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Native turns: gpi/gpi2/zz in turns, probabilities keyed by decimal integers
|
||
// ---------------------------------------------------------------------------
|
||
|
||
struct TurnsService {
|
||
jobs: Mutex<HashMap<String, Value>>,
|
||
}
|
||
|
||
impl TurnsService {
|
||
fn answer(&self, req: &Request) -> Response {
|
||
if req.header_value("Authorization") != Some("apiKey turns-key") {
|
||
return Response::json(401, &json!({"error": "unauthorised"}));
|
||
}
|
||
let path = req.url.strip_prefix("https://turns.test/v0.4").unwrap_or("");
|
||
match (req.method, path) {
|
||
(Method::Get, "/backends/simulator") => Response::json(200, &json!({"backend": "simulator", "status": "available", "qubits": 8, "supported_native_gates": ["gpi", "gpi2", "zz"]})),
|
||
(Method::Post, "/jobs") => {
|
||
let b = body_json(req);
|
||
assert_eq!(b["type"], wai_quantum_qpu::native_turns::JOB_TYPE);
|
||
assert_eq!(b["input"]["gateset"], "native");
|
||
assert_eq!(b["settings"]["error_mitigation"]["debiasing"], false);
|
||
let width = b["input"]["qubits"].as_u64().unwrap() as usize;
|
||
let turns = |v: &Value| -> f64 {
|
||
let t = v.as_f64().unwrap();
|
||
assert!((-1.0..=1.0).contains(&t), "a phase in turns: {t}");
|
||
t * 2.0 * PI
|
||
};
|
||
let mut body = String::new();
|
||
for g in b["input"]["circuit"].as_array().unwrap() {
|
||
match g["gate"].as_str().unwrap() {
|
||
"gpi" | "gpi2" => {
|
||
let (q, phi) = (g["target"].as_u64().unwrap(), turns(&g["phase"]));
|
||
let rot = if g["gate"] == "gpi" { "x" } else { "rx(pi/2)" };
|
||
body.push_str(&format!("rz({}) q[{q}];\n{rot} q[{q}];\nrz({}) q[{q}];\n", angle_text(-phi), angle_text(phi)));
|
||
}
|
||
"zz" => {
|
||
let t = &g["targets"];
|
||
let (a, c, theta) = (t[0].as_u64().unwrap(), t[1].as_u64().unwrap(), turns(&g["angle"]));
|
||
// ZZ(θ) = exp(−iπθ·Z⊗Z) = CX·(I⊗RZ(2πθ))·CX.
|
||
body.push_str(&format!("cx q[{a}],q[{c}];\nrz({}) q[{c}];\ncx q[{a}],q[{c}];\n", angle_text(theta)));
|
||
}
|
||
other => panic!("not a native gate: {other}"),
|
||
}
|
||
}
|
||
let p = physical_distribution(width, &body);
|
||
let probabilities: serde_json::Map<String, Value> = p.iter().enumerate().filter(|&(_, &x)| x > 1e-12).map(|(k, &x)| (k.to_string(), json!(x))).collect();
|
||
let id = format!("turns-{}", self.jobs.lock().unwrap().len());
|
||
self.jobs.lock().unwrap().insert(id.clone(), Value::Object(probabilities));
|
||
Response::json(200, &json!({"id": id, "status": "submitted", "session_id": null}))
|
||
}
|
||
(Method::Get, p) if p.ends_with("/results/probabilities") => {
|
||
let id = &p["/jobs/".len()..p.len() - "/results/probabilities".len()];
|
||
Response::json(200, &self.jobs.lock().unwrap()[id])
|
||
}
|
||
(Method::Get, p) if p.starts_with("/jobs/") => Response::json(200, &json!({"id": &p["/jobs/".len()..], "status": "completed", "stats": {"kwh": 0.0, "qubits": 3}})),
|
||
_ => not_found(req),
|
||
}
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// The tests
|
||
// ---------------------------------------------------------------------------
|
||
|
||
fn no_wait<P: Provider>(s: QpuSampler<P>) -> QpuSampler<P> {
|
||
s.with_wait(Wait { interval: std::time::Duration::ZERO, polls: 10 }, |_| {})
|
||
}
|
||
|
||
/// Circuits whose counts must match their exact distribution.
|
||
fn circuits() -> Vec<Circuit> {
|
||
let mut ghz = Circuit::new(4);
|
||
ghz.h(0).cx(0, 1).cx(1, 2).cx(2, 3);
|
||
let mut mixed = Circuit::new(3);
|
||
mixed.h(0).t(0).h(1).cx(0, 2).s(2).h(2).cz(1, 2).h(1);
|
||
vec![probe_circuit(3), ghz, Circuit::qft(3), mixed]
|
||
}
|
||
|
||
/// The classical fidelity of counts against the exact distribution.
|
||
fn fidelity(c: &Circuit, counts: &[u64]) -> f64 {
|
||
let p = reference_distribution(c).unwrap();
|
||
let total: u64 = counts.iter().sum();
|
||
p.iter().zip(counts).map(|(a, &k)| (a * k as f64 / total as f64).sqrt()).sum::<f64>().powi(2)
|
||
}
|
||
|
||
fn exercise<P: Provider>(s: &QpuSampler<P>) {
|
||
for c in circuits() {
|
||
let (counts, record) = s.run(&c, 4000).unwrap_or_else(|e| panic!("{}: {e}", s.device().name));
|
||
assert_eq!(counts.iter().sum::<u64>(), 4000);
|
||
let f = fidelity(&c, &counts);
|
||
assert!(f > 0.999, "{}: fidelity {f} on {c:?}", s.device().name);
|
||
assert_eq!(record.shots, 4000);
|
||
}
|
||
let (probe, _) = s.run(&probe_circuit(3), 1000).unwrap();
|
||
check_probe(&probe, 0.99).unwrap();
|
||
}
|
||
|
||
#[test]
|
||
fn sampler_v2_round_trips_through_its_contract() {
|
||
let service = SamplerV2Service::new();
|
||
let transport = Scripted::new(|r: &Request| service.answer(r));
|
||
let client = SamplerV2::new(&transport, Secret::new("sampler-key/+="), "crn:test", "test_device").with_endpoints("https://sampler.test/api/v1", "https://iam.test/identity/token").with_clock(|| 1_000);
|
||
let s = no_wait(QpuSampler::new(client).unwrap());
|
||
assert_eq!(s.device().coupling.qubits(), 6);
|
||
assert_eq!(s.device().provenance, Provenance::Silicon);
|
||
exercise(&s);
|
||
assert!(s.jobs().iter().all(|j| j.polls == 2), "{:?}", s.jobs());
|
||
// The key goes to the token endpoint only, form-encoded, and the token is
|
||
// minted once.
|
||
let requests = transport.requests();
|
||
let iam: Vec<&Request> = requests.iter().filter(|r| r.url.contains("iam.test")).collect();
|
||
assert_eq!(iam.len(), 1);
|
||
assert!(String::from_utf8_lossy(iam[0].body.as_ref().unwrap()).ends_with("apikey=sampler-key%2F%2B%3D"));
|
||
for r in requests.iter().filter(|r| !r.url.contains("iam.test")) {
|
||
let all = format!("{} {:?} {}", r.url, r.headers, String::from_utf8_lossy(r.body.as_deref().unwrap_or_default()));
|
||
assert!(!all.contains("sampler-key"), "the key leaked into {}", r.url);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn prx_circuits_round_trip_through_their_contract() {
|
||
let service = PrxService { jobs: Mutex::new(HashMap::new()) };
|
||
let transport = Scripted::new(|r: &Request| service.answer(r));
|
||
let s = no_wait(QpuSampler::new(PrxCircuits::new(&transport, Secret::new("prx-token"), "test-qc").with_base("https://prx.test/api/v1")).unwrap());
|
||
assert_eq!(s.device().coupling.qubits(), 5);
|
||
exercise(&s);
|
||
// A mock quantum computer is never silicon.
|
||
let mock = PrxCircuits::new(&transport, Secret::new("prx-token"), "test-qc:mock").with_base("https://prx.test/api/v1");
|
||
assert!(mock.device().is_err() || matches!(mock.device().unwrap().provenance, Provenance::Simulator(_)));
|
||
}
|
||
|
||
#[test]
|
||
fn native_turns_round_trip_through_their_contract_exactly() {
|
||
let service = TurnsService { jobs: Mutex::new(HashMap::new()) };
|
||
let transport = Scripted::new(|r: &Request| service.answer(r));
|
||
let s = no_wait(QpuSampler::new(NativeTurns::new(&transport, Secret::new("turns-key"), "simulator").with_base("https://turns.test/v0.4")).unwrap());
|
||
assert!(matches!(&s.device().provenance, Provenance::Simulator(name) if name.contains("native-turns simulator")));
|
||
exercise(&s);
|
||
assert!(s.jobs().iter().all(|j| j.swaps == 0 && j.service_kwh == Some(0.0)));
|
||
// Every phase on the wire is an exact binary fraction of a turn.
|
||
for r in transport.requests().iter().filter(|r| r.method == Method::Post) {
|
||
for g in body_json(r)["input"]["circuit"].as_array().unwrap() {
|
||
let t = g.get("phase").or_else(|| g.get("angle")).unwrap().as_f64().unwrap();
|
||
assert_eq!((t * 4294967296.0).fract(), 0.0, "{g}");
|
||
}
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn a_device_certified_through_the_fabric_carries_its_provenance() {
|
||
let service = TurnsService { jobs: Mutex::new(HashMap::new()) };
|
||
let transport = Scripted::new(|r: &Request| service.answer(r));
|
||
let s = no_wait(QpuSampler::new(NativeTurns::new(&transport, Secret::new("turns-key"), "simulator").with_base("https://turns.test/v0.4")).unwrap());
|
||
let member = s.member(QPU_DEFAULT, 2000).unwrap();
|
||
assert_eq!((member.class, member.kernels.len()), (FabricClass::Qpu, 2));
|
||
for (entry, gs) in member.kernels.iter().zip(Gateset::ALL) {
|
||
assert_eq!(entry.kernel, sample_kernel(gs));
|
||
assert_eq!(entry.certificate.provenance, s.device().provenance);
|
||
let Parity::Statistical { fidelity, shots } = entry.certificate.parity else { panic!("{:?}", entry.certificate.parity) };
|
||
assert_eq!(shots, 2000);
|
||
assert!(fidelity > 0.999, "{gs:?}: {fidelity}");
|
||
}
|
||
assert!(member.id.starts_with("remote/qpu-native-turns-simulator-"), "{}", member.id);
|
||
// The report names every certificate by its hash, and every job.
|
||
let r = wai_quantum_qpu::report(&s, &member);
|
||
assert_eq!(r["certificates"].as_array().unwrap().len(), 2);
|
||
for (c, entry) in r["certificates"].as_array().unwrap().iter().zip(&member.kernels) {
|
||
let hash: String = entry.certificate.hash().iter().map(|b| format!("{b:02x}")).collect();
|
||
assert_eq!(c["hash"], hash.as_str());
|
||
assert_eq!(c["parity"]["statistical"]["shots"], 2000);
|
||
}
|
||
assert_eq!(r["jobs"].as_array().unwrap().len(), 2 * QPU_DEFAULT.cases);
|
||
}
|
||
|
||
#[test]
|
||
fn the_probe_catches_a_service_with_the_other_bit_order() {
|
||
let service = SamplerV2Service { flip_bit_order: true, ..SamplerV2Service::new() };
|
||
let transport = Scripted::new(|r: &Request| service.answer(r));
|
||
let client = SamplerV2::new(&transport, Secret::new("k"), "crn:test", "test_device").with_endpoints("https://sampler.test/api/v1", "https://iam.test/identity/token").with_clock(|| 1_000);
|
||
let s = no_wait(QpuSampler::new(client).unwrap());
|
||
let (counts, _) = s.run(&probe_circuit(3), 500).unwrap();
|
||
let e = check_probe(&counts, 0.99).unwrap_err();
|
||
assert!(e.contains("the most on index 4"), "{e}");
|
||
}
|
||
|
||
#[test]
|
||
fn failed_and_unfinished_jobs_are_errors() {
|
||
let service = SamplerV2Service { fail_jobs: true, ..SamplerV2Service::new() };
|
||
let transport = Scripted::new(|r: &Request| service.answer(r));
|
||
let client = SamplerV2::new(&transport, Secret::new("k"), "crn:test", "test_device").with_endpoints("https://sampler.test/api/v1", "https://iam.test/identity/token").with_clock(|| 1_000);
|
||
let s = no_wait(QpuSampler::new(client).unwrap());
|
||
assert!(matches!(s.run(&probe_circuit(2), 10), Err(QpuError::Job { status, .. }) if status == "Failed"));
|
||
let service = SamplerV2Service::new();
|
||
let transport = Scripted::new(|r: &Request| service.answer(r));
|
||
let client = SamplerV2::new(&transport, Secret::new("k"), "crn:test", "test_device").with_endpoints("https://sampler.test/api/v1", "https://iam.test/identity/token").with_clock(|| 1_000);
|
||
let s = QpuSampler::new(client).unwrap().with_wait(Wait { interval: std::time::Duration::ZERO, polls: 1 }, |_| {});
|
||
assert!(matches!(s.run(&probe_circuit(2), 10), Err(QpuError::Timeout { .. })));
|
||
// A circuit wider than the device has no layout.
|
||
assert!(matches!(s.run(&Circuit::new(7), 10), Err(QpuError::Compile(_))));
|
||
}
|
||
|
||
#[test]
|
||
fn a_rejected_request_is_an_http_error_with_the_body() {
|
||
let transport = Scripted::new(|_: &Request| Response::json(403, &json!({"detail": "this token is not allowed here"})));
|
||
let client = PrxCircuits::new(&transport, Secret::new("x"), "qc").with_base("https://prx.test/api/v1");
|
||
match client.device() {
|
||
Err(QpuError::Http { status: 403, body }) => assert!(body.contains("not allowed")),
|
||
other => panic!("{other:?}"),
|
||
}
|
||
assert!(matches!(client.status("j"), Err(QpuError::Http { .. })));
|
||
}
|
||
|
||
/// `curl` end to end against a local server: the headers arrive, the body
|
||
/// arrives on its own, and the status comes back.
|
||
#[test]
|
||
fn curl_carries_a_request_to_a_server_and_back() {
|
||
use std::io::{BufRead, BufReader, Read, Write};
|
||
if std::process::Command::new("curl").arg("--version").output().is_err() {
|
||
println!("no curl on this host: the transport test runs where curl is");
|
||
return;
|
||
}
|
||
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||
let port = listener.local_addr().unwrap().port();
|
||
let server = std::thread::spawn(move || {
|
||
let (stream, _) = listener.accept().unwrap();
|
||
let mut reader = BufReader::new(stream.try_clone().unwrap());
|
||
let mut head = Vec::new();
|
||
loop {
|
||
let mut line = String::new();
|
||
reader.read_line(&mut line).unwrap();
|
||
if line == "\r\n" || line.is_empty() {
|
||
break;
|
||
}
|
||
head.push(line.trim_end().to_string());
|
||
}
|
||
let len: usize = head.iter().find_map(|h| h.to_ascii_lowercase().strip_prefix("content-length: ").map(|v| v.parse().unwrap())).unwrap_or(0);
|
||
let mut body = vec![0u8; len];
|
||
reader.read_exact(&mut body).unwrap();
|
||
let reply = b"{\"id\":\"job-7\"}";
|
||
let mut stream = stream;
|
||
write!(stream, "HTTP/1.1 201 Created\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n", reply.len()).unwrap();
|
||
stream.write_all(reply).unwrap();
|
||
(head, body)
|
||
});
|
||
let curl = wai_quantum_qpu::transport::Curl::new(20);
|
||
let r = curl.send(&Request::post(format!("http://127.0.0.1:{port}/jobs"), b"{\"shots\":5}".to_vec()).header("Authorization", "Bearer t-1").header("Content-Type", "application/json")).unwrap();
|
||
let (head, body) = server.join().unwrap();
|
||
assert_eq!(r.status, 201);
|
||
assert_eq!(r.body, b"{\"id\":\"job-7\"}");
|
||
assert_eq!(head[0], "POST /jobs HTTP/1.1");
|
||
assert!(head.iter().any(|h| h == "Authorization: Bearer t-1"), "{head:?}");
|
||
assert_eq!(body, b"{\"shots\":5}");
|
||
}
|