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    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        if let Some(p) = anticommuting_row {
243            // Random outcome case
244            // Set the p-th stabilizer to Z_qubit
245            for j in 0..self.num_qubits {
246                self.x_matrix[[p, j]] = false;
247                self.z_matrix[[p, j]] = j == qubit;
248            }
249
250            // Random measurement outcome
251            let mut rng = thread_rng();
252            let outcome = rng.gen_bool(0.5);
253            self.phase[p] = outcome;
254
255            // Update other stabilizers that anticommute
256            for i in 0..self.num_qubits {
257                if i != p && self.x_matrix[[i, qubit]] {
258                    // Multiply by stabilizer p
259                    for j in 0..self.num_qubits {
260                        self.x_matrix[[i, j]] ^= self.x_matrix[[p, j]];
261                        self.z_matrix[[i, j]] ^= self.z_matrix[[p, j]];
262                    }
263                    // Update phase
264                    self.phase[i] ^= self.phase[p];
265                }
266            }
267
268            Ok(outcome) // Measurement outcome
269        } else {
270            // Deterministic outcome
271            // Check if Z_qubit is in the stabilizer group
272            let mut outcome = false;
273
274            for i in 0..self.num_qubits {
275                if self.z_matrix[[i, qubit]] && !self.x_matrix[[i, qubit]] {
276                    outcome = self.phase[i];
277                    break;
278                }
279            }
280
281            Ok(outcome)
282        }
283    }
284
285    /// Get the current stabilizer generators as strings
286    pub fn get_stabilizers(&self) -> Vec<String> {
287        let mut stabilizers = Vec::new();
288
289        for i in 0..self.num_qubits {
290            let mut stab = String::new();
291
292            // Phase
293            if self.phase[i] {
294                stab.push('-');
295            } else {
296                stab.push('+');
297            }
298
299            // Pauli string
300            for j in 0..self.num_qubits {
301                let has_x = self.x_matrix[[i, j]];
302                let has_z = self.z_matrix[[i, j]];
303
304                match (has_x, has_z) {
305                    (false, false) => stab.push('I'),
306                    (true, false) => stab.push('X'),
307                    (false, true) => stab.push('Z'),
308                    (true, true) => stab.push('Y'),
309                }
310            }
311
312            stabilizers.push(stab);
313        }
314
315        stabilizers
316    }
317}
318
319/// Stabilizer simulator that efficiently simulates Clifford circuits
320pub struct StabilizerSimulator {
321    tableau: StabilizerTableau,
322    measurement_record: Vec<(usize, bool)>,
323}
324
325impl StabilizerSimulator {
326    /// Create a new stabilizer simulator
327    pub fn new(num_qubits: usize) -> Self {
328        Self {
329            tableau: StabilizerTableau::new(num_qubits),
330            measurement_record: Vec::new(),
331        }
332    }
333
334    /// Apply a gate to the simulator
335    pub fn apply_gate(&mut self, gate: StabilizerGate) -> Result<(), QuantRS2Error> {
336        match gate {
337            StabilizerGate::H(q) => self.tableau.apply_h(q),
338            StabilizerGate::S(q) => self.tableau.apply_s(q),
339            StabilizerGate::X(q) => self.tableau.apply_x(q),
340            StabilizerGate::Y(q) => self.tableau.apply_y(q),
341            StabilizerGate::Z(q) => self.tableau.apply_z(q),
342            StabilizerGate::CNOT(c, t) => self.tableau.apply_cnot(c, t),
343        }
344    }
345
346    /// Measure a qubit
347    pub fn measure(&mut self, qubit: usize) -> Result<bool, QuantRS2Error> {
348        let outcome = self.tableau.measure(qubit)?;
349        self.measurement_record.push((qubit, outcome));
350        Ok(outcome)
351    }
352
353    /// Get the current stabilizers
354    pub fn get_stabilizers(&self) -> Vec<String> {
355        self.tableau.get_stabilizers()
356    }
357
358    /// Get measurement record
359    pub fn get_measurements(&self) -> &[(usize, bool)] {
360        &self.measurement_record
361    }
362
363    /// Reset the simulator
364    pub fn reset(&mut self) {
365        let num_qubits = self.tableau.num_qubits;
366        self.tableau = StabilizerTableau::new(num_qubits);
367        self.measurement_record.clear();
368    }
369
370    /// Get the number of qubits
371    pub const fn num_qubits(&self) -> usize {
372        self.tableau.num_qubits
373    }
374
375    /// Get the state vector (for compatibility with other simulators)
376    /// Note: This is expensive for stabilizer states and returns a sparse representation
377    pub fn get_statevector(&self) -> Vec<Complex64> {
378        let n = self.tableau.num_qubits;
379        let dim = 1 << n;
380        let mut state = vec![Complex64::new(0.0, 0.0); dim];
381
382        // For a stabilizer state, we can determine which computational basis states
383        // have non-zero amplitude by finding the simultaneous +1 eigenstates of all stabilizers
384        // This is a simplified implementation that assumes the state is in |0...0>
385        // A full implementation would need to solve the stabilizer equations
386
387        // For now, return a simple state
388        state[0] = Complex64::new(1.0, 0.0);
389        state
390    }
391}
392
393/// Gates supported by the stabilizer simulator
394#[derive(Debug, Clone, Copy)]
395pub enum StabilizerGate {
396    H(usize),
397    S(usize),
398    X(usize),
399    Y(usize),
400    Z(usize),
401    CNOT(usize, usize),
402}
403
404/// Check if a circuit can be simulated by the stabilizer simulator
405pub fn is_clifford_circuit<const N: usize>(circuit: &Circuit<N>) -> bool {
406    // Check if all gates in the circuit are Clifford gates
407    // Clifford gates: H, S, S†, CNOT, X, Y, Z, CZ
408    circuit.gates().iter().all(|gate| {
409        matches!(
410            gate.name(),
411            "H" | "S" | "S†" | "CNOT" | "X" | "Y" | "Z" | "CZ" | "Phase" | "PhaseDagger"
412        )
413    })
414}
415
416/// Convert a gate operation to a stabilizer gate
417fn gate_to_stabilizer(gate: &Arc<dyn GateOp + Send + Sync>) -> Option<StabilizerGate> {
418    let gate_name = gate.name();
419    let qubits = gate.qubits();
420
421    match gate_name {
422        "H" => {
423            if qubits.len() == 1 {
424                Some(StabilizerGate::H(qubits[0].0 as usize))
425            } else {
426                None
427            }
428        }
429        "S" | "Phase" => {
430            if qubits.len() == 1 {
431                Some(StabilizerGate::S(qubits[0].0 as usize))
432            } else {
433                None
434            }
435        }
436        "X" => {
437            if qubits.len() == 1 {
438                Some(StabilizerGate::X(qubits[0].0 as usize))
439            } else {
440                None
441            }
442        }
443        "Y" => {
444            if qubits.len() == 1 {
445                Some(StabilizerGate::Y(qubits[0].0 as usize))
446            } else {
447                None
448            }
449        }
450        "Z" => {
451            if qubits.len() == 1 {
452                Some(StabilizerGate::Z(qubits[0].0 as usize))
453            } else {
454                None
455            }
456        }
457        "CNOT" => {
458            if qubits.len() == 2 {
459                Some(StabilizerGate::CNOT(
460                    qubits[0].0 as usize,
461                    qubits[1].0 as usize,
462                ))
463            } else {
464                None
465            }
466        }
467        "CZ" => {
468            if qubits.len() == 2 {
469                // CZ = H(target) CNOT H(target)
470                // For now, we can decompose this or add native support
471                // Let's return None for unsupported gates
472                None
473            } else {
474                None
475            }
476        }
477        _ => None,
478    }
479}
480
481/// Implement the Simulator trait for StabilizerSimulator
482impl Simulator for StabilizerSimulator {
483    fn run<const N: usize>(
484        &mut self,
485        circuit: &Circuit<N>,
486    ) -> crate::error::Result<SimulatorResult<N>> {
487        // Create a new simulator instance
488        let mut sim = Self::new(N);
489
490        // Apply all gates in the circuit
491        for gate in circuit.gates() {
492            if let Some(stab_gate) = gate_to_stabilizer(gate) {
493                // Ignore errors for now - in production we'd handle them properly
494                let _ = sim.apply_gate(stab_gate);
495            }
496        }
497
498        // Get the state vector (expensive operation)
499        let amplitudes = sim.get_statevector();
500
501        Ok(SimulatorResult::new(amplitudes))
502    }
503}
504
505/// Builder for creating circuits that can be simulated with the stabilizer formalism
506pub struct CliffordCircuitBuilder {
507    gates: Vec<StabilizerGate>,
508    num_qubits: usize,
509}
510
511impl CliffordCircuitBuilder {
512    /// Create a new Clifford circuit builder
513    pub const fn new(num_qubits: usize) -> Self {
514        Self {
515            gates: Vec::new(),
516            num_qubits,
517        }
518    }
519
520    /// Add a Hadamard gate
521    pub fn h(mut self, qubit: usize) -> Self {
522        self.gates.push(StabilizerGate::H(qubit));
523        self
524    }
525
526    /// Add an S gate
527    pub fn s(mut self, qubit: usize) -> Self {
528        self.gates.push(StabilizerGate::S(qubit));
529        self
530    }
531
532    /// Add a Pauli-X gate
533    pub fn x(mut self, qubit: usize) -> Self {
534        self.gates.push(StabilizerGate::X(qubit));
535        self
536    }
537
538    /// Add a Pauli-Y gate
539    pub fn y(mut self, qubit: usize) -> Self {
540        self.gates.push(StabilizerGate::Y(qubit));
541        self
542    }
543
544    /// Add a Pauli-Z gate
545    pub fn z(mut self, qubit: usize) -> Self {
546        self.gates.push(StabilizerGate::Z(qubit));
547        self
548    }
549
550    /// Add a CNOT gate
551    pub fn cnot(mut self, control: usize, target: usize) -> Self {
552        self.gates.push(StabilizerGate::CNOT(control, target));
553        self
554    }
555
556    /// Build and run the circuit
557    pub fn run(self) -> Result<StabilizerSimulator, QuantRS2Error> {
558        let mut sim = StabilizerSimulator::new(self.num_qubits);
559
560        for gate in self.gates {
561            sim.apply_gate(gate)?;
562        }
563
564        Ok(sim)
565    }
566}
567
568#[cfg(test)]
569mod tests {
570    use super::*;
571
572    #[test]
573    fn test_stabilizer_init() {
574        let sim = StabilizerSimulator::new(3);
575        let stabs = sim.get_stabilizers();
576
577        assert_eq!(stabs.len(), 3);
578        assert_eq!(stabs[0], "+ZII");
579        assert_eq!(stabs[1], "+IZI");
580        assert_eq!(stabs[2], "+IIZ");
581    }
582
583    #[test]
584    fn test_hadamard_gate() {
585        let mut sim = StabilizerSimulator::new(1);
586        sim.apply_gate(StabilizerGate::H(0)).unwrap();
587
588        let stabs = sim.get_stabilizers();
589        assert_eq!(stabs[0], "+X");
590    }
591
592    #[test]
593    fn test_bell_state() {
594        let mut sim = StabilizerSimulator::new(2);
595        sim.apply_gate(StabilizerGate::H(0)).unwrap();
596        sim.apply_gate(StabilizerGate::CNOT(0, 1)).unwrap();
597
598        let stabs = sim.get_stabilizers();
599        assert!(stabs.contains(&"+XX".to_string()));
600        assert!(stabs.contains(&"+ZZ".to_string()));
601    }
602
603    #[test]
604    fn test_ghz_state() {
605        let mut sim = StabilizerSimulator::new(3);
606        sim.apply_gate(StabilizerGate::H(0)).unwrap();
607        sim.apply_gate(StabilizerGate::CNOT(0, 1)).unwrap();
608        sim.apply_gate(StabilizerGate::CNOT(1, 2)).unwrap();
609
610        let stabs = sim.get_stabilizers();
611        assert!(stabs.contains(&"+XXX".to_string()));
612        assert!(stabs.contains(&"+ZZI".to_string()));
613        assert!(stabs.contains(&"+IZZ".to_string()));
614    }
615}