use crate::quantum::{BaseGate, Circuit, Gate};
fn splitmix64(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)
}
#[derive(Debug)]
pub struct NotClifford(pub String);
#[derive(Clone)]
pub struct Tableau {
n: usize,
words: usize,
xs: Vec<u64>,
zs: Vec<u64>,
r: Vec<u8>,
}
impl Tableau {
pub fn identity(n: usize) -> Tableau {
let words = n.div_ceil(64).max(1);
let rows = 2 * n + 1;
let mut t = Tableau { n, words, xs: vec![0; rows * words], zs: vec![0; rows * words], r: vec![0; rows] };
for i in 0..n {
t.set_x(i, i, 1); t.set_z(n + i, i, 1); }
t
}
#[inline]
fn xget(&self, row: usize, j: usize) -> u64 {
(self.xs[row * self.words + (j >> 6)] >> (j & 63)) & 1
}
#[inline]
fn zget(&self, row: usize, j: usize) -> u64 {
(self.zs[row * self.words + (j >> 6)] >> (j & 63)) & 1
}
#[inline]
fn set_x(&mut self, row: usize, j: usize, v: u64) {
let w = row * self.words + (j >> 6);
let b = 1u64 << (j & 63);
if v != 0 {
self.xs[w] |= b;
} else {
self.xs[w] &= !b;
}
}
#[inline]
fn set_z(&mut self, row: usize, j: usize, v: u64) {
let w = row * self.words + (j >> 6);
let b = 1u64 << (j & 63);
if v != 0 {
self.zs[w] |= b;
} else {
self.zs[w] &= !b;
}
}
#[inline]
fn flip_x(&mut self, row: usize, j: usize) {
self.xs[row * self.words + (j >> 6)] ^= 1u64 << (j & 63);
}
#[inline]
fn flip_z(&mut self, row: usize, j: usize) {
self.zs[row * self.words + (j >> 6)] ^= 1u64 << (j & 63);
}
fn cnot(&mut self, a: usize, b: usize) {
for i in 0..2 * self.n {
let xa = self.xget(i, a);
let za = self.zget(i, a);
let xb = self.xget(i, b);
let zb = self.zget(i, b);
self.r[i] ^= (xa & zb & (xb ^ za ^ 1)) as u8;
if xa != 0 {
self.flip_x(i, b);
}
if zb != 0 {
self.flip_z(i, a);
}
}
}
fn hadamard(&mut self, a: usize) {
for i in 0..2 * self.n {
let xa = self.xget(i, a);
let za = self.zget(i, a);
self.r[i] ^= (xa & za) as u8;
self.set_x(i, a, za);
self.set_z(i, a, xa);
}
}
fn phase(&mut self, a: usize) {
for i in 0..2 * self.n {
let xa = self.xget(i, a);
let za = self.zget(i, a);
self.r[i] ^= (xa & za) as u8;
if xa != 0 {
self.flip_z(i, a);
}
let _ = za;
}
}
fn x(&mut self, a: usize) {
for i in 0..2 * self.n {
self.r[i] ^= self.zget(i, a) as u8;
}
}
fn z(&mut self, a: usize) {
for i in 0..2 * self.n {
self.r[i] ^= self.xget(i, a) as u8;
}
}
fn y(&mut self, a: usize) {
for i in 0..2 * self.n {
self.r[i] ^= (self.xget(i, a) ^ self.zget(i, a)) as u8;
}
}
fn cz(&mut self, a: usize, b: usize) {
self.hadamard(b);
self.cnot(a, b);
self.hadamard(b);
}
fn apply(&mut self, g: &Gate) -> Result<(), NotClifford> {
let t = g.target as usize;
match (g.base, g.controls.as_slice()) {
(BaseGate::I, []) => {}
(BaseGate::H, []) => self.hadamard(t),
(BaseGate::S, []) => self.phase(t),
(BaseGate::Sdg, []) => {
self.phase(t);
self.phase(t);
self.phase(t);
}
(BaseGate::X, []) => self.x(t),
(BaseGate::Y, []) => self.y(t),
(BaseGate::Z, []) => self.z(t),
(BaseGate::P, []) if g.param == 1 => self.z(t),
(BaseGate::P, []) if g.param == 2 => self.phase(t),
(BaseGate::X, [c]) => self.cnot(*c as usize, t),
(BaseGate::Z, [c]) => self.cz(*c as usize, t),
(BaseGate::P, [c]) if g.param == 1 => self.cz(*c as usize, t),
(BaseGate::Y, [c]) => {
self.phase(t);
self.cnot(*c as usize, t);
self.phase(t);
self.phase(t);
self.phase(t);
}
(base, ctrls) => {
return Err(NotClifford(format!("{base:?} with controls {ctrls:?} param {}", g.param)));
}
}
Ok(())
}
fn g_exp(x1: u64, z1: u64, x2: u64, z2: u64) -> i32 {
let (x2, z2) = (x2 as i32, z2 as i32);
match (x1, z1) {
(0, 0) => 0,
(1, 1) => z2 - x2,
(1, 0) => z2 * (2 * x2 - 1),
_ => x2 * (1 - 2 * z2),
}
}
fn rowsum(&mut self, h: usize, i: usize) {
let mut s = 2 * self.r[h] as i32 + 2 * self.r[i] as i32;
for j in 0..self.n {
s += Self::g_exp(self.xget(i, j), self.zget(i, j), self.xget(h, j), self.zget(h, j));
}
self.r[h] = if s.rem_euclid(4) == 2 { 1 } else { 0 };
for w in 0..self.words {
self.xs[h * self.words + w] ^= self.xs[i * self.words + w];
self.zs[h * self.words + w] ^= self.zs[i * self.words + w];
}
}
fn copy_row(&mut self, dst: usize, src: usize) {
for w in 0..self.words {
self.xs[dst * self.words + w] = self.xs[src * self.words + w];
self.zs[dst * self.words + w] = self.zs[src * self.words + w];
}
self.r[dst] = self.r[src];
}
fn zero_row(&mut self, row: usize) {
for w in 0..self.words {
self.xs[row * self.words + w] = 0;
self.zs[row * self.words + w] = 0;
}
self.r[row] = 0;
}
pub fn measure(&mut self, a: usize, rng: &mut u64) -> u8 {
let n = self.n;
let mut p = None;
for row in n..2 * n {
if self.xget(row, a) == 1 {
p = Some(row);
break;
}
}
if let Some(p) = p {
for i in 0..2 * n {
if i != p && self.xget(i, a) == 1 {
self.rowsum(i, p);
}
}
self.copy_row(p - n, p); self.zero_row(p);
self.set_z(p, a, 1);
let bit = (splitmix64(rng) & 1) as u8;
self.r[p] = bit;
bit
} else {
let scratch = 2 * n;
self.zero_row(scratch);
for i in 0..n {
if self.xget(i, a) == 1 {
self.rowsum(scratch, i + n);
}
}
self.r[scratch]
}
}
}
pub fn run(circuit: &Circuit) -> Result<Tableau, NotClifford> {
let mut t = Tableau::identity(circuit.n_qubits as usize);
for g in &circuit.ops {
t.apply(g)?;
}
Ok(t)
}
fn shot(base: &Tableau, rng: &mut u64) -> u64 {
let mut t = base.clone();
let mut out = 0u64;
for q in 0..t.n.min(64) {
if t.measure(q, rng) == 1 {
out |= 1u64 << q;
}
}
out
}
pub fn sample(circuit: &Circuit, seed: u64, shots: u64) -> Result<Vec<(u64, u64)>, NotClifford> {
let base = run(circuit)?;
let mut rng = seed ^ 0x5DEE_CE66_D3A9_1B2C;
let mut counts: std::collections::HashMap<u64, u64> = std::collections::HashMap::new();
for _ in 0..shots {
*counts.entry(shot(&base, &mut rng)).or_insert(0) += 1;
}
let mut v: Vec<(u64, u64)> = counts.into_iter().collect();
v.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
Ok(v)
}
pub fn max_practical_qubits() -> usize {
4096
}
#[cfg(test)]
mod tests {
use super::*;
fn ghz(n: u8) -> Circuit {
let mut c = Circuit::new(n);
c.h(0);
for i in 1..n {
c.cx(i - 1, i);
}
c
}
#[test]
fn zero_state_is_deterministic_zero() {
let c = Circuit::new(4);
let s = sample(&c, 1, 200).unwrap();
assert_eq!(s.len(), 1);
assert_eq!(s[0].0, 0);
assert_eq!(s[0].1, 200);
}
#[test]
fn ghz_samples_all_or_nothing() {
let s = sample(&ghz(5), 42, 4000).unwrap();
assert_eq!(s.len(), 2, "GHZ has exactly two outcomes");
let outcomes: Vec<u64> = s.iter().map(|(o, _)| *o).collect();
assert!(outcomes.contains(&0) && outcomes.contains(&0b11111));
for (_, cnt) in &s {
assert!((*cnt as i64 - 2000).abs() < 250, "GHZ halves should be ~even: {cnt}");
}
}
#[test]
fn matches_dense_support_on_a_clifford_circuit() {
let mut c = Circuit::new(4);
c.h(0);
c.h(1);
c.cx(0, 2);
c.cx(1, 3);
c.s(2);
c.cz(0, 1);
let stab = sample(&c, 7, 8000).unwrap();
let dense = c.simulate().unwrap();
let probs = dense.prob_weights();
let total: i128 = probs.iter().sum();
for (o, _) in &stab {
assert!(probs[*o as usize] > 0, "stabilizer outcome {o} has zero dense probability");
}
let dense_support = probs.iter().filter(|&&p| p > 0).count();
assert_eq!(stab.len(), dense_support, "distinct-outcome count must match dense");
let expect = 8000.0 / dense_support as f64;
for (_, cnt) in &stab {
assert!((*cnt as f64 - expect).abs() < expect * 0.35, "freq off: {cnt} vs {expect}");
}
let _ = total;
}
#[test]
fn scales_past_the_dense_wall() {
let n = 200u8;
let c = ghz(n);
let base = run(&c).unwrap();
let mut rng = 123u64;
for _ in 0..50 {
let mut t = base.clone();
let first = t.measure(0, &mut rng);
for q in 1..n as usize {
assert_eq!(t.measure(q, &mut rng), first, "GHZ parity broken at large n");
}
}
}
#[test]
fn deterministic_in_seed() {
let a = sample(&ghz(6), 99, 500).unwrap();
let b = sample(&ghz(6), 99, 500).unwrap();
assert_eq!(a, b, "same seed must give the same histogram");
}
#[test]
fn rejects_non_clifford() {
let mut c = Circuit::new(2);
c.h(0);
c.t(0); assert!(run(&c).is_err());
let mut c2 = Circuit::new(3);
c2.ccx(0, 1, 2);
assert!(run(&c2).is_err());
}
}