quantrs2_sim/
stim_executor.rs

1//! Stim circuit executor with detector and observable support
2//!
3//! This module provides execution capabilities for Stim circuits, including:
4//! - Running circuits with the stabilizer simulator
5//! - Tracking measurement records for detector evaluation
6//! - Computing detector values from measurement parities
7//! - Computing observable values for logical qubit tracking
8//!
9//! ## Detector Model
10//!
11//! Detectors check parity conditions on measurement results. A detector triggers
12//! when the XOR of its referenced measurement results differs from the expected value.
13//!
14//! ## Observable Model
15//!
16//! Observables track logical qubit values through a circuit. They are computed
17//! as the XOR of specified measurement results.
18
19use crate::error::{Result, SimulatorError};
20use crate::stabilizer::{StabilizerSimulator, StabilizerTableau};
21use crate::stim_parser::{
22    MeasurementBasis, PauliTarget, PauliType, SingleQubitGateType, StimCircuit, StimInstruction,
23    TwoQubitGateType,
24};
25use scirs2_core::random::prelude::*;
26
27/// Record of a detector definition
28#[derive(Debug, Clone)]
29pub struct DetectorRecord {
30    /// Detector index
31    pub index: usize,
32    /// Coordinates for visualization/debugging
33    pub coordinates: Vec<f64>,
34    /// Measurement record indices this detector depends on (negative = relative)
35    pub record_targets: Vec<i32>,
36    /// Expected parity (usually false for no error)
37    pub expected_parity: bool,
38}
39
40/// Record of an observable definition
41#[derive(Debug, Clone)]
42pub struct ObservableRecord {
43    /// Observable index
44    pub index: usize,
45    /// Measurement record indices this observable depends on
46    pub record_targets: Vec<i32>,
47}
48
49/// Stim circuit executor with full error correction support
50#[derive(Debug, Clone)]
51pub struct StimExecutor {
52    /// Stabilizer simulator for circuit execution
53    simulator: StabilizerSimulator,
54    /// Measurement record (chronological order)
55    measurement_record: Vec<bool>,
56    /// Detector definitions
57    detectors: Vec<DetectorRecord>,
58    /// Observable definitions
59    observables: Vec<ObservableRecord>,
60    /// Error tracking state for E/ELSE_CORRELATED_ERROR chains
61    last_error_triggered: bool,
62    /// Number of qubits
63    num_qubits: usize,
64}
65
66impl StimExecutor {
67    /// Create a new Stim executor
68    #[must_use]
69    pub fn new(num_qubits: usize) -> Self {
70        Self {
71            simulator: StabilizerSimulator::new(num_qubits),
72            measurement_record: Vec::new(),
73            detectors: Vec::new(),
74            observables: Vec::new(),
75            last_error_triggered: false,
76            num_qubits,
77        }
78    }
79
80    /// Create from a Stim circuit (auto-determines qubit count)
81    #[must_use]
82    pub fn from_circuit(circuit: &StimCircuit) -> Self {
83        Self::new(circuit.num_qubits)
84    }
85
86    /// Execute a full Stim circuit
87    pub fn execute(&mut self, circuit: &StimCircuit) -> Result<ExecutionResult> {
88        // Reset state for new execution
89        self.measurement_record.clear();
90        self.detectors.clear();
91        self.observables.clear();
92        self.last_error_triggered = false;
93
94        // Execute each instruction
95        for instruction in &circuit.instructions {
96            self.execute_instruction(instruction)?;
97        }
98
99        // Compute detector and observable values
100        let detector_values = self.compute_detector_values();
101        let observable_values = self.compute_observable_values();
102
103        Ok(ExecutionResult {
104            measurement_record: self.measurement_record.clone(),
105            detector_values,
106            observable_values,
107            num_measurements: self.measurement_record.len(),
108            num_detectors: self.detectors.len(),
109            num_observables: self.observables.len(),
110        })
111    }
112
113    /// Execute a single instruction
114    fn execute_instruction(&mut self, instruction: &StimInstruction) -> Result<()> {
115        match instruction {
116            // Gates
117            StimInstruction::SingleQubitGate { gate_type, qubit } => {
118                self.execute_single_qubit_gate(*gate_type, *qubit)?;
119            }
120            StimInstruction::TwoQubitGate {
121                gate_type,
122                control,
123                target,
124            } => {
125                self.execute_two_qubit_gate(*gate_type, *control, *target)?;
126            }
127
128            // Measurements
129            StimInstruction::Measure { basis, qubits } => {
130                self.execute_measurement(*basis, qubits)?;
131            }
132
133            // Reset
134            StimInstruction::Reset { qubits } => {
135                self.execute_reset(qubits)?;
136            }
137
138            // Measure and reset
139            StimInstruction::MeasureReset { basis, qubits } => {
140                self.execute_measure_reset(*basis, qubits)?;
141            }
142
143            // Detectors and observables
144            StimInstruction::Detector {
145                coordinates,
146                record_targets,
147            } => {
148                self.process_detector(coordinates, record_targets)?;
149            }
150            StimInstruction::ObservableInclude {
151                observable_index,
152                record_targets,
153            } => {
154                self.process_observable(*observable_index, record_targets)?;
155            }
156
157            // Noise instructions (stochastic)
158            StimInstruction::XError {
159                probability,
160                qubits,
161            } => {
162                self.execute_x_error(*probability, qubits)?;
163            }
164            StimInstruction::YError {
165                probability,
166                qubits,
167            } => {
168                self.execute_y_error(*probability, qubits)?;
169            }
170            StimInstruction::ZError {
171                probability,
172                qubits,
173            } => {
174                self.execute_z_error(*probability, qubits)?;
175            }
176            StimInstruction::Depolarize1 {
177                probability,
178                qubits,
179            } => {
180                self.execute_depolarize1(*probability, qubits)?;
181            }
182            StimInstruction::Depolarize2 {
183                probability,
184                qubit_pairs,
185            } => {
186                self.execute_depolarize2(*probability, qubit_pairs)?;
187            }
188            StimInstruction::CorrelatedError {
189                probability,
190                targets,
191            } => {
192                self.execute_correlated_error(*probability, targets)?;
193            }
194            StimInstruction::ElseCorrelatedError {
195                probability,
196                targets,
197            } => {
198                self.execute_else_correlated_error(*probability, targets)?;
199            }
200            StimInstruction::PauliChannel1 { px, py, pz, qubits } => {
201                self.execute_pauli_channel_1(*px, *py, *pz, qubits)?;
202            }
203            StimInstruction::PauliChannel2 {
204                probabilities,
205                qubit_pairs,
206            } => {
207                self.execute_pauli_channel_2(probabilities, qubit_pairs)?;
208            }
209
210            // Metadata instructions (no execution effect)
211            StimInstruction::Comment(_)
212            | StimInstruction::Tick
213            | StimInstruction::ShiftCoords { .. }
214            | StimInstruction::QubitCoords { .. } => {}
215
216            // Repeat blocks
217            StimInstruction::Repeat {
218                count,
219                instructions,
220            } => {
221                for _ in 0..*count {
222                    for inst in instructions {
223                        self.execute_instruction(inst)?;
224                    }
225                }
226            }
227        }
228
229        Ok(())
230    }
231
232    /// Execute a single-qubit gate
233    fn execute_single_qubit_gate(
234        &mut self,
235        gate_type: SingleQubitGateType,
236        qubit: usize,
237    ) -> Result<()> {
238        let gate = gate_type.to_stabilizer_gate(qubit);
239        self.simulator.apply_gate(gate).map_err(|e| {
240            SimulatorError::InvalidOperation(format!("Gate execution failed: {:?}", e))
241        })
242    }
243
244    /// Execute a two-qubit gate
245    fn execute_two_qubit_gate(
246        &mut self,
247        gate_type: TwoQubitGateType,
248        control: usize,
249        target: usize,
250    ) -> Result<()> {
251        let gate = gate_type.to_stabilizer_gate(control, target);
252        self.simulator.apply_gate(gate).map_err(|e| {
253            SimulatorError::InvalidOperation(format!("Gate execution failed: {:?}", e))
254        })
255    }
256
257    /// Execute measurements
258    fn execute_measurement(&mut self, basis: MeasurementBasis, qubits: &[usize]) -> Result<()> {
259        for &qubit in qubits {
260            let outcome = match basis {
261                MeasurementBasis::Z => self.simulator.measure(qubit),
262                MeasurementBasis::X => self.simulator.tableau.measure_x(qubit),
263                MeasurementBasis::Y => self.simulator.tableau.measure_y(qubit),
264            }
265            .map_err(|e| {
266                SimulatorError::InvalidOperation(format!("Measurement failed: {:?}", e))
267            })?;
268
269            self.measurement_record.push(outcome);
270        }
271        Ok(())
272    }
273
274    /// Execute reset operations
275    fn execute_reset(&mut self, qubits: &[usize]) -> Result<()> {
276        for &qubit in qubits {
277            self.simulator
278                .tableau
279                .reset(qubit)
280                .map_err(|e| SimulatorError::InvalidOperation(format!("Reset failed: {:?}", e)))?;
281        }
282        Ok(())
283    }
284
285    /// Execute measure-and-reset operations
286    fn execute_measure_reset(&mut self, basis: MeasurementBasis, qubits: &[usize]) -> Result<()> {
287        self.execute_measurement(basis, qubits)?;
288        self.execute_reset(qubits)
289    }
290
291    /// Process a DETECTOR instruction
292    fn process_detector(&mut self, coordinates: &[f64], record_targets: &[i32]) -> Result<()> {
293        let detector = DetectorRecord {
294            index: self.detectors.len(),
295            coordinates: coordinates.to_vec(),
296            record_targets: record_targets.to_vec(),
297            expected_parity: false, // Default expectation is no error (parity = 0)
298        };
299        self.detectors.push(detector);
300        Ok(())
301    }
302
303    /// Process an OBSERVABLE_INCLUDE instruction
304    fn process_observable(
305        &mut self,
306        observable_index: usize,
307        record_targets: &[i32],
308    ) -> Result<()> {
309        // Find or create the observable record
310        while self.observables.len() <= observable_index {
311            self.observables.push(ObservableRecord {
312                index: self.observables.len(),
313                record_targets: Vec::new(),
314            });
315        }
316
317        // Add the record targets to the observable
318        self.observables[observable_index]
319            .record_targets
320            .extend_from_slice(record_targets);
321
322        Ok(())
323    }
324
325    /// Compute detector values from measurement record
326    pub fn compute_detector_values(&self) -> Vec<bool> {
327        self.detectors
328            .iter()
329            .map(|detector| {
330                let parity = self.compute_record_parity(&detector.record_targets);
331                // Detector fires when parity differs from expected
332                parity != detector.expected_parity
333            })
334            .collect()
335    }
336
337    /// Compute observable values from measurement record
338    pub fn compute_observable_values(&self) -> Vec<bool> {
339        self.observables
340            .iter()
341            .map(|observable| self.compute_record_parity(&observable.record_targets))
342            .collect()
343    }
344
345    /// Compute XOR parity of measurement results at given record indices
346    fn compute_record_parity(&self, record_targets: &[i32]) -> bool {
347        let record_len = self.measurement_record.len() as i32;
348
349        record_targets
350            .iter()
351            .filter_map(|&idx| {
352                // Convert relative (negative) to absolute index
353                let abs_idx = if idx < 0 {
354                    (record_len + idx) as usize
355                } else {
356                    idx as usize
357                };
358
359                self.measurement_record.get(abs_idx).copied()
360            })
361            .fold(false, |acc, x| acc ^ x)
362    }
363
364    // ==================== Error/Noise Execution ====================
365
366    /// Execute X errors with given probability
367    fn execute_x_error(&mut self, probability: f64, qubits: &[usize]) -> Result<()> {
368        let mut rng = thread_rng();
369        for &qubit in qubits {
370            if rng.gen_bool(probability) {
371                self.simulator
372                    .apply_gate(crate::stabilizer::StabilizerGate::X(qubit))
373                    .map_err(|e| {
374                        SimulatorError::InvalidOperation(format!("X error failed: {:?}", e))
375                    })?;
376            }
377        }
378        Ok(())
379    }
380
381    /// Execute Y errors with given probability
382    fn execute_y_error(&mut self, probability: f64, qubits: &[usize]) -> Result<()> {
383        let mut rng = thread_rng();
384        for &qubit in qubits {
385            if rng.gen_bool(probability) {
386                self.simulator
387                    .apply_gate(crate::stabilizer::StabilizerGate::Y(qubit))
388                    .map_err(|e| {
389                        SimulatorError::InvalidOperation(format!("Y error failed: {:?}", e))
390                    })?;
391            }
392        }
393        Ok(())
394    }
395
396    /// Execute Z errors with given probability
397    fn execute_z_error(&mut self, probability: f64, qubits: &[usize]) -> Result<()> {
398        let mut rng = thread_rng();
399        for &qubit in qubits {
400            if rng.gen_bool(probability) {
401                self.simulator
402                    .apply_gate(crate::stabilizer::StabilizerGate::Z(qubit))
403                    .map_err(|e| {
404                        SimulatorError::InvalidOperation(format!("Z error failed: {:?}", e))
405                    })?;
406            }
407        }
408        Ok(())
409    }
410
411    /// Execute single-qubit depolarizing noise
412    fn execute_depolarize1(&mut self, probability: f64, qubits: &[usize]) -> Result<()> {
413        let mut rng = thread_rng();
414        // Depolarizing: with prob p, apply X, Y, or Z uniformly at random
415        for &qubit in qubits {
416            if rng.gen_bool(probability) {
417                let error_type: u8 = rng.gen_range(0..3);
418                let gate = match error_type {
419                    0 => crate::stabilizer::StabilizerGate::X(qubit),
420                    1 => crate::stabilizer::StabilizerGate::Y(qubit),
421                    _ => crate::stabilizer::StabilizerGate::Z(qubit),
422                };
423                self.simulator.apply_gate(gate).map_err(|e| {
424                    SimulatorError::InvalidOperation(format!("Depolarizing error failed: {:?}", e))
425                })?;
426            }
427        }
428        Ok(())
429    }
430
431    /// Execute two-qubit depolarizing noise
432    fn execute_depolarize2(
433        &mut self,
434        probability: f64,
435        qubit_pairs: &[(usize, usize)],
436    ) -> Result<()> {
437        let mut rng = thread_rng();
438        // Two-qubit depolarizing: 15 non-identity Pauli pairs
439        for &(q1, q2) in qubit_pairs {
440            if rng.gen_bool(probability) {
441                // Select one of 15 non-identity two-qubit Paulis
442                let error_idx: u8 = rng.gen_range(0..15);
443                let (pauli1, pauli2) = Self::two_qubit_pauli_from_index(error_idx);
444                self.apply_pauli_to_qubit(pauli1, q1)?;
445                self.apply_pauli_to_qubit(pauli2, q2)?;
446            }
447        }
448        Ok(())
449    }
450
451    /// Execute a correlated error (E instruction)
452    fn execute_correlated_error(
453        &mut self,
454        probability: f64,
455        targets: &[PauliTarget],
456    ) -> Result<()> {
457        let mut rng = thread_rng();
458        self.last_error_triggered = rng.gen_bool(probability);
459
460        if self.last_error_triggered {
461            for target in targets {
462                self.apply_pauli_to_qubit(target.pauli, target.qubit)?;
463            }
464        }
465        Ok(())
466    }
467
468    /// Execute an else-correlated error (ELSE_CORRELATED_ERROR instruction)
469    fn execute_else_correlated_error(
470        &mut self,
471        probability: f64,
472        targets: &[PauliTarget],
473    ) -> Result<()> {
474        // Only consider triggering if the previous E did NOT trigger
475        if !self.last_error_triggered {
476            let mut rng = thread_rng();
477            self.last_error_triggered = rng.gen_bool(probability);
478
479            if self.last_error_triggered {
480                for target in targets {
481                    self.apply_pauli_to_qubit(target.pauli, target.qubit)?;
482                }
483            }
484        }
485        // If previous error triggered, this one doesn't run (else branch not taken)
486        Ok(())
487    }
488
489    /// Execute Pauli channel (single qubit)
490    fn execute_pauli_channel_1(
491        &mut self,
492        px: f64,
493        py: f64,
494        pz: f64,
495        qubits: &[usize],
496    ) -> Result<()> {
497        let mut rng = thread_rng();
498        for &qubit in qubits {
499            let r: f64 = rng.gen();
500            if r < px {
501                self.simulator
502                    .apply_gate(crate::stabilizer::StabilizerGate::X(qubit))
503                    .map_err(|e| {
504                        SimulatorError::InvalidOperation(format!("Pauli channel failed: {:?}", e))
505                    })?;
506            } else if r < px + py {
507                self.simulator
508                    .apply_gate(crate::stabilizer::StabilizerGate::Y(qubit))
509                    .map_err(|e| {
510                        SimulatorError::InvalidOperation(format!("Pauli channel failed: {:?}", e))
511                    })?;
512            } else if r < px + py + pz {
513                self.simulator
514                    .apply_gate(crate::stabilizer::StabilizerGate::Z(qubit))
515                    .map_err(|e| {
516                        SimulatorError::InvalidOperation(format!("Pauli channel failed: {:?}", e))
517                    })?;
518            }
519            // Otherwise, identity (no error)
520        }
521        Ok(())
522    }
523
524    /// Execute Pauli channel (two qubits)
525    fn execute_pauli_channel_2(
526        &mut self,
527        probabilities: &[f64],
528        qubit_pairs: &[(usize, usize)],
529    ) -> Result<()> {
530        if probabilities.len() != 15 {
531            return Err(SimulatorError::InvalidOperation(
532                "PAULI_CHANNEL_2 requires 15 probabilities".to_string(),
533            ));
534        }
535
536        let mut rng = thread_rng();
537        for &(q1, q2) in qubit_pairs {
538            let r: f64 = rng.gen();
539            let mut cumulative = 0.0;
540
541            for (i, &p) in probabilities.iter().enumerate() {
542                cumulative += p;
543                if r < cumulative {
544                    let (pauli1, pauli2) = Self::two_qubit_pauli_from_index(i as u8);
545                    self.apply_pauli_to_qubit(pauli1, q1)?;
546                    self.apply_pauli_to_qubit(pauli2, q2)?;
547                    break;
548                }
549            }
550        }
551        Ok(())
552    }
553
554    /// Apply a Pauli operator to a qubit
555    fn apply_pauli_to_qubit(&mut self, pauli: PauliType, qubit: usize) -> Result<()> {
556        let gate = match pauli {
557            PauliType::I => return Ok(()), // Identity, do nothing
558            PauliType::X => crate::stabilizer::StabilizerGate::X(qubit),
559            PauliType::Y => crate::stabilizer::StabilizerGate::Y(qubit),
560            PauliType::Z => crate::stabilizer::StabilizerGate::Z(qubit),
561        };
562        self.simulator.apply_gate(gate).map_err(|e| {
563            SimulatorError::InvalidOperation(format!("Pauli application failed: {:?}", e))
564        })
565    }
566
567    /// Convert index (0-14) to two-qubit Pauli pair (excluding II)
568    /// Order: IX, IY, IZ, XI, XX, XY, XZ, YI, YX, YY, YZ, ZI, ZX, ZY, ZZ
569    fn two_qubit_pauli_from_index(idx: u8) -> (PauliType, PauliType) {
570        // Mapping: 0=I, 1=X, 2=Y, 3=Z
571        // Index maps to (p1, p2) excluding (0,0)
572        let idx = idx + 1; // Skip II
573        let p1 = idx / 4;
574        let p2 = idx % 4;
575
576        let to_pauli = |i| match i {
577            0 => PauliType::I,
578            1 => PauliType::X,
579            2 => PauliType::Y,
580            _ => PauliType::Z,
581        };
582
583        (to_pauli(p1), to_pauli(p2))
584    }
585
586    // ==================== Accessors ====================
587
588    /// Get the measurement record
589    #[must_use]
590    pub fn measurement_record(&self) -> &[bool] {
591        &self.measurement_record
592    }
593
594    /// Get detector definitions
595    #[must_use]
596    pub fn detectors(&self) -> &[DetectorRecord] {
597        &self.detectors
598    }
599
600    /// Get observable definitions
601    #[must_use]
602    pub fn observables(&self) -> &[ObservableRecord] {
603        &self.observables
604    }
605
606    /// Get the stabilizer simulator
607    #[must_use]
608    pub fn simulator(&self) -> &StabilizerSimulator {
609        &self.simulator
610    }
611
612    /// Get the current stabilizers
613    #[must_use]
614    pub fn get_stabilizers(&self) -> Vec<String> {
615        self.simulator.get_stabilizers()
616    }
617
618    /// Get the number of qubits
619    #[must_use]
620    pub fn num_qubits(&self) -> usize {
621        self.num_qubits
622    }
623}
624
625/// Result of executing a Stim circuit
626#[derive(Debug, Clone)]
627pub struct ExecutionResult {
628    /// Full measurement record
629    pub measurement_record: Vec<bool>,
630    /// Detector values (true = detector fired)
631    pub detector_values: Vec<bool>,
632    /// Observable values
633    pub observable_values: Vec<bool>,
634    /// Total number of measurements
635    pub num_measurements: usize,
636    /// Total number of detectors
637    pub num_detectors: usize,
638    /// Total number of observables
639    pub num_observables: usize,
640}
641
642impl ExecutionResult {
643    /// Check if any detector fired (indicating an error was detected)
644    #[must_use]
645    pub fn any_detector_fired(&self) -> bool {
646        self.detector_values.iter().any(|&x| x)
647    }
648
649    /// Count how many detectors fired
650    #[must_use]
651    pub fn detector_fire_count(&self) -> usize {
652        self.detector_values.iter().filter(|&&x| x).count()
653    }
654
655    /// Get a bit-packed representation of measurements
656    #[must_use]
657    pub fn packed_measurements(&self) -> Vec<u8> {
658        self.measurement_record
659            .chunks(8)
660            .map(|chunk| {
661                let mut byte = 0u8;
662                for (i, &bit) in chunk.iter().enumerate() {
663                    if bit {
664                        byte |= 1 << i;
665                    }
666                }
667                byte
668            })
669            .collect()
670    }
671
672    /// Get a bit-packed representation of detector values
673    #[must_use]
674    pub fn packed_detectors(&self) -> Vec<u8> {
675        self.detector_values
676            .chunks(8)
677            .map(|chunk| {
678                let mut byte = 0u8;
679                for (i, &bit) in chunk.iter().enumerate() {
680                    if bit {
681                        byte |= 1 << i;
682                    }
683                }
684                byte
685            })
686            .collect()
687    }
688}
689
690#[cfg(test)]
691mod tests {
692    use super::*;
693
694    #[test]
695    fn test_basic_execution() {
696        let circuit_str = r#"
697            H 0
698            CNOT 0 1
699            M 0 1
700        "#;
701
702        let circuit = StimCircuit::from_str(circuit_str).unwrap();
703        let mut executor = StimExecutor::from_circuit(&circuit);
704        let result = executor.execute(&circuit).unwrap();
705
706        assert_eq!(result.num_measurements, 2);
707        // Bell state: measurements should be correlated
708        assert_eq!(result.measurement_record[0], result.measurement_record[1]);
709    }
710
711    #[test]
712    fn test_detector_execution() {
713        // Deterministic test: prepare |00⟩ state, measure both
714        // M0=0, M1=0, XOR=0, detector should not fire
715        let circuit_str = r#"
716            M 0 1
717            DETECTOR rec[-1] rec[-2]
718        "#;
719
720        let circuit = StimCircuit::from_str(circuit_str).unwrap();
721        let mut executor = StimExecutor::from_circuit(&circuit);
722        let result = executor.execute(&circuit).unwrap();
723
724        assert_eq!(result.num_detectors, 1);
725        // |00⟩ state: M0=0, M1=0, XOR=0, detector should NOT fire
726        assert!(!result.measurement_record[0]);
727        assert!(!result.measurement_record[1]);
728        assert!(!result.detector_values[0]);
729    }
730
731    #[test]
732    fn test_detector_with_error() {
733        // Test detector firing when error is introduced
734        // Apply X to qubit 0, measure both: M0=1, M1=0, XOR=1
735        let circuit_str = r#"
736            X 0
737            M 0 1
738            DETECTOR rec[-1] rec[-2]
739        "#;
740
741        let circuit = StimCircuit::from_str(circuit_str).unwrap();
742        let mut executor = StimExecutor::from_circuit(&circuit);
743        let result = executor.execute(&circuit).unwrap();
744
745        assert_eq!(result.num_detectors, 1);
746        // |10⟩ state: M0=1, M1=0, XOR=1, detector SHOULD fire
747        assert!(result.measurement_record[0]);
748        assert!(!result.measurement_record[1]);
749        assert!(result.detector_values[0]);
750    }
751
752    #[test]
753    fn test_observable_execution() {
754        let circuit_str = r#"
755            H 0
756            CNOT 0 1
757            M 0 1
758            OBSERVABLE_INCLUDE(0) rec[-1]
759            OBSERVABLE_INCLUDE(1) rec[-2]
760        "#;
761
762        let circuit = StimCircuit::from_str(circuit_str).unwrap();
763        let mut executor = StimExecutor::from_circuit(&circuit);
764        let result = executor.execute(&circuit).unwrap();
765
766        assert_eq!(result.num_observables, 2);
767    }
768
769    #[test]
770    fn test_measure_reset() {
771        let circuit_str = r#"
772            H 0
773            MR 0
774            M 0
775        "#;
776
777        let circuit = StimCircuit::from_str(circuit_str).unwrap();
778        let mut executor = StimExecutor::from_circuit(&circuit);
779        let result = executor.execute(&circuit).unwrap();
780
781        assert_eq!(result.num_measurements, 2);
782        // After reset, second measurement should be 0
783        assert!(!result.measurement_record[1]);
784    }
785
786    #[test]
787    fn test_noise_execution() {
788        // Note: This test is probabilistic, but with p=1.0 it's deterministic
789        let circuit_str = r#"
790            X_ERROR(1.0) 0
791            M 0
792        "#;
793
794        let circuit = StimCircuit::from_str(circuit_str).unwrap();
795        let mut executor = StimExecutor::from_circuit(&circuit);
796        let result = executor.execute(&circuit).unwrap();
797
798        // X error on |0⟩ gives |1⟩, so measurement should be 1
799        assert!(result.measurement_record[0]);
800    }
801
802    #[test]
803    fn test_correlated_error() {
804        // E with probability 1.0 should always trigger
805        let circuit_str = r#"
806            E(1.0) X0 X1
807            M 0 1
808        "#;
809
810        let circuit = StimCircuit::from_str(circuit_str).unwrap();
811        let mut executor = StimExecutor::from_circuit(&circuit);
812        let result = executor.execute(&circuit).unwrap();
813
814        // Both qubits should be flipped
815        assert!(result.measurement_record[0]);
816        assert!(result.measurement_record[1]);
817    }
818
819    #[test]
820    fn test_else_correlated_error() {
821        // E(1.0) always triggers, so ELSE should not
822        let circuit_str = r#"
823            E(1.0) X0
824            ELSE_CORRELATED_ERROR(1.0) X1
825            M 0 1
826        "#;
827
828        let circuit = StimCircuit::from_str(circuit_str).unwrap();
829        let mut executor = StimExecutor::from_circuit(&circuit);
830        let result = executor.execute(&circuit).unwrap();
831
832        // E triggers, ELSE doesn't
833        assert!(result.measurement_record[0]); // X0 applied
834        assert!(!result.measurement_record[1]); // X1 NOT applied
835    }
836
837    #[test]
838    fn test_packed_measurements() {
839        let mut result = ExecutionResult {
840            measurement_record: vec![true, false, true, true, false, false, true, false, true],
841            detector_values: vec![],
842            observable_values: vec![],
843            num_measurements: 9,
844            num_detectors: 0,
845            num_observables: 0,
846        };
847
848        let packed = result.packed_measurements();
849        // First 8 bits: 1,0,1,1,0,0,1,0 = 0b01001101 = 77
850        // 9th bit: 1 = 0b00000001 = 1
851        assert_eq!(packed[0], 0b01001101);
852        assert_eq!(packed[1], 0b00000001);
853    }
854
855    #[test]
856    fn test_detector_fire_count() {
857        let result = ExecutionResult {
858            measurement_record: vec![],
859            detector_values: vec![true, false, true, true, false],
860            observable_values: vec![],
861            num_measurements: 0,
862            num_detectors: 5,
863            num_observables: 0,
864        };
865
866        assert!(result.any_detector_fired());
867        assert_eq!(result.detector_fire_count(), 3);
868    }
869}