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