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