1use crate::quantum::{BaseGate, Circuit, Gate};
17
18fn splitmix64(s: &mut u64) -> u64 {
19 *s = s.wrapping_add(0x9E37_79B9_7F4A_7C15);
20 let mut z = *s;
21 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
22 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
23 z ^ (z >> 31)
24}
25
26#[derive(Debug)]
28pub struct NotClifford(pub String);
29
30#[derive(Clone)]
38pub struct Tableau {
39 n: usize,
40 words: usize,
41 xs: Vec<u64>,
42 zs: Vec<u64>,
43 r: Vec<u8>,
44}
45
46impl Tableau {
47 pub fn identity(n: usize) -> Tableau {
49 let words = n.div_ceil(64).max(1);
50 let rows = 2 * n + 1;
51 let mut t = Tableau { n, words, xs: vec![0; rows * words], zs: vec![0; rows * words], r: vec![0; rows] };
52 for i in 0..n {
53 t.set_x(i, i, 1); t.set_z(n + i, i, 1); }
56 t
57 }
58
59 #[inline]
60 fn xget(&self, row: usize, j: usize) -> u64 {
61 (self.xs[row * self.words + (j >> 6)] >> (j & 63)) & 1
62 }
63 #[inline]
64 fn zget(&self, row: usize, j: usize) -> u64 {
65 (self.zs[row * self.words + (j >> 6)] >> (j & 63)) & 1
66 }
67 #[inline]
68 fn set_x(&mut self, row: usize, j: usize, v: u64) {
69 let w = row * self.words + (j >> 6);
70 let b = 1u64 << (j & 63);
71 if v != 0 {
72 self.xs[w] |= b;
73 } else {
74 self.xs[w] &= !b;
75 }
76 }
77 #[inline]
78 fn set_z(&mut self, row: usize, j: usize, v: u64) {
79 let w = row * self.words + (j >> 6);
80 let b = 1u64 << (j & 63);
81 if v != 0 {
82 self.zs[w] |= b;
83 } else {
84 self.zs[w] &= !b;
85 }
86 }
87 #[inline]
88 fn flip_x(&mut self, row: usize, j: usize) {
89 self.xs[row * self.words + (j >> 6)] ^= 1u64 << (j & 63);
90 }
91 #[inline]
92 fn flip_z(&mut self, row: usize, j: usize) {
93 self.zs[row * self.words + (j >> 6)] ^= 1u64 << (j & 63);
94 }
95
96 fn cnot(&mut self, a: usize, b: usize) {
98 for i in 0..2 * self.n {
99 let xa = self.xget(i, a);
100 let za = self.zget(i, a);
101 let xb = self.xget(i, b);
102 let zb = self.zget(i, b);
103 self.r[i] ^= (xa & zb & (xb ^ za ^ 1)) as u8;
104 if xa != 0 {
105 self.flip_x(i, b);
106 }
107 if zb != 0 {
108 self.flip_z(i, a);
109 }
110 }
111 }
112 fn hadamard(&mut self, a: usize) {
113 for i in 0..2 * self.n {
114 let xa = self.xget(i, a);
115 let za = self.zget(i, a);
116 self.r[i] ^= (xa & za) as u8;
117 self.set_x(i, a, za);
119 self.set_z(i, a, xa);
120 }
121 }
122 fn phase(&mut self, a: usize) {
123 for i in 0..2 * self.n {
124 let xa = self.xget(i, a);
125 let za = self.zget(i, a);
126 self.r[i] ^= (xa & za) as u8;
127 if xa != 0 {
128 self.flip_z(i, a);
129 }
130 let _ = za;
131 }
132 }
133 fn x(&mut self, a: usize) {
134 for i in 0..2 * self.n {
135 self.r[i] ^= self.zget(i, a) as u8;
136 }
137 }
138 fn z(&mut self, a: usize) {
139 for i in 0..2 * self.n {
140 self.r[i] ^= self.xget(i, a) as u8;
141 }
142 }
143 fn y(&mut self, a: usize) {
144 for i in 0..2 * self.n {
145 self.r[i] ^= (self.xget(i, a) ^ self.zget(i, a)) as u8;
146 }
147 }
148 fn cz(&mut self, a: usize, b: usize) {
149 self.hadamard(b);
150 self.cnot(a, b);
151 self.hadamard(b);
152 }
153
154 fn apply(&mut self, g: &Gate) -> Result<(), NotClifford> {
156 let t = g.target as usize;
157 match (g.base, g.controls.as_slice()) {
158 (BaseGate::I, []) => {}
159 (BaseGate::H, []) => self.hadamard(t),
160 (BaseGate::S, []) => self.phase(t),
161 (BaseGate::Sdg, []) => {
162 self.phase(t);
163 self.phase(t);
164 self.phase(t);
165 }
166 (BaseGate::X, []) => self.x(t),
167 (BaseGate::Y, []) => self.y(t),
168 (BaseGate::Z, []) => self.z(t),
169 (BaseGate::P, []) if g.param == 1 => self.z(t),
170 (BaseGate::P, []) if g.param == 2 => self.phase(t),
171 (BaseGate::X, [c]) => self.cnot(*c as usize, t),
172 (BaseGate::Z, [c]) => self.cz(*c as usize, t),
173 (BaseGate::P, [c]) if g.param == 1 => self.cz(*c as usize, t),
174 (BaseGate::Y, [c]) => {
175 self.phase(t);
177 self.cnot(*c as usize, t);
178 self.phase(t);
179 self.phase(t);
180 self.phase(t);
181 }
182 (base, ctrls) => {
183 return Err(NotClifford(format!("{base:?} with controls {ctrls:?} param {}", g.param)));
184 }
185 }
186 Ok(())
187 }
188
189 fn g_exp(x1: u64, z1: u64, x2: u64, z2: u64) -> i32 {
191 let (x2, z2) = (x2 as i32, z2 as i32);
192 match (x1, z1) {
193 (0, 0) => 0,
194 (1, 1) => z2 - x2,
195 (1, 0) => z2 * (2 * x2 - 1),
196 _ => x2 * (1 - 2 * z2),
197 }
198 }
199 fn rowsum(&mut self, h: usize, i: usize) {
200 let mut s = 2 * self.r[h] as i32 + 2 * self.r[i] as i32;
201 for j in 0..self.n {
202 s += Self::g_exp(self.xget(i, j), self.zget(i, j), self.xget(h, j), self.zget(h, j));
203 }
204 self.r[h] = if s.rem_euclid(4) == 2 { 1 } else { 0 };
205 for w in 0..self.words {
206 self.xs[h * self.words + w] ^= self.xs[i * self.words + w];
207 self.zs[h * self.words + w] ^= self.zs[i * self.words + w];
208 }
209 }
210 fn copy_row(&mut self, dst: usize, src: usize) {
211 for w in 0..self.words {
212 self.xs[dst * self.words + w] = self.xs[src * self.words + w];
213 self.zs[dst * self.words + w] = self.zs[src * self.words + w];
214 }
215 self.r[dst] = self.r[src];
216 }
217 fn zero_row(&mut self, row: usize) {
218 for w in 0..self.words {
219 self.xs[row * self.words + w] = 0;
220 self.zs[row * self.words + w] = 0;
221 }
222 self.r[row] = 0;
223 }
224
225 pub fn measure(&mut self, a: usize, rng: &mut u64) -> u8 {
228 let n = self.n;
229 let mut p = None;
231 for row in n..2 * n {
232 if self.xget(row, a) == 1 {
233 p = Some(row);
234 break;
235 }
236 }
237 if let Some(p) = p {
238 for i in 0..2 * n {
240 if i != p && self.xget(i, a) == 1 {
241 self.rowsum(i, p);
242 }
243 }
244 self.copy_row(p - n, p); self.zero_row(p);
246 self.set_z(p, a, 1);
247 let bit = (splitmix64(rng) & 1) as u8;
248 self.r[p] = bit;
249 bit
250 } else {
251 let scratch = 2 * n;
253 self.zero_row(scratch);
254 for i in 0..n {
255 if self.xget(i, a) == 1 {
256 self.rowsum(scratch, i + n);
257 }
258 }
259 self.r[scratch]
260 }
261 }
262}
263
264pub fn run(circuit: &Circuit) -> Result<Tableau, NotClifford> {
271 let mut t = Tableau::identity(circuit.n_qubits as usize);
272 for g in &circuit.ops {
273 t.apply(g)?;
274 }
275 Ok(t)
276}
277
278fn shot(base: &Tableau, rng: &mut u64) -> u64 {
281 let mut t = base.clone();
282 let mut out = 0u64;
283 for q in 0..t.n.min(64) {
284 if t.measure(q, rng) == 1 {
285 out |= 1u64 << q;
286 }
287 }
288 out
289}
290
291pub fn sample(circuit: &Circuit, seed: u64, shots: u64) -> Result<Vec<(u64, u64)>, NotClifford> {
295 let base = run(circuit)?;
296 let mut rng = seed ^ 0x5DEE_CE66_D3A9_1B2C;
297 let mut counts: std::collections::HashMap<u64, u64> = std::collections::HashMap::new();
298 for _ in 0..shots {
299 *counts.entry(shot(&base, &mut rng)).or_insert(0) += 1;
300 }
301 let mut v: Vec<(u64, u64)> = counts.into_iter().collect();
302 v.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
303 Ok(v)
304}
305
306pub fn max_practical_qubits() -> usize {
309 4096
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315
316 fn ghz(n: u8) -> Circuit {
317 let mut c = Circuit::new(n);
318 c.h(0);
319 for i in 1..n {
320 c.cx(i - 1, i);
321 }
322 c
323 }
324
325 #[test]
326 fn zero_state_is_deterministic_zero() {
327 let c = Circuit::new(4);
328 let s = sample(&c, 1, 200).unwrap();
329 assert_eq!(s.len(), 1);
330 assert_eq!(s[0].0, 0);
331 assert_eq!(s[0].1, 200);
332 }
333
334 #[test]
335 fn ghz_samples_all_or_nothing() {
336 let s = sample(&ghz(5), 42, 4000).unwrap();
337 assert_eq!(s.len(), 2, "GHZ has exactly two outcomes");
339 let outcomes: Vec<u64> = s.iter().map(|(o, _)| *o).collect();
340 assert!(outcomes.contains(&0) && outcomes.contains(&0b11111));
341 for (_, cnt) in &s {
342 assert!((*cnt as i64 - 2000).abs() < 250, "GHZ halves should be ~even: {cnt}");
343 }
344 }
345
346 #[test]
347 fn matches_dense_support_on_a_clifford_circuit() {
348 let mut c = Circuit::new(4);
351 c.h(0);
352 c.h(1);
353 c.cx(0, 2);
354 c.cx(1, 3);
355 c.s(2);
356 c.cz(0, 1);
357 let stab = sample(&c, 7, 8000).unwrap();
358 let dense = c.simulate().unwrap();
359 let probs = dense.prob_weights();
360 let total: i128 = probs.iter().sum();
361 for (o, _) in &stab {
363 assert!(probs[*o as usize] > 0, "stabilizer outcome {o} has zero dense probability");
364 }
365 let dense_support = probs.iter().filter(|&&p| p > 0).count();
366 assert_eq!(stab.len(), dense_support, "distinct-outcome count must match dense");
367 let expect = 8000.0 / dense_support as f64;
369 for (_, cnt) in &stab {
370 assert!((*cnt as f64 - expect).abs() < expect * 0.35, "freq off: {cnt} vs {expect}");
371 }
372 let _ = total;
373 }
374
375 #[test]
376 fn scales_past_the_dense_wall() {
377 let n = 200u8;
380 let c = ghz(n);
381 let base = run(&c).unwrap();
382 let mut rng = 123u64;
383 for _ in 0..50 {
384 let mut t = base.clone();
385 let first = t.measure(0, &mut rng);
386 for q in 1..n as usize {
387 assert_eq!(t.measure(q, &mut rng), first, "GHZ parity broken at large n");
388 }
389 }
390 }
391
392 #[test]
393 fn deterministic_in_seed() {
394 let a = sample(&ghz(6), 99, 500).unwrap();
395 let b = sample(&ghz(6), 99, 500).unwrap();
396 assert_eq!(a, b, "same seed must give the same histogram");
397 }
398
399 #[test]
400 fn rejects_non_clifford() {
401 let mut c = Circuit::new(2);
402 c.h(0);
403 c.t(0); assert!(run(&c).is_err());
405 let mut c2 = Circuit::new(3);
407 c2.ccx(0, 1, 2);
408 assert!(run(&c2).is_err());
409 }
410}