quantrs2_sim/
stabilizer.rs

1//! Stabilizer simulator for efficient simulation of Clifford circuits
2//!
3//! The stabilizer formalism provides an efficient way to simulate quantum circuits
4//! that consist only of Clifford gates (H, S, CNOT) and Pauli measurements.
5//! This implementation uses the tableau representation and leverages SciRS2
6//! for efficient data structures and operations.
7
8use crate::simulator::{Simulator, SimulatorResult};
9use scirs2_core::ndarray::{Array2, ArrayView2};
10use scirs2_core::Complex64;
11use quantrs2_circuit::prelude::*;
12use quantrs2_core::gate::GateOp;
13use quantrs2_core::prelude::*;
14use scirs2_core::random::prelude::*;
15use std::collections::HashMap;
16use std::sync::Arc;
17
18/// Stabilizer tableau representation
19///
20/// The tableau stores generators of the stabilizer group as rows.
21/// Each row represents a Pauli string with phase.
22#[derive(Debug, Clone)]
23pub struct StabilizerTableau {
24    /// Number of qubits
25    num_qubits: usize,
26    /// X part of stabilizers (n x n matrix)
27    x_matrix: Array2<bool>,
28    /// Z part of stabilizers (n x n matrix)
29    z_matrix: Array2<bool>,
30    /// Phase vector (n elements, each is 0 or 1 representing +1 or -1)
31    phase: Vec<bool>,
32    /// Destabilizers X part (n x n matrix)
33    destab_x: Array2<bool>,
34    /// Destabilizers Z part (n x n matrix)
35    destab_z: Array2<bool>,
36    /// Destabilizer phases
37    destab_phase: Vec<bool>,
38}
39
40impl StabilizerTableau {
41    /// Create a new tableau in the |0...0⟩ state
42    pub fn new(num_qubits: usize) -> Self {
43        let mut x_matrix = Array2::from_elem((num_qubits, num_qubits), false);
44        let mut z_matrix = Array2::from_elem((num_qubits, num_qubits), false);
45        let mut destab_x = Array2::from_elem((num_qubits, num_qubits), false);
46        let mut destab_z = Array2::from_elem((num_qubits, num_qubits), false);
47
48        // Initialize stabilizers as Z_i and destabilizers as X_i
49        for i in 0..num_qubits {
50            z_matrix[[i, i]] = true; // Stabilizer i is Z_i
51            destab_x[[i, i]] = true; // Destabilizer i is X_i
52        }
53
54        Self {
55            num_qubits,
56            x_matrix,
57            z_matrix,
58            phase: vec![false; num_qubits],
59            destab_x,
60            destab_z,
61            destab_phase: vec![false; num_qubits],
62        }
63    }
64
65    /// Apply a Hadamard gate
66    pub fn apply_h(&mut self, qubit: usize) -> Result<(), QuantRS2Error> {
67        if qubit >= self.num_qubits {
68            return Err(QuantRS2Error::InvalidQubitId(qubit as u32));
69        }
70
71        // H: X ↔ Z, phase changes according to anticommutation
72        for i in 0..self.num_qubits {
73            // For stabilizers
74            let x_val = self.x_matrix[[i, qubit]];
75            let z_val = self.z_matrix[[i, qubit]];
76
77            // Update phase: if both X and Z are present, add a phase
78            if x_val && z_val {
79                self.phase[i] = !self.phase[i];
80            }
81
82            // Swap X and Z
83            self.x_matrix[[i, qubit]] = z_val;
84            self.z_matrix[[i, qubit]] = x_val;
85
86            // For destabilizers
87            let dx_val = self.destab_x[[i, qubit]];
88            let dz_val = self.destab_z[[i, qubit]];
89
90            if dx_val && dz_val {
91                self.destab_phase[i] = !self.destab_phase[i];
92            }
93
94            self.destab_x[[i, qubit]] = dz_val;
95            self.destab_z[[i, qubit]] = dx_val;
96        }
97
98        Ok(())
99    }
100
101    /// Apply an S gate (phase gate)
102    pub fn apply_s(&mut self, qubit: usize) -> Result<(), QuantRS2Error> {
103        if qubit >= self.num_qubits {
104            return Err(QuantRS2Error::InvalidQubitId(qubit as u32));
105        }
106
107        // S: X → Y, Z → Z
108        // Y = iXZ, so we need to track the phase change
109        for i in 0..self.num_qubits {
110            // For stabilizers
111            if self.x_matrix[[i, qubit]] && !self.z_matrix[[i, qubit]] {
112                // X → Y = iXZ
113                self.z_matrix[[i, qubit]] = true;
114                self.phase[i] = !self.phase[i]; // Multiply by i = -1 in {+1, -1}
115            }
116
117            // For destabilizers
118            if self.destab_x[[i, qubit]] && !self.destab_z[[i, qubit]] {
119                self.destab_z[[i, qubit]] = true;
120                self.destab_phase[i] = !self.destab_phase[i];
121            }
122        }
123
124        Ok(())
125    }
126
127    /// Apply a CNOT gate
128    pub fn apply_cnot(&mut self, control: usize, target: usize) -> Result<(), QuantRS2Error> {
129        if control >= self.num_qubits || target >= self.num_qubits {
130            return Err(QuantRS2Error::InvalidQubitId(control.max(target) as u32));
131        }
132
133        if control == target {
134            return Err(QuantRS2Error::InvalidInput(
135                "CNOT control and target must be different".to_string(),
136            ));
137        }
138
139        // CNOT: X_c → X_c X_t, Z_t → Z_c Z_t
140        for i in 0..self.num_qubits {
141            // For stabilizers
142            if self.x_matrix[[i, control]] {
143                self.x_matrix[[i, target]] ^= true;
144            }
145            if self.z_matrix[[i, target]] {
146                self.z_matrix[[i, control]] ^= true;
147            }
148
149            // For destabilizers
150            if self.destab_x[[i, control]] {
151                self.destab_x[[i, target]] ^= true;
152            }
153            if self.destab_z[[i, target]] {
154                self.destab_z[[i, control]] ^= true;
155            }
156        }
157
158        Ok(())
159    }
160
161    /// Apply a Pauli X gate
162    pub fn apply_x(&mut self, qubit: usize) -> Result<(), QuantRS2Error> {
163        if qubit >= self.num_qubits {
164            return Err(QuantRS2Error::InvalidQubitId(qubit as u32));
165        }
166
167        // X anticommutes with Z
168        for i in 0..self.num_qubits {
169            if self.z_matrix[[i, qubit]] {
170                self.phase[i] = !self.phase[i];
171            }
172            if self.destab_z[[i, qubit]] {
173                self.destab_phase[i] = !self.destab_phase[i];
174            }
175        }
176
177        Ok(())
178    }
179
180    /// Apply a Pauli Y gate
181    pub fn apply_y(&mut self, qubit: usize) -> Result<(), QuantRS2Error> {
182        if qubit >= self.num_qubits {
183            return Err(QuantRS2Error::InvalidQubitId(qubit as u32));
184        }
185
186        // Y = iXZ, anticommutes with both X and Z
187        for i in 0..self.num_qubits {
188            let has_x = self.x_matrix[[i, qubit]];
189            let has_z = self.z_matrix[[i, qubit]];
190
191            if has_x != has_z {
192                self.phase[i] = !self.phase[i];
193            }
194
195            let has_dx = self.destab_x[[i, qubit]];
196            let has_dz = self.destab_z[[i, qubit]];
197
198            if has_dx != has_dz {
199                self.destab_phase[i] = !self.destab_phase[i];
200            }
201        }
202
203        Ok(())
204    }
205
206    /// Apply a Pauli Z gate
207    pub fn apply_z(&mut self, qubit: usize) -> Result<(), QuantRS2Error> {
208        if qubit >= self.num_qubits {
209            return Err(QuantRS2Error::InvalidQubitId(qubit as u32));
210        }
211
212        // Z anticommutes with X
213        for i in 0..self.num_qubits {
214            if self.x_matrix[[i, qubit]] {
215                self.phase[i] = !self.phase[i];
216            }
217            if self.destab_x[[i, qubit]] {
218                self.destab_phase[i] = !self.destab_phase[i];
219            }
220        }
221
222        Ok(())
223    }
224
225    /// Measure a qubit in the computational basis
226    /// Returns the measurement outcome (0 or 1)
227    pub fn measure(&mut self, qubit: usize) -> Result<bool, QuantRS2Error> {
228        if qubit >= self.num_qubits {
229            return Err(QuantRS2Error::InvalidQubitId(qubit as u32));
230        }
231
232        // Find a stabilizer that anticommutes with Z_qubit
233        let mut anticommuting_row = None;
234
235        for i in 0..self.num_qubits {
236            if self.x_matrix[[i, qubit]] {
237                anticommuting_row = Some(i);
238                break;
239            }
240        }
241
242        match anticommuting_row {
243            Some(p) => {
244                // Random outcome case
245                // Set the p-th stabilizer to Z_qubit
246                for j in 0..self.num_qubits {
247                    self.x_matrix[[p, j]] = false;
248                    self.z_matrix[[p, j]] = j == qubit;
249                }
250
251                // Random measurement outcome
252                let mut rng = thread_rng();
253                let outcome = rng.gen_bool(0.5);
254                self.phase[p] = outcome;
255
256                // Update other stabilizers that anticommute
257                for i in 0..self.num_qubits {
258                    if i != p && self.x_matrix[[i, qubit]] {
259                        // Multiply by stabilizer p
260                        for j in 0..self.num_qubits {
261                            self.x_matrix[[i, j]] ^= self.x_matrix[[p, j]];
262                            self.z_matrix[[i, j]] ^= self.z_matrix[[p, j]];
263                        }
264                        // Update phase
265                        self.phase[i] ^= self.phase[p];
266                    }
267                }
268
269                Ok(outcome) // Measurement outcome
270            }
271            None => {
272                // Deterministic outcome
273                // Check if Z_qubit is in the stabilizer group
274                let mut outcome = false;
275
276                for i in 0..self.num_qubits {
277                    if self.z_matrix[[i, qubit]] && !self.x_matrix[[i, qubit]] {
278                        outcome = self.phase[i];
279                        break;
280                    }
281                }
282
283                Ok(outcome)
284            }
285        }
286    }
287
288    /// Get the current stabilizer generators as strings
289    pub fn get_stabilizers(&self) -> Vec<String> {
290        let mut stabilizers = Vec::new();
291
292        for i in 0..self.num_qubits {
293            let mut stab = String::new();
294
295            // Phase
296            if self.phase[i] {
297                stab.push('-');
298            } else {
299                stab.push('+');
300            }
301
302            // Pauli string
303            for j in 0..self.num_qubits {
304                let has_x = self.x_matrix[[i, j]];
305                let has_z = self.z_matrix[[i, j]];
306
307                match (has_x, has_z) {
308                    (false, false) => stab.push('I'),
309                    (true, false) => stab.push('X'),
310                    (false, true) => stab.push('Z'),
311                    (true, true) => stab.push('Y'),
312                }
313            }
314
315            stabilizers.push(stab);
316        }
317
318        stabilizers
319    }
320}
321
322/// Stabilizer simulator that efficiently simulates Clifford circuits
323pub struct StabilizerSimulator {
324    tableau: StabilizerTableau,
325    measurement_record: Vec<(usize, bool)>,
326}
327
328impl StabilizerSimulator {
329    /// Create a new stabilizer simulator
330    pub fn new(num_qubits: usize) -> Self {
331        Self {
332            tableau: StabilizerTableau::new(num_qubits),
333            measurement_record: Vec::new(),
334        }
335    }
336
337    /// Apply a gate to the simulator
338    pub fn apply_gate(&mut self, gate: StabilizerGate) -> Result<(), QuantRS2Error> {
339        match gate {
340            StabilizerGate::H(q) => self.tableau.apply_h(q),
341            StabilizerGate::S(q) => self.tableau.apply_s(q),
342            StabilizerGate::X(q) => self.tableau.apply_x(q),
343            StabilizerGate::Y(q) => self.tableau.apply_y(q),
344            StabilizerGate::Z(q) => self.tableau.apply_z(q),
345            StabilizerGate::CNOT(c, t) => self.tableau.apply_cnot(c, t),
346        }
347    }
348
349    /// Measure a qubit
350    pub fn measure(&mut self, qubit: usize) -> Result<bool, QuantRS2Error> {
351        let outcome = self.tableau.measure(qubit)?;
352        self.measurement_record.push((qubit, outcome));
353        Ok(outcome)
354    }
355
356    /// Get the current stabilizers
357    pub fn get_stabilizers(&self) -> Vec<String> {
358        self.tableau.get_stabilizers()
359    }
360
361    /// Get measurement record
362    pub fn get_measurements(&self) -> &[(usize, bool)] {
363        &self.measurement_record
364    }
365
366    /// Reset the simulator
367    pub fn reset(&mut self) {
368        let num_qubits = self.tableau.num_qubits;
369        self.tableau = StabilizerTableau::new(num_qubits);
370        self.measurement_record.clear();
371    }
372
373    /// Get the number of qubits
374    pub fn num_qubits(&self) -> usize {
375        self.tableau.num_qubits
376    }
377
378    /// Get the state vector (for compatibility with other simulators)
379    /// Note: This is expensive for stabilizer states and returns a sparse representation
380    pub fn get_statevector(&self) -> Vec<Complex64> {
381        let n = self.tableau.num_qubits;
382        let dim = 1 << n;
383        let mut state = vec![Complex64::new(0.0, 0.0); dim];
384
385        // For a stabilizer state, we can determine which computational basis states
386        // have non-zero amplitude by finding the simultaneous +1 eigenstates of all stabilizers
387        // This is a simplified implementation that assumes the state is in |0...0>
388        // A full implementation would need to solve the stabilizer equations
389
390        // For now, return a simple state
391        state[0] = Complex64::new(1.0, 0.0);
392        state
393    }
394}
395
396/// Gates supported by the stabilizer simulator
397#[derive(Debug, Clone, Copy)]
398pub enum StabilizerGate {
399    H(usize),
400    S(usize),
401    X(usize),
402    Y(usize),
403    Z(usize),
404    CNOT(usize, usize),
405}
406
407/// Check if a circuit can be simulated by the stabilizer simulator
408pub fn is_clifford_circuit<const N: usize>(circuit: &Circuit<N>) -> bool {
409    // Check if all gates in the circuit are Clifford gates
410    // Clifford gates: H, S, S†, CNOT, X, Y, Z, CZ
411    circuit.gates().iter().all(|gate| {
412        matches!(
413            gate.name(),
414            "H" | "S" | "S†" | "CNOT" | "X" | "Y" | "Z" | "CZ" | "Phase" | "PhaseDagger"
415        )
416    })
417}
418
419/// Convert a gate operation to a stabilizer gate
420fn gate_to_stabilizer(gate: &Arc<dyn GateOp + Send + Sync>) -> Option<StabilizerGate> {
421    let gate_name = gate.name();
422    let qubits = gate.qubits();
423
424    match gate_name {
425        "H" => {
426            if qubits.len() == 1 {
427                Some(StabilizerGate::H(qubits[0].0 as usize))
428            } else {
429                None
430            }
431        }
432        "S" | "Phase" => {
433            if qubits.len() == 1 {
434                Some(StabilizerGate::S(qubits[0].0 as usize))
435            } else {
436                None
437            }
438        }
439        "X" => {
440            if qubits.len() == 1 {
441                Some(StabilizerGate::X(qubits[0].0 as usize))
442            } else {
443                None
444            }
445        }
446        "Y" => {
447            if qubits.len() == 1 {
448                Some(StabilizerGate::Y(qubits[0].0 as usize))
449            } else {
450                None
451            }
452        }
453        "Z" => {
454            if qubits.len() == 1 {
455                Some(StabilizerGate::Z(qubits[0].0 as usize))
456            } else {
457                None
458            }
459        }
460        "CNOT" => {
461            if qubits.len() == 2 {
462                Some(StabilizerGate::CNOT(
463                    qubits[0].0 as usize,
464                    qubits[1].0 as usize,
465                ))
466            } else {
467                None
468            }
469        }
470        "CZ" => {
471            if qubits.len() == 2 {
472                // CZ = H(target) CNOT H(target)
473                // For now, we can decompose this or add native support
474                // Let's return None for unsupported gates
475                None
476            } else {
477                None
478            }
479        }
480        _ => None,
481    }
482}
483
484/// Implement the Simulator trait for StabilizerSimulator
485impl Simulator for StabilizerSimulator {
486    fn run<const N: usize>(
487        &mut self,
488        circuit: &Circuit<N>,
489    ) -> crate::error::Result<SimulatorResult<N>> {
490        // Create a new simulator instance
491        let mut sim = StabilizerSimulator::new(N);
492
493        // Apply all gates in the circuit
494        for gate in circuit.gates() {
495            if let Some(stab_gate) = gate_to_stabilizer(gate) {
496                // Ignore errors for now - in production we'd handle them properly
497                let _ = sim.apply_gate(stab_gate);
498            }
499        }
500
501        // Get the state vector (expensive operation)
502        let amplitudes = sim.get_statevector();
503
504        Ok(SimulatorResult::new(amplitudes))
505    }
506}
507
508/// Builder for creating circuits that can be simulated with the stabilizer formalism
509pub struct CliffordCircuitBuilder {
510    gates: Vec<StabilizerGate>,
511    num_qubits: usize,
512}
513
514impl CliffordCircuitBuilder {
515    /// Create a new Clifford circuit builder
516    pub fn new(num_qubits: usize) -> Self {
517        Self {
518            gates: Vec::new(),
519            num_qubits,
520        }
521    }
522
523    /// Add a Hadamard gate
524    pub fn h(mut self, qubit: usize) -> Self {
525        self.gates.push(StabilizerGate::H(qubit));
526        self
527    }
528
529    /// Add an S gate
530    pub fn s(mut self, qubit: usize) -> Self {
531        self.gates.push(StabilizerGate::S(qubit));
532        self
533    }
534
535    /// Add a Pauli-X gate
536    pub fn x(mut self, qubit: usize) -> Self {
537        self.gates.push(StabilizerGate::X(qubit));
538        self
539    }
540
541    /// Add a Pauli-Y gate
542    pub fn y(mut self, qubit: usize) -> Self {
543        self.gates.push(StabilizerGate::Y(qubit));
544        self
545    }
546
547    /// Add a Pauli-Z gate
548    pub fn z(mut self, qubit: usize) -> Self {
549        self.gates.push(StabilizerGate::Z(qubit));
550        self
551    }
552
553    /// Add a CNOT gate
554    pub fn cnot(mut self, control: usize, target: usize) -> Self {
555        self.gates.push(StabilizerGate::CNOT(control, target));
556        self
557    }
558
559    /// Build and run the circuit
560    pub fn run(self) -> Result<StabilizerSimulator, QuantRS2Error> {
561        let mut sim = StabilizerSimulator::new(self.num_qubits);
562
563        for gate in self.gates {
564            sim.apply_gate(gate)?;
565        }
566
567        Ok(sim)
568    }
569}
570
571#[cfg(test)]
572mod tests {
573    use super::*;
574
575    #[test]
576    fn test_stabilizer_init() {
577        let sim = StabilizerSimulator::new(3);
578        let stabs = sim.get_stabilizers();
579
580        assert_eq!(stabs.len(), 3);
581        assert_eq!(stabs[0], "+ZII");
582        assert_eq!(stabs[1], "+IZI");
583        assert_eq!(stabs[2], "+IIZ");
584    }
585
586    #[test]
587    fn test_hadamard_gate() {
588        let mut sim = StabilizerSimulator::new(1);
589        sim.apply_gate(StabilizerGate::H(0)).unwrap();
590
591        let stabs = sim.get_stabilizers();
592        assert_eq!(stabs[0], "+X");
593    }
594
595    #[test]
596    fn test_bell_state() {
597        let mut sim = StabilizerSimulator::new(2);
598        sim.apply_gate(StabilizerGate::H(0)).unwrap();
599        sim.apply_gate(StabilizerGate::CNOT(0, 1)).unwrap();
600
601        let stabs = sim.get_stabilizers();
602        assert!(stabs.contains(&"+XX".to_string()));
603        assert!(stabs.contains(&"+ZZ".to_string()));
604    }
605
606    #[test]
607    fn test_ghz_state() {
608        let mut sim = StabilizerSimulator::new(3);
609        sim.apply_gate(StabilizerGate::H(0)).unwrap();
610        sim.apply_gate(StabilizerGate::CNOT(0, 1)).unwrap();
611        sim.apply_gate(StabilizerGate::CNOT(1, 2)).unwrap();
612
613        let stabs = sim.get_stabilizers();
614        assert!(stabs.contains(&"+XXX".to_string()));
615        assert!(stabs.contains(&"+ZZI".to_string()));
616        assert!(stabs.contains(&"+IZZ".to_string()));
617    }
618}