Skip to main content

quantrs2_sim/stabilizer/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use quantrs2_circuit::prelude::*;
6use quantrs2_core::gate::GateOp;
7use quantrs2_core::prelude::*;
8use scirs2_core::random::prelude::*;
9use std::sync::Arc;
10
11use super::types::{
12    CliffordCircuitBuilder, StabilizerGate, StabilizerSimulator, StabilizerTableau,
13};
14
15/// Phase encoding for Stim compatibility
16/// 0 = +1, 1 = +i, 2 = -1, 3 = -i
17pub type StabilizerPhase = u8;
18/// Phase constants for clarity
19pub mod phase {
20    /// Phase +1
21    pub const PLUS_ONE: u8 = 0;
22    /// Phase +i
23    pub const PLUS_I: u8 = 1;
24    /// Phase -1
25    pub const MINUS_ONE: u8 = 2;
26    /// Phase -i
27    pub const MINUS_I: u8 = 3;
28}
29/// Check if a circuit can be simulated by the stabilizer simulator
30#[must_use]
31pub fn is_clifford_circuit<const N: usize>(circuit: &Circuit<N>) -> bool {
32    circuit.gates().iter().all(|gate| {
33        matches!(
34            gate.name(),
35            "H" | "S" | "S†" | "CNOT" | "X" | "Y" | "Z" | "CZ" | "Phase" | "PhaseDagger"
36        )
37    })
38}
39/// Convert a gate operation to a stabilizer gate
40pub(super) fn gate_to_stabilizer(gate: &Arc<dyn GateOp + Send + Sync>) -> Option<StabilizerGate> {
41    let gate_name = gate.name();
42    let qubits = gate.qubits();
43    match gate_name {
44        "H" => {
45            if qubits.len() == 1 {
46                Some(StabilizerGate::H(qubits[0].0 as usize))
47            } else {
48                None
49            }
50        }
51        "S" | "Phase" => {
52            if qubits.len() == 1 {
53                Some(StabilizerGate::S(qubits[0].0 as usize))
54            } else {
55                None
56            }
57        }
58        "X" => {
59            if qubits.len() == 1 {
60                Some(StabilizerGate::X(qubits[0].0 as usize))
61            } else {
62                None
63            }
64        }
65        "Y" => {
66            if qubits.len() == 1 {
67                Some(StabilizerGate::Y(qubits[0].0 as usize))
68            } else {
69                None
70            }
71        }
72        "Z" => {
73            if qubits.len() == 1 {
74                Some(StabilizerGate::Z(qubits[0].0 as usize))
75            } else {
76                None
77            }
78        }
79        "CNOT" => {
80            if qubits.len() == 2 {
81                Some(StabilizerGate::CNOT(
82                    qubits[0].0 as usize,
83                    qubits[1].0 as usize,
84                ))
85            } else {
86                None
87            }
88        }
89        "CZ" => {
90            if qubits.len() == 2 {
91                Some(StabilizerGate::CZ(
92                    qubits[0].0 as usize,
93                    qubits[1].0 as usize,
94                ))
95            } else {
96                None
97            }
98        }
99        _ => None,
100    }
101}
102#[cfg(test)]
103mod tests {
104    use super::*;
105    #[test]
106    fn test_stabilizer_init() {
107        let sim = StabilizerSimulator::new(3);
108        let stabs = sim.get_stabilizers();
109        assert_eq!(stabs.len(), 3);
110        assert_eq!(stabs[0], "+ZII");
111        assert_eq!(stabs[1], "+IZI");
112        assert_eq!(stabs[2], "+IIZ");
113    }
114    #[test]
115    fn test_hadamard_gate() {
116        let mut sim = StabilizerSimulator::new(1);
117        sim.apply_gate(StabilizerGate::H(0))
118            .expect("Hadamard gate application should succeed");
119        let stabs = sim.get_stabilizers();
120        assert_eq!(stabs[0], "+X");
121    }
122    #[test]
123    fn test_bell_state() {
124        let mut sim = StabilizerSimulator::new(2);
125        sim.apply_gate(StabilizerGate::H(0))
126            .expect("Hadamard gate application should succeed");
127        sim.apply_gate(StabilizerGate::CNOT(0, 1))
128            .expect("CNOT gate application should succeed");
129        let stabs = sim.get_stabilizers();
130        assert!(stabs.contains(&"+XX".to_string()));
131        assert!(stabs.contains(&"+ZZ".to_string()));
132    }
133    #[test]
134    fn test_ghz_state() {
135        let mut sim = StabilizerSimulator::new(3);
136        sim.apply_gate(StabilizerGate::H(0))
137            .expect("Hadamard gate application should succeed");
138        sim.apply_gate(StabilizerGate::CNOT(0, 1))
139            .expect("CNOT gate application should succeed");
140        sim.apply_gate(StabilizerGate::CNOT(1, 2))
141            .expect("CNOT gate application should succeed");
142        let stabs = sim.get_stabilizers();
143        assert!(stabs.contains(&"+XXX".to_string()));
144        assert!(stabs.contains(&"+ZZI".to_string()));
145        assert!(stabs.contains(&"+IZZ".to_string()));
146    }
147    #[test]
148    fn test_s_dag_gate() {
149        let mut sim = StabilizerSimulator::new(1);
150        sim.apply_gate(StabilizerGate::S(0))
151            .expect("S gate application should succeed");
152        sim.apply_gate(StabilizerGate::SDag(0))
153            .expect("S† gate application should succeed");
154        let stabs = sim.get_stabilizers();
155        assert_eq!(stabs[0], "+Z");
156    }
157    #[test]
158    fn test_sqrt_x_gate() {
159        let mut sim1 = StabilizerSimulator::new(1);
160        sim1.apply_gate(StabilizerGate::SqrtX(0))
161            .expect("√X gate application should succeed");
162        sim1.apply_gate(StabilizerGate::SqrtX(0))
163            .expect("√X gate application should succeed");
164        let stabs1 = sim1.get_stabilizers();
165        let mut sim2 = StabilizerSimulator::new(1);
166        sim2.apply_gate(StabilizerGate::X(0))
167            .expect("X gate application should succeed");
168        let stabs2 = sim2.get_stabilizers();
169        assert!(stabs1[0] == "+Z" || stabs1[0] == "-Z");
170        assert!(stabs2[0] == "+Z" || stabs2[0] == "-Z");
171    }
172    #[test]
173    fn test_sqrt_y_gate() {
174        let mut sim1 = StabilizerSimulator::new(1);
175        sim1.apply_gate(StabilizerGate::SqrtY(0))
176            .expect("√Y gate application should succeed");
177        sim1.apply_gate(StabilizerGate::SqrtY(0))
178            .expect("√Y gate application should succeed");
179        let stabs1 = sim1.get_stabilizers();
180        let mut sim2 = StabilizerSimulator::new(1);
181        sim2.apply_gate(StabilizerGate::Y(0))
182            .expect("Y gate application should succeed");
183        let stabs2 = sim2.get_stabilizers();
184        assert_eq!(stabs1[0], stabs2[0]);
185    }
186    #[test]
187    fn test_cz_gate() {
188        let mut sim = StabilizerSimulator::new(2);
189        sim.apply_gate(StabilizerGate::H(0))
190            .expect("Hadamard gate application should succeed");
191        sim.apply_gate(StabilizerGate::H(1))
192            .expect("Hadamard gate application should succeed");
193        sim.apply_gate(StabilizerGate::CZ(0, 1))
194            .expect("CZ gate application should succeed");
195        let stabs = sim.get_stabilizers();
196        assert!(stabs.len() == 2);
197    }
198    #[test]
199    fn test_cy_gate() {
200        let mut sim = StabilizerSimulator::new(2);
201        sim.apply_gate(StabilizerGate::H(0))
202            .expect("Hadamard gate application should succeed");
203        sim.apply_gate(StabilizerGate::CY(0, 1))
204            .expect("CY gate application should succeed");
205        let stabs = sim.get_stabilizers();
206        assert!(stabs.len() == 2);
207    }
208    #[test]
209    fn test_swap_gate() {
210        let mut sim = StabilizerSimulator::new(2);
211        sim.apply_gate(StabilizerGate::X(1))
212            .expect("X gate application should succeed");
213        sim.apply_gate(StabilizerGate::SWAP(0, 1))
214            .expect("SWAP gate application should succeed");
215        let stabs = sim.get_stabilizers();
216        assert!(stabs.len() == 2);
217    }
218    #[test]
219    fn test_builder_pattern_new_gates() {
220        let sim = CliffordCircuitBuilder::new(2)
221            .h(0)
222            .s_dag(0)
223            .sqrt_x(1)
224            .cz(0, 1)
225            .run()
226            .expect("Circuit execution should succeed");
227        let stabs = sim.get_stabilizers();
228        assert!(stabs.len() == 2);
229    }
230    #[test]
231    fn test_large_clifford_circuit() {
232        let mut sim = StabilizerSimulator::new(100);
233        for i in 0..100 {
234            sim.apply_gate(StabilizerGate::H(i))
235                .expect("Hadamard gate application should succeed");
236        }
237        for i in 0..99 {
238            sim.apply_gate(StabilizerGate::CNOT(i, i + 1))
239                .expect("CNOT gate application should succeed");
240        }
241        let stabs = sim.get_stabilizers();
242        assert_eq!(stabs.len(), 100);
243    }
244    #[test]
245    fn test_measurement_randomness() {
246        let mut sim = StabilizerSimulator::new(1);
247        sim.apply_gate(StabilizerGate::H(0))
248            .expect("Hadamard gate application should succeed");
249        let mut outcomes = Vec::new();
250        for _ in 0..10 {
251            let mut test_sim = StabilizerSimulator::new(1);
252            test_sim
253                .apply_gate(StabilizerGate::H(0))
254                .expect("Hadamard gate application should succeed");
255            let outcome = test_sim.measure(0).expect("Measurement should succeed");
256            outcomes.push(outcome);
257        }
258        let first = outcomes[0];
259        let all_same = outcomes.iter().all(|&x| x == first);
260        assert!(
261            !all_same || outcomes.len() < 5,
262            "Measurements should show randomness"
263        );
264    }
265    #[test]
266    fn test_measure_x_basis() {
267        let mut sim = StabilizerSimulator::new(1);
268        sim.apply_gate(StabilizerGate::H(0))
269            .expect("Hadamard gate application should succeed");
270        let outcome = sim
271            .tableau
272            .measure_x(0)
273            .expect("X-basis measurement should succeed");
274        assert!(!outcome);
275    }
276    #[test]
277    fn test_measure_y_basis() {
278        let mut sim = StabilizerSimulator::new(1);
279        sim.apply_gate(StabilizerGate::H(0)).unwrap();
280        sim.apply_gate(StabilizerGate::S(0)).unwrap();
281        let stabs = sim.get_stabilizers();
282        assert_eq!(stabs[0], "+Y");
283        let outcome = sim
284            .tableau
285            .measure_y(0)
286            .expect("Y-basis measurement should succeed");
287        assert!(!outcome);
288    }
289    #[test]
290    fn test_reset_operation() {
291        let mut sim = StabilizerSimulator::new(1);
292        sim.apply_gate(StabilizerGate::X(0))
293            .expect("X gate application should succeed");
294        sim.tableau.reset(0).expect("Reset should succeed");
295        let outcome = sim.measure(0).expect("Measurement should succeed");
296        assert!(!outcome);
297        let stabs = sim.get_stabilizers();
298        assert_eq!(stabs[0], "+Z");
299    }
300    #[test]
301    fn test_reset_from_superposition() {
302        let mut sim = StabilizerSimulator::new(1);
303        sim.apply_gate(StabilizerGate::H(0))
304            .expect("Hadamard gate application should succeed");
305        sim.tableau.reset(0).expect("Reset should succeed");
306        let outcome = sim.measure(0).expect("Measurement should succeed");
307        assert!(!outcome);
308    }
309    #[test]
310    fn test_x_y_measurements_commute() {
311        let mut sim = StabilizerSimulator::new(2);
312        sim.apply_gate(StabilizerGate::H(0)).unwrap();
313        sim.apply_gate(StabilizerGate::H(1)).unwrap();
314        sim.apply_gate(StabilizerGate::S(1)).unwrap();
315        let _outcome_x = sim.tableau.measure_x(0).unwrap();
316        let _outcome_y = sim.tableau.measure_y(1).unwrap();
317    }
318    #[test]
319    fn test_imaginary_phase_tracking() {
320        let mut tableau = StabilizerTableau::new(1);
321        tableau.apply_h(0).unwrap();
322        tableau.apply_s(0).unwrap();
323        let stabs = tableau.get_stabilizers();
324        assert_eq!(stabs[0], "+Y");
325    }
326    #[test]
327    fn test_imaginary_phase_with_s_dag() {
328        let mut tableau = StabilizerTableau::new(1);
329        tableau.apply_h(0).unwrap();
330        tableau.apply_s_dag(0).unwrap();
331        let stabs = tableau.get_stabilizers();
332        assert_eq!(stabs[0], "-Y");
333    }
334    #[test]
335    fn test_stim_format_identity() {
336        let mut tableau = StabilizerTableau::with_format(2, true);
337        let stabs = tableau.get_stabilizers();
338        assert_eq!(stabs[0], "+Z_");
339        assert_eq!(stabs[1], "+_Z");
340        tableau.apply_h(0).unwrap();
341        let stabs = tableau.get_stabilizers();
342        assert_eq!(stabs[0], "+X_");
343        assert_eq!(stabs[1], "+_Z");
344    }
345    #[test]
346    fn test_standard_format_identity() {
347        let tableau = StabilizerTableau::with_format(2, false);
348        let stabs = tableau.get_stabilizers();
349        assert_eq!(stabs[0], "+ZI");
350        assert_eq!(stabs[1], "+IZ");
351    }
352    #[test]
353    fn test_destabilizers_output() {
354        let mut tableau = StabilizerTableau::new(2);
355        let destabs = tableau.get_destabilizers();
356        assert_eq!(destabs[0], "+XI");
357        assert_eq!(destabs[1], "+IX");
358        tableau.apply_h(0).unwrap();
359        let destabs = tableau.get_destabilizers();
360        assert_eq!(destabs[0], "+ZI");
361        assert_eq!(destabs[1], "+IX");
362    }
363    #[test]
364    fn test_phase_constants() {
365        assert_eq!(phase::PLUS_ONE, 0);
366        assert_eq!(phase::PLUS_I, 1);
367        assert_eq!(phase::MINUS_ONE, 2);
368        assert_eq!(phase::MINUS_I, 3);
369    }
370    #[test]
371    fn test_phase_arithmetic() {
372        assert_eq!(
373            StabilizerTableau::negate_phase(phase::PLUS_ONE),
374            phase::MINUS_ONE
375        );
376        assert_eq!(
377            StabilizerTableau::negate_phase(phase::PLUS_I),
378            phase::MINUS_I
379        );
380        assert_eq!(
381            StabilizerTableau::negate_phase(phase::MINUS_ONE),
382            phase::PLUS_ONE
383        );
384        assert_eq!(
385            StabilizerTableau::negate_phase(phase::MINUS_I),
386            phase::PLUS_I
387        );
388        assert_eq!(
389            StabilizerTableau::multiply_by_i(phase::PLUS_ONE),
390            phase::PLUS_I
391        );
392        assert_eq!(
393            StabilizerTableau::multiply_by_i(phase::PLUS_I),
394            phase::MINUS_ONE
395        );
396        assert_eq!(
397            StabilizerTableau::multiply_by_i(phase::MINUS_ONE),
398            phase::MINUS_I
399        );
400        assert_eq!(
401            StabilizerTableau::multiply_by_i(phase::MINUS_I),
402            phase::PLUS_ONE
403        );
404        assert_eq!(
405            StabilizerTableau::multiply_by_minus_i(phase::PLUS_ONE),
406            phase::MINUS_I
407        );
408        assert_eq!(
409            StabilizerTableau::multiply_by_minus_i(phase::PLUS_I),
410            phase::PLUS_ONE
411        );
412        assert_eq!(
413            StabilizerTableau::multiply_by_minus_i(phase::MINUS_ONE),
414            phase::PLUS_I
415        );
416        assert_eq!(
417            StabilizerTableau::multiply_by_minus_i(phase::MINUS_I),
418            phase::MINUS_ONE
419        );
420    }
421    #[test]
422    fn test_y_gate_phase_tracking() {
423        let mut tableau = StabilizerTableau::new(1);
424        tableau.apply_y(0).unwrap();
425        let stabs = tableau.get_stabilizers();
426        assert_eq!(stabs[0], "-Z");
427    }
428    #[test]
429    fn test_sqrt_gates_produce_imaginary_phases() {
430        let mut tableau = StabilizerTableau::new(1);
431        tableau.apply_sqrt_y(0).unwrap();
432        let stabs = tableau.get_stabilizers();
433        assert_eq!(stabs[0], "-X");
434    }
435    #[test]
436    fn test_cz_gate_to_stabilizer_conversion() {
437        // Regression: the "CZ" arm previously returned None unconditionally,
438        // silently dropping every CZ gate during stabilizer simulation.
439        let mut circuit = Circuit::<2>::new();
440        circuit.cz(0, 1).expect("cz gate should be added");
441        let gates = circuit.gates();
442        assert_eq!(gates.len(), 1);
443        let converted = gate_to_stabilizer(&gates[0]);
444        assert!(
445            matches!(converted, Some(StabilizerGate::CZ(0, 1))),
446            "CZ must convert to StabilizerGate::CZ, got {converted:?}"
447        );
448    }
449    #[test]
450    fn test_stabilizer_run_applies_cz() {
451        // Full path through the Simulator trait: a Clifford circuit that
452        // contains a CZ must simulate successfully. Before the fix the CZ
453        // converted to None; with the stricter caller that None would now be
454        // reported as an unsupported-gate error, so `run` returning Ok proves
455        // the CZ is genuinely recognised and applied.
456        use crate::simulator::Simulator;
457        let mut circuit = Circuit::<2>::new();
458        circuit
459            .h(0)
460            .expect("h(0)")
461            .h(1)
462            .expect("h(1)")
463            .cz(0, 1)
464            .expect("cz(0,1)");
465        let mut sim = StabilizerSimulator::new(2);
466        assert!(
467            sim.run(&circuit).is_ok(),
468            "Clifford circuit containing a CZ must simulate successfully"
469        );
470
471        // At the tableau level the CZ must actually entangle the two qubits:
472        // H(0) H(1) CZ(0,1) prepares a cluster state with stabilizers XZ and ZX.
473        let mut tableau_sim = StabilizerSimulator::new(2);
474        tableau_sim
475            .apply_gate(StabilizerGate::H(0))
476            .expect("h(0) on tableau");
477        tableau_sim
478            .apply_gate(StabilizerGate::H(1))
479            .expect("h(1) on tableau");
480        tableau_sim
481            .apply_gate(StabilizerGate::CZ(0, 1))
482            .expect("cz(0,1) on tableau");
483        let stabs = tableau_sim.get_stabilizers();
484        assert!(
485            stabs.contains(&"+XZ".to_string()),
486            "cluster-state stabilizer +XZ missing, got {stabs:?}"
487        );
488        assert!(
489            stabs.contains(&"+ZX".to_string()),
490            "cluster-state stabilizer +ZX missing, got {stabs:?}"
491        );
492    }
493    #[test]
494    fn test_stabilizer_run_rejects_non_clifford() {
495        // Non-Clifford gates must surface an honest error rather than being
496        // silently skipped.
497        use crate::simulator::Simulator;
498        let mut circuit = Circuit::<1>::new();
499        circuit.t(0).expect("t gate should be added");
500        let mut sim = StabilizerSimulator::new(1);
501        let result = sim.run(&circuit);
502        assert!(
503            result.is_err(),
504            "running a non-Clifford T gate through the stabilizer simulator must error"
505        );
506    }
507}