1use crate::error::{Result, SimulatorError};
6
7use super::types::{
8 ErrorType, MeasurementBasis, PauliTarget, PauliType, SingleQubitGateType, StimCircuit,
9 StimInstruction, TwoQubitGateType,
10};
11
12pub(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}
134fn 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}
152fn 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}