Skip to main content

ruqu_core/
stabilizer.rs

1//! Aaronson-Gottesman stabilizer simulator for Clifford circuits.
2//!
3//! Uses a tableau of 2n rows and (2n+1) columns to represent the stabilizer
4//! and destabilizer generators of an n-qubit state.  Each Clifford gate is
5//! applied in O(n) time and each measurement in O(n^2), enabling simulation
6//! of millions of qubits for circuits composed entirely of Clifford gates.
7//!
8//! Reference: Aaronson & Gottesman, "Improved Simulation of Stabilizer
9//! Circuits", Phys. Rev. A 70, 052328 (2004).
10
11use crate::error::{QuantumError, Result};
12use crate::gate::Gate;
13use crate::types::MeasurementOutcome;
14use rand::rngs::StdRng;
15use rand::{Rng, SeedableRng};
16
17/// Stabilizer state for efficient Clifford circuit simulation.
18///
19/// Uses the Aaronson-Gottesman tableau representation to simulate
20/// Clifford circuits in O(n^2) time per gate, enabling simulation
21/// of millions of qubits.
22pub struct StabilizerState {
23    num_qubits: usize,
24    /// Tableau: 2n rows, each row has n X-bits, n Z-bits, and 1 phase bit.
25    /// Stored as a flat `Vec<bool>` for simplicity.
26    /// Row i occupies indices `[i * stride .. (i+1) * stride)`.
27    /// Layout within a row: `x[0..n], z[0..n], r` (total width = 2n + 1).
28    tableau: Vec<bool>,
29    rng: StdRng,
30    measurement_record: Vec<MeasurementOutcome>,
31}
32
33impl StabilizerState {
34    // -----------------------------------------------------------------------
35    // Construction
36    // -----------------------------------------------------------------------
37
38    /// Create a new stabilizer state representing |00...0>.
39    ///
40    /// The initial tableau has destabilizer i = X_i, stabilizer i = Z_i,
41    /// and all phase bits set to 0.
42    pub fn new(num_qubits: usize) -> Result<Self> {
43        Self::new_with_seed(num_qubits, 0)
44    }
45
46    /// Create a new stabilizer state with a specific RNG seed.
47    pub fn new_with_seed(num_qubits: usize, seed: u64) -> Result<Self> {
48        if num_qubits == 0 {
49            return Err(QuantumError::CircuitError(
50                "stabilizer state requires at least 1 qubit".into(),
51            ));
52        }
53
54        let n = num_qubits;
55        let stride = 2 * n + 1;
56        let total = 2 * n * stride;
57        let mut tableau = vec![false; total];
58
59        // Destabilizer i (row i): X_i  =>  x[i]=1, rest zero
60        for i in 0..n {
61            tableau[i * stride + i] = true; // x bit for qubit i
62        }
63        // Stabilizer i (row n+i): Z_i  =>  z[i]=1, rest zero
64        for i in 0..n {
65            tableau[(n + i) * stride + n + i] = true; // z bit for qubit i
66        }
67
68        Ok(Self {
69            num_qubits,
70            tableau,
71            rng: StdRng::seed_from_u64(seed),
72            measurement_record: Vec::new(),
73        })
74    }
75
76    // -----------------------------------------------------------------------
77    // Tableau access helpers
78    // -----------------------------------------------------------------------
79
80    #[inline]
81    fn stride(&self) -> usize {
82        2 * self.num_qubits + 1
83    }
84
85    /// Get the X bit for `(row, col)`.
86    #[inline]
87    fn x(&self, row: usize, col: usize) -> bool {
88        self.tableau[row * self.stride() + col]
89    }
90
91    /// Get the Z bit for `(row, col)`.
92    #[inline]
93    fn z(&self, row: usize, col: usize) -> bool {
94        self.tableau[row * self.stride() + self.num_qubits + col]
95    }
96
97    /// Get the phase bit for `row`.
98    #[inline]
99    fn r(&self, row: usize) -> bool {
100        self.tableau[row * self.stride() + 2 * self.num_qubits]
101    }
102
103    #[inline]
104    fn set_x(&mut self, row: usize, col: usize, val: bool) {
105        let idx = row * self.stride() + col;
106        self.tableau[idx] = val;
107    }
108
109    #[inline]
110    fn set_z(&mut self, row: usize, col: usize, val: bool) {
111        let idx = row * self.stride() + self.num_qubits + col;
112        self.tableau[idx] = val;
113    }
114
115    #[inline]
116    fn set_r(&mut self, row: usize, val: bool) {
117        let idx = row * self.stride() + 2 * self.num_qubits;
118        self.tableau[idx] = val;
119    }
120
121    /// Multiply row `target` by row `source` (left-multiply the Pauli string
122    /// of `target` by that of `source`), updating the phase of `target`.
123    ///
124    /// Uses the `g` function to accumulate the phase contribution from
125    /// each qubit position.
126    fn row_mult(&mut self, target: usize, source: usize) {
127        let n = self.num_qubits;
128        let mut phase_sum: i32 = 0;
129
130        // Accumulate phase from commutation relations
131        for j in 0..n {
132            phase_sum += g(
133                self.x(source, j),
134                self.z(source, j),
135                self.x(target, j),
136                self.z(target, j),
137            );
138        }
139
140        // Combine phases: new_r = (2*r_target + 2*r_source + phase_sum) mod 4
141        // r=1 means phase -1 (i.e. factor of i^2 = -1), so we work mod 4 in
142        // units of i.  r_bit maps to 0 or 2.
143        let total = 2 * (self.r(target) as i32)
144            + 2 * (self.r(source) as i32)
145            + phase_sum;
146        // Result phase bit: total mod 4 == 2 => r=1, else r=0
147        let new_r = ((total % 4) + 4) % 4 == 2;
148        self.set_r(target, new_r);
149
150        // XOR the X and Z bits
151        let stride = self.stride();
152        for j in 0..n {
153            let sx = self.tableau[source * stride + j];
154            self.tableau[target * stride + j] ^= sx;
155        }
156        for j in 0..n {
157            let sz = self.tableau[source * stride + n + j];
158            self.tableau[target * stride + n + j] ^= sz;
159        }
160    }
161
162    // -----------------------------------------------------------------------
163    // Clifford gate operations
164    // -----------------------------------------------------------------------
165
166    /// Apply a Hadamard gate on `qubit`.
167    ///
168    /// Conjugation rules: H X H = Z, H Z H = X, H Y H = -Y.
169    /// Tableau update: swap X and Z columns for this qubit in every row,
170    /// and flip the phase bit where both X and Z were set (Y -> -Y).
171    pub fn hadamard(&mut self, qubit: usize) {
172        let n = self.num_qubits;
173        for i in 0..(2 * n) {
174            let xi = self.x(i, qubit);
175            let zi = self.z(i, qubit);
176            // phase flip for Y entries: if both x and z are set
177            if xi && zi {
178                self.set_r(i, !self.r(i));
179            }
180            // swap x and z
181            self.set_x(i, qubit, zi);
182            self.set_z(i, qubit, xi);
183        }
184    }
185
186    /// Apply the phase gate (S gate) on `qubit`.
187    ///
188    /// Conjugation rules: S X S^dag = Y, S Z S^dag = Z, S Y S^dag = -X.
189    /// Tableau update: Z_j -> Z_j XOR X_j, phase flipped where X and Z
190    /// are both set.
191    pub fn phase_gate(&mut self, qubit: usize) {
192        let n = self.num_qubits;
193        for i in 0..(2 * n) {
194            let xi = self.x(i, qubit);
195            let zi = self.z(i, qubit);
196            // Phase update: r ^= (x AND z)
197            if xi && zi {
198                self.set_r(i, !self.r(i));
199            }
200            // z -> z XOR x
201            self.set_z(i, qubit, zi ^ xi);
202        }
203    }
204
205    /// Apply a CNOT gate with `control` and `target`.
206    ///
207    /// Conjugation rules:
208    ///   X_c -> X_c X_t,  Z_t -> Z_c Z_t,
209    ///   X_t -> X_t,      Z_c -> Z_c.
210    /// Tableau update for every row:
211    ///   phase ^= x_c AND z_t AND (x_t XOR z_c XOR 1)
212    ///   x_t ^= x_c
213    ///   z_c ^= z_t
214    pub fn cnot(&mut self, control: usize, target: usize) {
215        let n = self.num_qubits;
216        for i in 0..(2 * n) {
217            let xc = self.x(i, control);
218            let zt = self.z(i, target);
219            let xt = self.x(i, target);
220            let zc = self.z(i, control);
221            // Phase update
222            if xc && zt && (xt == zc) {
223                self.set_r(i, !self.r(i));
224            }
225            // x_target ^= x_control
226            self.set_x(i, target, xt ^ xc);
227            // z_control ^= z_target
228            self.set_z(i, control, zc ^ zt);
229        }
230    }
231
232    /// Apply a Pauli-X gate on `qubit`.
233    ///
234    /// Conjugation: X commutes with X, anticommutes with Z and Y.
235    /// Tableau update: flip phase where Z bit is set for this qubit.
236    pub fn x_gate(&mut self, qubit: usize) {
237        let n = self.num_qubits;
238        for i in 0..(2 * n) {
239            if self.z(i, qubit) {
240                self.set_r(i, !self.r(i));
241            }
242        }
243    }
244
245    /// Apply a Pauli-Y gate on `qubit`.
246    ///
247    /// Conjugation: Y anticommutes with both X and Z.
248    /// Tableau update: flip phase where X or Z (but via XOR: where x XOR z).
249    pub fn y_gate(&mut self, qubit: usize) {
250        let n = self.num_qubits;
251        for i in 0..(2 * n) {
252            let xi = self.x(i, qubit);
253            let zi = self.z(i, qubit);
254            // Y anticommutes with X and Z, commutes with Y and I
255            // phase flips when exactly one of x,z is set (i.e. X or Z, not Y or I)
256            if xi ^ zi {
257                self.set_r(i, !self.r(i));
258            }
259        }
260    }
261
262    /// Apply a Pauli-Z gate on `qubit`.
263    ///
264    /// Conjugation: Z commutes with Z, anticommutes with X and Y.
265    /// Tableau update: flip phase where X bit is set for this qubit.
266    pub fn z_gate(&mut self, qubit: usize) {
267        let n = self.num_qubits;
268        for i in 0..(2 * n) {
269            if self.x(i, qubit) {
270                self.set_r(i, !self.r(i));
271            }
272        }
273    }
274
275    /// Apply a CZ (controlled-Z) gate on `q1` and `q2`.
276    ///
277    /// CZ = (I x H) . CNOT . (I x H).  Implemented by decomposition.
278    pub fn cz(&mut self, q1: usize, q2: usize) {
279        self.hadamard(q2);
280        self.cnot(q1, q2);
281        self.hadamard(q2);
282    }
283
284    /// Apply a SWAP gate on `q1` and `q2`.
285    ///
286    /// SWAP = CNOT(q1,q2) . CNOT(q2,q1) . CNOT(q1,q2).
287    pub fn swap(&mut self, q1: usize, q2: usize) {
288        self.cnot(q1, q2);
289        self.cnot(q2, q1);
290        self.cnot(q1, q2);
291    }
292
293    // -----------------------------------------------------------------------
294    // Measurement
295    // -----------------------------------------------------------------------
296
297    /// Measure `qubit` in the computational (Z) basis.
298    ///
299    /// Follows the Aaronson-Gottesman algorithm:
300    /// 1. Check if any stabilizer generator anticommutes with Z on the
301    ///    measured qubit (i.e. has its X bit set for that qubit).
302    /// 2. If yes (random outcome): collapse the state and record the result.
303    /// 3. If no (deterministic outcome): compute the result from phases.
304    pub fn measure(&mut self, qubit: usize) -> Result<MeasurementOutcome> {
305        if qubit >= self.num_qubits {
306            return Err(QuantumError::InvalidQubitIndex {
307                index: qubit as u32,
308                num_qubits: self.num_qubits as u32,
309            });
310        }
311
312        let n = self.num_qubits;
313
314        // Search for a stabilizer (rows n..2n-1) that anticommutes with Z_qubit.
315        // A generator anticommutes with Z_qubit iff its X bit for that qubit is 1.
316        let p = (n..(2 * n)).find(|&i| self.x(i, qubit));
317
318        if let Some(p) = p {
319            // --- Random outcome ---
320            // For every other row that anticommutes with Z_qubit, multiply it by row p
321            // to make it commute.
322            for i in 0..(2 * n) {
323                if i != p && self.x(i, qubit) {
324                    self.row_mult(i, p);
325                }
326            }
327
328            // Move row p to the destabilizer: copy stabilizer p to destabilizer (p-n),
329            // then set row p to be +/- Z_qubit.
330            let dest_row = p - n;
331            let stride = self.stride();
332            // Copy row p to destabilizer row
333            for j in 0..stride {
334                self.tableau[dest_row * stride + j] = self.tableau[p * stride + j];
335            }
336
337            // Clear row p and set it to Z_qubit with random phase
338            for j in 0..stride {
339                self.tableau[p * stride + j] = false;
340            }
341            self.set_z(p, qubit, true);
342
343            let result: bool = self.rng.gen();
344            self.set_r(p, result);
345
346            let outcome = MeasurementOutcome {
347                qubit: qubit as u32,
348                result,
349                probability: 0.5,
350            };
351            self.measurement_record.push(outcome.clone());
352            Ok(outcome)
353        } else {
354            // --- Deterministic outcome ---
355            // No stabilizer anticommutes with Z_qubit, so Z_qubit is in the
356            // stabilizer group.  We need to determine its sign.
357            //
358            // Use a scratch row technique: set a temporary row to the identity,
359            // then multiply in every destabilizer whose corresponding stabilizer
360            // has x[qubit]=1... but since we confirmed no stabilizer has x set,
361            // we look at destabilizers instead.
362            //
363            // Actually per the CHP algorithm: accumulate into a scratch state
364            // by multiplying destabilizer rows whose *destabilizer* X bit for
365            // this qubit is set.  The accumulated phase gives the measurement
366            // outcome.
367
368            // We'll use the first extra technique: allocate a scratch row
369            // initialized to +I and multiply in all generators from rows 0..n
370            // (destabilizers) that have x[qubit]=1 in the *stabilizer* row n+i.
371            // Wait -- let me re-read the CHP paper carefully.
372            //
373            // Per Aaronson-Gottesman (Section III.C, deterministic case):
374            // Set scratch = identity. For each i in 0..n, if destabilizer i
375            // has x[qubit]=1, multiply scratch by stabilizer (n+i).
376            // The phase of the scratch row gives the measurement result.
377
378            let stride = self.stride();
379            let mut scratch = vec![false; stride];
380
381            for i in 0..n {
382                // Check destabilizer row i: does it have x[qubit] set?
383                if self.x(i, qubit) {
384                    // Multiply scratch by stabilizer row (n+i)
385                    let stab_row = n + i;
386                    let mut phase_sum: i32 = 0;
387                    for j in 0..n {
388                        let sx = scratch[j];
389                        let sz = scratch[n + j];
390                        let rx = self.x(stab_row, j);
391                        let rz = self.z(stab_row, j);
392                        phase_sum += g(rx, rz, sx, sz);
393                    }
394                    let scratch_r = scratch[2 * n];
395                    let stab_r = self.r(stab_row);
396                    let total = 2 * (scratch_r as i32)
397                        + 2 * (stab_r as i32)
398                        + phase_sum;
399                    scratch[2 * n] = ((total % 4) + 4) % 4 == 2;
400
401                    for j in 0..n {
402                        scratch[j] ^= self.x(stab_row, j);
403                    }
404                    for j in 0..n {
405                        scratch[n + j] ^= self.z(stab_row, j);
406                    }
407                }
408            }
409
410            let result = scratch[2 * n]; // phase bit = measurement outcome
411
412            let outcome = MeasurementOutcome {
413                qubit: qubit as u32,
414                result,
415                probability: 1.0,
416            };
417            self.measurement_record.push(outcome.clone());
418            Ok(outcome)
419        }
420    }
421
422    // -----------------------------------------------------------------------
423    // Accessors
424    // -----------------------------------------------------------------------
425
426    /// Return the number of qubits in this stabilizer state.
427    pub fn num_qubits(&self) -> usize {
428        self.num_qubits
429    }
430
431    /// Return the measurement record accumulated so far.
432    pub fn measurement_record(&self) -> &[MeasurementOutcome] {
433        &self.measurement_record
434    }
435
436    /// Create a copy of this stabilizer state with a new RNG seed.
437    ///
438    /// The quantum state (tableau) is duplicated exactly; only the RNG
439    /// and measurement record are reset.  This is used by the Clifford+T
440    /// backend to fork stabilizer terms during T-gate decomposition.
441    pub fn clone_with_seed(&self, seed: u64) -> Result<Self> {
442        Ok(Self {
443            num_qubits: self.num_qubits,
444            tableau: self.tableau.clone(),
445            rng: StdRng::seed_from_u64(seed),
446            measurement_record: Vec::new(),
447        })
448    }
449
450    /// Check whether a gate is a Clifford gate (simulable by this backend).
451    ///
452    /// Clifford gates are: H, X, Y, Z, S, Sdg, CNOT, CZ, SWAP.
453    /// Measure and Reset are also supported (non-unitary but handled).
454    /// T, Tdg, Rx, Ry, Rz, Phase, Rzz, and custom unitaries are NOT Clifford
455    /// in general.
456    pub fn is_clifford_gate(gate: &Gate) -> bool {
457        matches!(
458            gate,
459            Gate::H(_)
460                | Gate::X(_)
461                | Gate::Y(_)
462                | Gate::Z(_)
463                | Gate::S(_)
464                | Gate::Sdg(_)
465                | Gate::CNOT(_, _)
466                | Gate::CZ(_, _)
467                | Gate::SWAP(_, _)
468                | Gate::Measure(_)
469                | Gate::Barrier
470        )
471    }
472
473    // -----------------------------------------------------------------------
474    // Gate dispatch
475    // -----------------------------------------------------------------------
476
477    /// Apply a gate from the `Gate` enum, returning measurement outcomes if any.
478    ///
479    /// Returns an error for non-Clifford gates.
480    pub fn apply_gate(&mut self, gate: &Gate) -> Result<Vec<MeasurementOutcome>> {
481        match gate {
482            Gate::H(q) => {
483                self.hadamard(*q as usize);
484                Ok(vec![])
485            }
486            Gate::X(q) => {
487                self.x_gate(*q as usize);
488                Ok(vec![])
489            }
490            Gate::Y(q) => {
491                self.y_gate(*q as usize);
492                Ok(vec![])
493            }
494            Gate::Z(q) => {
495                self.z_gate(*q as usize);
496                Ok(vec![])
497            }
498            Gate::S(q) => {
499                self.phase_gate(*q as usize);
500                Ok(vec![])
501            }
502            Gate::Sdg(q) => {
503                // S^dag = S^3: apply S three times
504                let qu = *q as usize;
505                self.phase_gate(qu);
506                self.phase_gate(qu);
507                self.phase_gate(qu);
508                Ok(vec![])
509            }
510            Gate::CNOT(c, t) => {
511                self.cnot(*c as usize, *t as usize);
512                Ok(vec![])
513            }
514            Gate::CZ(q1, q2) => {
515                self.cz(*q1 as usize, *q2 as usize);
516                Ok(vec![])
517            }
518            Gate::SWAP(q1, q2) => {
519                self.swap(*q1 as usize, *q2 as usize);
520                Ok(vec![])
521            }
522            Gate::Measure(q) => {
523                let outcome = self.measure(*q as usize)?;
524                Ok(vec![outcome])
525            }
526            Gate::Barrier => Ok(vec![]),
527            _ => Err(QuantumError::CircuitError(format!(
528                "gate {:?} is not a Clifford gate and cannot be simulated \
529                 by the stabilizer backend",
530                gate
531            ))),
532        }
533    }
534}
535
536// ---------------------------------------------------------------------------
537// Phase accumulation helper
538// ---------------------------------------------------------------------------
539
540/// Compute the phase contribution when multiplying two single-qubit Pauli
541/// operators encoded as (x, z) bits.
542///
543/// Returns 0, +1, or -1 representing a phase of i^0, i^1, or i^{-1}.
544///
545/// Encoding: (0,0)=I, (1,0)=X, (1,1)=Y, (0,1)=Z.
546#[inline]
547fn g(x1: bool, z1: bool, x2: bool, z2: bool) -> i32 {
548    if !x1 && !z1 {
549        return 0; // I * anything = 0 phase
550    }
551    if x1 && z1 {
552        // Y * ...
553        if x2 && z2 { 0 } else if x2 { 1 } else if z2 { -1 } else { 0 }
554    } else if x1 && !z1 {
555        // X * ...
556        if x2 && z2 { -1 } else if x2 { 0 } else if z2 { 1 } else { 0 }
557    } else {
558        // Z * ...  (z1 && !x1)
559        if x2 && z2 { 1 } else if x2 { -1 } else { 0 }
560    }
561}
562
563#[cfg(test)]
564mod tests {
565    use super::*;
566
567    #[test]
568    fn test_initial_state_measurement() {
569        // |0> state: measuring should give 0 deterministically
570        let mut state = StabilizerState::new(1).unwrap();
571        let outcome = state.measure(0).unwrap();
572        assert!(!outcome.result, "measuring |0> should yield 0");
573        assert_eq!(outcome.probability, 1.0);
574    }
575
576    #[test]
577    fn test_x_gate_flips() {
578        // X|0> = |1>: measuring should give 1 deterministically
579        let mut state = StabilizerState::new(1).unwrap();
580        state.x_gate(0);
581        let outcome = state.measure(0).unwrap();
582        assert!(outcome.result, "measuring X|0> should yield 1");
583        assert_eq!(outcome.probability, 1.0);
584    }
585
586    #[test]
587    fn test_hadamard_creates_superposition() {
588        // H|0> = |+>: measurement should be random (prob 0.5)
589        let mut state = StabilizerState::new_with_seed(1, 42).unwrap();
590        state.hadamard(0);
591        let outcome = state.measure(0).unwrap();
592        assert_eq!(outcome.probability, 0.5);
593    }
594
595    #[test]
596    fn test_bell_state() {
597        // Create Bell state |00> + |11> (up to normalization)
598        // Both qubits should always measure the same value.
599        let mut state = StabilizerState::new_with_seed(2, 123).unwrap();
600        state.hadamard(0);
601        state.cnot(0, 1);
602        let o0 = state.measure(0).unwrap();
603        let o1 = state.measure(1).unwrap();
604        assert_eq!(
605            o0.result, o1.result,
606            "Bell state qubits must be correlated"
607        );
608    }
609
610    #[test]
611    fn test_z_gate_phase() {
612        // Z|0> = |0> (no change)
613        let mut state = StabilizerState::new(1).unwrap();
614        state.z_gate(0);
615        let outcome = state.measure(0).unwrap();
616        assert!(!outcome.result, "Z|0> should still be |0>");
617
618        // Z|1> = -|1> (global phase, same measurement)
619        let mut state2 = StabilizerState::new(1).unwrap();
620        state2.x_gate(0);
621        state2.z_gate(0);
622        let outcome2 = state2.measure(0).unwrap();
623        assert!(outcome2.result, "Z|1> should still measure as |1>");
624    }
625
626    #[test]
627    fn test_phase_gate() {
628        // S^2 = Z: applying S twice should act as Z
629        let mut s1 = StabilizerState::new_with_seed(1, 99).unwrap();
630        s1.hadamard(0);
631        s1.phase_gate(0);
632        s1.phase_gate(0);
633        // Now state is Z H|0> = Z|+> = |->
634
635        let mut s2 = StabilizerState::new_with_seed(1, 99).unwrap();
636        s2.hadamard(0);
637        s2.z_gate(0);
638        // Also |->
639
640        // Measuring in X basis: H then measure
641        s1.hadamard(0);
642        s2.hadamard(0);
643        let o1 = s1.measure(0).unwrap();
644        let o2 = s2.measure(0).unwrap();
645        assert_eq!(o1.result, o2.result, "S^2 should equal Z");
646    }
647
648    #[test]
649    fn test_cz_gate() {
650        // CZ on |+0> should give |0+> + |1-> = |00> + |01> + |10> - |11>
651        // This is a product state in the X-Z basis.
652        // After CZ, measuring qubit 0 in Z basis should still be random.
653        let mut state = StabilizerState::new_with_seed(2, 777).unwrap();
654        state.hadamard(0);
655        state.cz(0, 1);
656        let o = state.measure(0).unwrap();
657        assert_eq!(o.probability, 0.5);
658    }
659
660    #[test]
661    fn test_swap_gate() {
662        // Prepare |10>, SWAP -> |01>
663        let mut state = StabilizerState::new(2).unwrap();
664        state.x_gate(0);
665        state.swap(0, 1);
666        let o0 = state.measure(0).unwrap();
667        let o1 = state.measure(1).unwrap();
668        assert!(!o0.result, "after SWAP, qubit 0 should be |0>");
669        assert!(o1.result, "after SWAP, qubit 1 should be |1>");
670    }
671
672    #[test]
673    fn test_is_clifford_gate() {
674        assert!(StabilizerState::is_clifford_gate(&Gate::H(0)));
675        assert!(StabilizerState::is_clifford_gate(&Gate::CNOT(0, 1)));
676        assert!(StabilizerState::is_clifford_gate(&Gate::S(0)));
677        assert!(!StabilizerState::is_clifford_gate(&Gate::T(0)));
678        assert!(!StabilizerState::is_clifford_gate(&Gate::Rx(0, 0.5)));
679    }
680
681    #[test]
682    fn test_apply_gate_dispatch() {
683        let mut state = StabilizerState::new(2).unwrap();
684        state.apply_gate(&Gate::H(0)).unwrap();
685        state.apply_gate(&Gate::CNOT(0, 1)).unwrap();
686        let outcomes = state.apply_gate(&Gate::Measure(0)).unwrap();
687        assert_eq!(outcomes.len(), 1);
688    }
689
690    #[test]
691    fn test_non_clifford_rejected() {
692        let mut state = StabilizerState::new(1).unwrap();
693        let result = state.apply_gate(&Gate::T(0));
694        assert!(result.is_err());
695    }
696
697    #[test]
698    fn test_measurement_record() {
699        let mut state = StabilizerState::new(2).unwrap();
700        state.x_gate(1);
701        state.measure(0).unwrap();
702        state.measure(1).unwrap();
703        let record = state.measurement_record();
704        assert_eq!(record.len(), 2);
705        assert!(!record[0].result);
706        assert!(record[1].result);
707    }
708
709    #[test]
710    fn test_invalid_qubit_measure() {
711        let mut state = StabilizerState::new(2).unwrap();
712        let result = state.measure(5);
713        assert!(result.is_err());
714    }
715
716    #[test]
717    fn test_y_gate() {
718        // Y|0> = i|1>, so measurement should give 1
719        let mut state = StabilizerState::new(1).unwrap();
720        state.y_gate(0);
721        let outcome = state.measure(0).unwrap();
722        assert!(outcome.result, "Y|0> should measure as |1>");
723    }
724
725    #[test]
726    fn test_sdg_gate() {
727        // Sdg = S^3, and S^4 = I, so S . Sdg = I
728        let mut state = StabilizerState::new_with_seed(1, 42).unwrap();
729        state.hadamard(0);
730        state.phase_gate(0); // S
731        state.apply_gate(&Gate::Sdg(0)).unwrap(); // Sdg
732        // Should be back to H|0> = |+>
733        state.hadamard(0);
734        let outcome = state.measure(0).unwrap();
735        assert!(!outcome.result, "S.Sdg should be identity");
736        assert_eq!(outcome.probability, 1.0);
737    }
738
739    #[test]
740    fn test_g_function() {
741        // I * anything = 0
742        assert_eq!(g(false, false, true, true), 0);
743        // X * Y = iZ  => phase +1
744        assert_eq!(g(true, false, true, true), -1);
745        // X * Z = -iY => phase -1... wait: g(X, Z) = g(1,0, 0,1) = 1
746        // Actually X*Z = -iY, but g returns the exponent of i in the
747        // *product* commutation, and we get +1 here because the Pauli
748        // product rule for X*Z uses a different sign convention.
749        assert_eq!(g(true, false, false, true), 1);
750        // Y * X = -iZ => phase -1... g(1,1, 1,0) = 1
751        assert_eq!(g(true, true, true, false), 1);
752    }
753
754    #[test]
755    fn test_ghz_state() {
756        // GHZ state: H on q0, then CNOT chain
757        let n = 5;
758        let mut state = StabilizerState::new_with_seed(n, 314).unwrap();
759        state.hadamard(0);
760        for i in 0..(n - 1) {
761            state.cnot(i, i + 1);
762        }
763        // All qubits should measure the same value
764        let first = state.measure(0).unwrap();
765        for i in 1..n {
766            let oi = state.measure(i).unwrap();
767            assert_eq!(
768                first.result, oi.result,
769                "GHZ state: qubit {} disagrees with qubit 0",
770                i
771            );
772        }
773    }
774}