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    Err(SimulatorError::NotImplemented(
461        "REPEAT blocks not yet implemented".to_string(),
462    ))
463}
464#[cfg(test)]
465mod tests {
466    use super::*;
467    #[test]
468    fn test_parse_single_qubit_gates() {
469        let circuit = StimCircuit::from_str("H 0\nS 1\nX 2").unwrap();
470        assert_eq!(circuit.num_qubits, 3);
471        assert_eq!(circuit.instructions.len(), 3);
472        match &circuit.instructions[0] {
473            StimInstruction::SingleQubitGate { gate_type, qubit } => {
474                assert_eq!(*gate_type, SingleQubitGateType::H);
475                assert_eq!(*qubit, 0);
476            }
477            _ => panic!("Expected SingleQubitGate"),
478        }
479    }
480    #[test]
481    fn test_parse_two_qubit_gates() {
482        let circuit = StimCircuit::from_str("CNOT 0 1\nCZ 1 2").unwrap();
483        assert_eq!(circuit.num_qubits, 3);
484        assert_eq!(circuit.instructions.len(), 2);
485        match &circuit.instructions[0] {
486            StimInstruction::TwoQubitGate {
487                gate_type,
488                control,
489                target,
490            } => {
491                assert_eq!(*gate_type, TwoQubitGateType::CNOT);
492                assert_eq!(*control, 0);
493                assert_eq!(*target, 1);
494            }
495            _ => panic!("Expected TwoQubitGate"),
496        }
497    }
498    #[test]
499    fn test_parse_measurements() {
500        let circuit = StimCircuit::from_str("M 0 1\nMX 2\nMY 3").unwrap();
501        assert_eq!(circuit.instructions.len(), 3);
502        match &circuit.instructions[0] {
503            StimInstruction::Measure { basis, qubits } => {
504                assert_eq!(*basis, MeasurementBasis::Z);
505                assert_eq!(*qubits, vec![0, 1]);
506            }
507            _ => panic!("Expected Measure"),
508        }
509        match &circuit.instructions[1] {
510            StimInstruction::Measure { basis, qubits } => {
511                assert_eq!(*basis, MeasurementBasis::X);
512                assert_eq!(*qubits, vec![2]);
513            }
514            _ => panic!("Expected Measure"),
515        }
516    }
517    #[test]
518    fn test_parse_reset() {
519        let circuit = StimCircuit::from_str("R 0 1 2").unwrap();
520        assert_eq!(circuit.instructions.len(), 1);
521        match &circuit.instructions[0] {
522            StimInstruction::Reset { qubits } => {
523                assert_eq!(*qubits, vec![0, 1, 2]);
524            }
525            _ => panic!("Expected Reset"),
526        }
527    }
528    #[test]
529    fn test_parse_comments() {
530        let circuit = StimCircuit::from_str("# Bell state\nH 0\nCNOT 0 1\n# End").unwrap();
531        assert_eq!(circuit.metadata.len(), 2);
532        assert_eq!(circuit.gates().len(), 2);
533    }
534    #[test]
535    fn test_bell_state_circuit() {
536        let stim_code = r#"
537# Bell state preparation
538H 0
539CNOT 0 1
540M 0 1
541        "#;
542        let circuit = StimCircuit::from_str(stim_code).unwrap();
543        assert_eq!(circuit.num_qubits, 2);
544        let gates = circuit.gates();
545        assert_eq!(gates.len(), 2);
546        let measurements = circuit.measurements();
547        assert_eq!(measurements.len(), 1);
548        assert_eq!(measurements[0].1, vec![0, 1]);
549    }
550    #[test]
551    fn test_to_stim_string() {
552        let mut circuit = StimCircuit::new();
553        circuit.add_instruction(StimInstruction::SingleQubitGate {
554            gate_type: SingleQubitGateType::H,
555            qubit: 0,
556        });
557        circuit.add_instruction(StimInstruction::TwoQubitGate {
558            gate_type: TwoQubitGateType::CNOT,
559            control: 0,
560            target: 1,
561        });
562        circuit.add_instruction(StimInstruction::Measure {
563            basis: MeasurementBasis::Z,
564            qubits: vec![0, 1],
565        });
566        let stim_string = circuit.to_stim_string();
567        let parsed = StimCircuit::from_str(&stim_string).unwrap();
568        assert_eq!(parsed.num_qubits, circuit.num_qubits);
569        assert_eq!(parsed.gates().len(), circuit.gates().len());
570    }
571    #[test]
572    fn test_circuit_statistics() {
573        let stim_code = r#"
574H 0
575H 1
576CNOT 0 1
577CNOT 1 2
578M 0 1 2
579R 0
580        "#;
581        let circuit = StimCircuit::from_str(stim_code).unwrap();
582        let stats = circuit.statistics();
583        assert_eq!(stats.num_qubits, 3);
584        assert_eq!(stats.num_gates, 4);
585        assert_eq!(stats.num_measurements, 3);
586        assert_eq!(stats.num_resets, 1);
587        assert_eq!(stats.gate_counts.get("H"), Some(&2));
588        assert_eq!(stats.gate_counts.get("CNOT"), Some(&2));
589    }
590    #[test]
591    fn test_error_invalid_instruction() {
592        let result = StimCircuit::from_str("INVALID_GATE 0");
593        assert!(result.is_err());
594    }
595    #[test]
596    fn test_error_invalid_qubit() {
597        let result = StimCircuit::from_str("H abc");
598        assert!(result.is_err());
599    }
600    #[test]
601    fn test_error_wrong_arity() {
602        let result = StimCircuit::from_str("CNOT 0");
603        assert!(result.is_err());
604    }
605    #[test]
606    fn test_case_insensitive() {
607        let circuit = StimCircuit::from_str("h 0\ncnot 0 1").unwrap();
608        assert_eq!(circuit.gates().len(), 2);
609    }
610    #[test]
611    fn test_detector_parsing() {
612        let circuit = StimCircuit::from_str("DETECTOR rec[-1] rec[-2]").unwrap();
613        assert_eq!(circuit.instructions.len(), 1);
614        match &circuit.instructions[0] {
615            StimInstruction::Detector { record_targets, .. } => {
616                assert_eq!(record_targets.len(), 2);
617                assert_eq!(record_targets[0], -1);
618                assert_eq!(record_targets[1], -2);
619            }
620            _ => panic!("Expected Detector"),
621        }
622    }
623    #[test]
624    fn test_observable_include_parsing() {
625        let circuit = StimCircuit::from_str("OBSERVABLE_INCLUDE(0) rec[-1]").unwrap();
626        assert_eq!(circuit.instructions.len(), 1);
627        match &circuit.instructions[0] {
628            StimInstruction::ObservableInclude {
629                observable_index,
630                record_targets,
631            } => {
632                assert_eq!(*observable_index, 0);
633                assert_eq!(record_targets.len(), 1);
634                assert_eq!(record_targets[0], -1);
635            }
636            _ => panic!("Expected ObservableInclude"),
637        }
638    }
639    #[test]
640    fn test_measure_reset_parsing() {
641        let circuit = StimCircuit::from_str("MR 0 1\nMRX 2").unwrap();
642        assert_eq!(circuit.instructions.len(), 2);
643        match &circuit.instructions[0] {
644            StimInstruction::MeasureReset { basis, qubits } => {
645                assert_eq!(*basis, MeasurementBasis::Z);
646                assert_eq!(qubits, &vec![0, 1]);
647            }
648            _ => panic!("Expected MeasureReset"),
649        }
650    }
651    #[test]
652    fn test_depolarize1_parsing() {
653        let circuit = StimCircuit::from_str("DEPOLARIZE1(0.01) 0 1 2").unwrap();
654        assert_eq!(circuit.instructions.len(), 1);
655        match &circuit.instructions[0] {
656            StimInstruction::Depolarize1 {
657                probability,
658                qubits,
659            } => {
660                assert!((probability - 0.01).abs() < 1e-10);
661                assert_eq!(qubits, &vec![0, 1, 2]);
662            }
663            _ => panic!("Expected Depolarize1"),
664        }
665    }
666    #[test]
667    fn test_x_error_parsing() {
668        let circuit = StimCircuit::from_str("X_ERROR(0.05) 0 1").unwrap();
669        assert_eq!(circuit.instructions.len(), 1);
670        match &circuit.instructions[0] {
671            StimInstruction::XError {
672                probability,
673                qubits,
674            } => {
675                assert!((probability - 0.05).abs() < 1e-10);
676                assert_eq!(qubits, &vec![0, 1]);
677            }
678            _ => panic!("Expected XError"),
679        }
680    }
681    #[test]
682    fn test_pauli_channel_1_parsing() {
683        let circuit = StimCircuit::from_str("PAULI_CHANNEL_1(0.01,0.02,0.03) 0 1").unwrap();
684        assert_eq!(circuit.instructions.len(), 1);
685        match &circuit.instructions[0] {
686            StimInstruction::PauliChannel1 { px, py, pz, qubits } => {
687                assert!((px - 0.01).abs() < 1e-10);
688                assert!((py - 0.02).abs() < 1e-10);
689                assert!((pz - 0.03).abs() < 1e-10);
690                assert_eq!(qubits, &vec![0, 1]);
691            }
692            _ => panic!("Expected PauliChannel1"),
693        }
694    }
695    #[test]
696    fn test_correlated_error_parsing() {
697        let circuit = StimCircuit::from_str("CORRELATED_ERROR(0.1) X0 Y1 Z2").unwrap();
698        assert_eq!(circuit.instructions.len(), 1);
699        match &circuit.instructions[0] {
700            StimInstruction::CorrelatedError {
701                probability,
702                targets,
703            } => {
704                assert!((probability - 0.1).abs() < 1e-10);
705                assert_eq!(targets.len(), 3);
706                assert_eq!(targets[0].pauli, PauliType::X);
707                assert_eq!(targets[0].qubit, 0);
708                assert_eq!(targets[1].pauli, PauliType::Y);
709                assert_eq!(targets[1].qubit, 1);
710                assert_eq!(targets[2].pauli, PauliType::Z);
711                assert_eq!(targets[2].qubit, 2);
712            }
713            _ => panic!("Expected CorrelatedError"),
714        }
715    }
716    #[test]
717    fn test_shift_coords_parsing() {
718        let circuit = StimCircuit::from_str("SHIFT_COORDS 1.0 2.0 3.0").unwrap();
719        assert_eq!(circuit.instructions.len(), 1);
720        match &circuit.instructions[0] {
721            StimInstruction::ShiftCoords { shifts } => {
722                assert_eq!(shifts.len(), 3);
723                assert!((shifts[0] - 1.0).abs() < 1e-10);
724                assert!((shifts[1] - 2.0).abs() < 1e-10);
725                assert!((shifts[2] - 3.0).abs() < 1e-10);
726            }
727            _ => panic!("Expected ShiftCoords"),
728        }
729    }
730    #[test]
731    fn test_full_error_correction_circuit() {
732        let circuit_str = r#"
733            # Surface code preparation
734            H 0
735            CNOT 0 1
736            CNOT 0 2
737            M 1 2
738            DETECTOR rec[-1] rec[-2]
739            OBSERVABLE_INCLUDE(0) rec[-1]
740            X_ERROR(0.01) 0
741            Z_ERROR(0.01) 1 2
742        "#;
743        let circuit = StimCircuit::from_str(circuit_str).unwrap();
744        assert!(circuit.instructions.len() >= 7);
745        let has_detector = circuit
746            .instructions
747            .iter()
748            .any(|inst| matches!(inst, StimInstruction::Detector { .. }));
749        assert!(has_detector);
750        let has_observable = circuit
751            .instructions
752            .iter()
753            .any(|inst| matches!(inst, StimInstruction::ObservableInclude { .. }));
754        assert!(has_observable);
755    }
756    #[test]
757    fn test_e_shorthand_parsing() {
758        let circuit = StimCircuit::from_str("E(0.1) X0 Y1 Z2").unwrap();
759        assert_eq!(circuit.instructions.len(), 1);
760        match &circuit.instructions[0] {
761            StimInstruction::CorrelatedError {
762                probability,
763                targets,
764            } => {
765                assert!((probability - 0.1).abs() < 1e-10);
766                assert_eq!(targets.len(), 3);
767                assert_eq!(targets[0].pauli, PauliType::X);
768                assert_eq!(targets[0].qubit, 0);
769            }
770            _ => panic!("Expected CorrelatedError"),
771        }
772    }
773    #[test]
774    fn test_else_correlated_error_parsing() {
775        let circuit = StimCircuit::from_str("ELSE_CORRELATED_ERROR(0.2) X0 Z1").unwrap();
776        assert_eq!(circuit.instructions.len(), 1);
777        match &circuit.instructions[0] {
778            StimInstruction::ElseCorrelatedError {
779                probability,
780                targets,
781            } => {
782                assert!((probability - 0.2).abs() < 1e-10);
783                assert_eq!(targets.len(), 2);
784                assert_eq!(targets[0].pauli, PauliType::X);
785                assert_eq!(targets[0].qubit, 0);
786                assert_eq!(targets[1].pauli, PauliType::Z);
787                assert_eq!(targets[1].qubit, 1);
788            }
789            _ => panic!("Expected ElseCorrelatedError"),
790        }
791    }
792    #[test]
793    fn test_e_else_chain() {
794        let circuit_str = r#"
795            E(0.1) X0
796            ELSE_CORRELATED_ERROR(0.2) Y0
797        "#;
798        let circuit = StimCircuit::from_str(circuit_str).unwrap();
799        assert_eq!(circuit.instructions.len(), 2);
800        assert!(matches!(
801            &circuit.instructions[0],
802            StimInstruction::CorrelatedError { .. }
803        ));
804        assert!(matches!(
805            &circuit.instructions[1],
806            StimInstruction::ElseCorrelatedError { .. }
807        ));
808    }
809}