open-standards/wai/wai-quantum-qpu/src/native_turns.rs
Transaction Science 157dcb1cc5
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
wai-quantum 0.5.0: provenance, sampling members, device programs, OpenCL, a simulated FPGA, f64 on accelerators, and QPU clients
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]>
2026-10-14 09:02:11 -04:00

196 lines
8.4 KiB
Rust

//! A native-gate circuit service in turns: the gates run as written.
//!
//! - **Auth**: `Authorization: apiKey <key>`.
//! - **Device**: `GET /backends/{backend}`: its `qubits`. Every pair of a
//! trapped-ion register interacts, so the map is complete and routing never
//! inserts a `SWAP`.
//! - **Submit**: `POST /jobs` of the circuit job type with the `native`
//! gateset (`gpi`, `gpi2`, `zz`), phases and angles in turns, so every dyadic
//! angle crosses the wire exactly; debiasing off, so the job runs the
//! program as compiled. On the simulator, `noise.model` picks a device's
//! noise.
//! - **Results**: `GET /jobs/{id}/results/probabilities`: probabilities keyed by
//! decimal integers whose bit `p` is physical qubit `p`, read back onto
//! logical qubits through the program's final layout, and made counts by
//! largest remainders.
//!
//! The service reports an energy estimate per job (`stats.kwh`); it is kept as
//! the service's figure ([`JobInfo::service_kwh`]).
use crate::transport::{Request, Transport};
use crate::{Device, JobInfo, JobStatus, Provider, QpuError, Secret, counts_from_probabilities, field, field_str, json_ok, logical_index};
use serde_json::{Value, json};
use wai_quantum::quantum_fabric::Provenance;
use wai_quantum::quantum_fabric::sampling::Counts;
use wai_quantum::quantum_toolchain::native::{Basis, Coupling, Dyadic, NativeGate, NativeProgram};
/// The service's API base.
pub const BASE: &str = "https://api.ionq.co/v0.4"; // allow-name: the service's endpoint
/// The job type of a gate-list circuit.
pub const JOB_TYPE: &str = "ionq.circuit.v1"; // allow-name: a protocol job type
/// A client for one backend: `"simulator"` or a processor such as
/// a processor's backend name.
pub struct NativeTurns<T: Transport> {
transport: T,
key: Secret,
backend: String,
noise: Option<String>,
base: String,
}
impl<T: Transport> NativeTurns<T> {
pub fn new(transport: T, key: Secret, backend: impl Into<String>) -> Self {
NativeTurns { transport, key, backend: backend.into(), noise: None, base: BASE.into() }
}
/// From `WAI_QPU_TURNS_KEY` and `WAI_QPU_TURNS_BACKEND` (default
/// `simulator`), with `WAI_QPU_TURNS_NOISE` as the simulator's noise model
/// when set.
pub fn from_env(transport: T) -> Result<Self, QpuError> {
let backend = std::env::var("WAI_QPU_TURNS_BACKEND").ok().filter(|b| !b.is_empty()).unwrap_or_else(|| "simulator".into());
let mut c = NativeTurns::new(transport, Secret::from_env("WAI_QPU_TURNS_KEY")?, backend);
c.noise = std::env::var("WAI_QPU_TURNS_NOISE").ok().filter(|n| !n.is_empty());
Ok(c)
}
/// The simulator with a device's noise model, by the name the service
/// gives it.
pub fn with_noise(mut self, model: impl Into<String>) -> Self {
self.noise = Some(model.into());
self
}
/// Another endpoint: a test server.
pub fn with_base(mut self, base: impl Into<String>) -> Self {
self.base = base.into();
self
}
fn authed(&self, r: Request) -> Request {
r.header("Authorization", format!("apiKey {}", self.key.expose())).header("Accept", "application/json")
}
fn is_simulator(&self) -> bool {
self.backend == "simulator"
}
}
/// A dyadic angle in turns, `m/2^k`: exact in binary64.
pub fn turns(d: Dyadic) -> f64 {
let (m, k) = d.turns();
m as f64 / (1u64 << k) as f64
}
fn complete(n: u16) -> Coupling {
let mut pairs = Vec::new();
for a in 0..n {
for b in a + 1..n {
pairs.push((a, b));
}
}
Coupling::new(n, &pairs).expect("a complete map is valid")
}
impl<T: Transport> Provider for NativeTurns<T> {
fn device(&self) -> Result<Device, QpuError> {
let r = self.transport.send(&self.authed(Request::get(format!("{}/backends/{}", self.base, self.backend))))?;
let v = json_ok(&r)?;
let n = field(&v, &["qubits"])?.as_u64().filter(|&n| n > 0 && n <= 64).ok_or_else(|| QpuError::Protocol("qubits".into()))?;
let provenance = if self.is_simulator() {
Provenance::Simulator(format!("native-turns simulator, noise {}", self.noise.as_deref().unwrap_or("ideal")))
} else {
Provenance::Silicon
};
Ok(Device { name: format!("native-turns:{}", self.backend), basis: Basis::GpiZz, coupling: complete(n as u16), provenance })
}
fn submit(&self, program: &NativeProgram, shots: u64) -> Result<String, QpuError> {
if program.basis != Basis::GpiZz {
return Err(QpuError::Config("this service runs gpi, gpi2 and zz".into()));
}
let mut circuit: Vec<Value> = Vec::with_capacity(program.gates.len());
let mut width = program.final_layout.iter().copied().max().map_or(0, |q| q + 1);
for g in &program.gates {
circuit.push(match *g {
NativeGate::Gpi(q, p) => {
width = width.max(q + 1);
json!({"gate": "gpi", "target": q, "phase": turns(p)})
}
NativeGate::Gpi2(q, p) => {
width = width.max(q + 1);
json!({"gate": "gpi2", "target": q, "phase": turns(p)})
}
NativeGate::Zz(a, b, t) => {
width = width.max(a.max(b) + 1);
json!({"gate": "zz", "targets": [a, b], "angle": turns(t)})
}
other => return Err(QpuError::Config(format!("{other:?} is not one of this service's native gates"))),
});
}
let mut body = json!({
"type": JOB_TYPE,
"backend": self.backend,
"shots": shots,
"settings": {"error_mitigation": {"debiasing": false}},
"input": {"gateset": "native", "qubits": width, "circuit": circuit}
});
if self.is_simulator()
&& let Some(model) = &self.noise
{
body["noise"] = json!({"model": model});
}
let r = self.transport.send(&self.authed(Request::post(format!("{}/jobs", self.base), body.to_string().into_bytes()).header("Content-Type", "application/json")))?;
Ok(field_str(&json_ok(&r)?, &["id"])?.to_string())
}
fn status(&self, id: &str) -> Result<JobStatus, QpuError> {
let r = self.transport.send(&self.authed(Request::get(format!("{}/jobs/{id}", self.base))))?;
let v = json_ok(&r)?;
Ok(match field_str(&v, &["status"])? {
"submitted" | "ready" | "started" => JobStatus::Pending,
"completed" => JobStatus::Done,
other => JobStatus::Ended { status: other.to_string(), reason: v.get("failure").map(|f| f.to_string()).unwrap_or_default() },
})
}
fn counts(&self, id: &str, program: &NativeProgram, shots: u64) -> Result<(Counts, JobInfo), QpuError> {
let job = json_ok(&self.transport.send(&self.authed(Request::get(format!("{}/jobs/{id}", self.base))))?)?;
let service_kwh = job.get("stats").and_then(|s| s.get("kwh")).and_then(|k| k.as_f64());
let r = self.transport.send(&self.authed(Request::get(format!("{}/jobs/{id}/results/probabilities", self.base))))?;
let v = json_ok(&r)?;
let map = v.as_object().ok_or_else(|| QpuError::Protocol("probabilities is not a map".into()))?;
let mut p = vec![0.0f64; 1 << program.n_qubits];
for (key, prob) in map {
let physical: u64 = key.parse().map_err(|_| QpuError::Counts(format!("outcome {key:?} is not a decimal integer")))?;
let prob = prob.as_f64().filter(|x| x.is_finite() && *x >= 0.0).ok_or_else(|| QpuError::Counts(format!("probability of {key:?}")))?;
p[logical_index(physical, &program.final_layout)] += prob;
}
Ok((counts_from_probabilities(&p, shots), JobInfo { service_kwh }))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn turns_are_exact_binary_fractions() {
let d = |m, k| Dyadic::new(m, k).unwrap();
assert_eq!(turns(Dyadic::ZERO), 0.0);
assert_eq!(turns(d(1, 2)), 0.25);
assert_eq!(turns(d(3, 2)), 0.75);
assert_eq!(turns(d(1, 32)), 1.0 / 4294967296.0);
assert_eq!(json!(turns(d(5, 4))).to_string(), "0.3125");
}
#[test]
fn every_pair_of_the_register_interacts() {
let c = complete(4);
for a in 0..4 {
for b in 0..4 {
assert_eq!(c.connected(a, b), a != b);
}
}
}
}