Skip to main content

quantrs2_sim/stim_parser/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::error::{Result, SimulatorError};
6
7use super::types::{
8    ErrorType, MeasurementBasis, PauliTarget, PauliType, SingleQubitGateType, StimCircuit,
9    StimInstruction, TwoQubitGateType,
10};
11
12/// Parse a single Stim instruction from a line
13pub(super) fn parse_instruction(line: &str) -> Result<StimInstruction> {
14    let parts: Vec<&str> = line.split_whitespace().collect();
15    if parts.is_empty() {
16        return Err(SimulatorError::InvalidOperation(
17            "Empty instruction".to_string(),
18        ));
19    }
20    let instruction_name = if let Some(paren_pos) = parts[0].find('(') {
21        parts[0][..paren_pos].to_uppercase()
22    } else {
23        parts[0].to_uppercase()
24    };
25    match instruction_name.as_str() {
26        "H" => parse_single_qubit_gate(&parts, SingleQubitGateType::H),
27        "S" => parse_single_qubit_gate(&parts, SingleQubitGateType::S),
28        "S_DAG" => parse_single_qubit_gate(&parts, SingleQubitGateType::SDag),
29        "SQRT_X" => parse_single_qubit_gate(&parts, SingleQubitGateType::SqrtX),
30        "SQRT_X_DAG" => parse_single_qubit_gate(&parts, SingleQubitGateType::SqrtXDag),
31        "SQRT_Y" => parse_single_qubit_gate(&parts, SingleQubitGateType::SqrtY),
32        "SQRT_Y_DAG" => parse_single_qubit_gate(&parts, SingleQubitGateType::SqrtYDag),
33        "X" => parse_single_qubit_gate(&parts, SingleQubitGateType::X),
34        "Y" => parse_single_qubit_gate(&parts, SingleQubitGateType::Y),
35        "Z" => parse_single_qubit_gate(&parts, SingleQubitGateType::Z),
36        "CNOT" | "CX" => parse_two_qubit_gate(&parts, TwoQubitGateType::CNOT),
37        "CZ" => parse_two_qubit_gate(&parts, TwoQubitGateType::CZ),
38        "CY" => parse_two_qubit_gate(&parts, TwoQubitGateType::CY),
39        "SWAP" => parse_two_qubit_gate(&parts, TwoQubitGateType::SWAP),
40        "M" => parse_measurement(&parts, MeasurementBasis::Z),
41        "MX" => parse_measurement(&parts, MeasurementBasis::X),
42        "MY" => parse_measurement(&parts, MeasurementBasis::Y),
43        "R" => parse_reset(&parts),
44        "TICK" => Ok(StimInstruction::Tick),
45        "DETECTOR" => parse_detector(&parts),
46        "OBSERVABLE_INCLUDE" => parse_observable_include(&parts),
47        "MR" => parse_measure_reset(&parts, MeasurementBasis::Z),
48        "MRX" => parse_measure_reset(&parts, MeasurementBasis::X),
49        "MRY" => parse_measure_reset(&parts, MeasurementBasis::Y),
50        "DEPOLARIZE1" => parse_depolarize1(&parts),
51        "DEPOLARIZE2" => parse_depolarize2(&parts),
52        "X_ERROR" => parse_error(&parts, ErrorType::X),
53        "Y_ERROR" => parse_error(&parts, ErrorType::Y),
54        "Z_ERROR" => parse_error(&parts, ErrorType::Z),
55        "PAULI_CHANNEL_1" => parse_pauli_channel_1(&parts),
56        "PAULI_CHANNEL_2" => parse_pauli_channel_2(&parts),
57        "CORRELATED_ERROR" | "E" => parse_correlated_error(&parts),
58        "ELSE_CORRELATED_ERROR" => parse_else_correlated_error(&parts),
59        "SHIFT_COORDS" => parse_shift_coords(&parts),
60        "QUBIT_COORDS" => parse_qubit_coords(&parts),
61        "REPEAT" => parse_repeat(&parts),
62        _ => Err(SimulatorError::InvalidOperation(format!(
63            "Unknown instruction: {}",
64            instruction_name
65        ))),
66    }
67}
68fn parse_single_qubit_gate(
69    parts: &[&str],
70    gate_type: SingleQubitGateType,
71) -> Result<StimInstruction> {
72    if parts.len() != 2 {
73        return Err(SimulatorError::InvalidOperation(format!(
74            "Single-qubit gate requires 1 qubit argument, got {}",
75            parts.len() - 1
76        )));
77    }
78    let qubit = parts[1].parse::<usize>().map_err(|_| {
79        SimulatorError::InvalidOperation(format!("Invalid qubit index: {}", parts[1]))
80    })?;
81    Ok(StimInstruction::SingleQubitGate { gate_type, qubit })
82}
83fn parse_two_qubit_gate(parts: &[&str], gate_type: TwoQubitGateType) -> Result<StimInstruction> {
84    if parts.len() != 3 {
85        return Err(SimulatorError::InvalidOperation(format!(
86            "Two-qubit gate requires 2 qubit arguments, got {}",
87            parts.len() - 1
88        )));
89    }
90    let control = parts[1].parse::<usize>().map_err(|_| {
91        SimulatorError::InvalidOperation(format!("Invalid control qubit: {}", parts[1]))
92    })?;
93    let target = parts[2].parse::<usize>().map_err(|_| {
94        SimulatorError::InvalidOperation(format!("Invalid target qubit: {}", parts[2]))
95    })?;
96    Ok(StimInstruction::TwoQubitGate {
97        gate_type,
98        control,
99        target,
100    })
101}
102fn parse_measurement(parts: &[&str], basis: MeasurementBasis) -> Result<StimInstruction> {
103    if parts.len() < 2 {
104        return Err(SimulatorError::InvalidOperation(
105            "Measurement requires at least 1 qubit".to_string(),
106        ));
107    }
108    let qubits = parts[1..]
109        .iter()
110        .map(|s| {
111            s.parse::<usize>().map_err(|_| {
112                SimulatorError::InvalidOperation(format!("Invalid qubit index: {}", s))
113            })
114        })
115        .collect::<Result<Vec<_>>>()?;
116    Ok(StimInstruction::Measure { basis, qubits })
117}
118fn parse_reset(parts: &[&str]) -> Result<StimInstruction> {
119    if parts.len() < 2 {
120        return Err(SimulatorError::InvalidOperation(
121            "Reset requires at least 1 qubit".to_string(),
122        ));
123    }
124    let qubits = parts[1..]
125        .iter()
126        .map(|s| {
127            s.parse::<usize>().map_err(|_| {
128                SimulatorError::InvalidOperation(format!("Invalid qubit index: {}", s))
129            })
130        })
131        .collect::<Result<Vec<_>>>()?;
132    Ok(StimInstruction::Reset { qubits })
133}
134/// Parse parameter from instruction name (e.g., "DEPOLARIZE1(0.01)")
135fn parse_parameter(instruction: &str) -> Result<f64> {
136    let start = instruction
137        .find('(')
138        .ok_or_else(|| SimulatorError::InvalidOperation("Missing parameter".to_string()))?;
139    let end = instruction.find(')').ok_or_else(|| {
140        SimulatorError::InvalidOperation("Missing closing parenthesis".to_string())
141    })?;
142    instruction[start + 1..end]
143        .trim()
144        .parse::<f64>()
145        .map_err(|_| {
146            SimulatorError::InvalidOperation(format!(
147                "Invalid parameter: {}",
148                &instruction[start + 1..end]
149            ))
150        })
151}
152/// Parse multiple parameters from instruction (e.g., "PAULI_CHANNEL_1(0.01,0.02,0.03)")
153fn parse_parameters(instruction: &str) -> Result<Vec<f64>> {
154    let start = instruction
155        .find('(')
156        .ok_or_else(|| SimulatorError::InvalidOperation("Missing parameters".to_string()))?;
157    let end = instruction.find(')').ok_or_else(|| {
158        SimulatorError::InvalidOperation("Missing closing parenthesis".to_string())
159    })?;
160    instruction[start + 1..end]
161        .split(',')
162        .map(|s| {
163            s.trim()
164                .parse::<f64>()
165                .map_err(|_| SimulatorError::InvalidOperation(format!("Invalid parameter: {}", s)))
166        })
167        .collect::<Result<Vec<_>>>()
168}
169fn parse_detector(parts: &[&str]) -> Result<StimInstruction> {
170    let mut coordinates = Vec::new();
171    let mut record_targets = Vec::new();
172    for part in parts.iter().skip(1) {
173        if part.starts_with("rec[") {
174            let idx_str = part.trim_start_matches("rec[").trim_end_matches(']');
175            let idx = idx_str.parse::<i32>().map_err(|_| {
176                SimulatorError::InvalidOperation(format!("Invalid record target: {}", part))
177            })?;
178            record_targets.push(idx);
179        } else {
180            if let Ok(coord) = part.parse::<f64>() {
181                coordinates.push(coord);
182            }
183        }
184    }
185    Ok(StimInstruction::Detector {
186        coordinates,
187        record_targets,
188    })
189}
190fn parse_observable_include(parts: &[&str]) -> Result<StimInstruction> {
191    if parts.is_empty() {
192        return Err(SimulatorError::InvalidOperation(
193            "OBSERVABLE_INCLUDE requires index".to_string(),
194        ));
195    }
196    let observable_index = parse_parameter(parts[0])? as usize;
197    let mut record_targets = Vec::new();
198    for part in parts.iter().skip(1) {
199        if part.starts_with("rec[") {
200            let idx_str = part.trim_start_matches("rec[").trim_end_matches(']');
201            let idx = idx_str.parse::<i32>().map_err(|_| {
202                SimulatorError::InvalidOperation(format!("Invalid record target: {}", part))
203            })?;
204            record_targets.push(idx);
205        }
206    }
207    Ok(StimInstruction::ObservableInclude {
208        observable_index,
209        record_targets,
210    })
211}
212fn parse_measure_reset(parts: &[&str], basis: MeasurementBasis) -> Result<StimInstruction> {
213    if parts.len() < 2 {
214        return Err(SimulatorError::InvalidOperation(
215            "Measure-reset requires at least 1 qubit".to_string(),
216        ));
217    }
218    let qubits = parts[1..]
219        .iter()
220        .map(|s| {
221            s.parse::<usize>().map_err(|_| {
222                SimulatorError::InvalidOperation(format!("Invalid qubit index: {}", s))
223            })
224        })
225        .collect::<Result<Vec<_>>>()?;
226    Ok(StimInstruction::MeasureReset { basis, qubits })
227}
228fn parse_depolarize1(parts: &[&str]) -> Result<StimInstruction> {
229    if parts.is_empty() {
230        return Err(SimulatorError::InvalidOperation(
231            "DEPOLARIZE1 requires probability".to_string(),
232        ));
233    }
234    let probability = parse_parameter(parts[0])?;
235    let qubits = parts[1..]
236        .iter()
237        .map(|s| {
238            s.parse::<usize>().map_err(|_| {
239                SimulatorError::InvalidOperation(format!("Invalid qubit index: {}", s))
240            })
241        })
242        .collect::<Result<Vec<_>>>()?;
243    Ok(StimInstruction::Depolarize1 {
244        probability,
245        qubits,
246    })
247}
248fn parse_depolarize2(parts: &[&str]) -> Result<StimInstruction> {
249    if parts.is_empty() {
250        return Err(SimulatorError::InvalidOperation(
251            "DEPOLARIZE2 requires probability".to_string(),
252        ));
253    }
254    let probability = parse_parameter(parts[0])?;
255    let qubits: Vec<usize> = parts[1..]
256        .iter()
257        .map(|s| {
258            s.parse::<usize>().map_err(|_| {
259                SimulatorError::InvalidOperation(format!("Invalid qubit index: {}", s))
260            })
261        })
262        .collect::<Result<Vec<_>>>()?;
263    let mut qubit_pairs = Vec::new();
264    for chunk in qubits.chunks(2) {
265        if chunk.len() == 2 {
266            qubit_pairs.push((chunk[0], chunk[1]));
267        }
268    }
269    Ok(StimInstruction::Depolarize2 {
270        probability,
271        qubit_pairs,
272    })
273}
274fn parse_error(parts: &[&str], error_type: ErrorType) -> Result<StimInstruction> {
275    if parts.is_empty() {
276        return Err(SimulatorError::InvalidOperation(
277            "Error instruction requires probability".to_string(),
278        ));
279    }
280    let probability = parse_parameter(parts[0])?;
281    let qubits = parts[1..]
282        .iter()
283        .map(|s| {
284            s.parse::<usize>().map_err(|_| {
285                SimulatorError::InvalidOperation(format!("Invalid qubit index: {}", s))
286            })
287        })
288        .collect::<Result<Vec<_>>>()?;
289    match error_type {
290        ErrorType::X => Ok(StimInstruction::XError {
291            probability,
292            qubits,
293        }),
294        ErrorType::Y => Ok(StimInstruction::YError {
295            probability,
296            qubits,
297        }),
298        ErrorType::Z => Ok(StimInstruction::ZError {
299            probability,
300            qubits,
301        }),
302    }
303}
304fn parse_pauli_channel_1(parts: &[&str]) -> Result<StimInstruction> {
305    if parts.is_empty() {
306        return Err(SimulatorError::InvalidOperation(
307            "PAULI_CHANNEL_1 requires parameters".to_string(),
308        ));
309    }
310    let params = parse_parameters(parts[0])?;
311    if params.len() != 3 {
312        return Err(SimulatorError::InvalidOperation(
313            "PAULI_CHANNEL_1 requires 3 parameters (px, py, pz)".to_string(),
314        ));
315    }
316    let qubits = parts[1..]
317        .iter()
318        .map(|s| {
319            s.parse::<usize>().map_err(|_| {
320                SimulatorError::InvalidOperation(format!("Invalid qubit index: {}", s))
321            })
322        })
323        .collect::<Result<Vec<_>>>()?;
324    Ok(StimInstruction::PauliChannel1 {
325        px: params[0],
326        py: params[1],
327        pz: params[2],
328        qubits,
329    })
330}
331fn parse_pauli_channel_2(parts: &[&str]) -> Result<StimInstruction> {
332    if parts.is_empty() {
333        return Err(SimulatorError::InvalidOperation(
334            "PAULI_CHANNEL_2 requires parameters".to_string(),
335        ));
336    }
337    let probabilities = parse_parameters(parts[0])?;
338    if probabilities.len() != 15 {
339        return Err(SimulatorError::InvalidOperation(
340            "PAULI_CHANNEL_2 requires 15 parameters".to_string(),
341        ));
342    }
343    let qubits: Vec<usize> = parts[1..]
344        .iter()
345        .map(|s| {
346            s.parse::<usize>().map_err(|_| {
347                SimulatorError::InvalidOperation(format!("Invalid qubit index: {}", s))
348            })
349        })
350        .collect::<Result<Vec<_>>>()?;
351    let mut qubit_pairs = Vec::new();
352    for chunk in qubits.chunks(2) {
353        if chunk.len() == 2 {
354            qubit_pairs.push((chunk[0], chunk[1]));
355        }
356    }
357    Ok(StimInstruction::PauliChannel2 {
358        probabilities,
359        qubit_pairs,
360    })
361}
362fn parse_correlated_error(parts: &[&str]) -> Result<StimInstruction> {
363    if parts.is_empty() {
364        return Err(SimulatorError::InvalidOperation(
365            "CORRELATED_ERROR requires probability".to_string(),
366        ));
367    }
368    let probability = parse_parameter(parts[0])?;
369    let mut targets = Vec::new();
370    for part in parts.iter().skip(1) {
371        let pauli_char = part
372            .chars()
373            .next()
374            .ok_or_else(|| SimulatorError::InvalidOperation("Empty Pauli target".to_string()))?;
375        let pauli = match pauli_char.to_ascii_uppercase() {
376            'I' => PauliType::I,
377            'X' => PauliType::X,
378            'Y' => PauliType::Y,
379            'Z' => PauliType::Z,
380            _ => {
381                return Err(SimulatorError::InvalidOperation(format!(
382                    "Invalid Pauli type: {}",
383                    pauli_char
384                )));
385            }
386        };
387        let qubit = part[1..].parse::<usize>().map_err(|_| {
388            SimulatorError::InvalidOperation(format!("Invalid qubit in target: {}", part))
389        })?;
390        targets.push(PauliTarget { pauli, qubit });
391    }
392    Ok(StimInstruction::CorrelatedError {
393        probability,
394        targets,
395    })
396}
397fn parse_else_correlated_error(parts: &[&str]) -> Result<StimInstruction> {
398    if parts.is_empty() {
399        return Err(SimulatorError::InvalidOperation(
400            "ELSE_CORRELATED_ERROR requires probability".to_string(),
401        ));
402    }
403    let probability = parse_parameter(parts[0])?;
404    let mut targets = Vec::new();
405    for part in parts.iter().skip(1) {
406        let pauli_char = part
407            .chars()
408            .next()
409            .ok_or_else(|| SimulatorError::InvalidOperation("Empty Pauli target".to_string()))?;
410        let pauli = match pauli_char.to_ascii_uppercase() {
411            'I' => PauliType::I,
412            'X' => PauliType::X,
413            'Y' => PauliType::Y,
414            'Z' => PauliType::Z,
415            _ => {
416                return Err(SimulatorError::InvalidOperation(format!(
417                    "Invalid Pauli type: {}",
418                    pauli_char
419                )));
420            }
421        };
422        let qubit = part[1..].parse::<usize>().map_err(|_| {
423            SimulatorError::InvalidOperation(format!("Invalid qubit in target: {}", part))
424        })?;
425        targets.push(PauliTarget { pauli, qubit });
426    }
427    Ok(StimInstruction::ElseCorrelatedError {
428        probability,
429        targets,
430    })
431}
432fn parse_shift_coords(parts: &[&str]) -> Result<StimInstruction> {
433    let shifts = parts[1..]
434        .iter()
435        .map(|s| {
436            s.parse::<f64>().map_err(|_| {
437                SimulatorError::InvalidOperation(format!("Invalid coordinate shift: {}", s))
438            })
439        })
440        .collect::<Result<Vec<_>>>()?;
441    Ok(StimInstruction::ShiftCoords { shifts })
442}
443fn parse_qubit_coords(parts: &[&str]) -> Result<StimInstruction> {
444    if parts.is_empty() {
445        return Err(SimulatorError::InvalidOperation(
446            "QUBIT_COORDS requires qubit index".to_string(),
447        ));
448    }
449    let qubit = parse_parameter(parts[0])? as usize;
450    let coordinates = parts[1..]
451        .iter()
452        .map(|s| {
453            s.parse::<f64>()
454                .map_err(|_| SimulatorError::InvalidOperation(format!("Invalid coordinate: {}", s)))
455        })
456        .collect::<Result<Vec<_>>>()?;
457    Ok(StimInstruction::QubitCoords { qubit, coordinates })
458}
459fn parse_repeat(parts: &[&str]) -> Result<StimInstruction> {
460    // REPEAT blocks are handled at the `StimCircuit::from_str` level (multi-line).
461    // This fallback handles single-line `REPEAT N {}` with an empty body.
462    let count = parts
463        .get(1)
464        .and_then(|s| s.trim_end_matches('{').trim().parse::<usize>().ok())
465        .unwrap_or(1);
466    Ok(StimInstruction::Repeat {
467        count,
468        instructions: Vec::new(),
469    })
470}
471#[cfg(test)]
472mod tests {
473    use super::*;
474    #[test]
475    fn test_parse_single_qubit_gates() {
476        let circuit = StimCircuit::from_str("H 0\nS 1\nX 2").unwrap();
477        assert_eq!(circuit.num_qubits, 3);
478        assert_eq!(circuit.instructions.len(), 3);
479        match &circuit.instructions[0] {
480            StimInstruction::SingleQubitGate { gate_type, qubit } => {
481                assert_eq!(*gate_type, SingleQubitGateType::H);
482                assert_eq!(*qubit, 0);
483            }
484            _ => panic!("Expected SingleQubitGate"),
485        }
486    }
487    #[test]
488    fn test_parse_two_qubit_gates() {
489        let circuit = StimCircuit::from_str("CNOT 0 1\nCZ 1 2").unwrap();
490        assert_eq!(circuit.num_qubits, 3);
491        assert_eq!(circuit.instructions.len(), 2);
492        match &circuit.instructions[0] {
493            StimInstruction::TwoQubitGate {
494                gate_type,
495                control,
496                target,
497            } => {
498                assert_eq!(*gate_type, TwoQubitGateType::CNOT);
499                assert_eq!(*control, 0);
500                assert_eq!(*target, 1);
501            }
502            _ => panic!("Expected TwoQubitGate"),
503        }
504    }
505    #[test]
506    fn test_parse_measurements() {
507        let circuit = StimCircuit::from_str("M 0 1\nMX 2\nMY 3").unwrap();
508        assert_eq!(circuit.instructions.len(), 3);
509        match &circuit.instructions[0] {
510            StimInstruction::Measure { basis, qubits } => {
511                assert_eq!(*basis, MeasurementBasis::Z);
512                assert_eq!(*qubits, vec![0, 1]);
513            }
514            _ => panic!("Expected Measure"),
515        }
516        match &circuit.instructions[1] {
517            StimInstruction::Measure { basis, qubits } => {
518                assert_eq!(*basis, MeasurementBasis::X);
519                assert_eq!(*qubits, vec![2]);
520            }
521            _ => panic!("Expected Measure"),
522        }
523    }
524    #[test]
525    fn test_parse_reset() {
526        let circuit = StimCircuit::from_str("R 0 1 2").unwrap();
527        assert_eq!(circuit.instructions.len(), 1);
528        match &circuit.instructions[0] {
529            StimInstruction::Reset { qubits } => {
530                assert_eq!(*qubits, vec![0, 1, 2]);
531            }
532            _ => panic!("Expected Reset"),
533        }
534    }
535    #[test]
536    fn test_parse_comments() {
537        let circuit = StimCircuit::from_str("# Bell state\nH 0\nCNOT 0 1\n# End").unwrap();
538        assert_eq!(circuit.metadata.len(), 2);
539        assert_eq!(circuit.gates().len(), 2);
540    }
541    #[test]
542    fn test_bell_state_circuit() {
543        let stim_code = r#"
544# Bell state preparation
545H 0
546CNOT 0 1
547M 0 1
548        "#;
549        let circuit = StimCircuit::from_str(stim_code).unwrap();
550        assert_eq!(circuit.num_qubits, 2);
551        let gates = circuit.gates();
552        assert_eq!(gates.len(), 2);
553        let measurements = circuit.measurements();
554        assert_eq!(measurements.len(), 1);
555        assert_eq!(measurements[0].1, vec![0, 1]);
556    }
557    #[test]
558    fn test_to_stim_string() {
559        let mut circuit = StimCircuit::new();
560        circuit.add_instruction(StimInstruction::SingleQubitGate {
561            gate_type: SingleQubitGateType::H,
562            qubit: 0,
563        });
564        circuit.add_instruction(StimInstruction::TwoQubitGate {
565            gate_type: TwoQubitGateType::CNOT,
566            control: 0,
567            target: 1,
568        });
569        circuit.add_instruction(StimInstruction::Measure {
570            basis: MeasurementBasis::Z,
571            qubits: vec![0, 1],
572        });
573        let stim_string = circuit.to_stim_string();
574        let parsed = StimCircuit::from_str(&stim_string).unwrap();
575        assert_eq!(parsed.num_qubits, circuit.num_qubits);
576        assert_eq!(parsed.gates().len(), circuit.gates().len());
577    }
578    #[test]
579    fn test_circuit_statistics() {
580        let stim_code = r#"
581H 0
582H 1
583CNOT 0 1
584CNOT 1 2
585M 0 1 2
586R 0
587        "#;
588        let circuit = StimCircuit::from_str(stim_code).unwrap();
589        let stats = circuit.statistics();
590        assert_eq!(stats.num_qubits, 3);
591        assert_eq!(stats.num_gates, 4);
592        assert_eq!(stats.num_measurements, 3);
593        assert_eq!(stats.num_resets, 1);
594        assert_eq!(stats.gate_counts.get("H"), Some(&2));
595        assert_eq!(stats.gate_counts.get("CNOT"), Some(&2));
596    }
597    #[test]
598    fn test_error_invalid_instruction() {
599        let result = StimCircuit::from_str("INVALID_GATE 0");
600        assert!(result.is_err());
601    }
602    #[test]
603    fn test_error_invalid_qubit() {
604        let result = StimCircuit::from_str("H abc");
605        assert!(result.is_err());
606    }
607    #[test]
608    fn test_error_wrong_arity() {
609        let result = StimCircuit::from_str("CNOT 0");
610        assert!(result.is_err());
611    }
612    #[test]
613    fn test_case_insensitive() {
614        let circuit = StimCircuit::from_str("h 0\ncnot 0 1").unwrap();
615        assert_eq!(circuit.gates().len(), 2);
616    }
617    #[test]
618    fn test_detector_parsing() {
619        let circuit = StimCircuit::from_str("DETECTOR rec[-1] rec[-2]").unwrap();
620        assert_eq!(circuit.instructions.len(), 1);
621        match &circuit.instructions[0] {
622            StimInstruction::Detector { record_targets, .. } => {
623                assert_eq!(record_targets.len(), 2);
624                assert_eq!(record_targets[0], -1);
625                assert_eq!(record_targets[1], -2);
626            }
627            _ => panic!("Expected Detector"),
628        }
629    }
630    #[test]
631    fn test_observable_include_parsing() {
632        let circuit = StimCircuit::from_str("OBSERVABLE_INCLUDE(0) rec[-1]").unwrap();
633        assert_eq!(circuit.instructions.len(), 1);
634        match &circuit.instructions[0] {
635            StimInstruction::ObservableInclude {
636                observable_index,
637                record_targets,
638            } => {
639                assert_eq!(*observable_index, 0);
640                assert_eq!(record_targets.len(), 1);
641                assert_eq!(record_targets[0], -1);
642            }
643            _ => panic!("Expected ObservableInclude"),
644        }
645    }
646    #[test]
647    fn test_measure_reset_parsing() {
648        let circuit = StimCircuit::from_str("MR 0 1\nMRX 2").unwrap();
649        assert_eq!(circuit.instructions.len(), 2);
650        match &circuit.instructions[0] {
651            StimInstruction::MeasureReset { basis, qubits } => {
652                assert_eq!(*basis, MeasurementBasis::Z);
653                assert_eq!(qubits, &vec![0, 1]);
654            }
655            _ => panic!("Expected MeasureReset"),
656        }
657    }
658    #[test]
659    fn test_depolarize1_parsing() {
660        let circuit = StimCircuit::from_str("DEPOLARIZE1(0.01) 0 1 2").unwrap();
661        assert_eq!(circuit.instructions.len(), 1);
662        match &circuit.instructions[0] {
663            StimInstruction::Depolarize1 {
664                probability,
665                qubits,
666            } => {
667                assert!((probability - 0.01).abs() < 1e-10);
668                assert_eq!(qubits, &vec![0, 1, 2]);
669            }
670            _ => panic!("Expected Depolarize1"),
671        }
672    }
673    #[test]
674    fn test_x_error_parsing() {
675        let circuit = StimCircuit::from_str("X_ERROR(0.05) 0 1").unwrap();
676        assert_eq!(circuit.instructions.len(), 1);
677        match &circuit.instructions[0] {
678            StimInstruction::XError {
679                probability,
680                qubits,
681            } => {
682                assert!((probability - 0.05).abs() < 1e-10);
683                assert_eq!(qubits, &vec![0, 1]);
684            }
685            _ => panic!("Expected XError"),
686        }
687    }
688    #[test]
689    fn test_pauli_channel_1_parsing() {
690        let circuit = StimCircuit::from_str("PAULI_CHANNEL_1(0.01,0.02,0.03) 0 1").unwrap();
691        assert_eq!(circuit.instructions.len(), 1);
692        match &circuit.instructions[0] {
693            StimInstruction::PauliChannel1 { px, py, pz, qubits } => {
694                assert!((px - 0.01).abs() < 1e-10);
695                assert!((py - 0.02).abs() < 1e-10);
696                assert!((pz - 0.03).abs() < 1e-10);
697                assert_eq!(qubits, &vec![0, 1]);
698            }
699            _ => panic!("Expected PauliChannel1"),
700        }
701    }
702    #[test]
703    fn test_correlated_error_parsing() {
704        let circuit = StimCircuit::from_str("CORRELATED_ERROR(0.1) X0 Y1 Z2").unwrap();
705        assert_eq!(circuit.instructions.len(), 1);
706        match &circuit.instructions[0] {
707            StimInstruction::CorrelatedError {
708                probability,
709                targets,
710            } => {
711                assert!((probability - 0.1).abs() < 1e-10);
712                assert_eq!(targets.len(), 3);
713                assert_eq!(targets[0].pauli, PauliType::X);
714                assert_eq!(targets[0].qubit, 0);
715                assert_eq!(targets[1].pauli, PauliType::Y);
716                assert_eq!(targets[1].qubit, 1);
717                assert_eq!(targets[2].pauli, PauliType::Z);
718                assert_eq!(targets[2].qubit, 2);
719            }
720            _ => panic!("Expected CorrelatedError"),
721        }
722    }
723    #[test]
724    fn test_shift_coords_parsing() {
725        let circuit = StimCircuit::from_str("SHIFT_COORDS 1.0 2.0 3.0").unwrap();
726        assert_eq!(circuit.instructions.len(), 1);
727        match &circuit.instructions[0] {
728            StimInstruction::ShiftCoords { shifts } => {
729                assert_eq!(shifts.len(), 3);
730                assert!((shifts[0] - 1.0).abs() < 1e-10);
731                assert!((shifts[1] - 2.0).abs() < 1e-10);
732                assert!((shifts[2] - 3.0).abs() < 1e-10);
733            }
734            _ => panic!("Expected ShiftCoords"),
735        }
736    }
737    #[test]
738    fn test_full_error_correction_circuit() {
739        let circuit_str = r#"
740            # Surface code preparation
741            H 0
742            CNOT 0 1
743            CNOT 0 2
744            M 1 2
745            DETECTOR rec[-1] rec[-2]
746            OBSERVABLE_INCLUDE(0) rec[-1]
747            X_ERROR(0.01) 0
748            Z_ERROR(0.01) 1 2
749        "#;
750        let circuit = StimCircuit::from_str(circuit_str).unwrap();
751        assert!(circuit.instructions.len() >= 7);
752        let has_detector = circuit
753            .instructions
754            .iter()
755            .any(|inst| matches!(inst, StimInstruction::Detector { .. }));
756        assert!(has_detector);
757        let has_observable = circuit
758            .instructions
759            .iter()
760            .any(|inst| matches!(inst, StimInstruction::ObservableInclude { .. }));
761        assert!(has_observable);
762    }
763    #[test]
764    fn test_e_shorthand_parsing() {
765        let circuit = StimCircuit::from_str("E(0.1) X0 Y1 Z2").unwrap();
766        assert_eq!(circuit.instructions.len(), 1);
767        match &circuit.instructions[0] {
768            StimInstruction::CorrelatedError {
769                probability,
770                targets,
771            } => {
772                assert!((probability - 0.1).abs() < 1e-10);
773                assert_eq!(targets.len(), 3);
774                assert_eq!(targets[0].pauli, PauliType::X);
775                assert_eq!(targets[0].qubit, 0);
776            }
777            _ => panic!("Expected CorrelatedError"),
778        }
779    }
780    #[test]
781    fn test_else_correlated_error_parsing() {
782        let circuit = StimCircuit::from_str("ELSE_CORRELATED_ERROR(0.2) X0 Z1").unwrap();
783        assert_eq!(circuit.instructions.len(), 1);
784        match &circuit.instructions[0] {
785            StimInstruction::ElseCorrelatedError {
786                probability,
787                targets,
788            } => {
789                assert!((probability - 0.2).abs() < 1e-10);
790                assert_eq!(targets.len(), 2);
791                assert_eq!(targets[0].pauli, PauliType::X);
792                assert_eq!(targets[0].qubit, 0);
793                assert_eq!(targets[1].pauli, PauliType::Z);
794                assert_eq!(targets[1].qubit, 1);
795            }
796            _ => panic!("Expected ElseCorrelatedError"),
797        }
798    }
799    #[test]
800    fn test_e_else_chain() {
801        let circuit_str = r#"
802            E(0.1) X0
803            ELSE_CORRELATED_ERROR(0.2) Y0
804        "#;
805        let circuit = StimCircuit::from_str(circuit_str).unwrap();
806        assert_eq!(circuit.instructions.len(), 2);
807        assert!(matches!(
808            &circuit.instructions[0],
809            StimInstruction::CorrelatedError { .. }
810        ));
811        assert!(matches!(
812            &circuit.instructions[1],
813            StimInstruction::ElseCorrelatedError { .. }
814        ));
815    }
816}