use super::{Certificate, Member, MemberError, Presence, Provenance, REFERENCE, RunMeter, Window, certify_sampling, detect_local, lanes};
use crate::quantum::{BaseGate, Circuit, Gate, Gateset, QuantumError, circuit_hash};
use crate::quantum_energy::EnergyClass;
use std::future::Future;
use std::pin::Pin;
pub const KERNEL_CIRCUIT_SAMPLE: &str = "wai.quantum.circuit/sample";
pub const KERNEL_CIRCUIT2_SAMPLE: &str = "wai.quantum.circuit2/sample";
pub const fn sample_kernel(gs: Gateset) -> &'static str {
match gs {
Gateset::V1 => KERNEL_CIRCUIT_SAMPLE,
_ => KERNEL_CIRCUIT2_SAMPLE,
}
}
pub type Counts = Vec<u64>;
pub type SampleFuture<'r> = Pin<Box<dyn Future<Output = Result<(Counts, u64, Option<EnergyClass>), MemberError>> + 'r>>;
pub trait Sampler {
fn member_id(&self) -> String;
fn sample<'r>(&'r self, implementation: &'r str, c: &'r Circuit, shots: u64, meter: &'r mut dyn RunMeter) -> SampleFuture<'r>;
}
impl<S: Sampler + ?Sized> Sampler for &S {
fn member_id(&self) -> String {
(**self).member_id()
}
fn sample<'r>(&'r self, implementation: &'r str, c: &'r Circuit, shots: u64, meter: &'r mut dyn RunMeter) -> SampleFuture<'r> {
(**self).sample(implementation, c, shots, meter)
}
}
pub fn reference_distribution(c: &Circuit) -> Result<Vec<f64>, QuantumError> {
let weights = c.simulate()?.prob_weights();
let total: i128 = weights.iter().sum();
if total <= 0 {
return Ok(vec![0.0; weights.len()]);
}
let t = total as f64;
Ok(weights.iter().map(|&w| w as f64 / t).collect())
}
#[derive(Clone, Copy, Debug, Default)]
pub struct CpuSampler {
pub seed: u64,
}
impl Sampler for CpuSampler {
fn member_id(&self) -> String {
detect_local().id
}
fn sample<'r>(&'r self, implementation: &'r str, c: &'r Circuit, shots: u64, meter: &'r mut dyn RunMeter) -> SampleFuture<'r> {
Box::pin(async move {
if implementation != REFERENCE {
return Err(MemberError::Other(format!("the CPU reference sampler does not run implementation {implementation:?}")));
}
let mut window = Window::new(meter);
window.open();
let counts = c.simulate().map_err(MemberError::Circuit)?.sample_shots(self.seed, shots);
let (joules_micro, class) = window.close();
Ok((counts, joules_micro, class))
})
}
}
pub fn local_sampling_member() -> Member {
let local = detect_local();
let kernels = Gateset::ALL
.into_iter()
.map(|gs| super::KernelEntry {
kernel: sample_kernel(gs).into(),
implementation: REFERENCE.into(),
certificate: Certificate::reference(sample_kernel(gs), &local.id),
energy: None,
})
.collect();
Member { kernels, ..local }
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct QpuCorpus {
pub seed: u64,
pub cases: usize,
pub max_qubits: u8,
pub depth: usize,
}
pub const QPU_DEFAULT: QpuCorpus = QpuCorpus { seed: 7, cases: 16, max_qubits: 5, depth: 12 };
fn next(s: &mut u64) -> u64 {
*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)
}
impl QpuCorpus {
pub fn generator(&self) -> String {
format!("qpu-v1 seed={} cases={} qubits={} depth={}", self.seed, self.cases, self.max_qubits, self.depth)
}
pub fn circuits(&self, gs: Gateset) -> Vec<Circuit> {
let one = |base, target| Gate { base, controls: Vec::new(), target, param: 0 };
let cx = |control, target| Gate { base: BaseGate::X, controls: vec![control], target, param: 0 };
let ghz = |n: u8| {
let mut c = Circuit::with_gateset(n, gs);
c.ops.push(one(BaseGate::H, 0));
(1..n).for_each(|q| c.ops.push(cx(q - 1, q)));
c
};
let mut out = vec![ghz(2), ghz(3), ghz(self.max_qubits.max(3))];
let bases = [BaseGate::H, BaseGate::X, BaseGate::Y, BaseGate::Z, BaseGate::S, BaseGate::Sdg, BaseGate::T, BaseGate::Tdg, BaseGate::P];
let mut s = self.seed;
while out.len() < self.cases {
let n = 2 + (next(&mut s) % u64::from(self.max_qubits.max(3) - 1)) as u8;
let mut c = Circuit::with_gateset(n, gs);
for _ in 0..self.depth {
let target = (next(&mut s) % u64::from(n)) as u8;
if next(&mut s).is_multiple_of(3) {
let control = (target + 1 + (next(&mut s) % u64::from(n - 1)) as u8) % n;
let base = if next(&mut s).is_multiple_of(2) { BaseGate::X } else { BaseGate::Z };
c.ops.push(Gate { base, controls: vec![control], target, param: 0 });
} else {
let base = bases[(next(&mut s) % bases.len() as u64) as usize];
let param = if base == BaseGate::P { 1 + (next(&mut s) % 4) as u16 } else { 0 };
c.ops.push(Gate { base, controls: Vec::new(), target, param });
}
}
out.push(c);
}
out.truncate(self.cases);
out
}
}
pub fn case_input(c: &Circuit, shots: u64) -> Vec<u8> {
let mut o = lanes::circuit_bytes(c);
o.extend_from_slice(&shots.to_le_bytes());
o
}
pub fn check_counts(c: &Circuit, shots: u64, counts: &[u64]) -> Result<(), MemberError> {
let want = 1usize.checked_shl(u32::from(c.n_qubits)).unwrap_or(0);
if counts.len() != want {
return Err(MemberError::Other(format!("{} counts for a circuit of {} qubits; {want} expected", counts.len(), c.n_qubits)));
}
let total = counts.iter().try_fold(0u64, |a, &b| a.checked_add(b));
if total != Some(shots) {
return Err(MemberError::Other(format!("the counts sum to {total:?}, not {shots}")));
}
Ok(())
}
pub async fn certify_sampler<S: Sampler + ?Sized>(sampler: &S, implementation: &str, gs: Gateset, corpus: QpuCorpus, shots: u64, provenance: Provenance) -> Result<Certificate, MemberError> {
let mut cases = Vec::new();
for c in corpus.circuits(gs) {
let reference = reference_distribution(&c).map_err(MemberError::Circuit)?;
let (counts, _, _) = sampler.sample(implementation, &c, shots, &mut super::Unmetered).await?;
check_counts(&c, shots, &counts)?;
cases.push((case_input(&c, shots), reference, counts));
}
Ok(certify_sampling(sample_kernel(gs), implementation, &sampler.member_id(), &corpus.generator(), cases).with_provenance(provenance))
}
const DOMAIN_COUNTS: &[u8] = b"wai:quantum-fabric-counts\x01";
pub fn counts_hash(c: &Circuit, shots: u64, counts: &[u64]) -> Option<[u8; 32]> {
let mut h = blake3::Hasher::new();
h.update(DOMAIN_COUNTS);
h.update(&circuit_hash(c).ok()?);
h.update(&shots.to_le_bytes());
h.update(&(counts.len() as u64).to_le_bytes());
for k in counts {
h.update(&k.to_le_bytes());
}
Some(*h.finalize().as_bytes())
}
pub fn sampling_member(id: String, class: super::FabricClass, detail: String, entries: Vec<super::KernelEntry>) -> Member {
Member { id, class, presence: Presence::Detected, kernels: entries, detail }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::quantum_fabric::{FabricClass, KernelEntry, Parity, place, ready};
#[test]
fn the_sample_kernels_name_their_gateset() {
assert_eq!(sample_kernel(Gateset::V1), KERNEL_CIRCUIT_SAMPLE);
assert_eq!(sample_kernel(Gateset::V2), KERNEL_CIRCUIT2_SAMPLE);
assert_ne!(KERNEL_CIRCUIT_SAMPLE, crate::quantum_fabric::KERNEL_CIRCUIT_APPLY);
}
#[test]
fn the_reference_distribution_is_the_states_weights_normalised() {
let mut bell = Circuit::new(2);
bell.h(0).cx(0, 1);
let p = reference_distribution(&bell).unwrap();
assert_eq!(p.len(), 4);
assert!((p[0] - 0.5).abs() < 1e-9 && (p[3] - 0.5).abs() < 1e-9 && p[1] == 0.0 && p[2] == 0.0, "{p:?}");
let qft = Circuit::qft(4);
let p = reference_distribution(&qft).unwrap();
assert!((p.iter().sum::<f64>() - 1.0).abs() < 1e-12);
assert!(p.iter().all(|&x| (x - 1.0 / 16.0).abs() < 1e-6), "the QFT of |0⟩ is uniform");
}
#[test]
fn the_qpu_corpus_is_small_shallow_singly_controlled_and_the_same_in_both_gatesets() {
let v1 = QPU_DEFAULT.circuits(Gateset::V1);
let v2 = QPU_DEFAULT.circuits(Gateset::V2);
assert_eq!(v1.len(), QPU_DEFAULT.cases);
assert_eq!(v1.iter().map(|c| (&c.ops, c.n_qubits)).collect::<Vec<_>>(), v2.iter().map(|c| (&c.ops, c.n_qubits)).collect::<Vec<_>>());
for c in v1.iter().chain(&v2) {
assert!(c.validate().is_ok(), "{c:?}");
assert!((2..=QPU_DEFAULT.max_qubits).contains(&c.n_qubits));
assert!(c.ops.iter().all(|g| g.controls.len() <= 1 && g.param <= 4));
}
assert_eq!(QPU_DEFAULT.circuits(Gateset::V2), v2, "deterministic");
assert_eq!(QPU_DEFAULT.generator(), "qpu-v1 seed=7 cases=16 qubits=5 depth=12");
}
#[test]
fn the_cpu_sampler_is_deterministic_and_certifies_near_one() {
let s = CpuSampler { seed: 0x5a };
let qft = Circuit::qft(3);
let a = ready(s.sample(REFERENCE, &qft, 1000, &mut crate::quantum_fabric::Unmetered)).unwrap().unwrap().0;
let b = ready(s.sample(REFERENCE, &qft, 1000, &mut crate::quantum_fabric::Unmetered)).unwrap().unwrap().0;
assert_eq!(a, b);
assert_eq!(a.iter().sum::<u64>(), 1000);
assert!(ready(s.sample("other", &qft, 10, &mut crate::quantum_fabric::Unmetered)).unwrap().is_err());
for gs in Gateset::ALL {
let cert = ready(certify_sampler(&s, REFERENCE, gs, QPU_DEFAULT, 4000, Provenance::Silicon)).unwrap().unwrap();
let Parity::Statistical { fidelity, shots } = cert.parity else { panic!("{:?}", cert.parity) };
assert_eq!(shots, 4000);
assert!(fidelity > 0.99, "{gs:?}: {fidelity}");
assert_eq!((cert.cases, cert.kernel.as_str()), (QPU_DEFAULT.cases as u64, sample_kernel(gs)));
}
}
struct Fixed(Counts);
impl Sampler for Fixed {
fn member_id(&self) -> String {
"local/test".into()
}
fn sample<'r>(&'r self, _: &'r str, _: &'r Circuit, _: u64, _: &'r mut dyn RunMeter) -> SampleFuture<'r> {
let counts = self.0.clone();
Box::pin(async move { Ok((counts, 0, None)) })
}
}
#[test]
fn counts_of_the_wrong_shape_stop_a_certification() {
let short = ready(certify_sampler(&Fixed(vec![1, 1]), "x", Gateset::V2, QPU_DEFAULT, 2, Provenance::Silicon)).unwrap();
assert!(matches!(short, Err(MemberError::Other(_))), "{short:?}");
let mut bell = Circuit::new(2);
bell.h(0).cx(0, 1);
assert!(check_counts(&bell, 10, &[5, 0, 0, 5]).is_ok());
assert!(check_counts(&bell, 10, &[5, 0, 0, 4]).is_err());
assert!(check_counts(&bell, 10, &[10, 0, 0]).is_err());
}
#[test]
fn the_counts_hash_binds_circuit_shots_and_every_count() {
let mut bell = Circuit::new(2);
bell.h(0).cx(0, 1);
let h = counts_hash(&bell, 10, &[5, 0, 0, 5]).unwrap();
assert_ne!(counts_hash(&bell, 10, &[4, 0, 0, 6]), Some(h));
assert_ne!(counts_hash(&bell, 11, &[5, 0, 0, 5]), Some(h));
let mut other = bell.clone();
other.gateset = Gateset::V1;
assert_ne!(counts_hash(&other, 10, &[5, 0, 0, 5]), Some(h));
}
#[test]
fn placement_keeps_the_reference_and_puts_silicon_before_a_simulator() {
let local = local_sampling_member();
let simulated = |id: &str, fidelity| {
let mut entries = Vec::new();
for gs in Gateset::ALL {
let cert = Certificate {
kernel: sample_kernel(gs).into(),
implementation: "q".into(),
member: id.into(),
generator: QPU_DEFAULT.generator(),
parity: Parity::Statistical { fidelity, shots: 1000 },
cases: 16,
corpus: [3; 32],
provenance: Provenance::Silicon,
};
entries.push(KernelEntry { kernel: sample_kernel(gs).into(), implementation: "q".into(), certificate: cert, energy: None });
}
sampling_member(id.into(), FabricClass::Qpu, String::new(), entries)
};
let need = Parity::Statistical { fidelity: 0.9, shots: 1000 };
let qpu = simulated("a/qpu", 0.95);
let mut sim = simulated("a/aaa-sim", 0.999);
sim.kernels.iter_mut().for_each(|k| k.certificate.provenance = Provenance::Simulator("test 1".into()));
for gs in Gateset::ALL {
let all = [local.clone(), qpu.clone(), sim.clone()];
let (placed, _) = place(&all, sample_kernel(gs), &need).unwrap();
assert_eq!(placed.id, qpu.id, "silicon first, whatever the ids");
let pair = [sim.clone(), local.clone()];
let (placed, _) = place(&pair, sample_kernel(gs), &need).unwrap();
assert_eq!(placed.id, local.id, "the reference before a simulator");
let (placed, _) = place(std::slice::from_ref(&sim), sample_kernel(gs), &need).unwrap();
assert_eq!(placed.id, sim.id, "a simulator alone is placed");
let strict = Parity::Statistical { fidelity: 0.97, shots: 1000 };
assert!(place(std::slice::from_ref(&qpu), sample_kernel(gs), &strict).is_none());
}
}
}