1use crate::error::{Result, SimulatorError};
42use crate::stabilizer::StabilizerGate;
43use std::str::FromStr;
44
45#[derive(Debug, Clone, PartialEq)]
47pub enum StimInstruction {
48 SingleQubitGate {
50 gate_type: SingleQubitGateType,
51 qubit: usize,
52 },
53 TwoQubitGate {
55 gate_type: TwoQubitGateType,
56 control: usize,
57 target: usize,
58 },
59 Measure {
61 basis: MeasurementBasis,
62 qubits: Vec<usize>,
63 },
64 Reset { qubits: Vec<usize> },
66 Comment(String),
68 Tick,
70
71 Detector {
75 coordinates: Vec<f64>,
76 record_targets: Vec<i32>,
77 },
78
79 ObservableInclude {
82 observable_index: usize,
83 record_targets: Vec<i32>,
84 },
85
86 MeasureReset {
90 basis: MeasurementBasis,
91 qubits: Vec<usize>,
92 },
93
94 Depolarize1 {
98 probability: f64,
99 qubits: Vec<usize>,
100 },
101
102 Depolarize2 {
105 probability: f64,
106 qubit_pairs: Vec<(usize, usize)>,
107 },
108
109 XError {
112 probability: f64,
113 qubits: Vec<usize>,
114 },
115
116 YError {
119 probability: f64,
120 qubits: Vec<usize>,
121 },
122
123 ZError {
126 probability: f64,
127 qubits: Vec<usize>,
128 },
129
130 PauliChannel1 {
133 px: f64,
134 py: f64,
135 pz: f64,
136 qubits: Vec<usize>,
137 },
138
139 PauliChannel2 {
142 probabilities: Vec<f64>, qubit_pairs: Vec<(usize, usize)>,
144 },
145
146 CorrelatedError {
149 probability: f64,
150 targets: Vec<PauliTarget>,
151 },
152
153 ElseCorrelatedError {
156 probability: f64,
157 targets: Vec<PauliTarget>,
158 },
159
160 ShiftCoords { shifts: Vec<f64> },
164
165 QubitCoords { qubit: usize, coordinates: Vec<f64> },
168
169 Repeat {
172 count: usize,
173 instructions: Vec<StimInstruction>,
174 },
175}
176
177#[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
194pub enum TwoQubitGateType {
195 CNOT,
196 CZ,
197 CY,
198 SWAP,
199}
200
201#[derive(Debug, Clone, Copy, PartialEq, Eq)]
203pub struct PauliTarget {
204 pub pauli: PauliType,
205 pub qubit: usize,
206}
207
208#[derive(Debug, Clone, Copy, PartialEq, Eq)]
210pub enum PauliType {
211 I,
212 X,
213 Y,
214 Z,
215}
216
217#[derive(Debug, Clone, Copy, PartialEq, Eq)]
219pub enum MeasurementBasis {
220 Z,
221 X,
222 Y,
223}
224
225impl SingleQubitGateType {
226 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 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#[derive(Debug, Clone)]
257pub struct StimCircuit {
258 pub instructions: Vec<StimInstruction>,
260 pub num_qubits: usize,
262 pub metadata: Vec<String>,
264}
265
266impl StimCircuit {
267 pub fn new() -> Self {
269 Self {
270 instructions: Vec::new(),
271 num_qubits: 0,
272 metadata: Vec::new(),
273 }
274 }
275
276 pub fn add_instruction(&mut self, instruction: StimInstruction) {
278 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 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 if line.is_empty() {
308 continue;
309 }
310
311 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 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 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 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 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 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 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 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 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 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 output.push_str(" # nested instruction\n");
645 }
646 output.push_str("}\n");
647 }
648 }
649 }
650
651 output
652 }
653
654 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#[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
707fn 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 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 "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 "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 "M" => parse_measurement(&parts, MeasurementBasis::Z),
745 "MX" => parse_measurement(&parts, MeasurementBasis::X),
746 "MY" => parse_measurement(&parts, MeasurementBasis::Y),
747
748 "R" => parse_reset(&parts),
750
751 "TICK" => Ok(StimInstruction::Tick),
753
754 "DETECTOR" => parse_detector(&parts),
756 "OBSERVABLE_INCLUDE" => parse_observable_include(&parts),
757
758 "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 "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 "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#[derive(Debug, Clone, Copy)]
871enum ErrorType {
872 X,
873 Y,
874 Z,
875}
876
877fn 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
897fn 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 Err(SimulatorError::NotImplemented(
1281 "REPEAT blocks not yet implemented".to_string(),
1282 ))
1283}
1284
1285#[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 #[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 assert!(circuit.instructions.len() >= 7);
1616
1617 let has_detector = circuit
1619 .instructions
1620 .iter()
1621 .any(|inst| matches!(inst, StimInstruction::Detector { .. }));
1622 assert!(has_detector);
1623
1624 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 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 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}