use crate::error::{QuantumError, QuantumResult};
use crate::handle::LcgRng;
#[derive(Debug, Clone)]
pub struct StabilizerTableau {
n_qubits: usize,
x: Vec<bool>,
z: Vec<bool>,
r: Vec<bool>,
}
impl StabilizerTableau {
pub fn new(n_qubits: usize) -> QuantumResult<Self> {
if n_qubits == 0 {
return Err(QuantumError::InvalidQubitCount { n: 0 });
}
let n_rows = 2 * n_qubits + 1;
let mut x = vec![false; n_rows * n_qubits];
let mut z = vec![false; n_rows * n_qubits];
let r = vec![false; n_rows];
for i in 0..n_qubits {
x[i * n_qubits + i] = true;
z[(n_qubits + i) * n_qubits + i] = true;
}
Ok(Self { n_qubits, x, z, r })
}
#[must_use]
pub fn n_qubits(&self) -> usize {
self.n_qubits
}
#[inline]
fn n_rows(&self) -> usize {
2 * self.n_qubits + 1
}
#[inline]
fn idx(&self, row: usize, col: usize) -> usize {
row * self.n_qubits + col
}
#[inline]
fn check_qubit(&self, a: usize) -> QuantumResult<()> {
if a >= self.n_qubits {
return Err(QuantumError::QubitIndexOutOfRange {
index: a,
n_qubits: self.n_qubits,
});
}
Ok(())
}
pub fn h(&mut self, a: usize) -> QuantumResult<()> {
self.check_qubit(a)?;
let n_rows = self.n_rows();
for i in 0..n_rows {
let xi = self.idx(i, a);
let xa = self.x[xi];
let za = self.z[xi];
self.r[i] ^= xa & za;
self.x[xi] = za;
self.z[xi] = xa;
}
Ok(())
}
pub fn s(&mut self, a: usize) -> QuantumResult<()> {
self.check_qubit(a)?;
let n_rows = self.n_rows();
for i in 0..n_rows {
let xi = self.idx(i, a);
let xa = self.x[xi];
self.r[i] ^= xa & self.z[xi];
self.z[xi] ^= xa;
}
Ok(())
}
pub fn cnot(&mut self, control: usize, target: usize) -> QuantumResult<()> {
self.check_qubit(control)?;
self.check_qubit(target)?;
if control == target {
return Err(QuantumError::InvalidParameter {
name: "control and target must differ".into(),
});
}
let n_rows = self.n_rows();
for i in 0..n_rows {
let ci = self.idx(i, control);
let ti = self.idx(i, target);
let xc = self.x[ci];
let zc = self.z[ci];
let xt = self.x[ti];
let zt = self.z[ti];
self.r[i] ^= xc & zt & (xt ^ zc ^ true);
self.x[ti] = xt ^ xc;
self.z[ci] = zc ^ zt;
}
Ok(())
}
pub fn x(&mut self, a: usize) -> QuantumResult<()> {
self.check_qubit(a)?;
self.h(a)?;
self.s(a)?;
self.s(a)?;
self.h(a)
}
pub fn z(&mut self, a: usize) -> QuantumResult<()> {
self.check_qubit(a)?;
self.s(a)?;
self.s(a)
}
pub fn y(&mut self, a: usize) -> QuantumResult<()> {
self.check_qubit(a)?;
self.z(a)?;
self.x(a)
}
#[inline]
fn g(x1: bool, z1: bool, x2: bool, z2: bool) -> i32 {
let x2i = i32::from(x2);
let z2i = i32::from(z2);
match (x1, z1) {
(false, false) => 0,
(true, true) => z2i - x2i,
(true, false) => z2i * (2 * x2i - 1),
(false, true) => x2i * (1 - 2 * z2i),
}
}
fn rowsum(&mut self, h: usize, i: usize) {
let mut acc: i32 = 2 * i32::from(self.r[h]) + 2 * i32::from(self.r[i]);
for j in 0..self.n_qubits {
let hj = self.idx(h, j);
let ij = self.idx(i, j);
acc += Self::g(self.x[ij], self.z[ij], self.x[hj], self.z[hj]);
}
let m = acc.rem_euclid(4);
self.r[h] = m == 2;
for j in 0..self.n_qubits {
let hj = self.idx(h, j);
let ij = self.idx(i, j);
self.x[hj] ^= self.x[ij];
self.z[hj] ^= self.z[ij];
}
}
pub fn is_deterministic(&self, a: usize) -> QuantumResult<bool> {
self.check_qubit(a)?;
for p in self.n_qubits..(2 * self.n_qubits) {
if self.x[self.idx(p, a)] {
return Ok(false);
}
}
Ok(true)
}
fn copy_row(&mut self, dst: usize, src: usize) {
for j in 0..self.n_qubits {
let d = self.idx(dst, j);
let s = self.idx(src, j);
self.x[d] = self.x[s];
self.z[d] = self.z[s];
}
self.r[dst] = self.r[src];
}
fn zero_row(&mut self, row: usize) {
for j in 0..self.n_qubits {
let idx = self.idx(row, j);
self.x[idx] = false;
self.z[idx] = false;
}
self.r[row] = false;
}
pub fn measure(&mut self, a: usize, rng: &mut LcgRng) -> QuantumResult<bool> {
self.check_qubit(a)?;
let n = self.n_qubits;
let mut pivot: Option<usize> = None;
for p in n..(2 * n) {
if self.x[self.idx(p, a)] {
pivot = Some(p);
break;
}
}
match pivot {
Some(p) => {
for i in 0..(2 * n) {
if i != p && self.x[self.idx(i, a)] {
self.rowsum(i, p);
}
}
self.copy_row(p - n, p);
self.zero_row(p);
let bit = (rng.next_u32() & 1) == 1;
self.r[p] = bit;
let pa = self.idx(p, a);
self.z[pa] = true;
Ok(bit)
}
None => {
let scratch = 2 * n;
self.zero_row(scratch);
for i in 0..n {
if self.x[self.idx(i, a)] {
self.rowsum(scratch, i + n);
}
}
Ok(self.r[scratch])
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_zero_state_measures_all_zero_deterministically() {
let mut tab = StabilizerTableau::new(4).expect("valid 4-qubit tableau");
let mut rng = LcgRng::new(1);
for q in 0..4 {
assert!(
tab.is_deterministic(q).expect("qubit index in range"),
"qubit {q} not deterministic"
);
assert!(
!tab.measure(q, &mut rng).expect("measure qubit in range"),
"qubit {q} measured 1"
);
}
}
#[test]
fn hadamard_makes_qubit_non_deterministic() {
let mut tab = StabilizerTableau::new(2).expect("valid 2-qubit tableau");
tab.h(0).expect("hadamard on qubit 0");
assert!(!tab.is_deterministic(0).expect("qubit 0 determinism check"));
assert!(tab.is_deterministic(1).expect("qubit 1 determinism check"));
}
#[test]
fn hadamard_measure_reproducible_fixed_seed() {
let outcome = |seed: u64| {
let mut tab = StabilizerTableau::new(1).expect("valid 1-qubit tableau");
tab.h(0).expect("hadamard on qubit 0");
let mut rng = LcgRng::new(seed);
tab.measure(0, &mut rng).expect("measure qubit 0")
};
assert_eq!(outcome(123), outcome(123));
assert_eq!(outcome(999), outcome(999));
}
#[test]
fn bell_state_measurements_correlated() {
for seed in [1u64, 2, 3, 7, 42, 100, 2024] {
let mut tab = StabilizerTableau::new(2).expect("valid 2-qubit tableau");
tab.h(0).expect("hadamard on qubit 0");
tab.cnot(0, 1).expect("CNOT with control 0 and target 1");
let mut rng = LcgRng::new(seed);
let m0 = tab.measure(0, &mut rng).expect("measure qubit 0");
let m1 = tab.measure(1, &mut rng).expect("measure qubit 1");
assert_eq!(m0, m1, "Bell pair not correlated for seed {seed}");
}
}
#[test]
fn ghz_state_all_measurements_equal() {
for seed in [0u64, 5, 11, 77, 256, 9999] {
let mut tab = StabilizerTableau::new(3).expect("valid 3-qubit tableau");
tab.h(0).expect("hadamard on qubit 0");
tab.cnot(0, 1).expect("CNOT with control 0 and target 1");
tab.cnot(1, 2).expect("CNOT with control 1 and target 2");
let mut rng = LcgRng::new(seed);
let m0 = tab.measure(0, &mut rng).expect("measure qubit 0");
let m1 = tab.measure(1, &mut rng).expect("measure qubit 1");
let m2 = tab.measure(2, &mut rng).expect("measure qubit 2");
assert_eq!(m0, m1, "GHZ q0!=q1 seed {seed}");
assert_eq!(m1, m2, "GHZ q1!=q2 seed {seed}");
}
}
#[test]
fn x_gate_flips_zero_to_one() {
let mut tab = StabilizerTableau::new(1).expect("valid 1-qubit tableau");
tab.x(0).expect("X gate on qubit 0");
let mut rng = LcgRng::new(1);
assert!(tab.is_deterministic(0).expect("qubit 0 determinism check"));
assert!(tab.measure(0, &mut rng).expect("measure qubit 0"));
}
#[test]
fn double_hadamard_is_identity() {
let mut tab = StabilizerTableau::new(1).expect("valid 1-qubit tableau");
tab.h(0).expect("first hadamard on qubit 0");
tab.h(0).expect("second hadamard on qubit 0");
let mut rng = LcgRng::new(5);
assert!(tab.is_deterministic(0).expect("qubit 0 determinism check"));
assert!(!tab.measure(0, &mut rng).expect("measure qubit 0"));
}
#[test]
fn z_from_s_squared_leaves_zero_deterministic() {
let mut tab = StabilizerTableau::new(1).expect("valid 1-qubit tableau");
tab.z(0).expect("Z gate on qubit 0");
let mut rng = LcgRng::new(8);
assert!(tab.is_deterministic(0).expect("qubit 0 determinism check"));
assert!(!tab.measure(0, &mut rng).expect("measure qubit 0"));
}
#[test]
fn measuring_same_qubit_twice_gives_same_result() {
for seed in [1u64, 13, 88, 314] {
let mut tab = StabilizerTableau::new(1).expect("valid 1-qubit tableau");
tab.h(0).expect("hadamard on qubit 0");
let mut rng = LcgRng::new(seed);
let first = tab
.measure(0, &mut rng)
.expect("first measurement of qubit 0");
assert!(
tab.is_deterministic(0)
.expect("qubit 0 determinism check after collapse")
);
let second = tab
.measure(0, &mut rng)
.expect("second measurement of qubit 0");
assert_eq!(first, second, "collapse not respected for seed {seed}");
}
}
#[test]
fn n_qubits_getter() {
let tab = StabilizerTableau::new(5).expect("valid 5-qubit tableau");
assert_eq!(tab.n_qubits(), 5);
}
#[test]
fn out_of_range_qubit_errors() {
let mut tab = StabilizerTableau::new(2).expect("valid 2-qubit tableau");
let mut rng = LcgRng::new(1);
assert!(tab.h(2).is_err());
assert!(tab.s(5).is_err());
assert!(tab.cnot(0, 9).is_err());
assert!(tab.cnot(7, 1).is_err());
assert!(tab.measure(3, &mut rng).is_err());
assert!(tab.is_deterministic(4).is_err());
assert!(tab.x(2).is_err());
assert!(tab.y(2).is_err());
assert!(tab.z(2).is_err());
}
#[test]
fn zero_qubit_count_errors() {
assert!(StabilizerTableau::new(0).is_err());
}
#[test]
fn cnot_control_equals_target_errors() {
let mut tab = StabilizerTableau::new(3).expect("valid 3-qubit tableau");
assert!(tab.cnot(1, 1).is_err());
}
#[test]
fn deterministic_given_seed_identical_sequences() {
let run = || {
let mut tab = StabilizerTableau::new(3).expect("valid 3-qubit tableau");
let mut rng = LcgRng::new(2024);
tab.h(0).expect("hadamard on qubit 0");
tab.cnot(0, 1).expect("CNOT with control 0 and target 1");
tab.cnot(1, 2).expect("CNOT with control 1 and target 2");
tab.h(2).expect("hadamard on qubit 2");
let a = tab.measure(0, &mut rng).expect("measure qubit 0");
let b = tab.measure(1, &mut rng).expect("measure qubit 1");
let c = tab.measure(2, &mut rng).expect("measure qubit 2");
(a, b, c)
};
assert_eq!(run(), run());
}
#[test]
fn x_via_h_s_s_h_flips_zero() {
let mut tab = StabilizerTableau::new(1).expect("valid 1-qubit tableau");
tab.h(0).expect("hadamard on qubit 0");
tab.s(0).expect("first S gate on qubit 0");
tab.s(0).expect("second S gate on qubit 0");
tab.h(0).expect("second hadamard on qubit 0");
let mut rng = LcgRng::new(3);
assert!(tab.measure(0, &mut rng).expect("measure qubit 0"));
}
#[test]
fn y_gate_flips_zero_to_one() {
let mut tab = StabilizerTableau::new(1).expect("valid 1-qubit tableau");
tab.y(0).expect("Y gate on qubit 0");
let mut rng = LcgRng::new(4);
assert!(tab.is_deterministic(0).expect("qubit 0 determinism check"));
assert!(tab.measure(0, &mut rng).expect("measure qubit 0"));
}
#[test]
fn double_x_returns_to_zero() {
let mut tab = StabilizerTableau::new(1).expect("valid 1-qubit tableau");
tab.x(0).expect("first X gate on qubit 0");
tab.x(0).expect("second X gate on qubit 0");
let mut rng = LcgRng::new(6);
assert!(!tab.measure(0, &mut rng).expect("measure qubit 0"));
}
#[test]
fn random_outcome_distribution_balanced() {
let mut zeros = 0_u32;
let mut ones = 0_u32;
for seed in 0..200u64 {
let mut tab = StabilizerTableau::new(1).expect("valid 1-qubit tableau");
tab.h(0).expect("hadamard on qubit 0");
let mut rng = LcgRng::new(seed.wrapping_mul(2_654_435_761));
if tab.measure(0, &mut rng).expect("measure qubit 0") {
ones += 1;
} else {
zeros += 1;
}
}
assert!(
zeros > 20 && ones > 20,
"imbalanced: zeros={zeros} ones={ones}"
);
}
#[test]
fn two_qubit_independent_hadamards_uncorrelated_marginals() {
let mut q0_ones = 0_u32;
let mut q1_ones = 0_u32;
for seed in 0..150u64 {
let mut tab = StabilizerTableau::new(2).expect("valid 2-qubit tableau");
tab.h(0).expect("hadamard on qubit 0");
tab.h(1).expect("hadamard on qubit 1");
let mut rng = LcgRng::new(seed.wrapping_mul(1_000_003).wrapping_add(17));
if tab.measure(0, &mut rng).expect("measure qubit 0") {
q0_ones += 1;
}
if tab.measure(1, &mut rng).expect("measure qubit 1") {
q1_ones += 1;
}
}
assert!(q0_ones > 15 && q0_ones < 135, "q0 not random: {q0_ones}");
assert!(q1_ones > 15 && q1_ones < 135, "q1 not random: {q1_ones}");
}
}