1use crate::error::{Result, SimulatorError};
6use crate::stabilizer::StabilizerGate;
7
8use std::collections::HashMap;
9
10use super::functions::parse_instruction;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum PauliType {
15 I,
16 X,
17 Y,
18 Z,
19}
20#[derive(Debug, Clone)]
22pub struct StimCircuit {
23 pub instructions: Vec<StimInstruction>,
25 pub num_qubits: usize,
27 pub metadata: Vec<String>,
29}
30impl StimCircuit {
31 pub fn new() -> Self {
33 Self {
34 instructions: Vec::new(),
35 num_qubits: 0,
36 metadata: Vec::new(),
37 }
38 }
39 pub fn add_instruction(&mut self, instruction: StimInstruction) {
41 match &instruction {
42 StimInstruction::SingleQubitGate { qubit, .. } => {
43 self.num_qubits = self.num_qubits.max(*qubit + 1);
44 }
45 StimInstruction::TwoQubitGate {
46 control, target, ..
47 } => {
48 self.num_qubits = self.num_qubits.max(*control + 1).max(*target + 1);
49 }
50 StimInstruction::Measure { qubits, .. } | StimInstruction::Reset { qubits } => {
51 if let Some(&max_qubit) = qubits.iter().max() {
52 self.num_qubits = self.num_qubits.max(max_qubit + 1);
53 }
54 }
55 _ => {}
56 }
57 self.instructions.push(instruction);
58 }
59 pub fn from_str(s: &str) -> Result<Self> {
61 let mut circuit = Self::new();
62 for (line_num, line) in s.lines().enumerate() {
63 let line = line.trim();
64 if line.is_empty() {
65 continue;
66 }
67 if let Some(stripped) = line.strip_prefix('#') {
68 circuit.metadata.push(stripped.trim().to_string());
69 circuit.add_instruction(StimInstruction::Comment(stripped.trim().to_string()));
70 continue;
71 }
72 match parse_instruction(line) {
73 Ok(instruction) => circuit.add_instruction(instruction),
74 Err(e) => {
75 return Err(SimulatorError::InvalidOperation(format!(
76 "Line {}: {}",
77 line_num + 1,
78 e
79 )));
80 }
81 }
82 }
83 Ok(circuit)
84 }
85 pub fn gates(&self) -> Vec<StabilizerGate> {
87 self.instructions
88 .iter()
89 .filter_map(|inst| match inst {
90 StimInstruction::SingleQubitGate { gate_type, qubit } => {
91 Some(gate_type.to_stabilizer_gate(*qubit))
92 }
93 StimInstruction::TwoQubitGate {
94 gate_type,
95 control,
96 target,
97 } => Some(gate_type.to_stabilizer_gate(*control, *target)),
98 _ => None,
99 })
100 .collect()
101 }
102 pub fn measurements(&self) -> Vec<(MeasurementBasis, Vec<usize>)> {
104 self.instructions
105 .iter()
106 .filter_map(|inst| match inst {
107 StimInstruction::Measure { basis, qubits } => Some((*basis, qubits.clone())),
108 _ => None,
109 })
110 .collect()
111 }
112 pub fn resets(&self) -> Vec<Vec<usize>> {
114 self.instructions
115 .iter()
116 .filter_map(|inst| match inst {
117 StimInstruction::Reset { qubits } => Some(qubits.clone()),
118 _ => None,
119 })
120 .collect()
121 }
122 pub fn to_stim_string(&self) -> String {
124 let mut output = String::new();
125 for instruction in &self.instructions {
126 match instruction {
127 StimInstruction::SingleQubitGate { gate_type, qubit } => {
128 let gate_name = match gate_type {
129 SingleQubitGateType::H => "H",
130 SingleQubitGateType::S => "S",
131 SingleQubitGateType::SDag => "S_DAG",
132 SingleQubitGateType::SqrtX => "SQRT_X",
133 SingleQubitGateType::SqrtXDag => "SQRT_X_DAG",
134 SingleQubitGateType::SqrtY => "SQRT_Y",
135 SingleQubitGateType::SqrtYDag => "SQRT_Y_DAG",
136 SingleQubitGateType::X => "X",
137 SingleQubitGateType::Y => "Y",
138 SingleQubitGateType::Z => "Z",
139 };
140 output.push_str(&format!("{} {}\n", gate_name, qubit));
141 }
142 StimInstruction::TwoQubitGate {
143 gate_type,
144 control,
145 target,
146 } => {
147 let gate_name = match gate_type {
148 TwoQubitGateType::CNOT => "CNOT",
149 TwoQubitGateType::CZ => "CZ",
150 TwoQubitGateType::CY => "CY",
151 TwoQubitGateType::SWAP => "SWAP",
152 };
153 output.push_str(&format!("{} {} {}\n", gate_name, control, target));
154 }
155 StimInstruction::Measure { basis, qubits } => {
156 let measure_name = match basis {
157 MeasurementBasis::Z => "M",
158 MeasurementBasis::X => "MX",
159 MeasurementBasis::Y => "MY",
160 };
161 output.push_str(&format!(
162 "{} {}\n",
163 measure_name,
164 qubits
165 .iter()
166 .map(|q| q.to_string())
167 .collect::<Vec<_>>()
168 .join(" ")
169 ));
170 }
171 StimInstruction::Reset { qubits } => {
172 output.push_str(&format!(
173 "R {}\n",
174 qubits
175 .iter()
176 .map(|q| q.to_string())
177 .collect::<Vec<_>>()
178 .join(" ")
179 ));
180 }
181 StimInstruction::Comment(comment) => {
182 output.push_str(&format!("# {}\n", comment));
183 }
184 StimInstruction::Tick => {
185 output.push_str("TICK\n");
186 }
187 StimInstruction::Detector {
188 coordinates,
189 record_targets,
190 } => {
191 output.push_str("DETECTOR");
192 for coord in coordinates {
193 output.push_str(&format!(" {}", coord));
194 }
195 for target in record_targets {
196 output.push_str(&format!(" rec[{}]", target));
197 }
198 output.push('\n');
199 }
200 StimInstruction::ObservableInclude {
201 observable_index,
202 record_targets,
203 } => {
204 output.push_str(&format!("OBSERVABLE_INCLUDE({})", observable_index));
205 for target in record_targets {
206 output.push_str(&format!(" rec[{}]", target));
207 }
208 output.push('\n');
209 }
210 StimInstruction::MeasureReset { basis, qubits } => {
211 let measure_name = match basis {
212 MeasurementBasis::Z => "MR",
213 MeasurementBasis::X => "MRX",
214 MeasurementBasis::Y => "MRY",
215 };
216 output.push_str(&format!(
217 "{} {}\n",
218 measure_name,
219 qubits
220 .iter()
221 .map(|q| q.to_string())
222 .collect::<Vec<_>>()
223 .join(" ")
224 ));
225 }
226 StimInstruction::Depolarize1 {
227 probability,
228 qubits,
229 } => {
230 output.push_str(&format!(
231 "DEPOLARIZE1({}) {}\n",
232 probability,
233 qubits
234 .iter()
235 .map(|q| q.to_string())
236 .collect::<Vec<_>>()
237 .join(" ")
238 ));
239 }
240 StimInstruction::Depolarize2 {
241 probability,
242 qubit_pairs,
243 } => {
244 output.push_str(&format!("DEPOLARIZE2({})", probability));
245 for (q1, q2) in qubit_pairs {
246 output.push_str(&format!(" {} {}", q1, q2));
247 }
248 output.push('\n');
249 }
250 StimInstruction::XError {
251 probability,
252 qubits,
253 } => {
254 output.push_str(&format!(
255 "X_ERROR({}) {}\n",
256 probability,
257 qubits
258 .iter()
259 .map(|q| q.to_string())
260 .collect::<Vec<_>>()
261 .join(" ")
262 ));
263 }
264 StimInstruction::YError {
265 probability,
266 qubits,
267 } => {
268 output.push_str(&format!(
269 "Y_ERROR({}) {}\n",
270 probability,
271 qubits
272 .iter()
273 .map(|q| q.to_string())
274 .collect::<Vec<_>>()
275 .join(" ")
276 ));
277 }
278 StimInstruction::ZError {
279 probability,
280 qubits,
281 } => {
282 output.push_str(&format!(
283 "Z_ERROR({}) {}\n",
284 probability,
285 qubits
286 .iter()
287 .map(|q| q.to_string())
288 .collect::<Vec<_>>()
289 .join(" ")
290 ));
291 }
292 StimInstruction::PauliChannel1 { px, py, pz, qubits } => {
293 output.push_str(&format!(
294 "PAULI_CHANNEL_1({},{},{}) {}\n",
295 px,
296 py,
297 pz,
298 qubits
299 .iter()
300 .map(|q| q.to_string())
301 .collect::<Vec<_>>()
302 .join(" ")
303 ));
304 }
305 StimInstruction::PauliChannel2 {
306 probabilities,
307 qubit_pairs,
308 } => {
309 output.push_str(&format!(
310 "PAULI_CHANNEL_2({})",
311 probabilities
312 .iter()
313 .map(|p| p.to_string())
314 .collect::<Vec<_>>()
315 .join(",")
316 ));
317 for (q1, q2) in qubit_pairs {
318 output.push_str(&format!(" {} {}", q1, q2));
319 }
320 output.push('\n');
321 }
322 StimInstruction::CorrelatedError {
323 probability,
324 targets,
325 } => {
326 output.push_str(&format!("E({})", probability));
327 for target in targets {
328 let pauli_char = match target.pauli {
329 PauliType::I => 'I',
330 PauliType::X => 'X',
331 PauliType::Y => 'Y',
332 PauliType::Z => 'Z',
333 };
334 output.push_str(&format!(" {}{}", pauli_char, target.qubit));
335 }
336 output.push('\n');
337 }
338 StimInstruction::ElseCorrelatedError {
339 probability,
340 targets,
341 } => {
342 output.push_str(&format!("ELSE_CORRELATED_ERROR({})", probability));
343 for target in targets {
344 let pauli_char = match target.pauli {
345 PauliType::I => 'I',
346 PauliType::X => 'X',
347 PauliType::Y => 'Y',
348 PauliType::Z => 'Z',
349 };
350 output.push_str(&format!(" {}{}", pauli_char, target.qubit));
351 }
352 output.push('\n');
353 }
354 StimInstruction::ShiftCoords { shifts } => {
355 output.push_str(&format!(
356 "SHIFT_COORDS {}\n",
357 shifts
358 .iter()
359 .map(|s| s.to_string())
360 .collect::<Vec<_>>()
361 .join(" ")
362 ));
363 }
364 StimInstruction::QubitCoords { qubit, coordinates } => {
365 output.push_str(&format!(
366 "QUBIT_COORDS({}) {}\n",
367 qubit,
368 coordinates
369 .iter()
370 .map(|c| c.to_string())
371 .collect::<Vec<_>>()
372 .join(" ")
373 ));
374 }
375 StimInstruction::Repeat {
376 count,
377 instructions,
378 } => {
379 output.push_str(&format!("REPEAT {} {{\n", count));
380 for inst in instructions {
381 output.push_str(" # nested instruction\n");
382 }
383 output.push_str("}\n");
384 }
385 }
386 }
387 output
388 }
389 pub fn statistics(&self) -> CircuitStatistics {
391 let mut num_gates = 0;
392 let mut num_measurements = 0;
393 let mut num_resets = 0;
394 let mut gate_counts = std::collections::HashMap::new();
395 for instruction in &self.instructions {
396 match instruction {
397 StimInstruction::SingleQubitGate { gate_type, .. } => {
398 num_gates += 1;
399 *gate_counts.entry(format!("{:?}", gate_type)).or_insert(0) += 1;
400 }
401 StimInstruction::TwoQubitGate { gate_type, .. } => {
402 num_gates += 1;
403 *gate_counts.entry(format!("{:?}", gate_type)).or_insert(0) += 1;
404 }
405 StimInstruction::Measure { qubits, .. } => {
406 num_measurements += qubits.len();
407 }
408 StimInstruction::Reset { qubits } => {
409 num_resets += qubits.len();
410 }
411 _ => {}
412 }
413 }
414 CircuitStatistics {
415 num_qubits: self.num_qubits,
416 num_gates,
417 num_measurements,
418 num_resets,
419 gate_counts,
420 }
421 }
422}
423#[derive(Debug, Clone, Copy, PartialEq, Eq)]
425pub struct PauliTarget {
426 pub pauli: PauliType,
427 pub qubit: usize,
428}
429#[derive(Debug, Clone, PartialEq)]
431pub enum StimInstruction {
432 SingleQubitGate {
434 gate_type: SingleQubitGateType,
435 qubit: usize,
436 },
437 TwoQubitGate {
439 gate_type: TwoQubitGateType,
440 control: usize,
441 target: usize,
442 },
443 Measure {
445 basis: MeasurementBasis,
446 qubits: Vec<usize>,
447 },
448 Reset { qubits: Vec<usize> },
450 Comment(String),
452 Tick,
454 Detector {
457 coordinates: Vec<f64>,
458 record_targets: Vec<i32>,
459 },
460 ObservableInclude {
463 observable_index: usize,
464 record_targets: Vec<i32>,
465 },
466 MeasureReset {
469 basis: MeasurementBasis,
470 qubits: Vec<usize>,
471 },
472 Depolarize1 {
475 probability: f64,
476 qubits: Vec<usize>,
477 },
478 Depolarize2 {
481 probability: f64,
482 qubit_pairs: Vec<(usize, usize)>,
483 },
484 XError {
487 probability: f64,
488 qubits: Vec<usize>,
489 },
490 YError {
493 probability: f64,
494 qubits: Vec<usize>,
495 },
496 ZError {
499 probability: f64,
500 qubits: Vec<usize>,
501 },
502 PauliChannel1 {
505 px: f64,
506 py: f64,
507 pz: f64,
508 qubits: Vec<usize>,
509 },
510 PauliChannel2 {
513 probabilities: Vec<f64>,
514 qubit_pairs: Vec<(usize, usize)>,
515 },
516 CorrelatedError {
519 probability: f64,
520 targets: Vec<PauliTarget>,
521 },
522 ElseCorrelatedError {
525 probability: f64,
526 targets: Vec<PauliTarget>,
527 },
528 ShiftCoords { shifts: Vec<f64> },
531 QubitCoords { qubit: usize, coordinates: Vec<f64> },
534 Repeat {
537 count: usize,
538 instructions: Vec<StimInstruction>,
539 },
540}
541#[derive(Debug, Clone, Copy, PartialEq, Eq)]
543pub enum SingleQubitGateType {
544 H,
545 S,
546 SDag,
547 SqrtX,
548 SqrtXDag,
549 SqrtY,
550 SqrtYDag,
551 X,
552 Y,
553 Z,
554}
555impl SingleQubitGateType {
556 pub fn to_stabilizer_gate(self, qubit: usize) -> StabilizerGate {
558 match self {
559 Self::H => StabilizerGate::H(qubit),
560 Self::S => StabilizerGate::S(qubit),
561 Self::SDag => StabilizerGate::SDag(qubit),
562 Self::SqrtX => StabilizerGate::SqrtX(qubit),
563 Self::SqrtXDag => StabilizerGate::SqrtXDag(qubit),
564 Self::SqrtY => StabilizerGate::SqrtY(qubit),
565 Self::SqrtYDag => StabilizerGate::SqrtYDag(qubit),
566 Self::X => StabilizerGate::X(qubit),
567 Self::Y => StabilizerGate::Y(qubit),
568 Self::Z => StabilizerGate::Z(qubit),
569 }
570 }
571}
572#[derive(Debug, Clone, Copy, PartialEq, Eq)]
574pub enum TwoQubitGateType {
575 CNOT,
576 CZ,
577 CY,
578 SWAP,
579}
580impl TwoQubitGateType {
581 pub fn to_stabilizer_gate(self, control: usize, target: usize) -> StabilizerGate {
583 match self {
584 Self::CNOT => StabilizerGate::CNOT(control, target),
585 Self::CZ => StabilizerGate::CZ(control, target),
586 Self::CY => StabilizerGate::CY(control, target),
587 Self::SWAP => StabilizerGate::SWAP(control, target),
588 }
589 }
590}
591#[derive(Debug, Clone, Copy, PartialEq, Eq)]
593pub enum MeasurementBasis {
594 Z,
595 X,
596 Y,
597}
598#[derive(Debug, Clone)]
600pub struct CircuitStatistics {
601 pub num_qubits: usize,
602 pub num_gates: usize,
603 pub num_measurements: usize,
604 pub num_resets: usize,
605 pub gate_counts: std::collections::HashMap<String, usize>,
606}
607#[derive(Debug, Clone, Copy)]
609pub(super) enum ErrorType {
610 X,
611 Y,
612 Z,
613}