oxicuda_quantum/stabilizer/
tableau.rs1use crate::error::{QuantumError, QuantumResult};
18use crate::handle::LcgRng;
19
20#[derive(Debug, Clone)]
26pub struct StabilizerTableau {
27 n_qubits: usize,
28 x: Vec<bool>,
30 z: Vec<bool>,
32 r: Vec<bool>,
34}
35
36impl StabilizerTableau {
37 pub fn new(n_qubits: usize) -> QuantumResult<Self> {
42 if n_qubits == 0 {
43 return Err(QuantumError::InvalidQubitCount { n: 0 });
44 }
45 let n_rows = 2 * n_qubits + 1;
46 let mut x = vec![false; n_rows * n_qubits];
47 let mut z = vec![false; n_rows * n_qubits];
48 let r = vec![false; n_rows];
49
50 for i in 0..n_qubits {
51 x[i * n_qubits + i] = true;
53 z[(n_qubits + i) * n_qubits + i] = true;
55 }
56
57 Ok(Self { n_qubits, x, z, r })
58 }
59
60 #[must_use]
62 pub fn n_qubits(&self) -> usize {
63 self.n_qubits
64 }
65
66 #[inline]
68 fn n_rows(&self) -> usize {
69 2 * self.n_qubits + 1
70 }
71
72 #[inline]
74 fn idx(&self, row: usize, col: usize) -> usize {
75 row * self.n_qubits + col
76 }
77
78 #[inline]
80 fn check_qubit(&self, a: usize) -> QuantumResult<()> {
81 if a >= self.n_qubits {
82 return Err(QuantumError::QubitIndexOutOfRange {
83 index: a,
84 n_qubits: self.n_qubits,
85 });
86 }
87 Ok(())
88 }
89
90 pub fn h(&mut self, a: usize) -> QuantumResult<()> {
95 self.check_qubit(a)?;
96 let n_rows = self.n_rows();
97 for i in 0..n_rows {
98 let xi = self.idx(i, a);
99 let xa = self.x[xi];
100 let za = self.z[xi];
101 self.r[i] ^= xa & za;
102 self.x[xi] = za;
103 self.z[xi] = xa;
104 }
105 Ok(())
106 }
107
108 pub fn s(&mut self, a: usize) -> QuantumResult<()> {
112 self.check_qubit(a)?;
113 let n_rows = self.n_rows();
114 for i in 0..n_rows {
115 let xi = self.idx(i, a);
116 let xa = self.x[xi];
117 self.r[i] ^= xa & self.z[xi];
118 self.z[xi] ^= xa;
119 }
120 Ok(())
121 }
122
123 pub fn cnot(&mut self, control: usize, target: usize) -> QuantumResult<()> {
129 self.check_qubit(control)?;
130 self.check_qubit(target)?;
131 if control == target {
132 return Err(QuantumError::InvalidParameter {
133 name: "control and target must differ".into(),
134 });
135 }
136 let n_rows = self.n_rows();
137 for i in 0..n_rows {
138 let ci = self.idx(i, control);
139 let ti = self.idx(i, target);
140 let xc = self.x[ci];
141 let zc = self.z[ci];
142 let xt = self.x[ti];
143 let zt = self.z[ti];
144 self.r[i] ^= xc & zt & (xt ^ zc ^ true);
145 self.x[ti] = xt ^ xc;
146 self.z[ci] = zc ^ zt;
147 }
148 Ok(())
149 }
150
151 pub fn x(&mut self, a: usize) -> QuantumResult<()> {
153 self.check_qubit(a)?;
154 self.h(a)?;
155 self.s(a)?;
156 self.s(a)?;
157 self.h(a)
158 }
159
160 pub fn z(&mut self, a: usize) -> QuantumResult<()> {
162 self.check_qubit(a)?;
163 self.s(a)?;
164 self.s(a)
165 }
166
167 pub fn y(&mut self, a: usize) -> QuantumResult<()> {
172 self.check_qubit(a)?;
173 self.z(a)?;
174 self.x(a)
175 }
176
177 #[inline]
181 fn g(x1: bool, z1: bool, x2: bool, z2: bool) -> i32 {
182 let x2i = i32::from(x2);
183 let z2i = i32::from(z2);
184 match (x1, z1) {
185 (false, false) => 0,
186 (true, true) => z2i - x2i,
187 (true, false) => z2i * (2 * x2i - 1),
188 (false, true) => x2i * (1 - 2 * z2i),
189 }
190 }
191
192 fn rowsum(&mut self, h: usize, i: usize) {
195 let mut acc: i32 = 2 * i32::from(self.r[h]) + 2 * i32::from(self.r[i]);
196 for j in 0..self.n_qubits {
197 let hj = self.idx(h, j);
198 let ij = self.idx(i, j);
199 acc += Self::g(self.x[ij], self.z[ij], self.x[hj], self.z[hj]);
200 }
201 let m = acc.rem_euclid(4);
203 self.r[h] = m == 2;
204 for j in 0..self.n_qubits {
205 let hj = self.idx(h, j);
206 let ij = self.idx(i, j);
207 self.x[hj] ^= self.x[ij];
208 self.z[hj] ^= self.z[ij];
209 }
210 }
211
212 pub fn is_deterministic(&self, a: usize) -> QuantumResult<bool> {
217 self.check_qubit(a)?;
218 for p in self.n_qubits..(2 * self.n_qubits) {
219 if self.x[self.idx(p, a)] {
220 return Ok(false);
221 }
222 }
223 Ok(true)
224 }
225
226 fn copy_row(&mut self, dst: usize, src: usize) {
228 for j in 0..self.n_qubits {
229 let d = self.idx(dst, j);
230 let s = self.idx(src, j);
231 self.x[d] = self.x[s];
232 self.z[d] = self.z[s];
233 }
234 self.r[dst] = self.r[src];
235 }
236
237 fn zero_row(&mut self, row: usize) {
239 for j in 0..self.n_qubits {
240 let idx = self.idx(row, j);
241 self.x[idx] = false;
242 self.z[idx] = false;
243 }
244 self.r[row] = false;
245 }
246
247 pub fn measure(&mut self, a: usize, rng: &mut LcgRng) -> QuantumResult<bool> {
250 self.check_qubit(a)?;
251 let n = self.n_qubits;
252
253 let mut pivot: Option<usize> = None;
255 for p in n..(2 * n) {
256 if self.x[self.idx(p, a)] {
257 pivot = Some(p);
258 break;
259 }
260 }
261
262 match pivot {
263 Some(p) => {
264 for i in 0..(2 * n) {
266 if i != p && self.x[self.idx(i, a)] {
267 self.rowsum(i, p);
268 }
269 }
270 self.copy_row(p - n, p);
272 self.zero_row(p);
274 let bit = (rng.next_u32() & 1) == 1;
275 self.r[p] = bit;
276 let pa = self.idx(p, a);
277 self.z[pa] = true;
278 Ok(bit)
279 }
280 None => {
281 let scratch = 2 * n;
283 self.zero_row(scratch);
284 for i in 0..n {
285 if self.x[self.idx(i, a)] {
286 self.rowsum(scratch, i + n);
287 }
288 }
289 Ok(self.r[scratch])
290 }
291 }
292 }
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298
299 #[test]
300 fn new_zero_state_measures_all_zero_deterministically() {
301 let mut tab = StabilizerTableau::new(4).expect("valid 4-qubit tableau");
302 let mut rng = LcgRng::new(1);
303 for q in 0..4 {
304 assert!(
305 tab.is_deterministic(q).expect("qubit index in range"),
306 "qubit {q} not deterministic"
307 );
308 assert!(
309 !tab.measure(q, &mut rng).expect("measure qubit in range"),
310 "qubit {q} measured 1"
311 );
312 }
313 }
314
315 #[test]
316 fn hadamard_makes_qubit_non_deterministic() {
317 let mut tab = StabilizerTableau::new(2).expect("valid 2-qubit tableau");
318 tab.h(0).expect("hadamard on qubit 0");
319 assert!(!tab.is_deterministic(0).expect("qubit 0 determinism check"));
320 assert!(tab.is_deterministic(1).expect("qubit 1 determinism check"));
322 }
323
324 #[test]
325 fn hadamard_measure_reproducible_fixed_seed() {
326 let outcome = |seed: u64| {
327 let mut tab = StabilizerTableau::new(1).expect("valid 1-qubit tableau");
328 tab.h(0).expect("hadamard on qubit 0");
329 let mut rng = LcgRng::new(seed);
330 tab.measure(0, &mut rng).expect("measure qubit 0")
331 };
332 assert_eq!(outcome(123), outcome(123));
333 assert_eq!(outcome(999), outcome(999));
334 }
335
336 #[test]
337 fn bell_state_measurements_correlated() {
338 for seed in [1u64, 2, 3, 7, 42, 100, 2024] {
339 let mut tab = StabilizerTableau::new(2).expect("valid 2-qubit tableau");
340 tab.h(0).expect("hadamard on qubit 0");
341 tab.cnot(0, 1).expect("CNOT with control 0 and target 1");
342 let mut rng = LcgRng::new(seed);
343 let m0 = tab.measure(0, &mut rng).expect("measure qubit 0");
344 let m1 = tab.measure(1, &mut rng).expect("measure qubit 1");
345 assert_eq!(m0, m1, "Bell pair not correlated for seed {seed}");
346 }
347 }
348
349 #[test]
350 fn ghz_state_all_measurements_equal() {
351 for seed in [0u64, 5, 11, 77, 256, 9999] {
352 let mut tab = StabilizerTableau::new(3).expect("valid 3-qubit tableau");
353 tab.h(0).expect("hadamard on qubit 0");
354 tab.cnot(0, 1).expect("CNOT with control 0 and target 1");
355 tab.cnot(1, 2).expect("CNOT with control 1 and target 2");
356 let mut rng = LcgRng::new(seed);
357 let m0 = tab.measure(0, &mut rng).expect("measure qubit 0");
358 let m1 = tab.measure(1, &mut rng).expect("measure qubit 1");
359 let m2 = tab.measure(2, &mut rng).expect("measure qubit 2");
360 assert_eq!(m0, m1, "GHZ q0!=q1 seed {seed}");
361 assert_eq!(m1, m2, "GHZ q1!=q2 seed {seed}");
362 }
363 }
364
365 #[test]
366 fn x_gate_flips_zero_to_one() {
367 let mut tab = StabilizerTableau::new(1).expect("valid 1-qubit tableau");
368 tab.x(0).expect("X gate on qubit 0");
369 let mut rng = LcgRng::new(1);
370 assert!(tab.is_deterministic(0).expect("qubit 0 determinism check"));
371 assert!(tab.measure(0, &mut rng).expect("measure qubit 0"));
372 }
373
374 #[test]
375 fn double_hadamard_is_identity() {
376 let mut tab = StabilizerTableau::new(1).expect("valid 1-qubit tableau");
377 tab.h(0).expect("first hadamard on qubit 0");
378 tab.h(0).expect("second hadamard on qubit 0");
379 let mut rng = LcgRng::new(5);
380 assert!(tab.is_deterministic(0).expect("qubit 0 determinism check"));
381 assert!(!tab.measure(0, &mut rng).expect("measure qubit 0"));
382 }
383
384 #[test]
385 fn z_from_s_squared_leaves_zero_deterministic() {
386 let mut tab = StabilizerTableau::new(1).expect("valid 1-qubit tableau");
387 tab.z(0).expect("Z gate on qubit 0");
388 let mut rng = LcgRng::new(8);
389 assert!(tab.is_deterministic(0).expect("qubit 0 determinism check"));
390 assert!(!tab.measure(0, &mut rng).expect("measure qubit 0"));
391 }
392
393 #[test]
394 fn measuring_same_qubit_twice_gives_same_result() {
395 for seed in [1u64, 13, 88, 314] {
396 let mut tab = StabilizerTableau::new(1).expect("valid 1-qubit tableau");
397 tab.h(0).expect("hadamard on qubit 0");
398 let mut rng = LcgRng::new(seed);
399 let first = tab
400 .measure(0, &mut rng)
401 .expect("first measurement of qubit 0");
402 assert!(
404 tab.is_deterministic(0)
405 .expect("qubit 0 determinism check after collapse")
406 );
407 let second = tab
408 .measure(0, &mut rng)
409 .expect("second measurement of qubit 0");
410 assert_eq!(first, second, "collapse not respected for seed {seed}");
411 }
412 }
413
414 #[test]
415 fn n_qubits_getter() {
416 let tab = StabilizerTableau::new(5).expect("valid 5-qubit tableau");
417 assert_eq!(tab.n_qubits(), 5);
418 }
419
420 #[test]
421 fn out_of_range_qubit_errors() {
422 let mut tab = StabilizerTableau::new(2).expect("valid 2-qubit tableau");
423 let mut rng = LcgRng::new(1);
424 assert!(tab.h(2).is_err());
425 assert!(tab.s(5).is_err());
426 assert!(tab.cnot(0, 9).is_err());
427 assert!(tab.cnot(7, 1).is_err());
428 assert!(tab.measure(3, &mut rng).is_err());
429 assert!(tab.is_deterministic(4).is_err());
430 assert!(tab.x(2).is_err());
431 assert!(tab.y(2).is_err());
432 assert!(tab.z(2).is_err());
433 }
434
435 #[test]
436 fn zero_qubit_count_errors() {
437 assert!(StabilizerTableau::new(0).is_err());
438 }
439
440 #[test]
441 fn cnot_control_equals_target_errors() {
442 let mut tab = StabilizerTableau::new(3).expect("valid 3-qubit tableau");
443 assert!(tab.cnot(1, 1).is_err());
444 }
445
446 #[test]
447 fn deterministic_given_seed_identical_sequences() {
448 let run = || {
449 let mut tab = StabilizerTableau::new(3).expect("valid 3-qubit tableau");
450 let mut rng = LcgRng::new(2024);
451 tab.h(0).expect("hadamard on qubit 0");
452 tab.cnot(0, 1).expect("CNOT with control 0 and target 1");
453 tab.cnot(1, 2).expect("CNOT with control 1 and target 2");
454 tab.h(2).expect("hadamard on qubit 2");
455 let a = tab.measure(0, &mut rng).expect("measure qubit 0");
456 let b = tab.measure(1, &mut rng).expect("measure qubit 1");
457 let c = tab.measure(2, &mut rng).expect("measure qubit 2");
458 (a, b, c)
459 };
460 assert_eq!(run(), run());
461 }
462
463 #[test]
464 fn x_via_h_s_s_h_flips_zero() {
465 let mut tab = StabilizerTableau::new(1).expect("valid 1-qubit tableau");
466 tab.h(0).expect("hadamard on qubit 0");
468 tab.s(0).expect("first S gate on qubit 0");
469 tab.s(0).expect("second S gate on qubit 0");
470 tab.h(0).expect("second hadamard on qubit 0");
471 let mut rng = LcgRng::new(3);
472 assert!(tab.measure(0, &mut rng).expect("measure qubit 0"));
473 }
474
475 #[test]
476 fn y_gate_flips_zero_to_one() {
477 let mut tab = StabilizerTableau::new(1).expect("valid 1-qubit tableau");
479 tab.y(0).expect("Y gate on qubit 0");
480 let mut rng = LcgRng::new(4);
481 assert!(tab.is_deterministic(0).expect("qubit 0 determinism check"));
482 assert!(tab.measure(0, &mut rng).expect("measure qubit 0"));
483 }
484
485 #[test]
486 fn double_x_returns_to_zero() {
487 let mut tab = StabilizerTableau::new(1).expect("valid 1-qubit tableau");
488 tab.x(0).expect("first X gate on qubit 0");
489 tab.x(0).expect("second X gate on qubit 0");
490 let mut rng = LcgRng::new(6);
491 assert!(!tab.measure(0, &mut rng).expect("measure qubit 0"));
492 }
493
494 #[test]
495 fn random_outcome_distribution_balanced() {
496 let mut zeros = 0_u32;
498 let mut ones = 0_u32;
499 for seed in 0..200u64 {
500 let mut tab = StabilizerTableau::new(1).expect("valid 1-qubit tableau");
501 tab.h(0).expect("hadamard on qubit 0");
502 let mut rng = LcgRng::new(seed.wrapping_mul(2_654_435_761));
503 if tab.measure(0, &mut rng).expect("measure qubit 0") {
504 ones += 1;
505 } else {
506 zeros += 1;
507 }
508 }
509 assert!(
510 zeros > 20 && ones > 20,
511 "imbalanced: zeros={zeros} ones={ones}"
512 );
513 }
514
515 #[test]
516 fn two_qubit_independent_hadamards_uncorrelated_marginals() {
517 let mut q0_ones = 0_u32;
519 let mut q1_ones = 0_u32;
520 for seed in 0..150u64 {
521 let mut tab = StabilizerTableau::new(2).expect("valid 2-qubit tableau");
522 tab.h(0).expect("hadamard on qubit 0");
523 tab.h(1).expect("hadamard on qubit 1");
524 let mut rng = LcgRng::new(seed.wrapping_mul(1_000_003).wrapping_add(17));
525 if tab.measure(0, &mut rng).expect("measure qubit 0") {
526 q0_ones += 1;
527 }
528 if tab.measure(1, &mut rng).expect("measure qubit 1") {
529 q1_ones += 1;
530 }
531 }
532 assert!(q0_ones > 15 && q0_ones < 135, "q0 not random: {q0_ones}");
533 assert!(q1_ones > 15 && q1_ones < 135, "q1 not random: {q1_ones}");
534 }
535}