open-standards/wai/wai-quantum/src/engine/run.rs
Transaction Science 56b47afae1 wai-quantum engine::registry: methods, passes, noise channels and formats behind traits, the built-ins registered through them
Method, Pass, Channel and FrontEnd, and one registry the planner, the run,
the compile, the conversions and the verdict go through. Every built-in
registers through the same trait a host's own implements: the twelve
methods and the pulse simulator, the eight passes, the five channels and
the three formats. Registry::with_builtins() and register(...) are
explicit and stay in the process.

The engine checks what a registered component gives: a method's class and
the members the engine writes, a pass's output against its input on the
whole circuit by the ladder or a distance bound, a channel's Kraus
operators completely positive and trace preserving at each job's values,
a format's program reading. A result a registered component took part in
carries `components`, each identity (name, version, descriptor digest)
marked registered and certified false, sealed by its receipt; a result of
built-ins alone gives the bytes it gave, the eight pinned job digests
holding through any registry. Refusal codes J060 to J065. The example
examples/extension_points.rs, docs/extension-points.md, and the CI leg
wai-quantum-registry.yml.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-31 23:30:48 -04:00

1651 lines
92 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 worker: one job, run by its plan's method, its answers in the API's
//! terms. Bit strings are the program's classical bits, bit `n_bits − 1`
//! first; an amplitude's bit string names every qubit, qubit `n − 1` first.
use crate::engine::catalog::{self, MAX_OUTCOMES, Plan, Request};
use crate::engine::registry::{Job, Kind, Method, Registry};
use serde_json::{Value, json};
use std::collections::HashMap;
fn hex(b: &[u8]) -> String {
b.iter().map(|x| format!("{x:02x}")).collect()
}
/// The most characters of bit strings a counts map lists.
const MAX_COUNTS_CHARS: usize = 16 << 20;
/// A counts map: the most frequent outcomes (at most [`MAX_OUTCOMES`], and
/// at most [`MAX_COUNTS_CHARS`] characters of bit strings), and how many
/// shots the omitted ones held.
pub(crate) fn counts(map: HashMap<String, u64>) -> Value {
let mut v: Vec<(String, u64)> = map.into_iter().collect();
v.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
let width = v.first().map(|x| x.0.len()).unwrap_or(1).max(1);
let keep = MAX_OUTCOMES.min(MAX_COUNTS_CHARS / width).max(1);
let omitted: u64 = v.iter().skip(keep).map(|x| x.1).sum();
let distinct = v.len();
v.truncate(keep);
let mut o = serde_json::Map::new();
for (k, c) in v {
o.insert(k, json!(c));
}
json!({ "counts": o, "distinct_outcomes": distinct, "omitted_shots": omitted })
}
/// The basis index an amplitude's bit string names, with qubit `q` at bit
/// `pos(q)` of the index.
fn index_of(bits: &str, n: u32, pos: impl Fn(u32) -> u32) -> usize {
let b = bits.as_bytes();
let mut x = 0usize;
for q in 0..n {
if b[(n - 1 - q) as usize] == b'1' {
x |= 1 << pos(q);
}
}
x
}
/// `⟨ψ|P|ψ⟩` on the bit-exact state, exactly: the fixed-point amplitudes
/// multiplied and summed in 128-bit integers, then scaled once.
fn expect_fixed(amps: &[crate::quantum::Amp], string: &[(u32, char)]) -> f64 {
let (mut flip, mut zmask, mut ys) = (0usize, 0usize, 0u32);
for &(q, p) in string {
let bit = 1usize << q;
match p {
'X' => flip |= bit,
'Y' => {
flip |= bit;
zmask |= bit;
ys += 1;
}
_ => zmask |= bit,
}
}
let (mut re, mut im) = (0i128, 0i128);
for (x, a) in amps.iter().enumerate() {
let b = amps[x ^ flip];
let sign: i128 = if (x & zmask).count_ones() % 2 == 0 { 1 } else { -1 };
// conj(ψ[x⊕flip]) · ψ[x]
let pr = b.re as i128 * a.re as i128 + b.im as i128 * a.im as i128;
let pi = b.re as i128 * a.im as i128 - b.im as i128 * a.re as i128;
re += sign * pr;
im += sign * pi;
}
// times i^{ys}: the real part.
let v = match ys % 4 {
0 => re,
1 => -im,
2 => -re,
_ => im,
};
v as f64 / (1u128 << 60) as f64
}
fn expectation_entries(r: &Request, value: impl Fn(&[(u32, char)]) -> f64) -> Value {
Value::Array(r.observables.iter().map(|(text, coeff, string)| json!({ "observable": text, "coeff": coeff, "value": coeff * value(string) })).collect())
}
/// When a run started, on a target with a clock std can read. A browser tab
/// (wasm32-unknown-unknown) has none, so a result there carries no
/// `elapsed_ms`; its host can time the call itself.
#[derive(Clone, Copy)]
struct Started(Option<std::time::Instant>);
impl Started {
fn now() -> Started {
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
{
Started(None)
}
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
{
Started(Some(std::time::Instant::now()))
}
}
fn stamp(self, out: &mut Value) {
if let Some(t) = self.0 {
out["elapsed_ms"] = json!(t.elapsed().as_millis() as u64);
}
}
}
/// Runs `plan` for the job `request`: the result, or why it failed. A job
/// that asks for a verdict (an `intent`, or `"verdict": true`) has it in its
/// result under `verdict` ([`crate::engine::verdict`]), its intents checked
/// within the memory the plan admitted; one that does not gives the result
/// it always gave. The result names the engine that made it and the schema
/// it follows ([`crate::engine::schema::stamp`]).
pub fn run(request: &Value, plan: &Plan) -> Result<Value, String> {
run_in(Registry::builtins(), request, plan)
}
/// [`run`] through `reg`: its methods, channels, passes and front ends. A
/// result a registered component took part in carries `components`, the
/// identity of every component that made it.
pub(crate) fn run_in(reg: &Registry, request: &Value, plan: &Plan) -> Result<Value, String> {
run_noting(reg, request, plan, &mut None)
}
fn run_noting(reg: &Registry, request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<Value, String> {
let mut out = run_job(reg, request, plan, passes)?;
// A pulse run carries its own verdict (engine::pulse).
if crate::engine::verdict::asked(request) && catalog::reads_own(reg, request).is_none() {
out["verdict"] = crate::engine::verdict::of_run_in(reg, request, &out, plan.memory_bytes)?;
}
crate::engine::schema::stamp(&mut out);
let used = reg.used(request, &out);
if used.iter().any(|i| !i.is_builtin()) {
out["components"] = used.iter().map(|i| i.to_json()).collect();
}
Ok(out)
}
/// The provenance schema a result's `provenance` names.
pub const PROVENANCE: &str = "wai.quantum.provenance/1";
/// [`run`], the result carrying `provenance`: what it came from, under the
/// five categories cross-platform provenance surveys use. A host adds it to
/// the results it seals (the job service does); `host` names that host and
/// its version. A result without it reads as it always did.
///
/// - `circuit`: the program's BLAKE3 digest (of its text, as given), its
/// qubits, gates and measured bits;
/// - `computer`: what ran it: the method and its accuracy class, and a
/// device target's hash when the job named one (its layout, routes and noise
/// are in the result's own `target`);
/// - `compilation`: whether the job asked to be compiled, and if so each pass
/// in order with its check's verdict, the certificate's digest, the gate
/// set compiled to (its `basis`), the ε budget and the distance the result
/// carries; the passes take no random seed;
/// - `execution`: the shots, the seed and the plan the job ran under (its
/// identifier and times are the host's, outside the result);
/// - `software`: the engine's name and version, and the host's.
pub fn run_with_provenance(request: &Value, plan: &Plan, host: &str) -> Result<Value, String> {
run_with_provenance_in(Registry::builtins(), request, plan, host)
}
/// [`run_with_provenance`] through `reg`.
pub(crate) fn run_with_provenance_in(reg: &Registry, request: &Value, plan: &Plan, host: &str) -> Result<Value, String> {
let mut passes = None;
let mut out = run_noting(reg, request, plan, &mut passes)?;
out["provenance"] = provenance(request, plan, &out, passes, host);
Ok(out)
}
/// The provenance of `result` of `request` under `plan` ([`run_with_provenance`]).
pub fn provenance(request: &Value, plan: &Plan, result: &Value, passes: Option<Value>, host: &str) -> Value {
let qasm = request.get("qasm").and_then(Value::as_str).unwrap_or("");
let compiled = &result["compiled"];
let compilation = if request.get("compile").is_some_and(|c| !c.is_null()) {
json!({
"asked": true,
"passes": passes.unwrap_or(Value::Null),
"certificate": compiled["certificate"],
"basis": compiled["basis"],
"level": compiled["level"],
"epsilon": compiled["epsilon"],
"distance": compiled["distance"],
"seeded": false,
})
} else if result.get("target").is_some_and(Value::is_object) {
json!({ "asked": false, "device": "compiled exactly to the device target's gates, placed and routed: the result's target names the layout and routes" })
} else {
json!({ "asked": false, "status": "not-applicable", "why": "the job asked for no compile: the program ran as the reader read it, on the method's own gates" })
};
let mut computer = json!({ "kind": "simulator", "method": plan.method, "accuracy": result["accuracy"]["class"] });
if let Some(h) = result.pointer("/target/hash") {
computer["target"] = h.clone();
}
// Where the state ran, when the result names it: the member, or each
// partition's.
if let Some(f) = result.get("fabric") {
let members: Vec<Value> = match f.get("partitions").and_then(Value::as_array) {
Some(parts) => parts.iter().map(|p| p["member"].clone()).collect(),
None => vec![f["member"].clone()],
};
computer["fabric"] = json!({ "kernel": f["kernel"], "members": members });
}
json!({
"schema": PROVENANCE,
"circuit": {
"program": crate::engine::compile::hash_of_text(qasm),
"qubits": result.get("qubits").cloned().unwrap_or(json!(plan.qubits)),
"gates": result["gates"],
},
"computer": computer,
"compilation": compilation,
"execution": {
"shots": request.get("shots").cloned().unwrap_or(Value::Null),
"seed": request.get("seed").cloned().unwrap_or(json!(1)),
"plan": { "method": plan.method, "why": plan.why, "memory_bytes": plan.memory_bytes, "work": plan.work },
},
"software": { "engine": format!("wai-quantum {}", env!("CARGO_PKG_VERSION")), "host": host },
})
}
fn run_job(reg: &Registry, request: &Value, plan: &Plan, passes: &mut Option<Value>) -> Result<Value, String> {
// A method that reads its job's program itself: a pulse run is integrated
// by the pulse simulator (engine::pulse).
if let Some(m) = catalog::reads_own(reg, request) {
let mut out = Value::Null;
m.run(&Job::new(reg, request, plan.memory_bytes, None, &[]), plan, &mut out)?;
return Ok(out);
}
// A job that names a device target runs as the device would run it.
#[cfg(feature = "quantum_receipt")]
if crate::engine::targets::job::names_target(request) {
if request.get("mitigate").is_some_and(|m| !m.is_null()) {
return Err(crate::engine::refusal::tag(crate::engine::refusal::MITIGATE, "mitigate: a device target's noise is the device's own; mitigation runs under the job's \"noise\" model"));
}
return crate::engine::targets::job::run(request, plan);
}
#[cfg(not(feature = "quantum_receipt"))]
if request.get("target").is_some_and(|t| !t.is_null()) {
return Err("this build has no device targets (the engine's quantum_receipt feature)".into());
}
let (r, uses) = catalog::read_in(reg, request)?;
passes.clone_from(&r.compile_passes);
let started = Started::now();
let n = r.program.n_qubits;
let mut out = json!({
"method": plan.method,
"qubits": n,
"gates": r.program.ops.len(),
});
if let Some(c) = &r.compiled {
out["compiled"] = c.clone();
}
if let Some(x) = &r.repaired {
out["repaired"] = x.clone();
}
let m = reg.method(&plan.method).ok_or_else(|| format!("unknown method {}", plan.method))?;
let job = Job::new(reg, request, plan.memory_bytes, Some(&r), &uses);
// A registered method's result is held to the contract: what the engine
// wrote stays as it wrote it, and its accuracy class is one it declared.
let registered = reg.identity(Kind::Method, m.name()).is_some_and(|i| !i.is_builtin());
let before = registered.then(|| out.clone());
m.run(&job, plan, &mut out)?;
if let Some(b) = before {
contract(m, &b, &out)?;
}
if r.program.dynamic.is_none() {
#[cfg(feature = "quantum_noise")]
if let Some(mit) = &r.mitigate {
let o = crate::engine::mitigate::Options::read(mit, r.seed)?;
let noise = r.noise.ok_or("mitigation needs the job's noise")?;
out["mitigation"] = crate::engine::mitigate::run(&r.program, &noise, &r.observables, &o, out.get("counts")).map_err(|e| crate::engine::refusal::tag(crate::engine::refusal::MITIGATE, e))?;
}
}
carried(&r, plan, &mut out);
started.stamp(&mut out);
Ok(out)
}
/// A registered method's result held to the engine's contract (`J064`): the
/// members the engine writes as it wrote them, and an accuracy class the
/// method declared.
fn contract(m: &dyn Method, before: &Value, out: &Value) -> Result<(), String> {
use crate::engine::refusal::{CONTRACT, tag};
let name = m.name();
if !out.is_object() {
return Err(tag(CONTRACT, format!("the method {name} gave a result that is not an object")));
}
for k in crate::engine::registry::ENGINE_MEMBERS {
if before.get(k) != out.get(k) {
return Err(tag(CONTRACT, format!("the method {name} wrote `{k}`, which the engine writes")));
}
}
let class = out.pointer("/accuracy/class").and_then(Value::as_str).unwrap_or("");
if !m.classes().iter().any(|c| c == class) {
return Err(tag(CONTRACT, format!("the method {name} gave the accuracy class {class:?}; it declared {:?}", m.classes())));
}
Ok(())
}
/// A built-in method's run (its registry entry's,
/// [`crate::engine::registry::Method::run`]): `method` carried out for the
/// job, its answers written into `out`. A program that measures in the
/// middle runs branch by branch.
pub(crate) fn run_builtin(method: &str, job: &Job<'_>, plan: &Plan, out: &mut Value) -> Result<(), String> {
let r = job.request()?;
let request = job.json();
if let Some(steps) = &r.program.dynamic {
return run_dynamic(r, plan, steps, out);
}
let n = r.program.n_qubits;
match method {
"exact" => {
let c = r.program.dyadic()?;
let sv = c.simulate().map_err(|e| e.to_string())?;
out["accuracy"] = json!({ "class": "bit-exact" });
out["state_hash"] = json!(hex(&sv.statevector_hash()));
if r.shots > 0 {
let per_index = sv.sample_shots(r.seed, r.shots);
let mut map: HashMap<String, u64> = HashMap::new();
for (x, &k) in per_index.iter().enumerate() {
if k > 0 {
*map.entry(r.program.bitstring(|q| x >> q & 1 == 1)).or_insert(0) += k;
}
}
merge(out, counts(map));
}
if !r.observables.is_empty() {
out["expectations"] = expectation_entries(r, |s| expect_fixed(&sv.amps, s));
}
if !r.amplitudes.is_empty() {
let scale = (1u64 << 30) as f64;
out["amplitudes"] = Value::Array(
r.amplitudes
.iter()
.map(|b| {
let a = sv.amps[index_of(b, n, |q| q)];
json!({ "bits": b, "re": a.re as f64 / scale, "im": a.im as f64 / scale })
})
.collect(),
);
}
}
#[cfg(not(feature = "quantum_sv_metal"))]
"statevector_single" => return Err("this build has no GPU state vector".into()),
#[cfg(feature = "quantum_sv_metal")]
"statevector_single" => {
use crate::quantum_sv_metal::GpuState;
let gates = r.program.matrices();
// Wider fused gates run slower on the GPU than the passes they save.
let width = r.options.get("fusion").and_then(Value::as_u64).unwrap_or(3).clamp(1, 3) as usize;
let mut sv = GpuState::zero(n).map_err(|e| e.to_string())?;
sv.run(&gates, width).map_err(|e| e.to_string())?;
out["accuracy"] = json!({ "class": "single", "fusion": width, "device": sv.device(), "note": "single-precision amplitudes on the GPU, about 1e-7 relative rounding per gate; sums in double precision" });
if r.shots > 0 {
let mut map: HashMap<String, u64> = HashMap::new();
for x in sv.sample(r.shots as usize, r.seed) {
*map.entry(r.program.bitstring(|q| x >> (n - 1 - q) & 1 == 1)).or_insert(0) += 1;
}
merge(out, counts(map));
}
if !r.observables.is_empty() {
out["expectations"] = expectation_entries(r, |s| sv.expectation(&[(1.0, s.to_vec())]));
}
if !r.amplitudes.is_empty() {
out["amplitudes"] = Value::Array(
r.amplitudes
.iter()
.map(|b| {
let a = sv.amplitude(index_of(b, n, |q| n - 1 - q));
json!({ "bits": b, "re": a.re, "im": a.im })
})
.collect(),
);
}
}
"statevector" => {
let gates = r.program.matrices();
let width = r.options.get("fusion").and_then(Value::as_u64).unwrap_or(3).clamp(1, 5) as usize;
// On the CPU, or on the fabric's member the job's placement
// names: the CPU's bits either way.
let (sv, placed) = crate::engine::fabric::statevector(request, n, &gates, width, crate::engine::fabric::widest_of(r))?;
out["accuracy"] = json!({ "class": "exact", "fusion": width });
if let Some(f) = placed {
out["fabric"] = f;
}
if r.shots > 0 {
let mut map: HashMap<String, u64> = HashMap::new();
for x in sv.sample(r.shots as usize, r.seed) {
*map.entry(r.program.bitstring(|q| x >> (n - 1 - q) & 1 == 1)).or_insert(0) += 1;
}
merge(out, counts(map));
}
if !r.observables.is_empty() {
out["expectations"] = expectation_entries(r, |s| sv.expectation(&[(1.0, s.to_vec())]));
}
if !r.amplitudes.is_empty() {
out["amplitudes"] = Value::Array(
r.amplitudes
.iter()
.map(|b| {
let a = sv.amplitude(index_of(b, n, |q| n - 1 - q));
json!({ "bits": b, "re": a.re, "im": a.im })
})
.collect(),
);
}
}
"stabilizer" if r.noise.is_some() => {
use crate::quantum_tableau::{FrameStep, frame_counts, frame_expectations};
let noise = r.noise.ok_or("no noise model")?;
// Each op's gates, then its noise on the qubits it touched, as the
// other noisy methods apply it.
let mut steps = Vec::new();
for (gates, qs) in r.program.clifford_ops()? {
steps.extend(gates.into_iter().map(FrameStep::Gate));
if qs.len() == 2 && noise.depol2 > 0.0 {
steps.push(FrameStep::Depolarize2(qs[0], qs[1], noise.depol2));
}
for &q in &qs {
if qs.len() != 2 && noise.depol1 > 0.0 {
steps.push(FrameStep::Depolarize1(q, noise.depol1));
}
if noise.dephasing > 0.0 {
steps.push(FrameStep::Dephase(q, noise.dephasing));
}
}
}
let threads = std::thread::available_parallelism().map_or(1, |t| t.get()).min(8);
let frame_shots = catalog::frame_expectation_shots(r);
out["accuracy"] = json!({
"class": "estimate",
"shots": frame_shots,
"note": "Pauli frames against a noiseless stabilizer-tableau reference: counts are samples with the law of independent noisy shots; expectations are the noiseless values times the mean sign of the frames, with standard errors"
});
if r.shots > 0 {
let measured: Vec<u32> = r.program.measured.iter().map(|&(q, _)| q).collect();
let nb = r.program.n_bits as usize;
let mut map: HashMap<String, u64> = HashMap::new();
for (bits, k) in frame_counts(n, &steps, &measured, noise.readout, r.shots, r.seed, threads).map_err(|e| e.to_string())? {
let mut s = vec![b'0'; nb];
for (j, &(_, b)) in r.program.measured.iter().enumerate() {
if bits[j / 64] >> (j % 64) & 1 == 1 {
s[nb - 1 - b as usize] = b'1';
}
}
*map.entry(String::from_utf8(s).expect("ASCII")).or_insert(0) += k;
}
merge(out, counts(map));
}
if !r.observables.is_empty() {
let strings: Vec<Vec<(u32, char)>> = r.observables.iter().map(|(_, _, s)| s.clone()).collect();
let est = frame_expectations(n, &steps, &strings, frame_shots, r.seed, threads).map_err(|e| e.to_string())?;
out["expectations"] = Value::Array(
r.observables
.iter()
.zip(est)
.map(|((text, coeff, _), (v, se))| json!({ "observable": text, "coeff": coeff, "value": coeff * v, "stderr": coeff.abs() * se }))
.collect(),
);
}
}
"stabilizer" => {
use crate::quantum_tableau::Tableau;
let t = Tableau::run(n, &r.program.clifford()?).map_err(|e| e.to_string())?;
out["accuracy"] = json!({ "class": "exact" });
if r.shots > 0 {
let support = t.support();
let measured: Vec<u32> = r.program.measured.iter().map(|&(q, _)| q).collect();
let sampler = support.sampler(&measured);
let nb = r.program.n_bits as usize;
let mut map: HashMap<String, u64> = HashMap::new();
for (bits, k) in sampler.counts(r.shots, r.seed) {
let mut s = vec![b'0'; nb];
for (j, &(_, b)) in r.program.measured.iter().enumerate() {
if bits[j / 64] >> (j % 64) & 1 == 1 {
s[nb - 1 - b as usize] = b'1';
}
}
*map.entry(String::from_utf8(s).expect("ASCII")).or_insert(0) += k;
}
merge(out, counts(map));
out["outcome_dimension"] = json!(support.dimension());
}
if !r.observables.is_empty() {
let mut entries = Vec::new();
for (text, coeff, string) in &r.observables {
let v = t.expectation(string).map_err(|e| e.to_string())?;
entries.push(json!({ "observable": text, "coeff": coeff, "value": coeff * v as f64 }));
}
out["expectations"] = Value::Array(entries);
}
}
"pauli" => {
use crate::quantum_spd::{Pauli, SpdConfig};
let rc = r.program.rotations();
// As many terms as the job's memory holds: a term's string (two
// words of bits a 64 qubits, padded to a power of two), its
// coefficient and index slots, and room for a rotation's new terms.
let words = (n as u64).div_ceil(64).next_power_of_two();
let per_term = 2 * (16 * words + 8) + 16;
let fits = ((plan.memory_bytes.saturating_sub(256 << 20)) / per_term).max(1 << 16) as usize;
let max_terms = r.options.get("max_terms").and_then(Value::as_u64).map_or(fits, |m| (m as usize).clamp(1, fits));
let threads = std::thread::available_parallelism().map_or(1, |t| t.get());
let threshold = r.options.get("threshold").and_then(Value::as_f64).unwrap_or(1e-5).clamp(0.0, 1.0);
let max_weight = r.options.get("max_weight").and_then(Value::as_u64).map(|w| w as u32);
// The ladder: the same value at up to three thresholds a decade
// apart above the one asked for, so a reader sees it settle.
let ladder = r.options.get("ladder").and_then(Value::as_bool).unwrap_or(true) && threshold > 0.0;
let rungs: Vec<f64> = if ladder { [1000.0, 100.0, 10.0].iter().map(|f| threshold * f).filter(|&t| t < 1.0).chain([threshold]).collect() } else { vec![threshold] };
out["accuracy"] = json!({ "class": "bounded", "threshold": threshold, "max_weight": max_weight });
// On the CPU, or on the fabric's member the job's placement
// names: the CPU's bits either way.
let mut each = |propagate: &mut crate::engine::fabric::Propagator<'_>| -> Result<Vec<Value>, String> {
let mut entries = Vec::new();
for (text, coeff, string) in &r.observables {
let obs: Vec<(u32, Pauli)> = string.iter().map(|&(q, c)| (q, match c { 'X' => Pauli::X, 'Y' => Pauli::Y, _ => Pauli::Z })).collect();
let mut steps = Vec::new();
let mut last = None;
for &t in &rungs {
let (res, used) = propagate(&rc, &[(*coeff, obs.clone())], &SpdConfig { threshold: t, max_weight }, max_terms)?;
let mut step = json!({ "threshold": t, "value": res.value, "bound": res.truncation_bound, "peak_terms": res.peak_terms });
if used > t {
step["threshold_reached"] = json!(used);
}
steps.push(step);
last = Some((res, used));
}
let (res, used) = last.expect("one rung at least");
let mut entry = json!({ "observable": text, "coeff": coeff, "value": res.value, "bound": res.truncation_bound, "peak_terms": res.peak_terms });
if used > threshold {
// The sum outgrew the job's memory: the threshold rose to
// fit, and the bound covers what that dropped.
entry["threshold_reached"] = json!(used);
entry["max_terms"] = json!(max_terms);
}
if steps.len() > 1 {
entry["ladder"] = Value::Array(steps);
}
entries.push(entry);
}
Ok(entries)
};
let (entries, placed) = crate::engine::fabric::pauli(request, threads, &mut each)?;
if let Some(f) = placed {
out["fabric"] = f;
}
out["expectations"] = Value::Array(entries);
}
"mps" => {
use crate::quantum_mpsim::{Chain, MpsConfig};
let chi = catalog::mps_bond(r);
let cutoff = r.options.get("cutoff").and_then(Value::as_f64).unwrap_or(1e-12).clamp(0.0, 1e-2);
let mut chain = Chain::run(n, &r.program.local_matrices(), MpsConfig { max_bond: chi, cutoff }).map_err(|e| e.to_string())?;
let rep = chain.report();
out["accuracy"] = if rep.truncations == 0 {
json!({ "class": "exact", "max_bond": chi, "max_bond_used": rep.max_bond_used, "note": "no truncation dropped weight beyond the cutoff's rounding" })
} else {
json!({ "class": "heuristic", "max_bond": chi, "max_bond_used": rep.max_bond_used, "discarded_weight": rep.discarded, "fidelity_estimate": rep.fidelity_estimate, "truncations": rep.truncations })
};
if rep.truncations > 0 && rep.fidelity_estimate < 0.9 {
out["warning"] = json!(format!("the truncation dropped weight: the fidelity estimate is {:.3}; a larger max_bond may help, or the circuit may be too entangled for this method", rep.fidelity_estimate));
}
if r.shots > 0 {
let mut map: HashMap<String, u64> = HashMap::new();
for bits in chain.sample(r.shots, r.seed) {
*map.entry(r.program.bitstring(|q| bits[q as usize] == 1)).or_insert(0) += 1;
}
merge(out, counts(map));
}
if !r.observables.is_empty() {
let mut entries = Vec::new();
for (text, coeff, string) in &r.observables {
let v = chain.expectation(string).map_err(|e| e.to_string())?;
entries.push(json!({ "observable": text, "coeff": coeff, "value": coeff * v }));
}
out["expectations"] = Value::Array(entries);
}
if !r.amplitudes.is_empty() {
let mut entries = Vec::new();
for b in &r.amplitudes {
let bits: Vec<u8> = (0..n).map(|q| b.as_bytes()[(n - 1 - q) as usize] - b'0').collect();
let a = chain.amplitude(&bits).map_err(|e| e.to_string())?;
entries.push(json!({ "bits": b, "re": a.re, "im": a.im }));
}
out["amplitudes"] = Value::Array(entries);
}
}
#[cfg(not(feature = "quantum_cutrank_metal"))]
"cutrank_single" => return Err("this build has no GPU cut-rank table".into()),
#[cfg(feature = "quantum_cutrank_metal")]
"cutrank_single" => {
use crate::quantum_cutrank_metal::GpuCutRank;
let mut pm = catalog::product_measured(r, true)?;
let cut = pm.max_cut();
// The batch the plan's memory holds, the first table aside.
let one = catalog::cutrank_bytes(&pm, crate::quantum_cutrank::Precision::Single);
let batch = catalog::cutrank_gpu_batch(&pm, r.shots, one, plan.memory_bytes);
let mut gpu = GpuCutRank::with_batch(cut, batch).map_err(|e| e.to_string())?;
out["accuracy"] = json!({
"class": "single",
"max_cut": cut,
"order_work": pm.work(),
"device": gpu.device(),
"batch": gpu.batch(),
"note": "the state's sum taken qubit by qubit along an order of the qubits, in a table of 2^max_cut entries on the GPU, its arithmetic in single precision (about 1e-6 relative); counts are samples drawn from the table's own probabilities, seeded, each shot from a stream of its own, a batch of shots side by side; amplitudes carry the circuit's own global phase, found by tracking one amplitude of the Clifford part exactly through the circuit"
});
if r.shots > 0 {
let mut map: HashMap<String, u64> = HashMap::new();
for (bits, _) in gpu.sample(&pm, r.shots as usize, r.seed).map_err(|e| e.to_string())? {
*map.entry(r.program.bitstring(|q| bits[q as usize])).or_insert(0) += 1;
}
merge(out, counts(map));
}
if !r.amplitudes.is_empty() {
let fixed = catalog::cutrank_phase_with(r, &pm, true, &mut |p, x| gpu.amplitude(p, x).map_err(|e| e.to_string()))?;
if let Some(kappa) = fixed {
pm.scale(kappa);
}
let mut entries = Vec::new();
for b in &r.amplitudes {
let x: Vec<bool> = (0..n).map(|q| b.as_bytes()[(n - 1 - q) as usize] == b'1').collect();
let a = gpu.amplitude(&pm, &x).map_err(|e| e.to_string())?;
entries.push(json!({ "bits": b, "re": a.re, "im": a.im }));
}
out["amplitudes"] = Value::Array(entries);
if fixed.is_none() {
out["global_phase"] = json!("not fixed: tracking it exactly would take more than one job may; the amplitudes share one unknown phase, and their magnitudes and relative phases hold to single precision");
}
}
}
"cutrank" => {
use crate::quantum_cutrank::Precision;
// An order at least as cheap as the planner's (searched longer, from
// several seeds), and the precision the admitted memory allows.
let pm = catalog::product_measured(r, true)?;
let precision = if plan.memory_bytes >= catalog::cutrank_bytes(&pm, Precision::Double) { Precision::Double } else { Precision::Single };
let cut = pm.max_cut();
out["accuracy"] = json!({
"class": if precision == Precision::Double { "exact" } else { "single" },
"max_cut": cut,
"order_work": pm.work(),
"note": "the state's sum taken qubit by qubit along an order of the qubits, in a table of 2^max_cut entries (single class: the table stored in single precision, its arithmetic in double); counts are exact samples, seeded; amplitudes carry the circuit's own global phase, found by tracking one amplitude of the Clifford part exactly through the circuit"
});
if r.shots > 0 {
let mut map: HashMap<String, u64> = HashMap::new();
for (bits, _) in pm.sample_in(r.shots as usize, r.seed, cut, precision).map_err(|e| e.to_string())? {
*map.entry(r.program.bitstring(|q| bits[q as usize])).or_insert(0) += 1;
}
merge(out, counts(map));
}
if !r.amplitudes.is_empty() {
// The circuit's global phase: the lowering's own, times the
// ratio of one exactly tracked amplitude of the Clifford part
// to the graph form's at the same basis state.
let mut pm = pm;
let fixed = catalog::cutrank_phase(r, &pm, cut, precision)?;
if let Some(kappa) = fixed {
pm.scale(kappa);
}
let mut entries = Vec::new();
for b in &r.amplitudes {
let x: Vec<bool> = (0..n).map(|q| b.as_bytes()[(n - 1 - q) as usize] == b'1').collect();
let a = pm.amplitude_in(&x, cut, precision).map_err(|e| e.to_string())?;
entries.push(json!({ "bits": b, "re": a.re, "im": a.im }));
}
out["amplitudes"] = Value::Array(entries);
if fixed.is_none() {
out["global_phase"] = json!("not fixed: tracking it exactly would take more than one job may; the amplitudes share one unknown phase, and their magnitudes and relative phases are exact");
}
}
}
"stabilizer_rank" => {
use crate::quantum_stabrank::{amplitudes as exact_sums, exact_scale, sampled, sampled_bound, sampled_scale};
let c = r.program.clifford_t()?;
let t = c.t_count();
let mode = catalog::stabilizer_rank_mode(r, t)?;
let threads = if cfg!(target_arch = "wasm32") { 1 } else { std::thread::available_parallelism().map_or(1, |t| t.get()).min(16) };
// Every branch, or `terms` drawn: the sums and the scale they are read at.
let sums = |c: &crate::quantum_stabrank::CliffordT, xs: &[Vec<bool>], seed: u64| -> Result<(Vec<(f64, f64)>, Option<f64>), String> {
let tc = c.t_count();
match mode.sampled {
None => Ok((exact_sums(c, xs, threads).map_err(|e| e.to_string())?.iter().map(|s| s.value(exact_scale(tc))).collect(), None)),
Some((k, delta)) => Ok((sampled(c, xs, k, seed, threads).map_err(|e| e.to_string())?.iter().map(|s| s.value(sampled_scale(tc, k))).collect(), Some(sampled_bound(tc, k, delta)))),
}
};
out["accuracy"] = match mode.sampled {
None => json!({ "class": "exact", "t_count": t, "terms": 1u64.checked_shl(t).unwrap_or(u64::MAX), "note": "the state as the exact sum of its stabilizer branches, each T gate a sum of two Clifford gates of equal weight; every term is a whole number of sixteenths of a turn at a power of 1/√2, counted exactly, and the sum is evaluated once in double precision; an expectation value is the amplitude ⟨0|U†PU|0⟩, a sum over twice the T gates" }),
Some((k, delta)) => json!({ "class": "estimate", "t_count": t, "terms": k, "bound": sampled_bound(t, k, delta), "confidence": 1.0 - delta, "note": "the state's stabilizer branches drawn uniformly, seeded, each with its coefficient's phase: an unbiased estimate whose mean squared distance from the state is (sec²(π/8)^t − 1)/terms, so each amplitude is within the bound of the exact one with the probability stated (Markov's inequality), the bound computed from the T count, the terms and the confidence; an expectation value is the amplitude ⟨0|U†PU|0⟩, drawn over twice the T gates, with its own bound" }),
};
if !r.amplitudes.is_empty() {
let xs: Vec<Vec<bool>> = r.amplitudes.iter().map(|b| (0..n).map(|q| b.as_bytes()[(n - 1 - q) as usize] == b'1').collect()).collect();
let (values, bound) = sums(&c, &xs, r.seed)?;
out["amplitudes"] = Value::Array(
r.amplitudes
.iter()
.zip(values)
.map(|(b, (re, im))| {
let mut e = json!({ "bits": b, "re": re, "im": im });
if let (Some(eps), Some((_, delta))) = (bound, mode.sampled) {
e["bound"] = json!(eps);
e["confidence"] = json!(1.0 - delta);
}
e
})
.collect(),
);
}
if !r.observables.is_empty() {
let zero = vec![vec![false; n as usize]];
let mut entries = Vec::new();
for (i, (text, coeff, string)) in r.observables.iter().enumerate() {
let w = c.sandwich(string).map_err(|e| e.to_string())?;
let (values, bound) = sums(&w, &zero, r.seed.wrapping_add((i as u64 + 1).wrapping_mul(0x9E37_79B9_7F4A_7C15)))?;
let mut e = json!({ "observable": text, "coeff": coeff, "value": coeff * values[0].0 });
if let (Some(eps), Some((_, delta))) = (bound, mode.sampled) {
e["bound"] = json!(coeff.abs() * eps);
e["confidence"] = json!(1.0 - delta);
}
entries.push(e);
}
out["expectations"] = Value::Array(entries);
}
}
"tensor_network" => {
use crate::quantum_tn::{Network, PathOptions, contract_plan, find_path_restarts_seeded, sweep_orders, sweep_path, tensor_places};
// Independent path searches, side by side: the search's cost varies with its
// seed, and the cheapest of a few is much cheaper than one. A tab has one thread.
let restarts = r.options.get("path_restarts").and_then(Value::as_u64).map(|k| k.clamp(1, 64) as usize).unwrap_or(if cfg!(target_arch = "wasm32") { 1 } else { 8 });
let gates = r.program.matrices();
out["accuracy"] = json!({ "class": "exact" });
let mut entries = Vec::new();
let mut flops = 0.0;
for b in &r.amplitudes {
// qubit q's bit, in qubit order.
let bits: Vec<u8> = (0..n).map(|q| b.as_bytes()[(n - 1 - q) as usize] - b'0').collect();
// Controlled and diagonal gates split at their block-diagonal qubits.
let (mut net, place) = Network::amplitude_split_placed(n, &gates, &bits, &[]).map_err(|e| format!("{e:?}"))?;
net.reduce();
// The sweeps qubit by qubit and time by time, beside the search's own
// trees: a deep circuit on a line wants the first, which the search
// alone rarely finds.
let sweeps: Vec<_> = sweep_orders(&tensor_places(&net, &place)).iter().map(|o| sweep_path(o)).collect();
let plan = find_path_restarts_seeded(&net.shape(), &net.dims, &net.open, &PathOptions::default(), restarts, &sweeps);
flops += plan.total_flops();
let t = contract_plan(&net, &plan).map_err(|e| format!("{e:?}"))?;
let a = t.data.first().copied().unwrap_or(crate::gates::complex(0.0, 0.0));
entries.push(json!({ "bits": b, "re": a.re, "im": a.im }));
}
out["amplitudes"] = Value::Array(entries);
out["contraction_flops"] = json!(flops);
}
"density_matrix" => {
use crate::quantum_noisy::DensityMatrix;
let noise = r.noise.ok_or("no noise model")?;
// A registered channel joins the built-ins' after each gate, as its
// Kraus operators; without one, the noise model's own passes.
let registered: Vec<_> = job.registered_channels().collect();
let rho = if registered.is_empty() {
DensityMatrix::evolve(&r.program.matrices(), n, &noise).map_err(|e| format!("{e:?}"))?
} else {
DensityMatrix::evolve_steps(n, &noisy_steps(&r.program.matrices(), &noise, &registered)).map_err(|e| format!("{e:?}"))?
};
out["accuracy"] = json!({ "class": "exact", "note": "the noisy state itself, under the model; counts are seeded samples of its distribution" });
if r.shots > 0 {
let p = rho.probabilities(noise.readout);
let mut map: HashMap<String, u64> = HashMap::new();
for x in sample_distribution(&p, r.shots, r.seed) {
*map.entry(r.program.bitstring(|q| x >> (n - 1 - q) & 1 == 1)).or_insert(0) += 1;
}
merge(out, counts(map));
}
if !r.observables.is_empty() {
out["expectations"] = expectation_entries(r, |s| rho.expectation(&[(1.0, s.to_vec())]));
}
}
"trajectories" => {
use crate::quantum_noisy::branching_trajectories;
let noise = r.noise.ok_or("no noise model")?;
let count = catalog::trajectory_count(r);
// The plan reserved memory for this many states.
let states = ((plan.memory_bytes.saturating_sub(256 << 20)) / (catalog::amplitude_bytes(n) * if n < 16 { 8 } else { 1 }).max(1)).clamp(1, 16) as usize;
let threads = std::thread::available_parallelism().map_or(1, |t| t.get()).min(8);
let observables: Vec<crate::quantum_noisy::Observable> = r.observables.iter().map(|(_, c, s)| vec![(*c, s.clone())]).collect();
let run = branching_trajectories(&r.program.matrices(), n, &noise, &observables, count, r.seed, states, threads).map_err(|e| format!("{e:?}"))?;
out["accuracy"] = json!({
"class": "estimate",
"trajectories": count,
"histories": run.histories,
"gate_applications": run.gate_applications,
"note": "each shot is an independent noisy trajectory in law; shots that took the same errors share one state (histories counts the distinct ones); expectations are means over the trajectories with their standard errors"
});
if !r.observables.is_empty() {
out["expectations"] = Value::Array(
r.observables
.iter()
.zip(&run.expectations)
.map(|((text, coeff, _), e)| json!({ "observable": text, "coeff": coeff, "value": e.mean, "stderr": e.stderr }))
.collect(),
);
}
if r.shots > 0 {
let mut map: HashMap<String, u64> = HashMap::new();
for &(x, k) in &run.counts {
*map.entry(r.program.bitstring(|q| x >> (n - 1 - q) & 1 == 1)).or_insert(0) += k;
}
merge(out, counts(map));
out["shots"] = json!(count);
}
}
other => return Err(format!("unknown method {other}")),
}
Ok(())
}
/// What the program carries beyond its gates, in the result: its `output`s
/// (each a value, or the places of its bits in each outcome), a label when
/// it states durations the method does not apply, and the reader's warnings
/// and notes, each with its code. Each is present only when there is one, so
/// a program with none has the result it always had.
fn carried(r: &Request, plan: &Plan, out: &mut Value) {
use crate::qasm::OutputValue;
let extra = &r.program.extra;
if !extra.outputs.is_empty() {
out["outputs"] = Value::Array(
extra
.outputs
.iter()
.map(|o| match &o.value {
OutputValue::Bits(bits) => json!({ "name": o.name, "type": o.ty, "bits": bits }),
OutputValue::Value(_, v) => json!({ "name": o.name, "type": o.ty, "value": v }),
})
.collect(),
);
}
if extra.has_durations() {
out["timing"] = json!({
"applied": false,
"code": crate::qasm::diag::TIMING_IGNORED,
"why": format!("{} applies the gates in order and no durations: the program's delays, boxes and stretches change nothing it computes", plan.method),
});
}
if !r.diagnostics.is_empty() {
out["diagnostics"] = Value::Array(r.diagnostics.iter().map(|d| d.to_json()).collect());
}
}
/// A program that measures in the middle, resets, or acts on measured bits
/// ([`crate::engine::dynamic`]): counts by shot splitting, expectation values as the
/// exact mixture over its branches.
fn run_dynamic(r: &catalog::Request, plan: &Plan, steps: &[crate::qasm::Instr], out: &mut Value) -> Result<(), String> {
use crate::engine::dynamic::{self, Dense, Stabilizer};
let (n, nb) = (r.program.n_qubits, r.program.n_bits);
let strings: Vec<Vec<(u32, char)>> = r.observables.iter().map(|(_, _, s)| s.clone()).collect();
let (sampled, exact) = match plan.method.as_str() {
"statevector" => {
let gates = r.program.matrices();
// The states the plan admitted: its memory less the job's margin and the counts.
let states = (plan.memory_bytes.saturating_sub((256 << 20) + r.shots * 24) / catalog::amplitude_bytes(n)).max(1) as usize;
let start = Dense { sv: crate::quantum_sv::StateVector::zero(n).map_err(|e| format!("{e:?}"))?, gates: &gates };
let s = if r.shots > 0 { Some(dynamic::sample(steps, nb, start.clone(), r.shots, r.seed, states)?) } else { None };
let e = if strings.is_empty() { None } else { Some(dynamic::expectations(steps, &r.program.ops, nb, start, &strings, states)?) };
(s, e)
}
"stabilizer" => {
let ops: Vec<Vec<crate::quantum_tableau::Clifford>> = r.program.clifford_ops()?.into_iter().map(|(g, _)| g).collect();
let start = Stabilizer { t: crate::quantum_tableau::Tableau::new(n).map_err(|e| e.to_string())?, ops: &ops };
let s = if r.shots > 0 { Some(dynamic::sample(steps, nb, start.clone(), r.shots, r.seed, 64)?) } else { None };
let e = if strings.is_empty() { None } else { Some(dynamic::expectations(steps, &r.program.ops, nb, start, &strings, 64)?) };
(s, e)
}
m => return Err(format!("{m} does not run programs that measure in the middle")),
};
out["accuracy"] = json!({
"class": "exact",
"note": "counts: the shots split at each mid-circuit measurement and reset as independent shots would, so they have the law of independent runs; expectation values: the exact mixture over every branch of non-zero probability, of the state before the final measurements"
});
let mut d = json!({ "measurements_and_resets": dynamic::measurements(steps) });
if let Some(s) = sampled {
d["branches_run"] = json!(s.branches);
d["states_at_once"] = json!(s.peak_states);
merge(out, counts(s.counts));
}
if let Some((values, leaves)) = exact {
d["branches_weighed"] = json!(leaves);
out["expectations"] = Value::Array(
r.observables
.iter()
.zip(values)
.map(|((text, coeff, _), v)| json!({ "observable": text, "coeff": coeff, "value": coeff * v }))
.collect(),
);
}
out["dynamic"] = d;
Ok(())
}
/// The steps of `gates` under `noise` and the registered channels
/// `registered`: each gate, then the built-in channels in the order the
/// density matrix applies them ([`crate::quantum_noisy::DensityMatrix::evolve`]),
/// then each registered channel, a one-qubit one on each qubit the gate
/// touched and a two-qubit one on a two-qubit gate's pair.
fn noisy_steps(gates: &[crate::gates::Gate], noise: &crate::quantum_noisy::NoiseModel, registered: &[&crate::engine::registry::ChannelUse<'_>]) -> Vec<crate::quantum_noisy::Step> {
use crate::gates::Gate;
use crate::quantum_noisy::{Step, damping_kraus, pauli_gate, pauli_pair};
let mut steps = Vec::with_capacity(gates.len() * 4);
for g in gates {
steps.push(Step::Gate(g.clone()));
let k = g.qubits.len();
if k == 2 && noise.depol2 > 0.0 {
let (a, b) = (g.qubits[0], g.qubits[1]);
steps.push(Step::Channel((0..16u8).map(|j| (if j == 0 { 1.0 - noise.depol2 } else { noise.depol2 / 15.0 }, pauli_pair(a, b, j))).collect()));
}
for &q in &g.qubits {
if k != 2 && noise.depol1 > 0.0 {
steps.push(Step::Channel((0..4u8).map(|j| (if j == 0 { 1.0 - noise.depol1 } else { noise.depol1 / 3.0 }, pauli_gate(q, j))).collect()));
}
if noise.dephasing > 0.0 {
steps.push(Step::Channel(vec![(1.0 - noise.dephasing, pauli_gate(q, 0)), (noise.dephasing, pauli_gate(q, 3))]));
}
if noise.damping > 0.0 {
let [k0, k1] = damping_kraus(q, noise.damping);
steps.push(Step::Channel(vec![(1.0, k0), (1.0, k1)]));
}
}
for u in registered {
let places: Vec<Vec<u32>> = match u.channel.arity() {
1 => g.qubits.iter().map(|&q| vec![q]).collect(),
_ if k == 2 => vec![g.qubits.clone()],
_ => Vec::new(),
};
let ks = u.channel.kraus(&u.values);
for at in places {
steps.push(Step::Channel(ks.iter().map(|m| (1.0, Gate { qubits: at.clone(), matrix: m.clone() })).collect()));
}
}
}
steps
}
pub(crate) fn merge(out: &mut Value, extra: Value) {
if let (Some(o), Some(e)) = (out.as_object_mut(), extra.as_object()) {
for (k, v) in e {
o.insert(k.clone(), v.clone());
}
}
}
/// `shots` seeded draws from a probability vector (sorted uniforms swept
/// once through the cumulative distribution, as the state vector samples).
pub(crate) fn sample_distribution(p: &[f64], shots: u64, seed: u64) -> Vec<u64> {
let mut s = seed;
let mut u: Vec<f64> = (0..shots)
.map(|_| {
s = s.wrapping_add(0x9e37_79b9_7f4a_7c15);
let mut z = s;
z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
(((z ^ (z >> 31)) >> 11) as f64 + 0.5) / 9_007_199_254_740_992.0
})
.collect();
u.sort_by(|a, b| a.partial_cmp(b).unwrap());
let total: f64 = p.iter().sum();
let mut out = Vec::with_capacity(shots as usize);
let (mut cum, mut k) = (0.0, 0usize);
for (x, &px) in p.iter().enumerate() {
cum += px / total;
while k < u.len() && (u[k] < cum || x == p.len() - 1) {
out.push(x as u64);
k += 1;
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::catalog::plan;
const CAP: u64 = 8 << 30;
fn go(job: Value) -> Value {
let p = plan(&job, CAP).unwrap();
run(&job, &p).unwrap()
}
#[test]
#[cfg(feature = "quantum_toolchain")]
fn grover_as_the_ide_exports_it_finds_the_marked_item() {
// Its oracle and its diffusion are each a Z under four controls,
// written `ctrl(4) @ z`. Four rounds on five qubits find the marked
// item with probability 0.9992.
let marked = 0b10110u32;
let qasm = crate::quantum_toolchain::to_qasm(&crate::quantum_toolchain::grover(5, marked));
assert!(qasm.contains("ctrl(4) @ z"), "{qasm}");
let key: String = (0..5).rev().map(|q| if marked >> q & 1 == 1 { '1' } else { '0' }).collect();
for method in ["exact", "statevector"] {
let r = go(json!({ "qasm": qasm, "method": method, "shots": 1000, "seed": 3 }));
let hits = r["counts"][&key].as_u64().unwrap_or(0);
assert!(hits > 980, "{method}: {r}");
}
}
#[test]
fn a_bell_pair_is_the_same_on_every_method_that_takes_it() {
let qasm = "OPENQASM 3.0; qubit[2] q; bit[2] c; h q[0]; cx q[0], q[1]; c = measure q;";
for method in ["stabilizer", "exact", "statevector"] {
let r = go(json!({ "qasm": qasm, "method": method, "shots": 1000, "seed": 3 }));
let c = &r["counts"];
let (a, b) = (c["00"].as_u64().unwrap_or(0), c["11"].as_u64().unwrap_or(0));
assert_eq!(a + b, 1000, "{method}: {r}");
assert!(a > 400 && b > 400, "{method}: {r}");
assert!(c.get("01").is_none() && c.get("10").is_none(), "{method}: {r}");
}
for method in ["exact", "statevector", "pauli"] {
let r = go(json!({ "qasm": qasm, "method": method, "observables": ["Z0 Z1", "X0 X1", "Y0 Y1", "Z0"] }));
let v: Vec<f64> = r["expectations"].as_array().unwrap().iter().map(|e| e["value"].as_f64().unwrap()).collect();
for (got, want) in v.iter().zip([1.0, 1.0, -1.0, 0.0]) {
assert!((got - want).abs() < 1e-8, "{method}: {v:?}");
}
}
}
#[test]
fn bit_strings_put_the_highest_bit_first() {
let r = go(json!({ "qasm": "qubit[3] q; x q[0];", "method": "statevector", "shots": 10 }));
assert_eq!(r["counts"]["001"], json!(10), "{r}");
let r = go(json!({ "qasm": "qubit[3] q; x q[0];", "method": "exact", "shots": 10 }));
assert_eq!(r["counts"]["001"], json!(10), "{r}");
let r = go(json!({ "qasm": "qubit[3] q; x q[0];", "method": "stabilizer", "shots": 10 }));
assert_eq!(r["counts"]["001"], json!(10), "{r}");
// Measured into bits of other indices, the bits follow.
let r = go(json!({ "qasm": "qubit[3] q; bit[4] c; x q[0]; c[3] = measure q[0]; c[1] = measure q[2];", "method": "stabilizer", "shots": 10 }));
assert_eq!(r["counts"]["1000"], json!(10), "{r}");
// An amplitude names every qubit, the highest first.
for method in ["exact", "statevector", "tensor_network"] {
let r = go(json!({ "qasm": "qubit[3] q; x q[0];", "method": method, "amplitudes": ["001", "100"] }));
let a = r["amplitudes"].as_array().unwrap();
assert!((a[0]["re"].as_f64().unwrap().hypot(a[0]["im"].as_f64().unwrap()) - 1.0).abs() < 1e-8, "{method}: {r}");
assert!(a[1]["re"].as_f64().unwrap().abs() < 1e-12, "{method}: {r}");
}
}
#[test]
fn continuous_angles_agree_across_methods() {
let qasm = "qubit[5] q; rx(0.37) q[0]; ry(1.1) q[1]; cx q[0], q[2]; rzz(0.8) q[2], q[3]; u3(0.4, 0.2, -0.7) q[4]; cry(0.9) q[4], q[1]; crz(0.3) q[3], q[0];";
let obs = json!(["Z0", "X1 Z2", "Y3 Y4", "Z0 Z1 Z2 Z3 Z4"]);
let sv = go(json!({ "qasm": qasm, "method": "statevector", "observables": obs }));
let pa = go(json!({ "qasm": qasm, "method": "pauli", "observables": obs, "options": { "threshold": 0.0 } }));
for (a, b) in sv["expectations"].as_array().unwrap().iter().zip(pa["expectations"].as_array().unwrap()) {
assert!((a["value"].as_f64().unwrap() - b["value"].as_f64().unwrap()).abs() < 1e-9, "{a} vs {b}");
assert_eq!(b["bound"].as_f64().unwrap(), 0.0);
}
let amp = go(json!({ "qasm": qasm, "method": "statevector", "amplitudes": ["01101"] }));
let tn = go(json!({ "qasm": qasm, "method": "tensor_network", "amplitudes": ["01101"] }));
let (x, y) = (&amp["amplitudes"][0], &tn["amplitudes"][0]);
assert!((x["re"].as_f64().unwrap() - y["re"].as_f64().unwrap()).abs() < 1e-12 && (x["im"].as_f64().unwrap() - y["im"].as_f64().unwrap()).abs() < 1e-12, "{x} vs {y}");
}
#[test]
fn trajectory_counts_follow_the_density_matrix_and_share_histories() {
let qasm = "qubit[3] q; bit[3] c; h q[0]; cx q[0], q[1]; ry(0.7) q[2]; cx q[1], q[2]; c = measure q;";
let noise = json!({ "depolarizing_1q": 0.002, "depolarizing_2q": 0.01, "amplitude_damping": 0.01, "readout": 0.02 });
let shots = 20_000u64;
let dm = go(json!({ "qasm": qasm, "method": "density_matrix", "noise": noise, "shots": 1_000_000 }));
let tr = go(json!({ "qasm": qasm, "method": "trajectories", "noise": noise, "shots": shots, "seed": 4 }));
assert_eq!(tr["shots"], shots);
let total: u64 = tr["counts"].as_object().unwrap().values().map(|v| v.as_u64().unwrap()).sum();
assert_eq!(total, shots);
// Every outcome within five standard errors of the (finely sampled) exact distribution.
for (bits, c) in tr["counts"].as_object().unwrap() {
let p = dm["counts"][bits].as_u64().unwrap_or(0) as f64 / 1e6;
let f = c.as_u64().unwrap() as f64 / shots as f64;
let se = (p * (1.0 - p) / shots as f64).sqrt().max(2e-4);
assert!((f - p).abs() < 5.0 * se + 2e-3, "{bits}: {f} vs {p}");
}
let histories = tr["accuracy"]["histories"].as_u64().unwrap();
assert!(histories < shots / 4, "{histories} histories for {shots} shots");
}
#[test]
fn noisy_clifford_circuits_run_on_frames_and_agree_with_the_density_matrix() {
let qasm = "qubit[4] q; bit[4] c; h q[0]; cx q[0], q[1]; s q[1]; cx q[1], q[2]; h q[3]; cz q[3], q[2]; rx(pi/2) q[0]; c = measure q;";
let noise = json!({ "depolarizing_1q": 0.01, "depolarizing_2q": 0.03, "dephasing": 0.01, "readout": 0.02 });
let p = plan(&json!({ "qasm": qasm, "noise": noise, "shots": 100 }), CAP).unwrap();
// Small enough for the exact density matrix, which auto prefers.
assert_eq!(p.method, "density_matrix");
let shots = 100_000u64;
let dm = go(json!({ "qasm": qasm, "method": "density_matrix", "noise": noise, "shots": 1_000_000, "observables": ["Z0 Z1", "X3 Z2", "Y1"] }));
let st = go(json!({ "qasm": qasm, "method": "stabilizer", "noise": noise, "shots": shots, "seed": 5, "observables": ["Z0 Z1", "X3 Z2", "Y1"] }));
assert_eq!(st["method"], "stabilizer", "{st}");
assert_eq!(st["accuracy"]["class"], "estimate");
for (bits, c) in st["counts"].as_object().unwrap() {
let p = dm["counts"][bits].as_u64().unwrap_or(0) as f64 / 1e6;
let f = c.as_u64().unwrap() as f64 / shots as f64;
let se = (p * (1.0 - p) / shots as f64).sqrt().max(1e-4);
assert!((f - p).abs() < 5.0 * se + 2e-3, "{bits}: {f} vs {p}");
}
for k in 0..3 {
let (v, se) = (st["expectations"][k]["value"].as_f64().unwrap(), st["expectations"][k]["stderr"].as_f64().unwrap());
let want = dm["expectations"][k]["value"].as_f64().unwrap();
assert!((v - want).abs() < 5.0 * se + 1e-9, "observable {k}: {v} ± {se} vs {want}");
}
// Wide: auto takes the frames for a noisy Clifford circuit past the density matrix.
let wide = format!("qubit[300] q; h q[0]; {} ", (0..299).map(|i| format!("cx q[{i}], q[{}];", i + 1)).collect::<String>());
let w = go(json!({ "qasm": wide, "noise": { "depolarizing_2q": 0.001 }, "shots": 1000, "observables": ["Z0 Z299"] }));
assert_eq!(w["method"], "stabilizer", "{w}");
let v = w["expectations"][0]["value"].as_f64().unwrap();
assert!(v > 0.5 && v < 1.0, "{v}");
// Amplitude damping is not a Pauli channel.
let e = plan(&json!({ "qasm": wide, "method": "stabilizer", "noise": { "amplitude_damping": 0.01 } }), CAP).unwrap_err();
assert!(e.contains("amplitude damping"), "{e}");
}
#[test]
fn the_gpu_state_vector_agrees_where_there_is_one_and_is_refused_where_not() {
let qasm = "qubit[14] q; bit[14] c; h q; rx(0.3) q[0]; cx q[0], q[13]; crz(0.7) q[3], q[9]; ry(1.1) q[5]; c = measure q;";
let job = json!({ "qasm": qasm, "method": "statevector_single", "shots": 2000, "observables": ["Z0 Z13", "X5"], "amplitudes": ["00000000000000"] });
match crate::engine::catalog::gpu_max_qubits() {
Ok(_) => {
let g = go(job);
let d = go(json!({ "qasm": qasm, "method": "statevector", "observables": ["Z0 Z13", "X5"], "amplitudes": ["00000000000000"] }));
assert_eq!(g["accuracy"]["class"], "single");
for k in 0..2 {
let (a, b) = (g["expectations"][k]["value"].as_f64().unwrap(), d["expectations"][k]["value"].as_f64().unwrap());
assert!((a - b).abs() < 1e-5, "{a} vs {b}");
}
let (a, b) = (g["amplitudes"][0]["re"].as_f64().unwrap(), d["amplitudes"][0]["re"].as_f64().unwrap());
assert!((a - b).abs() < 1e-6);
assert_eq!(g["counts"].as_object().unwrap().values().map(|v| v.as_u64().unwrap()).sum::<u64>(), 2000);
}
Err(_) => {
let e = plan(&job, CAP).unwrap_err();
assert!(e.contains("needs a GPU"), "{e}");
}
}
}
#[test]
fn the_gpu_cut_rank_table_agrees_where_there_is_one_and_is_refused_where_not() {
// A Clifford bulk on 24 qubits, then a closing layer at any angles.
let mut q = String::from("qubit[24] q; bit[24] c; h q;");
for a in 0..24 {
q.push_str(&format!(" cz q[{a}], q[{}]; cx q[{a}], q[{}];", (a + 5) % 24, (a + 11) % 24));
}
q.push_str(" rx(0.3) q; ry(1.1) q[3]; rz(0.7) q[9]; c = measure q;");
let bits = ["000000000000000000000000", "101100111000101011110001"];
let job = json!({ "qasm": q, "method": "cutrank_single", "shots": 300, "seed": 3, "amplitudes": bits });
match crate::engine::catalog::gpu_cutrank_max_cut() {
Ok(_) => {
let g = go(job);
let d = go(json!({ "qasm": q, "method": "cutrank", "amplitudes": bits }));
assert_eq!(g["method"], "cutrank_single", "{g}");
assert_eq!(g["accuracy"]["class"], "single");
assert_eq!(g["accuracy"]["max_cut"], d["accuracy"]["max_cut"]);
for k in 0..2 {
let a = (g["amplitudes"][k]["re"].as_f64().unwrap(), g["amplitudes"][k]["im"].as_f64().unwrap());
let b = (d["amplitudes"][k]["re"].as_f64().unwrap(), d["amplitudes"][k]["im"].as_f64().unwrap());
let scale = b.0.hypot(b.1).max(1e-300);
assert!((a.0 - b.0).hypot(a.1 - b.1) / scale < 1e-5, "{a:?} vs {b:?}: the same amplitude, global phase and all");
}
assert_eq!(g["counts"].as_object().unwrap().values().map(|v| v.as_u64().unwrap()).sum::<u64>(), 300);
}
Err(_) => {
let e = plan(&job, CAP).unwrap_err();
assert!(e.contains("needs a GPU"), "{e}");
}
}
}
#[test]
fn a_pauli_sum_held_to_a_term_cap_keeps_its_bound() {
let qasm = "qubit[8] q; rx(0.7) q; rzz(0.4) q[0], q[1]; rzz(0.4) q[2], q[3]; rzz(0.4) q[4], q[5]; rzz(0.4) q[6], q[7]; ry(0.9) q; rzz(0.3) q[1], q[2]; rzz(0.3) q[3], q[4]; rzz(0.3) q[5], q[6]; rx(1.1) q; rzz(0.5) q[0], q[7]; ry(0.4) q;";
let exact = go(json!({ "qasm": qasm, "method": "statevector", "observables": ["Z0 Z5"] }));
let want = exact["expectations"][0]["value"].as_f64().unwrap();
let capped = go(json!({ "qasm": qasm, "method": "pauli", "observables": ["Z0 Z5"], "options": { "threshold": 0.0, "max_terms": 40 } }));
let e = &capped["expectations"][0];
assert!(e["threshold_reached"].as_f64().unwrap() > 0.0, "{e}");
let (v, b) = (e["value"].as_f64().unwrap(), e["bound"].as_f64().unwrap());
assert!((v - want).abs() <= b + 1e-9, "{v} vs {want}, bound {b}");
// Uncapped at threshold 0 it is exact.
let free = go(json!({ "qasm": qasm, "method": "pauli", "observables": ["Z0 Z5"], "options": { "threshold": 0.0 } }));
assert!(free["expectations"][0].get("threshold_reached").is_none());
assert!((free["expectations"][0]["value"].as_f64().unwrap() - want).abs() < 1e-9);
}
#[test]
fn pauli_answers_carry_a_threshold_ladder() {
let qasm = "qubit[6] q; rx(0.3) q[0]; rzz(0.4) q[0], q[1]; ry(0.9) q[1]; rzz(0.2) q[1], q[2]; rx(1.1) q[2]; cx q[2], q[3]; ry(0.5) q[3];";
let pa = go(json!({ "qasm": qasm, "method": "pauli", "observables": ["Z3", "X0 Z1"], "options": { "threshold": 1e-4 } }));
for e in pa["expectations"].as_array().unwrap() {
let ladder = e["ladder"].as_array().unwrap();
let ts: Vec<f64> = ladder.iter().map(|r| r["threshold"].as_f64().unwrap()).collect();
assert_eq!(ts.len(), 4, "{e}");
assert!((ts[0] - 0.1).abs() < 1e-15 && (ts[3] - 1e-4).abs() < 1e-18, "{ts:?}");
assert_eq!(ladder[3]["value"], e["value"]);
// A smaller threshold never drops more.
let bounds: Vec<f64> = ladder.iter().map(|r| r["bound"].as_f64().unwrap()).collect();
assert!(bounds.windows(2).all(|w| w[1] <= w[0] + 1e-15), "{bounds:?}");
}
let flat = go(json!({ "qasm": qasm, "method": "pauli", "observables": ["Z3"], "options": { "threshold": 1e-4, "ladder": false } }));
assert!(flat["expectations"][0].get("ladder").is_none());
}
#[test]
fn noise_is_modelled_exactly_and_by_trajectories() {
let qasm = "qubit[2] q; h q[0]; cx q[0], q[1];";
let noise = json!({ "depolarizing_2q": 0.2 });
let dm = go(json!({ "qasm": qasm, "method": "density_matrix", "noise": noise, "observables": ["Z0 Z1"] }));
let zz = dm["expectations"][0]["value"].as_f64().unwrap();
// Of the 15 two-qubit Paulis the channel applies (p/15 each), the 8
// that anticommute with Z⊗Z flip it: ⟨ZZ⟩ = 1 − 2·(8p/15).
assert!((zz - (1.0 - 16.0 * 0.2 / 15.0)).abs() < 1e-12, "{dm}");
let tr = go(json!({ "qasm": qasm, "method": "trajectories", "noise": noise, "observables": ["Z0 Z1"], "options": { "trajectories": 4000 } }));
let (mean, se) = (tr["expectations"][0]["value"].as_f64().unwrap(), tr["expectations"][0]["stderr"].as_f64().unwrap());
assert!((mean - zz).abs() < 5.0 * se + 1e-9, "trajectories {mean} ± {se} vs exact {zz}");
}
#[test]
fn a_thousand_qubit_clifford_circuit_is_sampled_and_read_exactly() {
let n = 1000;
let mut q = format!("qubit[{n}] q; h q[0];");
for i in 0..n - 1 {
q.push_str(&format!(" cx q[{i}], q[{}];", i + 1));
}
q.push_str(" rz(pi/2) q[0]; s q[0];");
let r = go(json!({ "qasm": q, "shots": 200, "observables": ["Z0 Z999", "X0", "Y0 Y1"] }));
assert_eq!(r["method"], "stabilizer", "{r}");
let c = r["counts"].as_object().unwrap();
assert_eq!(c.len(), 2, "{c:?}");
assert!(c.keys().all(|k| k == &"0".repeat(n) || k == &"1".repeat(n)));
assert_eq!(r["outcome_dimension"], json!(1));
let v: Vec<f64> = r["expectations"].as_array().unwrap().iter().map(|e| e["value"].as_f64().unwrap()).collect();
assert_eq!(v, vec![1.0, 0.0, 0.0]);
}
#[test]
fn the_matrix_product_state_agrees_and_reaches_wide_circuits() {
// Small: no truncation, so exact, and the state vector's values.
let qasm = "qubit[6] q; rx(0.37) q[0]; ry(1.1) q[1]; cx q[0], q[2]; rzz(0.8) q[2], q[3]; ccx q[1], q[3], q[5]; crz(0.3) q[4], q[0]; cswap q[0], q[2], q[4];";
let obs = json!(["Z0", "X1 Z2", "Y3 Y4", "Z0 Z1 Z2 Z3 Z4 Z5"]);
let sv = go(json!({ "qasm": qasm, "method": "statevector", "observables": obs, "amplitudes": ["010101"] }));
let m = go(json!({ "qasm": qasm, "method": "mps", "observables": obs, "amplitudes": ["010101"] }));
assert_eq!(m["accuracy"]["class"], "exact", "{m}");
for (a, b) in sv["expectations"].as_array().unwrap().iter().zip(m["expectations"].as_array().unwrap()) {
assert!((a["value"].as_f64().unwrap() - b["value"].as_f64().unwrap()).abs() < 1e-9, "{a} vs {b}");
}
let (x, y) = (&sv["amplitudes"][0], &m["amplitudes"][0]);
assert!((x["re"].as_f64().unwrap() - y["re"].as_f64().unwrap()).abs() < 1e-9 && (x["im"].as_f64().unwrap() - y["im"].as_f64().unwrap()).abs() < 1e-9);
// Wide: 200 qubits of low entanglement, sampled by auto.
let n = 200;
let mut q = format!("qubit[{n}] q;");
for i in 0..n {
q.push_str(&format!(" ry(0.3) q[{i}];"));
}
for i in 0..n - 1 {
q.push_str(&format!(" cx q[{i}], q[{}];", i + 1));
}
let w = go(json!({ "qasm": q, "shots": 100, "observables": ["Z0"] }));
assert_eq!(w["method"], "mps", "{w}");
assert_eq!(w["counts"].as_object().unwrap().values().map(|v| v.as_u64().unwrap()).sum::<u64>(), 100);
}
#[test]
fn the_exact_method_is_bit_exact_and_names_its_state() {
let job = json!({ "qasm": "qubit[4] q; h q; t q[1]; cx q[1], q[2]; cp(pi/8) q[0], q[3];", "method": "exact", "shots": 100, "observables": ["X0 X3"] });
let a = go(job.clone());
let b = go(job);
assert_eq!(a["state_hash"], b["state_hash"]);
assert_eq!(a["counts"], b["counts"]);
assert_eq!(a["state_hash"].as_str().unwrap().len(), 64);
}
/// Teleportation: q0's state arrives on q2 through two measurements and
/// the corrections they select, so ⟨Z2⟩ is cos θ exactly and q2 reads 1
/// with probability sin²(θ/2).
const TELEPORT: &str = "OPENQASM 3.0; qubit[3] q; bit[3] c; ry(THETA) q[0]; h q[1]; cx q[1], q[2]; cx q[0], q[1]; h q[0]; c[0] = measure q[0]; c[1] = measure q[1]; if (c[1]) x q[2]; if (c[0]) z q[2]; c[2] = measure q[2];";
#[test]
fn teleportation_delivers_the_state() {
for theta in [0.0, 0.7, 1.9, std::f64::consts::PI] {
let qasm = TELEPORT.replace("THETA", &format!("{theta}"));
let r = go(json!({ "qasm": qasm, "method": "statevector", "observables": ["Z2", "X2"], "shots": 20_000, "seed": 9 }));
assert_eq!(r["method"], "statevector", "{r}");
let z = r["expectations"][0]["value"].as_f64().unwrap();
let x = r["expectations"][1]["value"].as_f64().unwrap();
assert!((z - theta.cos()).abs() < 1e-12 && (x - theta.sin()).abs() < 1e-12, "θ {theta}: ⟨Z2⟩ {z}, ⟨X2⟩ {x}");
let ones: u64 = r["counts"].as_object().unwrap().iter().filter(|(k, _)| k.starts_with('1')).map(|(_, v)| v.as_u64().unwrap()).sum();
let p = (theta / 2.0).sin().powi(2);
let sd = (20_000.0 * p * (1.0 - p)).sqrt().max(1.0);
assert!((ones as f64 - 20_000.0 * p).abs() < 5.0 * sd, "θ {theta}: {ones} of 20000 read 1, expected {:.0}", 20_000.0 * p);
assert_eq!(r["counts"].as_object().unwrap().values().map(|v| v.as_u64().unwrap()).sum::<u64>(), 20_000);
// c0 and c1 are uniform and independent of what arrives.
assert_eq!(r["dynamic"]["branches_weighed"], 4, "{r}");
}
}
#[test]
fn a_clifford_teleport_runs_on_the_tableau_as_on_the_state_vector() {
// |+i⟩ teleported: ⟨Y2⟩ = 1.
let qasm = "qubit[3] q; bit[3] c; h q[0]; s q[0]; h q[1]; cx q[1], q[2]; cx q[0], q[1]; h q[0]; c[0] = measure q[0]; c[1] = measure q[1]; if (c[1] == 1) { x q[2]; } if (c[0] == 1) { z q[2]; } c[2] = measure q[2];";
let auto = go(json!({ "qasm": qasm, "observables": ["Y2", "Z2"] }));
assert_eq!(auto["method"], "stabilizer", "{auto}");
let sv = go(json!({ "qasm": qasm, "method": "statevector", "observables": ["Y2", "Z2"] }));
for (a, b) in auto["expectations"].as_array().unwrap().iter().zip(sv["expectations"].as_array().unwrap()) {
assert!((a["value"].as_f64().unwrap() - b["value"].as_f64().unwrap()).abs() < 1e-12, "{a} vs {b}");
}
assert_eq!(auto["expectations"][0]["value"], 1.0);
// The counts agree in law: c2 reads 0 or 1 evenly (Z on |+i⟩), c0 c1 uniform.
let st = go(json!({ "qasm": qasm, "method": "stabilizer", "shots": 40_000, "seed": 3 }));
assert_eq!(st["distinct_outcomes"], 8, "{st}");
for (k, v) in st["counts"].as_object().unwrap() {
let v = v.as_u64().unwrap() as f64;
assert!((v - 5_000.0).abs() < 5.0 * (5_000.0f64 * 7.0 / 8.0).sqrt(), "{k}: {v}");
}
}
#[test]
fn reset_and_measurement_mid_circuit() {
// A reset returns the qubit to |0⟩ whatever it held.
let r = go(json!({ "qasm": "qubit[2] q; bit[2] c; h q[0]; cx q[0], q[1]; reset q[0]; c = measure q;", "method": "statevector", "shots": 4000, "observables": ["Z0", "Z1"] }));
let counts = r["counts"].as_object().unwrap();
assert!(counts.keys().all(|k| k.ends_with('0')), "q0 always reads 0 after reset: {r}");
assert_eq!(r["expectations"][0]["value"], 1.0);
assert!(r["expectations"][1]["value"].as_f64().unwrap().abs() < 1e-12, "{r}");
// Measured, flipped, measured again: the second reading is the first's opposite.
for method in ["statevector", "stabilizer"] {
let r = go(json!({ "qasm": "qubit[1] q; bit[2] c; h q[0]; c[0] = measure q[0]; x q[0]; c[1] = measure q[0];", "method": method, "shots": 2000, "seed": 4 }));
let counts = r["counts"].as_object().unwrap();
assert!(counts.keys().all(|k| k == "01" || k == "10") && counts.len() == 2, "{method}: {r}");
}
}
/// The three-qubit bit-flip code: an injected flip is found by two
/// ancilla measurements and undone by the correction they select.
#[test]
fn a_syndrome_measurement_corrects_the_flip() {
for flipped in 0..3 {
let qasm = format!(
"qubit[5] q; bit[2] s; bit r; ry(0.7) q[0]; cx q[0], q[1]; cx q[0], q[2]; x q[{flipped}]; cx q[0], q[3]; cx q[1], q[3]; cx q[1], q[4]; cx q[2], q[4]; s[0] = measure q[3]; s[1] = measure q[4]; if (s == 1) x q[0]; if (s == 3) x q[1]; if (s == 2) x q[2]; cx q[0], q[1]; cx q[0], q[2]; r = measure q[0];"
);
let r = go(json!({ "qasm": qasm, "method": "statevector", "observables": ["Z0"], "shots": 1000 }));
assert!((r["expectations"][0]["value"].as_f64().unwrap() - 0.7f64.cos()).abs() < 1e-12, "flip on {flipped}: {r}");
// The syndrome is determined: one branch, and its bits name the flip.
assert_eq!(r["dynamic"]["branches_weighed"], 1, "{r}");
let want = ["01", "11", "10"][flipped];
assert!(r["counts"].as_object().unwrap().keys().all(|k| k.ends_with(want)), "flip on {flipped}: {r}");
}
}
/// A Clifford circuit on `n` qubits (Hadamards, phases, controlled-Z and
/// CX between pseudo-random pairs), then one layer of one-qubit gates at
/// generic angles on every qubit.
fn clifford_then_layer(n: u32, pairs: usize, seed: u64) -> String {
let mut s = seed;
let mut next = |m: u32| {
s = s.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1_442_695_040_888_963_407);
((s >> 33) % m as u64) as u32
};
let mut q = format!("OPENQASM 3.0; qubit[{n}] q; h q;");
for _ in 0..pairs {
let a = next(n);
let b = (a + 1 + next(n - 1)) % n;
q += &match next(4) {
0 => format!(" cz q[{a}], q[{b}];"),
1 => format!(" cx q[{a}], q[{b}]; s q[{b}];"),
2 => format!(" h q[{a}]; cz q[{a}], q[{b}];"),
_ => format!(" sdg q[{a}]; cx q[{b}], q[{a}];"),
};
}
for i in 0..n {
q += &format!(" t q[{i}]; ry({}) q[{i}]; rz({}) q[{i}];", 0.3 + 0.1 * i as f64, 1.1 - 0.07 * i as f64);
}
q
}
/// cutrank's amplitudes, global phase and all, are the state vector's.
fn assert_amplitudes_match(qasm: &str, n: u32, label: &str) {
let bits: Vec<String> = (0..24u64).map(|k| (0..n).map(|q| if (k.wrapping_mul(0x9e37_79b9) >> q) & 1 == 1 { '1' } else { '0' }).collect()).collect();
let cr = go(json!({ "qasm": qasm, "method": "cutrank", "amplitudes": bits }));
let sv = go(json!({ "qasm": qasm, "method": "statevector", "amplitudes": bits }));
assert_eq!(cr["method"], "cutrank", "{label}: {cr}");
assert_eq!(cr["accuracy"]["class"], "exact", "{label}: {cr}");
assert!(cr.get("global_phase").is_none(), "{label}: the phase is fixed: {cr}");
for (i, b) in bits.iter().enumerate() {
let get = |v: &Value| (v["amplitudes"][i]["re"].as_f64().unwrap(), v["amplitudes"][i]["im"].as_f64().unwrap());
let ((cre, cim), (sre, sim)) = (get(&cr), get(&sv));
assert!((cre - sre).abs() < 1e-10 && (cim - sim).abs() < 1e-10, "{label}, {b}: cutrank {cre} {cim}, statevector {sre} {sim}");
}
}
#[test]
fn cutrank_amplitudes_are_the_state_vectors_phase_and_all() {
for (n, seed) in [(6u32, 1u64), (9, 2), (11, 3)] {
assert_amplitudes_match(&clifford_then_layer(n, 3 * n as usize, seed), n, &format!("n {n}"));
}
// Gates whose lowering to Clifford ones drops a phase, in the Clifford part.
let phased = "OPENQASM 3.0; qubit[5] q; h q; rz(pi/2) q[0]; rx(pi) q[1]; ry(pi/2) q[2]; u(pi/2, 0, pi) q[3]; p(pi/2) q[4]; \
cp(pi) q[0], q[1]; rzz(pi/2) q[1], q[2]; iswap q[2], q[3]; sx q[4]; sxdg q[0]; cy q[3], q[4]; swap q[0], q[2]; \
rxx(pi/2) q[3], q[1]; crz(pi) q[4], q[0]; cu(pi, 0, pi, pi/2) q[2], q[1]; \
t q[0]; ry(0.4) q[1]; rz(1.3) q[2]; h q[3]; rx(0.7) q[3]; tdg q[4];";
assert_amplitudes_match(phased, 5, "phase-carrying lowerings");
}
#[test]
fn cutrank_counts_follow_the_exact_distribution() {
let n = 5u32;
let qasm = clifford_then_layer(n, 9, 7);
let all: Vec<String> = (0..1u32 << n).map(|x| format!("{x:05b}")).collect();
let sv = go(json!({ "qasm": qasm, "method": "statevector", "amplitudes": all }));
let shots = 40_000u64;
let cr = go(json!({ "qasm": qasm, "method": "cutrank", "shots": shots, "seed": 5 }));
let counts = cr["counts"].as_object().unwrap();
assert_eq!(counts.values().map(|v| v.as_u64().unwrap()).sum::<u64>(), shots);
for (i, b) in all.iter().enumerate() {
let (re, im) = (sv["amplitudes"][i]["re"].as_f64().unwrap(), sv["amplitudes"][i]["im"].as_f64().unwrap());
let p = re * re + im * im;
let got = counts.get(b).and_then(Value::as_u64).unwrap_or(0) as f64;
let sd = (shots as f64 * p * (1.0 - p)).sqrt().max(1.0);
assert!((got - shots as f64 * p).abs() < 5.0 * sd, "{b}: {got} of {shots}, expected {:.1}", shots as f64 * p);
}
// Seeded.
let again = go(json!({ "qasm": qasm, "method": "cutrank", "shots": shots, "seed": 5 }));
assert_eq!(again["counts"], cr["counts"]);
}
#[test]
fn auto_samples_a_wide_clifford_then_layer_circuit_by_cutrank() {
// 40 qubits: past the dense state vector in a test job's memory, and not Clifford.
let n = 40u32;
let mut qasm = format!("OPENQASM 3.0; qubit[{n}] q; bit[{n}] c; h q;");
for i in 0..n {
qasm += &format!(" cz q[{i}], q[{}];", (i + 1) % n);
qasm += &format!(" cz q[{i}], q[{}];", (i + 5) % n);
}
for i in 0..n {
qasm += &format!(" t q[{i}]; h q[{i}];");
}
qasm += " c = measure q;";
let r = go(json!({ "qasm": qasm, "shots": 200, "seed": 3 }));
assert_eq!(r["method"], "cutrank", "{r}");
assert_eq!(r["counts"].as_object().unwrap().values().map(|v| v.as_u64().unwrap()).sum::<u64>(), 200);
assert!(r["accuracy"]["max_cut"].as_u64().unwrap() <= 12, "{r}");
}
#[test]
fn cutrank_refuses_what_it_cannot_sum() {
let plan_of = |j: Value| plan(&j, CAP);
let e = plan_of(json!({ "qasm": "qubit[2] q; rx(0.3) q[0]; cx q[0], q[1];", "method": "cutrank", "shots": 10 })).unwrap_err();
assert!(e.contains("comes after a gate on qubit 0 that is not Clifford"), "{e}");
let e = plan_of(json!({ "qasm": "qubit[3] q; ccx q[0], q[1], q[2];", "method": "cutrank", "shots": 10 })).unwrap_err();
assert!(e.contains("not a Clifford gate"), "{e}");
let e = plan_of(json!({ "qasm": "qubit[2] q; h q[0]; t q[1];", "method": "cutrank", "observables": ["Z0"] })).unwrap_err();
assert!(e.contains("counts and amplitudes"), "{e}");
}
#[test]
fn dynamic_runs_repeat_with_their_seed() {
let qasm = TELEPORT.replace("THETA", "1.1");
let a = go(json!({ "qasm": qasm, "method": "statevector", "shots": 5000, "seed": 21 }));
let b = go(json!({ "qasm": qasm, "method": "statevector", "shots": 5000, "seed": 21 }));
let c = go(json!({ "qasm": qasm, "method": "statevector", "shots": 5000, "seed": 22 }));
assert_eq!(a["counts"], b["counts"]);
assert_ne!(a["counts"], c["counts"]);
}
/// The language the reader gained, run: a subroutine's own bit fed
/// forward (teleportation as a subroutine), a repeat-until-success loop on
/// measured bits under a declared bound, outputs, and timing labelled.
#[test]
fn subroutines_bounded_loops_outputs_and_timing_reach_the_result() {
// |1⟩ teleported from q[0] to q[2]: q[2] always reads 1.
let teleport = "OPENQASM 3.1;\nqubit[3] q;\noutput bit[1] r;\n\
def teleport(qubit src, qubit mid, qubit dst) {\n bit a; bit b;\n h mid; cx mid, dst;\n cx src, mid; h src;\n a = measure src; b = measure mid;\n if (b) x dst;\n if (a) z dst;\n}\n\
x q[0];\nteleport(q[0], q[1], q[2]);\nr[0] = measure q[2];\n";
let res = go(json!({ "qasm": teleport, "shots": 400, "seed": 5 }));
assert_eq!(res["counts"], json!({ "1": 400 }), "{res}");
assert_eq!(res["outputs"], json!([{ "name": "r", "type": "bit[1]", "bits": [0] }]));
// Repeat until success: a run stops at its first 1, or after 3 tries.
let rus = "qubit q;\nbit c;\nh q;\nc = measure q;\n@wai.max_iterations 3\nwhile (c == 0) { reset q; h q; c = measure q; }\n";
let res = go(json!({ "qasm": rus, "shots": 8000, "seed": 9 }));
let ones = res["counts"]["1"].as_u64().unwrap();
// P(1 within four tries) = 1 − 1/16.
assert!((7400..7600).contains(&ones), "{res}");
assert_eq!(res["diagnostics"][0]["code"], "Q311", "{res}");
// Durations are carried and the result says the method applied none.
let timed = go(json!({ "qasm": "qubit[2] q;\nh q[0];\ndelay[100ns] q[1];\ncx q[0], q[1];", "shots": 10 }));
assert_eq!((timed["timing"]["applied"].as_bool(), timed["timing"]["code"].as_str()), (Some(false), Some("Q400")), "{timed}");
// A program with none of it has the result it always had.
let plain = go(json!({ "qasm": "qubit[2] q;\nh q[0];\ncx q[0], q[1];", "shots": 10 }));
assert!(plain.get("timing").is_none() && plain.get("outputs").is_none() && plain.get("diagnostics").is_none(), "{plain}");
}
/// A result's provenance names each pass its compile ran, with the
/// verdict the certificate gives it, and the program's digest; the
/// result beside it is the one `run` gives.
#[test]
fn a_result_s_provenance_names_the_passes_its_certificate_ran() {
let qasm = "OPENQASM 3.0;\nqubit[2] q;\nh q[0];\nrz(0.3) q[0];\ncx q[0], q[1];\nh q[0];\nh q[0];\n";
let req = json!({ "qasm": qasm, "shots": 50, "compile": { "epsilon": 1e-4, "level": 2 } });
let plan = catalog::plan(&req, 1 << 30).unwrap();
let mut with = run_with_provenance(&req, &plan, "a test host 1").unwrap();
let p = with.as_object_mut().unwrap().remove("provenance").unwrap();
let mut plain = run(&req, &plan).unwrap();
for v in [&mut with, &mut plain] {
v.as_object_mut().unwrap().remove("elapsed_ms");
}
assert_eq!(with, plain, "provenance is added beside the result, which is unchanged");
let cert = crate::engine::compile::call(&json!({ "qasm": qasm, "epsilon": 1e-4, "level": 2 })).unwrap()["certificate"].clone();
let want: Vec<Value> = cert["passes"].as_array().unwrap().iter().map(|r| json!({ "pass": r["pass"], "verdict": r["check"]["verdict"] })).collect();
assert_eq!(p["compilation"]["passes"], Value::Array(want), "{p}");
assert_eq!(p["compilation"]["certificate"], crate::engine::compile::certificate_hash(&cert));
assert_eq!(p["circuit"]["program"].as_str().unwrap(), blake3::hash(qasm.as_bytes()).to_hex().as_str());
assert_eq!((p["schema"].as_str(), p["computer"]["method"].as_str(), p["software"]["host"].as_str()), (Some(PROVENANCE), Some("exact"), Some("a test host 1")), "{p}");
// A job that asked for no compile says so, and why.
let bell = json!({ "qasm": "OPENQASM 3.0; qubit[2] q; h q[0]; cx q[0], q[1];", "shots": 10 });
let p = run_with_provenance(&bell, &catalog::plan(&bell, 1 << 30).unwrap(), "h").unwrap()["provenance"].clone();
assert_eq!((p["compilation"]["asked"].as_bool(), p["compilation"]["status"].as_str()), (Some(false), Some("not-applicable")), "{p}");
}
/// A random Clifford+T program in the language, with every gate the
/// lowering turns into T gates, its global phases included.
fn clifford_t_program(n: u32, len: usize, seed: u64) -> String {
let mut s = seed;
let mut next = || {
s = s.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = s;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
};
let mut q = format!("OPENQASM 3.0;\nqubit[{n}] q;\n");
for _ in 0..len {
let a = (next() % u64::from(n)) as u32;
let b = (a + 1 + (next() % u64::from(n - 1)) as u32) % n;
let c = (0..n).find(|&x| x != a && x != b).unwrap();
let odd = ["pi/4", "3*pi/4", "-pi/4", "5*pi/4"][(next() % 4) as usize];
q.push_str(&match next() % 22 {
0 | 1 => format!("h q[{a}];\n"),
2 => format!("t q[{a}];\n"),
3 => format!("tdg q[{a}];\n"),
4 => format!("s q[{a}];\n"),
5 => format!("cx q[{a}], q[{b}];\n"),
6 => format!("cz q[{a}], q[{b}];\n"),
7 => format!("rz({odd}) q[{a}];\n"),
8 => format!("rx({odd}) q[{a}];\n"),
9 => format!("ry({odd}) q[{a}];\n"),
10 => format!("p({odd}) q[{a}];\n"),
11 => format!("cp(pi/2) q[{a}], q[{b}];\n"),
12 => format!("ccx q[{a}], q[{b}], q[{c}];\n"),
13 => format!("ccz q[{a}], q[{b}], q[{c}];\n"),
14 => format!("sx q[{a}];\n"),
15 | 16 => format!("cx q[{b}], q[{a}];\n"),
17 => format!("swap q[{a}], q[{b}];\n"),
18 => format!("h q[{b}];\n"),
19 => format!("y q[{a}];\n"),
_ => format!("rz(pi/2) q[{a}];\n"),
});
}
q
}
/// Ground truth first: the stabilizer-rank sum of a program, every
/// amplitude (phase and all) and every expectation value, is the dense
/// state vector's, on every size a test affords; checked amplitude by
/// amplitude and by fidelity.
#[test]
fn the_stabilizer_rank_method_is_the_dense_state_vector() {
let mut cases = 0;
for n in 3..=6u32 {
for seed in 0..10u64 {
let qasm = clifford_t_program(n, 2 * n as usize + 2, seed * 7 + u64::from(n));
let t = crate::qasm::read(&qasm).unwrap().program.clifford_t().unwrap().t_count();
// An expectation value sums 4^t branches.
if t > 9 {
continue;
}
let bits: Vec<String> = (0..1u32 << n).map(|x| format!("{x:0w$b}", w = n as usize)).collect();
let job = |method: &str| json!({ "qasm": qasm, "method": method, "amplitudes": bits, "observables": ["Z0", "X1 Z2", format!("Y{} X0", n - 1)] });
let sv = run(&job("statevector"), &plan(&job("statevector"), CAP).unwrap()).unwrap();
let sr_plan = plan(&job("stabilizer_rank"), CAP).unwrap();
let sr = run(&job("stabilizer_rank"), &sr_plan).unwrap();
assert_eq!((sr["accuracy"]["class"].as_str(), sr["accuracy"]["t_count"].as_u64()), (Some("exact"), Some(u64::from(t))), "{}", sr["accuracy"]);
let (mut ore, mut oim, mut norm) = (0.0, 0.0, 0.0);
for (a, b) in sv["amplitudes"].as_array().unwrap().iter().zip(sr["amplitudes"].as_array().unwrap()) {
let (ar, ai, br, bi) = (a["re"].as_f64().unwrap(), a["im"].as_f64().unwrap(), b["re"].as_f64().unwrap(), b["im"].as_f64().unwrap());
assert!((ar - br).abs() < 1e-12 && (ai - bi).abs() < 1e-12, "n={n} seed={seed} {}: {ar} {ai} against {br} {bi}\n{qasm}", a["bits"]);
ore += ar * br + ai * bi;
oim += ar * bi - ai * br;
norm += br * br + bi * bi;
}
assert!(((ore * ore + oim * oim) / norm - 1.0).abs() < 1e-12 && (ore - 1.0).abs() < 1e-12, "n={n} seed={seed}: the fidelity, and the overlap with the phase");
for (a, b) in sv["expectations"].as_array().unwrap().iter().zip(sr["expectations"].as_array().unwrap()) {
assert!((a["value"].as_f64().unwrap() - b["value"].as_f64().unwrap()).abs() < 1e-12, "n={n} seed={seed} {}", a["observable"]);
}
cases += 1;
}
}
assert!(cases >= 16, "{cases}");
}
/// Auto takes the stabilizer-rank sum by its T count, the sampled mode
/// states its bound and confidence, and the method refuses what it does not
/// take.
#[test]
fn auto_chooses_the_stabilizer_rank_sum_by_its_t_count() {
let wide = |ts: usize| {
let mut q = String::from("OPENQASM 3.0;\nqubit[40] q;\nh q;\n");
for i in 0..ts {
q.push_str(&format!("t q[{}];\ncx q[{}], q[{}];\n", i % 40, i % 40, (i + 7) % 40));
}
q.push_str("h q;\n");
json!({ "qasm": q, "amplitudes": ["0".repeat(40)], "observables": [] })
};
let p = plan(&wide(6), CAP).unwrap();
assert_eq!(p.method, "stabilizer_rank", "{}", p.why);
let many = plan(&wide(60), CAP).unwrap();
assert_eq!(many.method, "tensor_network", "too many T gates for one job's sum: {}", many.why);
assert!(many.why.contains("stabilizer_rank: 60 T gates"), "{}", many.why);
// Expectation values alone, past the dense state vector.
let mut e = wide(4);
e["amplitudes"] = json!([]);
e["observables"] = json!(["Z0 Z7", "X3"]);
assert_eq!(plan(&e, CAP).unwrap().method, "stabilizer_rank");
// The sampled mode states its bound and confidence, and its schema holds.
let mut s = wide(10);
s["method"] = json!("stabilizer_rank");
s["options"] = json!({ "terms": 2000, "confidence": 0.9 });
let out = run(&s, &plan(&s, CAP).unwrap()).unwrap();
assert_eq!(out["accuracy"]["class"], "estimate");
let b = out["accuracy"]["bound"].as_f64().unwrap();
assert!((b - crate::quantum_stabrank::sampled_bound(10, 2000, 0.1)).abs() < 1e-15 && out["amplitudes"][0]["bound"].as_f64() == Some(b), "{out}");
let errs = crate::engine::schema::Registry::engine().check("job-result.v1.json", &out);
assert!(errs.is_empty(), "{errs:?}");
// Refusals: counts, a gate past Clifford+T, noise, and options that do not read.
let refuse = |v: Value| plan(&v, CAP).unwrap_err();
assert!(refuse(json!({ "qasm": "OPENQASM 3.0;\nqubit[2] q;\nt q[0];\n", "method": "stabilizer_rank", "shots": 10 })).contains("amplitudes and expectation values"));
assert!(refuse(json!({ "qasm": "OPENQASM 3.0;\nqubit[2] q;\nrx(0.3) q[0];\n", "method": "stabilizer_rank", "amplitudes": ["00"] })).contains("needs a Clifford+T circuit"));
assert!(refuse(json!({ "qasm": "OPENQASM 3.0;\nqubit[2] q;\nt q[0];\n", "method": "stabilizer_rank", "amplitudes": ["00"], "noise": { "dephasing": 0.01 } })).contains("does not model noise"));
assert!(refuse(json!({ "qasm": "OPENQASM 3.0;\nqubit[2] q;\nt q[0];\n", "method": "stabilizer_rank", "amplitudes": ["00"], "options": { "terms": 10, "epsilon": 0.1 } })).ends_with("[J003]"));
let fine = json!({ "qasm": "OPENQASM 3.0;\nqubit[2] q;\nt q[0];\n", "method": "stabilizer_rank", "amplitudes": ["00"], "options": { "epsilon": 0.1 } });
assert!(plan(&fine, CAP).unwrap().why.contains("drawn"));
}
}