quantrs2_sim/
stim_parser.rs

1//! Stim circuit format parser
2//!
3//! This module provides parsing capabilities for Stim circuit files (.stim).
4//! Stim is a fast Clifford circuit simulator with a simple text-based circuit format.
5//!
6//! ## Supported Instructions
7//!
8//! ### Single-Qubit Clifford Gates
9//! - `H q`: Hadamard gate
10//! - `S q`: S gate (√Z)
11//! - `S_DAG q`: S† gate
12//! - `SQRT_X q`: √X gate
13//! - `SQRT_X_DAG q`: √X† gate
14//! - `SQRT_Y q`: √Y gate
15//! - `SQRT_Y_DAG q`: √Y† gate
16//! - `X q`, `Y q`, `Z q`: Pauli gates
17//!
18//! ### Two-Qubit Clifford Gates
19//! - `CNOT q1 q2`: Controlled-NOT
20//! - `CZ q1 q2`: Controlled-Z
21//! - `CY q1 q2`: Controlled-Y
22//! - `SWAP q1 q2`: SWAP gate
23//!
24//! ### Measurements
25//! - `M q1 q2 ...`: Z-basis measurement
26//! - `MX q1 q2 ...`: X-basis measurement
27//! - `MY q1 q2 ...`: Y-basis measurement
28//!
29//! ### Reset
30//! - `R q1 q2 ...`: Reset to |0⟩
31//!
32//! ## Example
33//!
34//! ```text
35//! # Bell state preparation
36//! H 0
37//! CNOT 0 1
38//! M 0 1
39//! ```
40
41use crate::error::{Result, SimulatorError};
42use crate::stabilizer::StabilizerGate;
43use std::str::FromStr;
44
45/// Stim instruction type
46#[derive(Debug, Clone, PartialEq)]
47pub enum StimInstruction {
48    /// Single-qubit gate
49    SingleQubitGate {
50        gate_type: SingleQubitGateType,
51        qubit: usize,
52    },
53    /// Two-qubit gate
54    TwoQubitGate {
55        gate_type: TwoQubitGateType,
56        control: usize,
57        target: usize,
58    },
59    /// Measurement
60    Measure {
61        basis: MeasurementBasis,
62        qubits: Vec<usize>,
63    },
64    /// Reset operation
65    Reset { qubits: Vec<usize> },
66    /// Comment (ignored during execution)
67    Comment(String),
68    /// Barrier/tick (for timing information)
69    Tick,
70
71    // ===== Advanced Error Correction Instructions =====
72    /// Detector annotation for error correction
73    /// DETECTOR [coords...] rec[-1] rec[-2] ...
74    Detector {
75        coordinates: Vec<f64>,
76        record_targets: Vec<i32>,
77    },
78
79    /// Observable annotation for logical observables
80    /// OBSERVABLE_INCLUDE(k) rec[-1] rec[-2] ...
81    ObservableInclude {
82        observable_index: usize,
83        record_targets: Vec<i32>,
84    },
85
86    // ===== Measurement and Reset Instructions =====
87    /// Measure and reset to |0⟩
88    /// MR q1 q2 ...
89    MeasureReset {
90        basis: MeasurementBasis,
91        qubits: Vec<usize>,
92    },
93
94    // ===== Noise Instructions =====
95    /// Single-qubit depolarizing noise
96    /// DEPOLARIZE1(p) q1 q2 ...
97    Depolarize1 {
98        probability: f64,
99        qubits: Vec<usize>,
100    },
101
102    /// Two-qubit depolarizing noise
103    /// DEPOLARIZE2(p) q1 q2 q3 q4 ...
104    Depolarize2 {
105        probability: f64,
106        qubit_pairs: Vec<(usize, usize)>,
107    },
108
109    /// X error (bit flip)
110    /// X_ERROR(p) q1 q2 ...
111    XError {
112        probability: f64,
113        qubits: Vec<usize>,
114    },
115
116    /// Y error (bit-phase flip)
117    /// Y_ERROR(p) q1 q2 ...
118    YError {
119        probability: f64,
120        qubits: Vec<usize>,
121    },
122
123    /// Z error (phase flip)
124    /// Z_ERROR(p) q1 q2 ...
125    ZError {
126        probability: f64,
127        qubits: Vec<usize>,
128    },
129
130    /// Pauli channel (single qubit)
131    /// PAULI_CHANNEL_1(px, py, pz) q1 q2 ...
132    PauliChannel1 {
133        px: f64,
134        py: f64,
135        pz: f64,
136        qubits: Vec<usize>,
137    },
138
139    /// Pauli channel (two qubits)
140    /// PAULI_CHANNEL_2(p_IX, p_IY, ..., p_ZZ) q1 q2
141    PauliChannel2 {
142        probabilities: Vec<f64>, // 15 probabilities for 2-qubit Paulis
143        qubit_pairs: Vec<(usize, usize)>,
144    },
145
146    /// Correlated error
147    /// CORRELATED_ERROR(p) X1 Y2 Z3 ... or shorthand: E(p) X1 Y2 Z3 ...
148    CorrelatedError {
149        probability: f64,
150        targets: Vec<PauliTarget>,
151    },
152
153    /// Else correlated error (conditional on previous E not triggering)
154    /// ELSE_CORRELATED_ERROR(p) X1 Y2 Z3 ...
155    ElseCorrelatedError {
156        probability: f64,
157        targets: Vec<PauliTarget>,
158    },
159
160    // ===== Coordinate and Metadata Instructions =====
161    /// Shift coordinate system
162    /// SHIFT_COORDS [dx, dy, dz, ...]
163    ShiftCoords { shifts: Vec<f64> },
164
165    /// Qubit coordinates
166    /// QUBIT_COORDS(q) x y z ...
167    QubitCoords { qubit: usize, coordinates: Vec<f64> },
168
169    /// Repeat block
170    /// REPEAT N { ... }
171    Repeat {
172        count: usize,
173        instructions: Vec<StimInstruction>,
174    },
175}
176
177/// Single-qubit Clifford gate types
178#[derive(Debug, Clone, Copy, PartialEq, Eq)]
179pub enum SingleQubitGateType {
180    H,
181    S,
182    SDag,
183    SqrtX,
184    SqrtXDag,
185    SqrtY,
186    SqrtYDag,
187    X,
188    Y,
189    Z,
190}
191
192/// Two-qubit Clifford gate types
193#[derive(Debug, Clone, Copy, PartialEq, Eq)]
194pub enum TwoQubitGateType {
195    CNOT,
196    CZ,
197    CY,
198    SWAP,
199}
200
201/// Pauli target for correlated errors
202#[derive(Debug, Clone, Copy, PartialEq, Eq)]
203pub struct PauliTarget {
204    pub pauli: PauliType,
205    pub qubit: usize,
206}
207
208/// Pauli operator type
209#[derive(Debug, Clone, Copy, PartialEq, Eq)]
210pub enum PauliType {
211    I,
212    X,
213    Y,
214    Z,
215}
216
217/// Measurement basis
218#[derive(Debug, Clone, Copy, PartialEq, Eq)]
219pub enum MeasurementBasis {
220    Z,
221    X,
222    Y,
223}
224
225impl SingleQubitGateType {
226    /// Convert to stabilizer gate
227    pub fn to_stabilizer_gate(self, qubit: usize) -> StabilizerGate {
228        match self {
229            Self::H => StabilizerGate::H(qubit),
230            Self::S => StabilizerGate::S(qubit),
231            Self::SDag => StabilizerGate::SDag(qubit),
232            Self::SqrtX => StabilizerGate::SqrtX(qubit),
233            Self::SqrtXDag => StabilizerGate::SqrtXDag(qubit),
234            Self::SqrtY => StabilizerGate::SqrtY(qubit),
235            Self::SqrtYDag => StabilizerGate::SqrtYDag(qubit),
236            Self::X => StabilizerGate::X(qubit),
237            Self::Y => StabilizerGate::Y(qubit),
238            Self::Z => StabilizerGate::Z(qubit),
239        }
240    }
241}
242
243impl TwoQubitGateType {
244    /// Convert to stabilizer gate
245    pub fn to_stabilizer_gate(self, control: usize, target: usize) -> StabilizerGate {
246        match self {
247            Self::CNOT => StabilizerGate::CNOT(control, target),
248            Self::CZ => StabilizerGate::CZ(control, target),
249            Self::CY => StabilizerGate::CY(control, target),
250            Self::SWAP => StabilizerGate::SWAP(control, target),
251        }
252    }
253}
254
255/// Stim circuit representation
256#[derive(Debug, Clone)]
257pub struct StimCircuit {
258    /// Instructions in the circuit
259    pub instructions: Vec<StimInstruction>,
260    /// Number of qubits (inferred from max qubit index)
261    pub num_qubits: usize,
262    /// Metadata/comments
263    pub metadata: Vec<String>,
264}
265
266impl StimCircuit {
267    /// Create new empty Stim circuit
268    pub fn new() -> Self {
269        Self {
270            instructions: Vec::new(),
271            num_qubits: 0,
272            metadata: Vec::new(),
273        }
274    }
275
276    /// Add an instruction
277    pub fn add_instruction(&mut self, instruction: StimInstruction) {
278        // Update num_qubits based on instruction
279        match &instruction {
280            StimInstruction::SingleQubitGate { qubit, .. } => {
281                self.num_qubits = self.num_qubits.max(*qubit + 1);
282            }
283            StimInstruction::TwoQubitGate {
284                control, target, ..
285            } => {
286                self.num_qubits = self.num_qubits.max(*control + 1).max(*target + 1);
287            }
288            StimInstruction::Measure { qubits, .. } | StimInstruction::Reset { qubits } => {
289                if let Some(&max_qubit) = qubits.iter().max() {
290                    self.num_qubits = self.num_qubits.max(max_qubit + 1);
291                }
292            }
293            _ => {}
294        }
295
296        self.instructions.push(instruction);
297    }
298
299    /// Parse from Stim format string
300    pub fn from_str(s: &str) -> Result<Self> {
301        let mut circuit = Self::new();
302
303        for (line_num, line) in s.lines().enumerate() {
304            let line = line.trim();
305
306            // Skip empty lines
307            if line.is_empty() {
308                continue;
309            }
310
311            // Handle comments
312            if let Some(stripped) = line.strip_prefix('#') {
313                circuit.metadata.push(stripped.trim().to_string());
314                circuit.add_instruction(StimInstruction::Comment(stripped.trim().to_string()));
315                continue;
316            }
317
318            // Parse instruction
319            match parse_instruction(line) {
320                Ok(instruction) => circuit.add_instruction(instruction),
321                Err(e) => {
322                    return Err(SimulatorError::InvalidOperation(format!(
323                        "Line {}: {}",
324                        line_num + 1,
325                        e
326                    )));
327                }
328            }
329        }
330
331        Ok(circuit)
332    }
333
334    /// Get all gates (excluding measurements, resets, comments)
335    pub fn gates(&self) -> Vec<StabilizerGate> {
336        self.instructions
337            .iter()
338            .filter_map(|inst| match inst {
339                StimInstruction::SingleQubitGate { gate_type, qubit } => {
340                    Some(gate_type.to_stabilizer_gate(*qubit))
341                }
342                StimInstruction::TwoQubitGate {
343                    gate_type,
344                    control,
345                    target,
346                } => Some(gate_type.to_stabilizer_gate(*control, *target)),
347                _ => None,
348            })
349            .collect()
350    }
351
352    /// Get measurement instructions
353    pub fn measurements(&self) -> Vec<(MeasurementBasis, Vec<usize>)> {
354        self.instructions
355            .iter()
356            .filter_map(|inst| match inst {
357                StimInstruction::Measure { basis, qubits } => Some((*basis, qubits.clone())),
358                _ => None,
359            })
360            .collect()
361    }
362
363    /// Get reset instructions
364    pub fn resets(&self) -> Vec<Vec<usize>> {
365        self.instructions
366            .iter()
367            .filter_map(|inst| match inst {
368                StimInstruction::Reset { qubits } => Some(qubits.clone()),
369                _ => None,
370            })
371            .collect()
372    }
373
374    /// Convert to Stim format string
375    pub fn to_stim_string(&self) -> String {
376        let mut output = String::new();
377
378        for instruction in &self.instructions {
379            match instruction {
380                StimInstruction::SingleQubitGate { gate_type, qubit } => {
381                    let gate_name = match gate_type {
382                        SingleQubitGateType::H => "H",
383                        SingleQubitGateType::S => "S",
384                        SingleQubitGateType::SDag => "S_DAG",
385                        SingleQubitGateType::SqrtX => "SQRT_X",
386                        SingleQubitGateType::SqrtXDag => "SQRT_X_DAG",
387                        SingleQubitGateType::SqrtY => "SQRT_Y",
388                        SingleQubitGateType::SqrtYDag => "SQRT_Y_DAG",
389                        SingleQubitGateType::X => "X",
390                        SingleQubitGateType::Y => "Y",
391                        SingleQubitGateType::Z => "Z",
392                    };
393                    output.push_str(&format!("{} {}\n", gate_name, qubit));
394                }
395                StimInstruction::TwoQubitGate {
396                    gate_type,
397                    control,
398                    target,
399                } => {
400                    let gate_name = match gate_type {
401                        TwoQubitGateType::CNOT => "CNOT",
402                        TwoQubitGateType::CZ => "CZ",
403                        TwoQubitGateType::CY => "CY",
404                        TwoQubitGateType::SWAP => "SWAP",
405                    };
406                    output.push_str(&format!("{} {} {}\n", gate_name, control, target));
407                }
408                StimInstruction::Measure { basis, qubits } => {
409                    let measure_name = match basis {
410                        MeasurementBasis::Z => "M",
411                        MeasurementBasis::X => "MX",
412                        MeasurementBasis::Y => "MY",
413                    };
414                    output.push_str(&format!(
415                        "{} {}\n",
416                        measure_name,
417                        qubits
418                            .iter()
419                            .map(|q| q.to_string())
420                            .collect::<Vec<_>>()
421                            .join(" ")
422                    ));
423                }
424                StimInstruction::Reset { qubits } => {
425                    output.push_str(&format!(
426                        "R {}\n",
427                        qubits
428                            .iter()
429                            .map(|q| q.to_string())
430                            .collect::<Vec<_>>()
431                            .join(" ")
432                    ));
433                }
434                StimInstruction::Comment(comment) => {
435                    output.push_str(&format!("# {}\n", comment));
436                }
437                StimInstruction::Tick => {
438                    output.push_str("TICK\n");
439                }
440
441                // Advanced error correction
442                StimInstruction::Detector {
443                    coordinates,
444                    record_targets,
445                } => {
446                    output.push_str("DETECTOR");
447                    for coord in coordinates {
448                        output.push_str(&format!(" {}", coord));
449                    }
450                    for target in record_targets {
451                        output.push_str(&format!(" rec[{}]", target));
452                    }
453                    output.push('\n');
454                }
455                StimInstruction::ObservableInclude {
456                    observable_index,
457                    record_targets,
458                } => {
459                    output.push_str(&format!("OBSERVABLE_INCLUDE({})", observable_index));
460                    for target in record_targets {
461                        output.push_str(&format!(" rec[{}]", target));
462                    }
463                    output.push('\n');
464                }
465
466                // Measure and reset
467                StimInstruction::MeasureReset { basis, qubits } => {
468                    let measure_name = match basis {
469                        MeasurementBasis::Z => "MR",
470                        MeasurementBasis::X => "MRX",
471                        MeasurementBasis::Y => "MRY",
472                    };
473                    output.push_str(&format!(
474                        "{} {}\n",
475                        measure_name,
476                        qubits
477                            .iter()
478                            .map(|q| q.to_string())
479                            .collect::<Vec<_>>()
480                            .join(" ")
481                    ));
482                }
483
484                // Noise instructions
485                StimInstruction::Depolarize1 {
486                    probability,
487                    qubits,
488                } => {
489                    output.push_str(&format!(
490                        "DEPOLARIZE1({}) {}\n",
491                        probability,
492                        qubits
493                            .iter()
494                            .map(|q| q.to_string())
495                            .collect::<Vec<_>>()
496                            .join(" ")
497                    ));
498                }
499                StimInstruction::Depolarize2 {
500                    probability,
501                    qubit_pairs,
502                } => {
503                    output.push_str(&format!("DEPOLARIZE2({})", probability));
504                    for (q1, q2) in qubit_pairs {
505                        output.push_str(&format!(" {} {}", q1, q2));
506                    }
507                    output.push('\n');
508                }
509                StimInstruction::XError {
510                    probability,
511                    qubits,
512                } => {
513                    output.push_str(&format!(
514                        "X_ERROR({}) {}\n",
515                        probability,
516                        qubits
517                            .iter()
518                            .map(|q| q.to_string())
519                            .collect::<Vec<_>>()
520                            .join(" ")
521                    ));
522                }
523                StimInstruction::YError {
524                    probability,
525                    qubits,
526                } => {
527                    output.push_str(&format!(
528                        "Y_ERROR({}) {}\n",
529                        probability,
530                        qubits
531                            .iter()
532                            .map(|q| q.to_string())
533                            .collect::<Vec<_>>()
534                            .join(" ")
535                    ));
536                }
537                StimInstruction::ZError {
538                    probability,
539                    qubits,
540                } => {
541                    output.push_str(&format!(
542                        "Z_ERROR({}) {}\n",
543                        probability,
544                        qubits
545                            .iter()
546                            .map(|q| q.to_string())
547                            .collect::<Vec<_>>()
548                            .join(" ")
549                    ));
550                }
551                StimInstruction::PauliChannel1 { px, py, pz, qubits } => {
552                    output.push_str(&format!(
553                        "PAULI_CHANNEL_1({},{},{}) {}\n",
554                        px,
555                        py,
556                        pz,
557                        qubits
558                            .iter()
559                            .map(|q| q.to_string())
560                            .collect::<Vec<_>>()
561                            .join(" ")
562                    ));
563                }
564                StimInstruction::PauliChannel2 {
565                    probabilities,
566                    qubit_pairs,
567                } => {
568                    output.push_str(&format!(
569                        "PAULI_CHANNEL_2({})",
570                        probabilities
571                            .iter()
572                            .map(|p| p.to_string())
573                            .collect::<Vec<_>>()
574                            .join(",")
575                    ));
576                    for (q1, q2) in qubit_pairs {
577                        output.push_str(&format!(" {} {}", q1, q2));
578                    }
579                    output.push('\n');
580                }
581                StimInstruction::CorrelatedError {
582                    probability,
583                    targets,
584                } => {
585                    output.push_str(&format!("E({})", probability));
586                    for target in targets {
587                        let pauli_char = match target.pauli {
588                            PauliType::I => 'I',
589                            PauliType::X => 'X',
590                            PauliType::Y => 'Y',
591                            PauliType::Z => 'Z',
592                        };
593                        output.push_str(&format!(" {}{}", pauli_char, target.qubit));
594                    }
595                    output.push('\n');
596                }
597                StimInstruction::ElseCorrelatedError {
598                    probability,
599                    targets,
600                } => {
601                    output.push_str(&format!("ELSE_CORRELATED_ERROR({})", probability));
602                    for target in targets {
603                        let pauli_char = match target.pauli {
604                            PauliType::I => 'I',
605                            PauliType::X => 'X',
606                            PauliType::Y => 'Y',
607                            PauliType::Z => 'Z',
608                        };
609                        output.push_str(&format!(" {}{}", pauli_char, target.qubit));
610                    }
611                    output.push('\n');
612                }
613
614                // Coordinate and metadata
615                StimInstruction::ShiftCoords { shifts } => {
616                    output.push_str(&format!(
617                        "SHIFT_COORDS {}\n",
618                        shifts
619                            .iter()
620                            .map(|s| s.to_string())
621                            .collect::<Vec<_>>()
622                            .join(" ")
623                    ));
624                }
625                StimInstruction::QubitCoords { qubit, coordinates } => {
626                    output.push_str(&format!(
627                        "QUBIT_COORDS({}) {}\n",
628                        qubit,
629                        coordinates
630                            .iter()
631                            .map(|c| c.to_string())
632                            .collect::<Vec<_>>()
633                            .join(" ")
634                    ));
635                }
636                StimInstruction::Repeat {
637                    count,
638                    instructions,
639                } => {
640                    output.push_str(&format!("REPEAT {} {{\n", count));
641                    for inst in instructions {
642                        // Recursively format nested instructions
643                        // For now, just indicate nested structure
644                        output.push_str("  # nested instruction\n");
645                    }
646                    output.push_str("}\n");
647                }
648            }
649        }
650
651        output
652    }
653
654    /// Get circuit statistics
655    pub fn statistics(&self) -> CircuitStatistics {
656        let mut num_gates = 0;
657        let mut num_measurements = 0;
658        let mut num_resets = 0;
659        let mut gate_counts = std::collections::HashMap::new();
660
661        for instruction in &self.instructions {
662            match instruction {
663                StimInstruction::SingleQubitGate { gate_type, .. } => {
664                    num_gates += 1;
665                    *gate_counts.entry(format!("{:?}", gate_type)).or_insert(0) += 1;
666                }
667                StimInstruction::TwoQubitGate { gate_type, .. } => {
668                    num_gates += 1;
669                    *gate_counts.entry(format!("{:?}", gate_type)).or_insert(0) += 1;
670                }
671                StimInstruction::Measure { qubits, .. } => {
672                    num_measurements += qubits.len();
673                }
674                StimInstruction::Reset { qubits } => {
675                    num_resets += qubits.len();
676                }
677                _ => {}
678            }
679        }
680
681        CircuitStatistics {
682            num_qubits: self.num_qubits,
683            num_gates,
684            num_measurements,
685            num_resets,
686            gate_counts,
687        }
688    }
689}
690
691impl Default for StimCircuit {
692    fn default() -> Self {
693        Self::new()
694    }
695}
696
697/// Circuit statistics
698#[derive(Debug, Clone)]
699pub struct CircuitStatistics {
700    pub num_qubits: usize,
701    pub num_gates: usize,
702    pub num_measurements: usize,
703    pub num_resets: usize,
704    pub gate_counts: std::collections::HashMap<String, usize>,
705}
706
707/// Parse a single Stim instruction from a line
708fn parse_instruction(line: &str) -> Result<StimInstruction> {
709    let parts: Vec<&str> = line.split_whitespace().collect();
710
711    if parts.is_empty() {
712        return Err(SimulatorError::InvalidOperation(
713            "Empty instruction".to_string(),
714        ));
715    }
716
717    // Extract instruction name (before any parentheses)
718    let instruction_name = if let Some(paren_pos) = parts[0].find('(') {
719        parts[0][..paren_pos].to_uppercase()
720    } else {
721        parts[0].to_uppercase()
722    };
723
724    match instruction_name.as_str() {
725        // Single-qubit gates
726        "H" => parse_single_qubit_gate(&parts, SingleQubitGateType::H),
727        "S" => parse_single_qubit_gate(&parts, SingleQubitGateType::S),
728        "S_DAG" => parse_single_qubit_gate(&parts, SingleQubitGateType::SDag),
729        "SQRT_X" => parse_single_qubit_gate(&parts, SingleQubitGateType::SqrtX),
730        "SQRT_X_DAG" => parse_single_qubit_gate(&parts, SingleQubitGateType::SqrtXDag),
731        "SQRT_Y" => parse_single_qubit_gate(&parts, SingleQubitGateType::SqrtY),
732        "SQRT_Y_DAG" => parse_single_qubit_gate(&parts, SingleQubitGateType::SqrtYDag),
733        "X" => parse_single_qubit_gate(&parts, SingleQubitGateType::X),
734        "Y" => parse_single_qubit_gate(&parts, SingleQubitGateType::Y),
735        "Z" => parse_single_qubit_gate(&parts, SingleQubitGateType::Z),
736
737        // Two-qubit gates
738        "CNOT" | "CX" => parse_two_qubit_gate(&parts, TwoQubitGateType::CNOT),
739        "CZ" => parse_two_qubit_gate(&parts, TwoQubitGateType::CZ),
740        "CY" => parse_two_qubit_gate(&parts, TwoQubitGateType::CY),
741        "SWAP" => parse_two_qubit_gate(&parts, TwoQubitGateType::SWAP),
742
743        // Measurements
744        "M" => parse_measurement(&parts, MeasurementBasis::Z),
745        "MX" => parse_measurement(&parts, MeasurementBasis::X),
746        "MY" => parse_measurement(&parts, MeasurementBasis::Y),
747
748        // Reset
749        "R" => parse_reset(&parts),
750
751        // Timing
752        "TICK" => Ok(StimInstruction::Tick),
753
754        // Advanced error correction
755        "DETECTOR" => parse_detector(&parts),
756        "OBSERVABLE_INCLUDE" => parse_observable_include(&parts),
757
758        // Measure and reset
759        "MR" => parse_measure_reset(&parts, MeasurementBasis::Z),
760        "MRX" => parse_measure_reset(&parts, MeasurementBasis::X),
761        "MRY" => parse_measure_reset(&parts, MeasurementBasis::Y),
762
763        // Noise instructions
764        "DEPOLARIZE1" => parse_depolarize1(&parts),
765        "DEPOLARIZE2" => parse_depolarize2(&parts),
766        "X_ERROR" => parse_error(&parts, ErrorType::X),
767        "Y_ERROR" => parse_error(&parts, ErrorType::Y),
768        "Z_ERROR" => parse_error(&parts, ErrorType::Z),
769        "PAULI_CHANNEL_1" => parse_pauli_channel_1(&parts),
770        "PAULI_CHANNEL_2" => parse_pauli_channel_2(&parts),
771        "CORRELATED_ERROR" | "E" => parse_correlated_error(&parts),
772        "ELSE_CORRELATED_ERROR" => parse_else_correlated_error(&parts),
773
774        // Coordinate and metadata
775        "SHIFT_COORDS" => parse_shift_coords(&parts),
776        "QUBIT_COORDS" => parse_qubit_coords(&parts),
777        "REPEAT" => parse_repeat(&parts),
778
779        _ => Err(SimulatorError::InvalidOperation(format!(
780            "Unknown instruction: {}",
781            instruction_name
782        ))),
783    }
784}
785
786fn parse_single_qubit_gate(
787    parts: &[&str],
788    gate_type: SingleQubitGateType,
789) -> Result<StimInstruction> {
790    if parts.len() != 2 {
791        return Err(SimulatorError::InvalidOperation(format!(
792            "Single-qubit gate requires 1 qubit argument, got {}",
793            parts.len() - 1
794        )));
795    }
796
797    let qubit = parts[1].parse::<usize>().map_err(|_| {
798        SimulatorError::InvalidOperation(format!("Invalid qubit index: {}", parts[1]))
799    })?;
800
801    Ok(StimInstruction::SingleQubitGate { gate_type, qubit })
802}
803
804fn parse_two_qubit_gate(parts: &[&str], gate_type: TwoQubitGateType) -> Result<StimInstruction> {
805    if parts.len() != 3 {
806        return Err(SimulatorError::InvalidOperation(format!(
807            "Two-qubit gate requires 2 qubit arguments, got {}",
808            parts.len() - 1
809        )));
810    }
811
812    let control = parts[1].parse::<usize>().map_err(|_| {
813        SimulatorError::InvalidOperation(format!("Invalid control qubit: {}", parts[1]))
814    })?;
815
816    let target = parts[2].parse::<usize>().map_err(|_| {
817        SimulatorError::InvalidOperation(format!("Invalid target qubit: {}", parts[2]))
818    })?;
819
820    Ok(StimInstruction::TwoQubitGate {
821        gate_type,
822        control,
823        target,
824    })
825}
826
827fn parse_measurement(parts: &[&str], basis: MeasurementBasis) -> Result<StimInstruction> {
828    if parts.len() < 2 {
829        return Err(SimulatorError::InvalidOperation(
830            "Measurement requires at least 1 qubit".to_string(),
831        ));
832    }
833
834    let qubits = parts[1..]
835        .iter()
836        .map(|s| {
837            s.parse::<usize>().map_err(|_| {
838                SimulatorError::InvalidOperation(format!("Invalid qubit index: {}", s))
839            })
840        })
841        .collect::<Result<Vec<_>>>()?;
842
843    Ok(StimInstruction::Measure { basis, qubits })
844}
845
846fn parse_reset(parts: &[&str]) -> Result<StimInstruction> {
847    if parts.len() < 2 {
848        return Err(SimulatorError::InvalidOperation(
849            "Reset requires at least 1 qubit".to_string(),
850        ));
851    }
852
853    let qubits = parts[1..]
854        .iter()
855        .map(|s| {
856            s.parse::<usize>().map_err(|_| {
857                SimulatorError::InvalidOperation(format!("Invalid qubit index: {}", s))
858            })
859        })
860        .collect::<Result<Vec<_>>>()?;
861
862    Ok(StimInstruction::Reset { qubits })
863}
864
865// ============================================================================
866// Advanced Instruction Parsing
867// ============================================================================
868
869/// Error type helper for parsing
870#[derive(Debug, Clone, Copy)]
871enum ErrorType {
872    X,
873    Y,
874    Z,
875}
876
877/// Parse parameter from instruction name (e.g., "DEPOLARIZE1(0.01)")
878fn parse_parameter(instruction: &str) -> Result<f64> {
879    let start = instruction
880        .find('(')
881        .ok_or_else(|| SimulatorError::InvalidOperation("Missing parameter".to_string()))?;
882    let end = instruction.find(')').ok_or_else(|| {
883        SimulatorError::InvalidOperation("Missing closing parenthesis".to_string())
884    })?;
885
886    instruction[start + 1..end]
887        .trim()
888        .parse::<f64>()
889        .map_err(|_| {
890            SimulatorError::InvalidOperation(format!(
891                "Invalid parameter: {}",
892                &instruction[start + 1..end]
893            ))
894        })
895}
896
897/// Parse multiple parameters from instruction (e.g., "PAULI_CHANNEL_1(0.01,0.02,0.03)")
898fn parse_parameters(instruction: &str) -> Result<Vec<f64>> {
899    let start = instruction
900        .find('(')
901        .ok_or_else(|| SimulatorError::InvalidOperation("Missing parameters".to_string()))?;
902    let end = instruction.find(')').ok_or_else(|| {
903        SimulatorError::InvalidOperation("Missing closing parenthesis".to_string())
904    })?;
905
906    instruction[start + 1..end]
907        .split(',')
908        .map(|s| {
909            s.trim()
910                .parse::<f64>()
911                .map_err(|_| SimulatorError::InvalidOperation(format!("Invalid parameter: {}", s)))
912        })
913        .collect::<Result<Vec<_>>>()
914}
915
916fn parse_detector(parts: &[&str]) -> Result<StimInstruction> {
917    // DETECTOR [coords...] rec[-1] rec[-2] ...
918    let mut coordinates = Vec::new();
919    let mut record_targets = Vec::new();
920
921    for part in parts.iter().skip(1) {
922        if part.starts_with("rec[") {
923            // Parse record target
924            let idx_str = part.trim_start_matches("rec[").trim_end_matches(']');
925            let idx = idx_str.parse::<i32>().map_err(|_| {
926                SimulatorError::InvalidOperation(format!("Invalid record target: {}", part))
927            })?;
928            record_targets.push(idx);
929        } else {
930            // Parse coordinate
931            if let Ok(coord) = part.parse::<f64>() {
932                coordinates.push(coord);
933            }
934        }
935    }
936
937    Ok(StimInstruction::Detector {
938        coordinates,
939        record_targets,
940    })
941}
942
943fn parse_observable_include(parts: &[&str]) -> Result<StimInstruction> {
944    // OBSERVABLE_INCLUDE(k) rec[-1] rec[-2] ...
945    if parts.is_empty() {
946        return Err(SimulatorError::InvalidOperation(
947            "OBSERVABLE_INCLUDE requires index".to_string(),
948        ));
949    }
950
951    let observable_index = parse_parameter(parts[0])? as usize;
952    let mut record_targets = Vec::new();
953
954    for part in parts.iter().skip(1) {
955        if part.starts_with("rec[") {
956            let idx_str = part.trim_start_matches("rec[").trim_end_matches(']');
957            let idx = idx_str.parse::<i32>().map_err(|_| {
958                SimulatorError::InvalidOperation(format!("Invalid record target: {}", part))
959            })?;
960            record_targets.push(idx);
961        }
962    }
963
964    Ok(StimInstruction::ObservableInclude {
965        observable_index,
966        record_targets,
967    })
968}
969
970fn parse_measure_reset(parts: &[&str], basis: MeasurementBasis) -> Result<StimInstruction> {
971    if parts.len() < 2 {
972        return Err(SimulatorError::InvalidOperation(
973            "Measure-reset requires at least 1 qubit".to_string(),
974        ));
975    }
976
977    let qubits = parts[1..]
978        .iter()
979        .map(|s| {
980            s.parse::<usize>().map_err(|_| {
981                SimulatorError::InvalidOperation(format!("Invalid qubit index: {}", s))
982            })
983        })
984        .collect::<Result<Vec<_>>>()?;
985
986    Ok(StimInstruction::MeasureReset { basis, qubits })
987}
988
989fn parse_depolarize1(parts: &[&str]) -> Result<StimInstruction> {
990    // DEPOLARIZE1(p) q1 q2 ...
991    if parts.is_empty() {
992        return Err(SimulatorError::InvalidOperation(
993            "DEPOLARIZE1 requires probability".to_string(),
994        ));
995    }
996
997    let probability = parse_parameter(parts[0])?;
998
999    let qubits = parts[1..]
1000        .iter()
1001        .map(|s| {
1002            s.parse::<usize>().map_err(|_| {
1003                SimulatorError::InvalidOperation(format!("Invalid qubit index: {}", s))
1004            })
1005        })
1006        .collect::<Result<Vec<_>>>()?;
1007
1008    Ok(StimInstruction::Depolarize1 {
1009        probability,
1010        qubits,
1011    })
1012}
1013
1014fn parse_depolarize2(parts: &[&str]) -> Result<StimInstruction> {
1015    // DEPOLARIZE2(p) q1 q2 q3 q4 ...
1016    if parts.is_empty() {
1017        return Err(SimulatorError::InvalidOperation(
1018            "DEPOLARIZE2 requires probability".to_string(),
1019        ));
1020    }
1021
1022    let probability = parse_parameter(parts[0])?;
1023
1024    let qubits: Vec<usize> = parts[1..]
1025        .iter()
1026        .map(|s| {
1027            s.parse::<usize>().map_err(|_| {
1028                SimulatorError::InvalidOperation(format!("Invalid qubit index: {}", s))
1029            })
1030        })
1031        .collect::<Result<Vec<_>>>()?;
1032
1033    // Group into pairs
1034    let mut qubit_pairs = Vec::new();
1035    for chunk in qubits.chunks(2) {
1036        if chunk.len() == 2 {
1037            qubit_pairs.push((chunk[0], chunk[1]));
1038        }
1039    }
1040
1041    Ok(StimInstruction::Depolarize2 {
1042        probability,
1043        qubit_pairs,
1044    })
1045}
1046
1047fn parse_error(parts: &[&str], error_type: ErrorType) -> Result<StimInstruction> {
1048    // X_ERROR(p) q1 q2 ...
1049    if parts.is_empty() {
1050        return Err(SimulatorError::InvalidOperation(
1051            "Error instruction requires probability".to_string(),
1052        ));
1053    }
1054
1055    let probability = parse_parameter(parts[0])?;
1056
1057    let qubits = parts[1..]
1058        .iter()
1059        .map(|s| {
1060            s.parse::<usize>().map_err(|_| {
1061                SimulatorError::InvalidOperation(format!("Invalid qubit index: {}", s))
1062            })
1063        })
1064        .collect::<Result<Vec<_>>>()?;
1065
1066    match error_type {
1067        ErrorType::X => Ok(StimInstruction::XError {
1068            probability,
1069            qubits,
1070        }),
1071        ErrorType::Y => Ok(StimInstruction::YError {
1072            probability,
1073            qubits,
1074        }),
1075        ErrorType::Z => Ok(StimInstruction::ZError {
1076            probability,
1077            qubits,
1078        }),
1079    }
1080}
1081
1082fn parse_pauli_channel_1(parts: &[&str]) -> Result<StimInstruction> {
1083    // PAULI_CHANNEL_1(px, py, pz) q1 q2 ...
1084    if parts.is_empty() {
1085        return Err(SimulatorError::InvalidOperation(
1086            "PAULI_CHANNEL_1 requires parameters".to_string(),
1087        ));
1088    }
1089
1090    let params = parse_parameters(parts[0])?;
1091    if params.len() != 3 {
1092        return Err(SimulatorError::InvalidOperation(
1093            "PAULI_CHANNEL_1 requires 3 parameters (px, py, pz)".to_string(),
1094        ));
1095    }
1096
1097    let qubits = parts[1..]
1098        .iter()
1099        .map(|s| {
1100            s.parse::<usize>().map_err(|_| {
1101                SimulatorError::InvalidOperation(format!("Invalid qubit index: {}", s))
1102            })
1103        })
1104        .collect::<Result<Vec<_>>>()?;
1105
1106    Ok(StimInstruction::PauliChannel1 {
1107        px: params[0],
1108        py: params[1],
1109        pz: params[2],
1110        qubits,
1111    })
1112}
1113
1114fn parse_pauli_channel_2(parts: &[&str]) -> Result<StimInstruction> {
1115    // PAULI_CHANNEL_2(p_IX, p_IY, ..., p_ZZ) q1 q2 ...
1116    if parts.is_empty() {
1117        return Err(SimulatorError::InvalidOperation(
1118            "PAULI_CHANNEL_2 requires parameters".to_string(),
1119        ));
1120    }
1121
1122    let probabilities = parse_parameters(parts[0])?;
1123    if probabilities.len() != 15 {
1124        return Err(SimulatorError::InvalidOperation(
1125            "PAULI_CHANNEL_2 requires 15 parameters".to_string(),
1126        ));
1127    }
1128
1129    let qubits: Vec<usize> = parts[1..]
1130        .iter()
1131        .map(|s| {
1132            s.parse::<usize>().map_err(|_| {
1133                SimulatorError::InvalidOperation(format!("Invalid qubit index: {}", s))
1134            })
1135        })
1136        .collect::<Result<Vec<_>>>()?;
1137
1138    // Group into pairs
1139    let mut qubit_pairs = Vec::new();
1140    for chunk in qubits.chunks(2) {
1141        if chunk.len() == 2 {
1142            qubit_pairs.push((chunk[0], chunk[1]));
1143        }
1144    }
1145
1146    Ok(StimInstruction::PauliChannel2 {
1147        probabilities,
1148        qubit_pairs,
1149    })
1150}
1151
1152fn parse_correlated_error(parts: &[&str]) -> Result<StimInstruction> {
1153    // CORRELATED_ERROR(p) X1 Y2 Z3 ...
1154    if parts.is_empty() {
1155        return Err(SimulatorError::InvalidOperation(
1156            "CORRELATED_ERROR requires probability".to_string(),
1157        ));
1158    }
1159
1160    let probability = parse_parameter(parts[0])?;
1161
1162    let mut targets = Vec::new();
1163    for part in parts.iter().skip(1) {
1164        // Parse Pauli target (e.g., "X1", "Y2", "Z3")
1165        let pauli_char = part
1166            .chars()
1167            .next()
1168            .ok_or_else(|| SimulatorError::InvalidOperation("Empty Pauli target".to_string()))?;
1169
1170        let pauli = match pauli_char.to_ascii_uppercase() {
1171            'I' => PauliType::I,
1172            'X' => PauliType::X,
1173            'Y' => PauliType::Y,
1174            'Z' => PauliType::Z,
1175            _ => {
1176                return Err(SimulatorError::InvalidOperation(format!(
1177                    "Invalid Pauli type: {}",
1178                    pauli_char
1179                )))
1180            }
1181        };
1182
1183        let qubit = part[1..].parse::<usize>().map_err(|_| {
1184            SimulatorError::InvalidOperation(format!("Invalid qubit in target: {}", part))
1185        })?;
1186
1187        targets.push(PauliTarget { pauli, qubit });
1188    }
1189
1190    Ok(StimInstruction::CorrelatedError {
1191        probability,
1192        targets,
1193    })
1194}
1195
1196fn parse_else_correlated_error(parts: &[&str]) -> Result<StimInstruction> {
1197    // ELSE_CORRELATED_ERROR(p) X1 Y2 Z3 ...
1198    // Same format as CORRELATED_ERROR, but applies only if previous E did not trigger
1199    if parts.is_empty() {
1200        return Err(SimulatorError::InvalidOperation(
1201            "ELSE_CORRELATED_ERROR requires probability".to_string(),
1202        ));
1203    }
1204
1205    let probability = parse_parameter(parts[0])?;
1206
1207    let mut targets = Vec::new();
1208    for part in parts.iter().skip(1) {
1209        // Parse Pauli target (e.g., "X1", "Y2", "Z3")
1210        let pauli_char = part
1211            .chars()
1212            .next()
1213            .ok_or_else(|| SimulatorError::InvalidOperation("Empty Pauli target".to_string()))?;
1214
1215        let pauli = match pauli_char.to_ascii_uppercase() {
1216            'I' => PauliType::I,
1217            'X' => PauliType::X,
1218            'Y' => PauliType::Y,
1219            'Z' => PauliType::Z,
1220            _ => {
1221                return Err(SimulatorError::InvalidOperation(format!(
1222                    "Invalid Pauli type: {}",
1223                    pauli_char
1224                )))
1225            }
1226        };
1227
1228        let qubit = part[1..].parse::<usize>().map_err(|_| {
1229            SimulatorError::InvalidOperation(format!("Invalid qubit in target: {}", part))
1230        })?;
1231
1232        targets.push(PauliTarget { pauli, qubit });
1233    }
1234
1235    Ok(StimInstruction::ElseCorrelatedError {
1236        probability,
1237        targets,
1238    })
1239}
1240
1241fn parse_shift_coords(parts: &[&str]) -> Result<StimInstruction> {
1242    // SHIFT_COORDS dx dy dz ...
1243    let shifts = parts[1..]
1244        .iter()
1245        .map(|s| {
1246            s.parse::<f64>().map_err(|_| {
1247                SimulatorError::InvalidOperation(format!("Invalid coordinate shift: {}", s))
1248            })
1249        })
1250        .collect::<Result<Vec<_>>>()?;
1251
1252    Ok(StimInstruction::ShiftCoords { shifts })
1253}
1254
1255fn parse_qubit_coords(parts: &[&str]) -> Result<StimInstruction> {
1256    // QUBIT_COORDS(q) x y z ...
1257    if parts.is_empty() {
1258        return Err(SimulatorError::InvalidOperation(
1259            "QUBIT_COORDS requires qubit index".to_string(),
1260        ));
1261    }
1262
1263    let qubit = parse_parameter(parts[0])? as usize;
1264
1265    let coordinates = parts[1..]
1266        .iter()
1267        .map(|s| {
1268            s.parse::<f64>()
1269                .map_err(|_| SimulatorError::InvalidOperation(format!("Invalid coordinate: {}", s)))
1270        })
1271        .collect::<Result<Vec<_>>>()?;
1272
1273    Ok(StimInstruction::QubitCoords { qubit, coordinates })
1274}
1275
1276fn parse_repeat(_parts: &[&str]) -> Result<StimInstruction> {
1277    // REPEAT N { ... }
1278    // This is a complex instruction that requires block parsing
1279    // For now, return a simplified implementation
1280    Err(SimulatorError::NotImplemented(
1281        "REPEAT blocks not yet implemented".to_string(),
1282    ))
1283}
1284
1285// ============================================================================
1286// Tests
1287// ============================================================================
1288
1289#[cfg(test)]
1290mod tests {
1291    use super::*;
1292
1293    #[test]
1294    fn test_parse_single_qubit_gates() {
1295        let circuit = StimCircuit::from_str("H 0\nS 1\nX 2").unwrap();
1296        assert_eq!(circuit.num_qubits, 3);
1297        assert_eq!(circuit.instructions.len(), 3);
1298
1299        match &circuit.instructions[0] {
1300            StimInstruction::SingleQubitGate { gate_type, qubit } => {
1301                assert_eq!(*gate_type, SingleQubitGateType::H);
1302                assert_eq!(*qubit, 0);
1303            }
1304            _ => panic!("Expected SingleQubitGate"),
1305        }
1306    }
1307
1308    #[test]
1309    fn test_parse_two_qubit_gates() {
1310        let circuit = StimCircuit::from_str("CNOT 0 1\nCZ 1 2").unwrap();
1311        assert_eq!(circuit.num_qubits, 3);
1312        assert_eq!(circuit.instructions.len(), 2);
1313
1314        match &circuit.instructions[0] {
1315            StimInstruction::TwoQubitGate {
1316                gate_type,
1317                control,
1318                target,
1319            } => {
1320                assert_eq!(*gate_type, TwoQubitGateType::CNOT);
1321                assert_eq!(*control, 0);
1322                assert_eq!(*target, 1);
1323            }
1324            _ => panic!("Expected TwoQubitGate"),
1325        }
1326    }
1327
1328    #[test]
1329    fn test_parse_measurements() {
1330        let circuit = StimCircuit::from_str("M 0 1\nMX 2\nMY 3").unwrap();
1331        assert_eq!(circuit.instructions.len(), 3);
1332
1333        match &circuit.instructions[0] {
1334            StimInstruction::Measure { basis, qubits } => {
1335                assert_eq!(*basis, MeasurementBasis::Z);
1336                assert_eq!(*qubits, vec![0, 1]);
1337            }
1338            _ => panic!("Expected Measure"),
1339        }
1340
1341        match &circuit.instructions[1] {
1342            StimInstruction::Measure { basis, qubits } => {
1343                assert_eq!(*basis, MeasurementBasis::X);
1344                assert_eq!(*qubits, vec![2]);
1345            }
1346            _ => panic!("Expected Measure"),
1347        }
1348    }
1349
1350    #[test]
1351    fn test_parse_reset() {
1352        let circuit = StimCircuit::from_str("R 0 1 2").unwrap();
1353        assert_eq!(circuit.instructions.len(), 1);
1354
1355        match &circuit.instructions[0] {
1356            StimInstruction::Reset { qubits } => {
1357                assert_eq!(*qubits, vec![0, 1, 2]);
1358            }
1359            _ => panic!("Expected Reset"),
1360        }
1361    }
1362
1363    #[test]
1364    fn test_parse_comments() {
1365        let circuit = StimCircuit::from_str("# Bell state\nH 0\nCNOT 0 1\n# End").unwrap();
1366        assert_eq!(circuit.metadata.len(), 2);
1367        assert_eq!(circuit.gates().len(), 2);
1368    }
1369
1370    #[test]
1371    fn test_bell_state_circuit() {
1372        let stim_code = r#"
1373# Bell state preparation
1374H 0
1375CNOT 0 1
1376M 0 1
1377        "#;
1378
1379        let circuit = StimCircuit::from_str(stim_code).unwrap();
1380        assert_eq!(circuit.num_qubits, 2);
1381
1382        let gates = circuit.gates();
1383        assert_eq!(gates.len(), 2);
1384
1385        let measurements = circuit.measurements();
1386        assert_eq!(measurements.len(), 1);
1387        assert_eq!(measurements[0].1, vec![0, 1]);
1388    }
1389
1390    #[test]
1391    fn test_to_stim_string() {
1392        let mut circuit = StimCircuit::new();
1393        circuit.add_instruction(StimInstruction::SingleQubitGate {
1394            gate_type: SingleQubitGateType::H,
1395            qubit: 0,
1396        });
1397        circuit.add_instruction(StimInstruction::TwoQubitGate {
1398            gate_type: TwoQubitGateType::CNOT,
1399            control: 0,
1400            target: 1,
1401        });
1402        circuit.add_instruction(StimInstruction::Measure {
1403            basis: MeasurementBasis::Z,
1404            qubits: vec![0, 1],
1405        });
1406
1407        let stim_string = circuit.to_stim_string();
1408        let parsed = StimCircuit::from_str(&stim_string).unwrap();
1409
1410        assert_eq!(parsed.num_qubits, circuit.num_qubits);
1411        assert_eq!(parsed.gates().len(), circuit.gates().len());
1412    }
1413
1414    #[test]
1415    fn test_circuit_statistics() {
1416        let stim_code = r#"
1417H 0
1418H 1
1419CNOT 0 1
1420CNOT 1 2
1421M 0 1 2
1422R 0
1423        "#;
1424
1425        let circuit = StimCircuit::from_str(stim_code).unwrap();
1426        let stats = circuit.statistics();
1427
1428        assert_eq!(stats.num_qubits, 3);
1429        assert_eq!(stats.num_gates, 4);
1430        assert_eq!(stats.num_measurements, 3);
1431        assert_eq!(stats.num_resets, 1);
1432        assert_eq!(stats.gate_counts.get("H"), Some(&2));
1433        assert_eq!(stats.gate_counts.get("CNOT"), Some(&2));
1434    }
1435
1436    #[test]
1437    fn test_error_invalid_instruction() {
1438        let result = StimCircuit::from_str("INVALID_GATE 0");
1439        assert!(result.is_err());
1440    }
1441
1442    #[test]
1443    fn test_error_invalid_qubit() {
1444        let result = StimCircuit::from_str("H abc");
1445        assert!(result.is_err());
1446    }
1447
1448    #[test]
1449    fn test_error_wrong_arity() {
1450        let result = StimCircuit::from_str("CNOT 0");
1451        assert!(result.is_err());
1452    }
1453
1454    #[test]
1455    fn test_case_insensitive() {
1456        let circuit = StimCircuit::from_str("h 0\ncnot 0 1").unwrap();
1457        assert_eq!(circuit.gates().len(), 2);
1458    }
1459
1460    // ===== Advanced Instruction Tests =====
1461
1462    #[test]
1463    fn test_detector_parsing() {
1464        let circuit = StimCircuit::from_str("DETECTOR rec[-1] rec[-2]").unwrap();
1465        assert_eq!(circuit.instructions.len(), 1);
1466
1467        match &circuit.instructions[0] {
1468            StimInstruction::Detector { record_targets, .. } => {
1469                assert_eq!(record_targets.len(), 2);
1470                assert_eq!(record_targets[0], -1);
1471                assert_eq!(record_targets[1], -2);
1472            }
1473            _ => panic!("Expected Detector"),
1474        }
1475    }
1476
1477    #[test]
1478    fn test_observable_include_parsing() {
1479        let circuit = StimCircuit::from_str("OBSERVABLE_INCLUDE(0) rec[-1]").unwrap();
1480        assert_eq!(circuit.instructions.len(), 1);
1481
1482        match &circuit.instructions[0] {
1483            StimInstruction::ObservableInclude {
1484                observable_index,
1485                record_targets,
1486            } => {
1487                assert_eq!(*observable_index, 0);
1488                assert_eq!(record_targets.len(), 1);
1489                assert_eq!(record_targets[0], -1);
1490            }
1491            _ => panic!("Expected ObservableInclude"),
1492        }
1493    }
1494
1495    #[test]
1496    fn test_measure_reset_parsing() {
1497        let circuit = StimCircuit::from_str("MR 0 1\nMRX 2").unwrap();
1498        assert_eq!(circuit.instructions.len(), 2);
1499
1500        match &circuit.instructions[0] {
1501            StimInstruction::MeasureReset { basis, qubits } => {
1502                assert_eq!(*basis, MeasurementBasis::Z);
1503                assert_eq!(qubits, &vec![0, 1]);
1504            }
1505            _ => panic!("Expected MeasureReset"),
1506        }
1507    }
1508
1509    #[test]
1510    fn test_depolarize1_parsing() {
1511        let circuit = StimCircuit::from_str("DEPOLARIZE1(0.01) 0 1 2").unwrap();
1512        assert_eq!(circuit.instructions.len(), 1);
1513
1514        match &circuit.instructions[0] {
1515            StimInstruction::Depolarize1 {
1516                probability,
1517                qubits,
1518            } => {
1519                assert!((probability - 0.01).abs() < 1e-10);
1520                assert_eq!(qubits, &vec![0, 1, 2]);
1521            }
1522            _ => panic!("Expected Depolarize1"),
1523        }
1524    }
1525
1526    #[test]
1527    fn test_x_error_parsing() {
1528        let circuit = StimCircuit::from_str("X_ERROR(0.05) 0 1").unwrap();
1529        assert_eq!(circuit.instructions.len(), 1);
1530
1531        match &circuit.instructions[0] {
1532            StimInstruction::XError {
1533                probability,
1534                qubits,
1535            } => {
1536                assert!((probability - 0.05).abs() < 1e-10);
1537                assert_eq!(qubits, &vec![0, 1]);
1538            }
1539            _ => panic!("Expected XError"),
1540        }
1541    }
1542
1543    #[test]
1544    fn test_pauli_channel_1_parsing() {
1545        let circuit = StimCircuit::from_str("PAULI_CHANNEL_1(0.01,0.02,0.03) 0 1").unwrap();
1546        assert_eq!(circuit.instructions.len(), 1);
1547
1548        match &circuit.instructions[0] {
1549            StimInstruction::PauliChannel1 { px, py, pz, qubits } => {
1550                assert!((px - 0.01).abs() < 1e-10);
1551                assert!((py - 0.02).abs() < 1e-10);
1552                assert!((pz - 0.03).abs() < 1e-10);
1553                assert_eq!(qubits, &vec![0, 1]);
1554            }
1555            _ => panic!("Expected PauliChannel1"),
1556        }
1557    }
1558
1559    #[test]
1560    fn test_correlated_error_parsing() {
1561        let circuit = StimCircuit::from_str("CORRELATED_ERROR(0.1) X0 Y1 Z2").unwrap();
1562        assert_eq!(circuit.instructions.len(), 1);
1563
1564        match &circuit.instructions[0] {
1565            StimInstruction::CorrelatedError {
1566                probability,
1567                targets,
1568            } => {
1569                assert!((probability - 0.1).abs() < 1e-10);
1570                assert_eq!(targets.len(), 3);
1571                assert_eq!(targets[0].pauli, PauliType::X);
1572                assert_eq!(targets[0].qubit, 0);
1573                assert_eq!(targets[1].pauli, PauliType::Y);
1574                assert_eq!(targets[1].qubit, 1);
1575                assert_eq!(targets[2].pauli, PauliType::Z);
1576                assert_eq!(targets[2].qubit, 2);
1577            }
1578            _ => panic!("Expected CorrelatedError"),
1579        }
1580    }
1581
1582    #[test]
1583    fn test_shift_coords_parsing() {
1584        let circuit = StimCircuit::from_str("SHIFT_COORDS 1.0 2.0 3.0").unwrap();
1585        assert_eq!(circuit.instructions.len(), 1);
1586
1587        match &circuit.instructions[0] {
1588            StimInstruction::ShiftCoords { shifts } => {
1589                assert_eq!(shifts.len(), 3);
1590                assert!((shifts[0] - 1.0).abs() < 1e-10);
1591                assert!((shifts[1] - 2.0).abs() < 1e-10);
1592                assert!((shifts[2] - 3.0).abs() < 1e-10);
1593            }
1594            _ => panic!("Expected ShiftCoords"),
1595        }
1596    }
1597
1598    #[test]
1599    fn test_full_error_correction_circuit() {
1600        let circuit_str = r#"
1601            # Surface code preparation
1602            H 0
1603            CNOT 0 1
1604            CNOT 0 2
1605            M 1 2
1606            DETECTOR rec[-1] rec[-2]
1607            OBSERVABLE_INCLUDE(0) rec[-1]
1608            X_ERROR(0.01) 0
1609            Z_ERROR(0.01) 1 2
1610        "#;
1611
1612        let circuit = StimCircuit::from_str(circuit_str).unwrap();
1613
1614        // Should parse all instructions (H, 2xCNOT, M, DETECTOR, OBSERVABLE_INCLUDE, 2 errors)
1615        assert!(circuit.instructions.len() >= 7);
1616
1617        // Find detector
1618        let has_detector = circuit
1619            .instructions
1620            .iter()
1621            .any(|inst| matches!(inst, StimInstruction::Detector { .. }));
1622        assert!(has_detector);
1623
1624        // Find observable
1625        let has_observable = circuit
1626            .instructions
1627            .iter()
1628            .any(|inst| matches!(inst, StimInstruction::ObservableInclude { .. }));
1629        assert!(has_observable);
1630    }
1631
1632    #[test]
1633    fn test_e_shorthand_parsing() {
1634        // E is shorthand for CORRELATED_ERROR
1635        let circuit = StimCircuit::from_str("E(0.1) X0 Y1 Z2").unwrap();
1636        assert_eq!(circuit.instructions.len(), 1);
1637
1638        match &circuit.instructions[0] {
1639            StimInstruction::CorrelatedError {
1640                probability,
1641                targets,
1642            } => {
1643                assert!((probability - 0.1).abs() < 1e-10);
1644                assert_eq!(targets.len(), 3);
1645                assert_eq!(targets[0].pauli, PauliType::X);
1646                assert_eq!(targets[0].qubit, 0);
1647            }
1648            _ => panic!("Expected CorrelatedError"),
1649        }
1650    }
1651
1652    #[test]
1653    fn test_else_correlated_error_parsing() {
1654        let circuit = StimCircuit::from_str("ELSE_CORRELATED_ERROR(0.2) X0 Z1").unwrap();
1655        assert_eq!(circuit.instructions.len(), 1);
1656
1657        match &circuit.instructions[0] {
1658            StimInstruction::ElseCorrelatedError {
1659                probability,
1660                targets,
1661            } => {
1662                assert!((probability - 0.2).abs() < 1e-10);
1663                assert_eq!(targets.len(), 2);
1664                assert_eq!(targets[0].pauli, PauliType::X);
1665                assert_eq!(targets[0].qubit, 0);
1666                assert_eq!(targets[1].pauli, PauliType::Z);
1667                assert_eq!(targets[1].qubit, 1);
1668            }
1669            _ => panic!("Expected ElseCorrelatedError"),
1670        }
1671    }
1672
1673    #[test]
1674    fn test_e_else_chain() {
1675        // Test E and ELSE_CORRELATED_ERROR in sequence
1676        let circuit_str = r#"
1677            E(0.1) X0
1678            ELSE_CORRELATED_ERROR(0.2) Y0
1679        "#;
1680
1681        let circuit = StimCircuit::from_str(circuit_str).unwrap();
1682        assert_eq!(circuit.instructions.len(), 2);
1683
1684        assert!(matches!(
1685            &circuit.instructions[0],
1686            StimInstruction::CorrelatedError { .. }
1687        ));
1688        assert!(matches!(
1689            &circuit.instructions[1],
1690            StimInstruction::ElseCorrelatedError { .. }
1691        ));
1692    }
1693}