1use crate::circuit::gate::{
16 CircuitGate, ClassicalDataOp, Directive, FrozenCircuit, Instruction, StandardGate,
17};
18use crate::circuit::operation::Operation;
19use crate::circuit::parameter::Parameter;
20use crate::circuit::{
21 Circuit, ClassicalBinaryOp, ClassicalCast, ClassicalCompareOp, ClassicalControlOp,
22 ClassicalExpr, ClassicalExprKind, ClassicalType, ClassicalUnaryOp, ClassicalValue,
23 ClassicalVar, Qubit,
24};
25use crate::ir::io_errors_equivalent;
26use indexmap::IndexMap;
27use std::collections::{HashMap, HashSet};
28use std::fmt::Write;
29use std::fs::File;
30use std::io::{self, Write as IoWrite};
31use std::path::Path;
32use std::sync::Arc;
33
34#[derive(Debug)]
35pub enum Qasm3DumpError {
36 IoError(io::Error),
37 FormatError(String),
38 UnsupportedInstruction(String),
39 UnsupportedClassicalData(String),
40 UnsupportedClassicalControl(String),
41 MeasureInGateNotAllowed,
42 ConflictingGateDefinition {
43 name: String,
44 existing_qubits: usize,
45 existing_params: usize,
46 conflicting_qubits: usize,
47 conflicting_params: usize,
48 },
49}
50
51impl PartialEq for Qasm3DumpError {
52 fn eq(&self, other: &Self) -> bool {
53 match (self, other) {
54 (Self::IoError(lhs), Self::IoError(rhs)) => io_errors_equivalent(lhs, rhs),
55 (Self::FormatError(lhs), Self::FormatError(rhs)) => lhs == rhs,
56 (Self::UnsupportedInstruction(lhs), Self::UnsupportedInstruction(rhs)) => lhs == rhs,
57 (Self::UnsupportedClassicalData(lhs), Self::UnsupportedClassicalData(rhs)) => {
58 lhs == rhs
59 }
60 (Self::UnsupportedClassicalControl(lhs), Self::UnsupportedClassicalControl(rhs)) => {
61 lhs == rhs
62 }
63 (Self::MeasureInGateNotAllowed, Self::MeasureInGateNotAllowed) => true,
64 (
65 Self::ConflictingGateDefinition {
66 name: lhs_name,
67 existing_qubits: lhs_existing_qubits,
68 existing_params: lhs_existing_params,
69 conflicting_qubits: lhs_conflicting_qubits,
70 conflicting_params: lhs_conflicting_params,
71 },
72 Self::ConflictingGateDefinition {
73 name: rhs_name,
74 existing_qubits: rhs_existing_qubits,
75 existing_params: rhs_existing_params,
76 conflicting_qubits: rhs_conflicting_qubits,
77 conflicting_params: rhs_conflicting_params,
78 },
79 ) => {
80 lhs_name == rhs_name
81 && lhs_existing_qubits == rhs_existing_qubits
82 && lhs_existing_params == rhs_existing_params
83 && lhs_conflicting_qubits == rhs_conflicting_qubits
84 && lhs_conflicting_params == rhs_conflicting_params
85 }
86 _ => false,
87 }
88 }
89}
90
91impl std::fmt::Display for Qasm3DumpError {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 match self {
94 Self::IoError(error) => write!(f, "IO error: {error}"),
95 Self::FormatError(s) => write!(f, "Format error: {s}"),
96 Self::UnsupportedInstruction(s) => write!(f, "Unsupported OpenQASM 3 instruction: {s}"),
97 Self::UnsupportedClassicalData(s) => {
98 write!(f, "Unsupported OpenQASM 3 classical data: {s}")
99 }
100 Self::UnsupportedClassicalControl(s) => {
101 write!(f, "Unsupported OpenQASM 3 classical control: {s}")
102 }
103 Self::MeasureInGateNotAllowed => {
104 write!(f, "Measurement inside gate definition is not allowed")
105 }
106 Self::ConflictingGateDefinition {
107 name,
108 existing_qubits,
109 existing_params,
110 conflicting_qubits,
111 conflicting_params,
112 } => write!(
113 f,
114 "Conflicting definitions for gate '{name}': existing definition has {existing_qubits} qubits and {existing_params} parameters, conflicting definition has {conflicting_qubits} qubits and {conflicting_params} parameters"
115 ),
116 }
117 }
118}
119
120impl std::error::Error for Qasm3DumpError {
121 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
122 match self {
123 Self::IoError(error) => Some(error),
124 _ => None,
125 }
126 }
127}
128impl From<std::fmt::Error> for Qasm3DumpError {
130 fn from(value: std::fmt::Error) -> Self {
131 Self::FormatError(value.to_string())
132 }
133}
134
135impl From<io::Error> for Qasm3DumpError {
136 fn from(value: io::Error) -> Self {
137 Self::IoError(value)
138 }
139}
140
141pub fn dump<P: AsRef<Path>>(circuit: &Circuit, path: P) -> Result<(), Qasm3DumpError> {
142 let qasm = dumps(circuit)?;
143 let mut file = File::create(path)?;
144 file.write_all(qasm.as_bytes())?;
145 Ok(())
146}
147
148pub fn to_path<P: AsRef<Path>>(circuit: &Circuit, path: P) -> Result<(), Qasm3DumpError> {
153 dump(circuit, path)
154}
155
156pub fn dumps(circuit: &Circuit) -> Result<String, Qasm3DumpError> {
157 let mut output = String::new();
158 writeln!(&mut output, "OPENQASM 3.0;")?;
159 writeln!(&mut output, "include \"stdgates.inc\";")?;
160
161 let mut used_standard_gates = HashSet::new();
162 collect_used_standard_gates(circuit.operations(), &mut used_standard_gates);
163 for gate in extension_definition_order(&used_standard_gates) {
164 writeln!(&mut output)?;
165 writeln!(&mut output, "{}", extension_gate_definition(gate).unwrap())?;
166 }
167
168 let mut defined_gates = IndexMap::new();
169 let mut unitary_gate_defs = IndexMap::new();
170 collect_gates(
171 circuit.operations(),
172 &mut defined_gates,
173 &mut unitary_gate_defs,
174 )?;
175 for gate in defined_gates.values() {
176 writeln!(&mut output)?;
177 dump_gate_definition(gate, &mut output)?;
178 }
179 for (label, frozen) in unitary_gate_defs.iter() {
180 writeln!(&mut output)?;
181 dump_unitary_gate_definition(label, frozen, &mut output)?;
182 }
183
184 writeln!(&mut output)?;
185 let mut name_allocator =
186 NameAllocator::new(reserved_top_level_names(&defined_gates, &unitary_gate_defs));
187 let param_map = dump_input_parameters(circuit, &mut output, &mut name_allocator)?;
188 let skipped_values = skipped_classical_value_declarations(circuit.operations())?;
189 let qubit_register = name_allocator.take("q");
190 let auto_measurements =
191 AutoMeasurementMap::new(circuit.operations(), name_allocator.take("meas"))?;
192 let qubit_map = logical_qubit_map(circuit, &qubit_register);
193 writeln!(&mut output, "qubit[{}] {qubit_register};", qubit_map.len())?;
194 dump_qubit_mapping_comment(&mut output, &qubit_map)?;
195 let classical_names =
196 ClassicalNameMap::new(circuit, &mut output, &skipped_values, &mut name_allocator)?;
197 auto_measurements.dump_declaration(&mut output)?;
198 writeln!(&mut output)?;
199 dump_global_phase(circuit, &mut output, ¶m_map)?;
200
201 dump_operations(
202 circuit,
203 circuit.operations(),
204 &mut output,
205 &qubit_map,
206 &qubit_register,
207 ¶m_map,
208 &classical_names,
209 &auto_measurements,
210 false,
211 )?;
212 Ok(output)
213}
214
215#[derive(Debug)]
216struct NameAllocator {
217 used: HashSet<String>,
218}
219
220impl NameAllocator {
221 fn new(used: HashSet<String>) -> Self {
222 Self { used }
223 }
224
225 fn take(&mut self, preferred: &str) -> String {
226 if self.used.insert(preferred.to_string()) {
227 return preferred.to_string();
228 }
229
230 let mut suffix = 0usize;
231 loop {
232 let candidate = format!("{preferred}{suffix}");
233 if self.used.insert(candidate.clone()) {
234 return candidate;
235 }
236 suffix += 1;
237 }
238 }
239
240 fn take_indexed(&mut self, prefix: &str, index: usize) -> String {
241 self.take(&format!("{prefix}{index}"))
242 }
243}
244
245fn reserved_top_level_names(
246 defined_gates: &IndexMap<String, CircuitGate>,
247 unitary_gate_defs: &IndexMap<String, Arc<FrozenCircuit>>,
248) -> HashSet<String> {
249 defined_gates
250 .keys()
251 .chain(unitary_gate_defs.keys())
252 .cloned()
253 .collect()
254}
255
256fn dump_input_parameters(
257 circuit: &Circuit,
258 output: &mut String,
259 name_allocator: &mut NameAllocator,
260) -> Result<HashMap<String, Parameter>, Qasm3DumpError> {
261 let mut param_map = HashMap::new();
262 for symbol in circuit.symbols() {
263 let name = name_allocator.take(symbol);
264 writeln!(output, "input angle[64] {name};")?;
265 if name != *symbol {
266 param_map.insert(symbol.clone(), Parameter::symbol(&name));
267 }
268 }
269 Ok(param_map)
270}
271
272fn logical_qubit_map(circuit: &Circuit, register_name: &str) -> HashMap<Qubit, String> {
273 let mut qubits = circuit.qubits();
274 qubits.sort_by_key(|qubit| qubit.index());
275 qubits
276 .into_iter()
277 .enumerate()
278 .map(|(logical_index, qubit)| (qubit, format!("{register_name}[{logical_index}]")))
279 .collect()
280}
281
282fn dump_qubit_mapping_comment(
283 output: &mut String,
284 qubit_map: &HashMap<Qubit, String>,
285) -> Result<(), Qasm3DumpError> {
286 if qubit_map.is_empty() {
287 return Ok(());
288 }
289
290 let mut mappings = qubit_map.iter().collect::<Vec<_>>();
291 mappings.sort_by_key(|(_, name)| {
292 name.trim_start_matches("q[")
293 .trim_end_matches(']')
294 .parse::<usize>()
295 .unwrap_or(usize::MAX)
296 });
297 if mappings
298 .iter()
299 .all(|(qubit, name)| **name == format!("q[{}]", qubit.index()))
300 {
301 return Ok(());
302 }
303
304 for (qubit, name) in mappings {
305 writeln!(output, "// {name} -> Q{}", qubit.index())?;
306 }
307 Ok(())
308}
309
310pub fn to_string(circuit: &Circuit) -> Result<String, Qasm3DumpError> {
314 dumps(circuit)
315}
316
317fn dump_global_phase(
318 circuit: &Circuit,
319 output: &mut String,
320 param_map: &HashMap<String, Parameter>,
321) -> Result<(), Qasm3DumpError> {
322 let phase = circuit.global_phase();
323 if phase.is_zero() {
324 return Ok(());
325 }
326 let phase = phase.substitute_many_simultaneous(param_map);
327 writeln!(output, "gphase({});", phase.to_string().replace("π", "pi"))?;
328 Ok(())
329}
330
331#[derive(Debug, Default)]
332struct ClassicalNameMap {
333 vars: HashMap<ClassicalVar, String>,
334 values: HashMap<ClassicalValue, String>,
335}
336
337impl ClassicalNameMap {
338 fn new(
339 circuit: &Circuit,
340 output: &mut String,
341 skipped_values: &HashSet<ClassicalValue>,
342 name_allocator: &mut NameAllocator,
343 ) -> Result<Self, Qasm3DumpError> {
344 let mut map = Self::default();
345 for (index, ty) in circuit.classical_vars().iter().copied().enumerate() {
346 let name = name_allocator.take_indexed("c", index);
347 writeln!(output, "{} {name};", classical_decl_type(ty)?)?;
348 map.vars
349 .insert(ClassicalVar::new(circuit.id(), index as u32, ty), name);
350 }
351 for (index, ty) in circuit.classical_values().iter().copied().enumerate() {
352 let value = ClassicalValue::new(circuit.id(), index as u32, ty);
353 if skipped_values.contains(&value) {
354 continue;
355 }
356 let name = name_allocator.take_indexed("v", index);
357 writeln!(output, "{} {name};", classical_decl_type(ty)?)?;
358 map.values.insert(value, name);
359 }
360 Ok(map)
361 }
362
363 fn var(&self, var: ClassicalVar) -> Result<&str, Qasm3DumpError> {
364 self.vars.get(&var).map(String::as_str).ok_or_else(|| {
365 Qasm3DumpError::UnsupportedClassicalData(format!(
366 "classical variable {} is not owned by this circuit",
367 var.index()
368 ))
369 })
370 }
371
372 fn value(&self, value: ClassicalValue) -> Result<&str, Qasm3DumpError> {
373 self.values.get(&value).map(String::as_str).ok_or_else(|| {
374 Qasm3DumpError::UnsupportedClassicalData(format!(
375 "classical value {} is not owned by this circuit",
376 value.index()
377 ))
378 })
379 }
380}
381
382fn classical_decl_type(ty: ClassicalType) -> Result<String, Qasm3DumpError> {
383 Ok(match ty {
384 ClassicalType::Bit => "bit".to_string(),
385 ClassicalType::Bool => "bool".to_string(),
386 ClassicalType::BitVec(width) => format!("bit[{}]", width.get()),
387 ClassicalType::UInt(width) => format!("uint[{}]", width.get()),
388 })
389}
390
391fn collect_gates(
392 operations: &[Operation],
393 defined_gates: &mut IndexMap<String, CircuitGate>,
394 unitary_gate_defs: &mut IndexMap<String, Arc<FrozenCircuit>>,
395) -> Result<(), Qasm3DumpError> {
396 for op in operations {
397 match &op.instruction {
398 Instruction::CircuitGate(gate) => {
399 collect_gates(
400 gate.circuit.circuit.operations(),
401 defined_gates,
402 unitary_gate_defs,
403 )?;
404 if let Some(existing) = defined_gates.get(gate.name.as_str()) {
405 if !Arc::ptr_eq(&existing.circuit, &gate.circuit) {
406 return Err(Qasm3DumpError::ConflictingGateDefinition {
407 name: gate.name.to_string(),
408 existing_qubits: existing.num_qubits(),
409 existing_params: existing.num_params(),
410 conflicting_qubits: gate.num_qubits(),
411 conflicting_params: gate.num_params(),
412 });
413 }
414 } else {
415 defined_gates.insert(gate.name.to_string(), *gate.clone());
416 }
417 }
418 Instruction::UnitaryGate(gate) => {
419 let Some(circuit) = gate.circuit() else {
420 return Err(Qasm3DumpError::UnsupportedInstruction(format!(
421 "matrix-only unitary gate '{}'",
422 gate.label()
423 )));
424 };
425 collect_gates(
426 circuit.circuit.operations(),
427 defined_gates,
428 unitary_gate_defs,
429 )?;
430 unitary_gate_defs
431 .entry(gate.label().to_string())
432 .or_insert_with(|| circuit.clone());
433 }
434 Instruction::ClassicalControl(control) => {
435 collect_control_gates(control, defined_gates, unitary_gate_defs)?
436 }
437 Instruction::Delay => {
438 return Err(Qasm3DumpError::UnsupportedInstruction("delay".to_string()));
439 }
440 _ => {}
441 }
442 }
443 Ok(())
444}
445
446fn collect_control_gates(
447 control: &ClassicalControlOp,
448 defined_gates: &mut IndexMap<String, CircuitGate>,
449 unitary_gate_defs: &mut IndexMap<String, Arc<FrozenCircuit>>,
450) -> Result<(), Qasm3DumpError> {
451 match control {
452 ClassicalControlOp::If(op) => {
453 collect_gates(
454 op.then_body().operations(),
455 defined_gates,
456 unitary_gate_defs,
457 )?;
458 if let Some(body) = op.else_body() {
459 collect_gates(body.operations(), defined_gates, unitary_gate_defs)?;
460 }
461 }
462 ClassicalControlOp::Switch(op) => {
463 for case in op.cases() {
464 collect_gates(case.body().operations(), defined_gates, unitary_gate_defs)?;
465 }
466 if let Some(body) = op.default() {
467 collect_gates(body.operations(), defined_gates, unitary_gate_defs)?;
468 }
469 }
470 ClassicalControlOp::While(_) => {
471 return Err(Qasm3DumpError::UnsupportedClassicalControl(
472 "while".to_string(),
473 ));
474 }
475 ClassicalControlOp::For(_) => {
476 return Err(Qasm3DumpError::UnsupportedClassicalControl(
477 "for".to_string(),
478 ));
479 }
480 ClassicalControlOp::Break | ClassicalControlOp::Continue => {
481 return Err(Qasm3DumpError::UnsupportedClassicalControl(
482 "break/continue".to_string(),
483 ));
484 }
485 }
486 Ok(())
487}
488
489fn dump_gate_definition(gate: &CircuitGate, output: &mut String) -> Result<(), Qasm3DumpError> {
490 if gate
491 .circuit
492 .circuit
493 .operations()
494 .iter()
495 .any(|operation| operation.instruction.has_measurement())
496 {
497 return Err(Qasm3DumpError::MeasureInGateNotAllowed);
498 }
499
500 let params: Vec<String> = gate.signature_params().iter().cloned().collect();
501 let params = if params.is_empty() {
502 String::new()
503 } else {
504 format!("({})", params.join(","))
505 };
506 let qubits: Vec<String> = (0..gate.num_qubits()).map(|i| format!("q{i}")).collect();
507 writeln!(
508 output,
509 "gate {}{} {} {{",
510 gate.name,
511 params,
512 qubits.join(",")
513 )?;
514
515 let mut qubit_map = HashMap::new();
516 for (index, qubit) in gate.circuit.circuit.qubits().iter().copied().enumerate() {
517 qubit_map.insert(qubit, format!("q{index}"));
518 }
519 dump_global_phase(&gate.circuit.circuit, output, &HashMap::new())?;
520 dump_operations(
521 &gate.circuit.circuit,
522 gate.circuit.circuit.operations(),
523 output,
524 &qubit_map,
525 "",
526 &HashMap::new(),
527 &ClassicalNameMap::default(),
528 &AutoMeasurementMap::default(),
529 true,
530 )?;
531 writeln!(output, "}}")?;
532 Ok(())
533}
534
535fn dump_unitary_gate_definition(
536 label: &str,
537 frozen: &FrozenCircuit,
538 output: &mut String,
539) -> Result<(), Qasm3DumpError> {
540 if frozen
541 .circuit
542 .operations()
543 .iter()
544 .any(|operation| operation.instruction.has_measurement())
545 {
546 return Err(Qasm3DumpError::MeasureInGateNotAllowed);
547 }
548 let qubits: Vec<String> = (0..frozen.circuit.qubits().len())
549 .map(|i| format!("q{i}"))
550 .collect();
551 writeln!(output, "gate {label} {} {{", qubits.join(","))?;
552 let mut qubit_map = HashMap::new();
553 for (index, qubit) in frozen.qubits_in_numeric_order().into_iter().enumerate() {
554 qubit_map.insert(qubit, format!("q{index}"));
555 }
556 dump_global_phase(&frozen.circuit, output, &HashMap::new())?;
557 dump_operations(
558 &frozen.circuit,
559 frozen.circuit.operations(),
560 output,
561 &qubit_map,
562 "",
563 &HashMap::new(),
564 &ClassicalNameMap::default(),
565 &AutoMeasurementMap::default(),
566 true,
567 )?;
568 writeln!(output, "}}")?;
569 Ok(())
570}
571
572#[allow(clippy::too_many_arguments)]
573fn dump_operations(
574 circuit: &Circuit,
575 operations: &[Operation],
576 output: &mut String,
577 qubit_map: &HashMap<Qubit, String>,
578 qubit_register: &str,
579 param_map: &HashMap<String, Parameter>,
580 classical_names: &ClassicalNameMap,
581 auto_measurements: &AutoMeasurementMap,
582 in_gate_body: bool,
583) -> Result<(), Qasm3DumpError> {
584 let mut index = 0;
585 let mut accepting_initializers = true;
586 while index < operations.len() {
587 let op = &operations[index];
588 if let Instruction::ClassicalData(ClassicalDataOp::Store { target, value }) =
589 &op.instruction
590 && accepting_initializers
591 && is_zero_initializer(*target, value)
592 {
593 index += 1;
594 continue;
595 }
596 accepting_initializers = false;
597
598 match &op.instruction {
599 Instruction::Standard(gate) => {
600 dump_standard_gate(gate, op, circuit, output, qubit_map, param_map)?
601 }
602 Instruction::McGate(gate) => {
603 dump_mc_gate(gate, op, circuit, output, qubit_map, param_map)?
604 }
605 Instruction::Directive(directive) => dump_directive(directive, op, output, qubit_map)?,
606 Instruction::CircuitGate(gate) => {
607 let params = format_params(op, circuit, param_map)?;
608 let qubits = map_qubits(op, qubit_map);
609 writeln!(output, "{}{} {};", gate.name, params, qubits.join(","))?;
610 }
611 Instruction::UnitaryGate(gate) => {
612 let qubits = map_qubits(op, qubit_map);
613 writeln!(output, "{} {};", gate.label(), qubits.join(","))?;
614 }
615 Instruction::ClassicalData(ClassicalDataOp::MeasureBit { result })
616 | Instruction::ClassicalData(ClassicalDataOp::MeasureBits { result }) => {
617 if in_gate_body {
618 return Err(Qasm3DumpError::MeasureInGateNotAllowed);
619 }
620 let next = operations.get(index + 1);
621 let (mut destination, consumes_store) =
622 measurement_destination(&op.instruction, *result, next)?;
623 if consumes_store
624 && operations[index + 2..]
625 .iter()
626 .any(|operation| operation.instruction.reads_value(*result))
627 {
628 return Err(Qasm3DumpError::UnsupportedClassicalData(format!(
629 "measurement value {} is read after being stored into a mutable variable",
630 result.index()
631 )));
632 }
633 if !consumes_store
634 && !operations[index + 1..]
635 .iter()
636 .any(|operation| operation.instruction.reads_value(*result))
637 {
638 destination = MeasurementDestination::Discard(*result);
639 }
640 dump_measurement(
641 op,
642 *result,
643 destination,
644 output,
645 qubit_map,
646 qubit_register,
647 classical_names,
648 auto_measurements,
649 )?;
650 if consumes_store {
651 index += 1;
652 }
653 }
654 Instruction::ClassicalData(ClassicalDataOp::Store { .. }) => {
655 dump_classical_store(op, output, classical_names)?;
656 }
657 Instruction::ClassicalControl(control) => {
658 if in_gate_body {
659 return Err(Qasm3DumpError::UnsupportedClassicalControl(
660 "control flow in gate body".to_string(),
661 ));
662 }
663 dump_control_flow(
664 control,
665 circuit,
666 output,
667 qubit_map,
668 qubit_register,
669 param_map,
670 classical_names,
671 auto_measurements,
672 )?;
673 }
674 Instruction::Delay => {
675 return Err(Qasm3DumpError::UnsupportedInstruction("delay".to_string()));
676 }
677 }
678 index += 1;
679 }
680 Ok(())
681}
682
683fn is_zero_initializer(target: ClassicalVar, value: &ClassicalExpr) -> bool {
684 match (target.ty(), value.kind()) {
685 (ClassicalType::Bit, ClassicalExprKind::BitLiteral(false)) => true,
686 (ClassicalType::Bool, ClassicalExprKind::BoolLiteral(false)) => true,
687 (ClassicalType::UInt(target_width), ClassicalExprKind::UIntLiteral { width, value: 0 }) => {
688 target_width == *width
689 }
690 (
691 ClassicalType::BitVec(target_width),
692 ClassicalExprKind::BitVecLiteral { width, value: 0 },
693 ) => target_width == *width,
694 _ => false,
695 }
696}
697
698fn dump_classical_store(
699 op: &Operation,
700 output: &mut String,
701 names: &ClassicalNameMap,
702) -> Result<(), Qasm3DumpError> {
703 let Instruction::ClassicalData(ClassicalDataOp::Store { target, value }) = &op.instruction
704 else {
705 return Err(Qasm3DumpError::UnsupportedClassicalData(
706 "non-store classical operation".to_string(),
707 ));
708 };
709 if target.ty() == ClassicalType::Bit {
710 return Err(Qasm3DumpError::UnsupportedClassicalData(
711 "scalar bit store assignment".to_string(),
712 ));
713 }
714 writeln!(
715 output,
716 "{} = {};",
717 names.var(*target)?,
718 classical_expr_to_qasm(value, names)?
719 )?;
720 Ok(())
721}
722
723#[derive(Debug, Clone, Copy)]
724enum MeasurementDestination {
725 Value(ClassicalValue),
726 Discard(ClassicalValue),
727 VarWhole(ClassicalVar),
728 VarBit(ClassicalVar, u32),
729}
730
731#[derive(Debug, Clone, Copy)]
732struct AutoMeasurementTarget {
733 start: usize,
734 width: usize,
735}
736
737#[derive(Debug, Default)]
738struct AutoMeasurementMap {
739 targets: HashMap<ClassicalValue, AutoMeasurementTarget>,
740 width: usize,
741 name: String,
742}
743
744impl AutoMeasurementMap {
745 fn new(operations: &[Operation], name: String) -> Result<Self, Qasm3DumpError> {
746 let mut map = Self {
747 name,
748 ..Self::default()
749 };
750 collect_auto_measurements(operations, &mut map)?;
751 Ok(map)
752 }
753
754 fn insert(&mut self, value: ClassicalValue) {
755 let width = value.ty().width() as usize;
756 let target = AutoMeasurementTarget {
757 start: self.width,
758 width,
759 };
760 self.width += width;
761 self.targets.insert(value, target);
762 }
763
764 fn target(&self, value: ClassicalValue) -> Option<AutoMeasurementTarget> {
765 self.targets.get(&value).copied()
766 }
767
768 fn dump_declaration(&self, output: &mut String) -> Result<(), Qasm3DumpError> {
769 if self.width > 0 {
770 writeln!(output, "bit[{}] {};", self.width, self.name)?;
771 }
772 Ok(())
773 }
774}
775
776fn collect_auto_measurements(
777 operations: &[Operation],
778 map: &mut AutoMeasurementMap,
779) -> Result<(), Qasm3DumpError> {
780 let mut index = 0;
781 while index < operations.len() {
782 let op = &operations[index];
783 match &op.instruction {
784 Instruction::ClassicalData(ClassicalDataOp::MeasureBit { result })
785 | Instruction::ClassicalData(ClassicalDataOp::MeasureBits { result }) => {
786 let next = operations.get(index + 1);
787 let (_, consumes_store) = measurement_destination(&op.instruction, *result, next)?;
788 let remaining_start = index + 1 + usize::from(consumes_store);
789 let read_later = operations[remaining_start..]
790 .iter()
791 .any(|operation| operation.instruction.reads_value(*result));
792 if !consumes_store && !read_later {
793 map.insert(*result);
794 }
795 if consumes_store {
796 index += 1;
797 }
798 }
799 Instruction::ClassicalControl(control) => {
800 collect_auto_measurements_in_control(control, map)?;
801 }
802 _ => {}
803 }
804 index += 1;
805 }
806 Ok(())
807}
808
809fn collect_auto_measurements_in_control(
810 control: &ClassicalControlOp,
811 map: &mut AutoMeasurementMap,
812) -> Result<(), Qasm3DumpError> {
813 match control {
814 ClassicalControlOp::If(op) => {
815 collect_auto_measurements(op.then_body().operations(), map)?;
816 if let Some(body) = op.else_body() {
817 collect_auto_measurements(body.operations(), map)?;
818 }
819 }
820 ClassicalControlOp::Switch(op) => {
821 for case in op.cases() {
822 collect_auto_measurements(case.body().operations(), map)?;
823 }
824 if let Some(body) = op.default() {
825 collect_auto_measurements(body.operations(), map)?;
826 }
827 }
828 ClassicalControlOp::While(op) => {
829 collect_auto_measurements(op.body().operations(), map)?;
830 }
831 ClassicalControlOp::For(op) => {
832 collect_auto_measurements(op.body().operations(), map)?;
833 }
834 ClassicalControlOp::Break | ClassicalControlOp::Continue => {}
835 }
836 Ok(())
837}
838
839fn skipped_classical_value_declarations(
840 operations: &[Operation],
841) -> Result<HashSet<ClassicalValue>, Qasm3DumpError> {
842 let mut skipped = HashSet::new();
843 collect_skipped_classical_values(operations, &mut skipped)?;
844 Ok(skipped)
845}
846
847fn collect_skipped_classical_values(
848 operations: &[Operation],
849 skipped: &mut HashSet<ClassicalValue>,
850) -> Result<(), Qasm3DumpError> {
851 let mut index = 0;
852 while index < operations.len() {
853 let op = &operations[index];
854 match &op.instruction {
855 Instruction::ClassicalData(ClassicalDataOp::MeasureBit { result })
856 | Instruction::ClassicalData(ClassicalDataOp::MeasureBits { result }) => {
857 let next = operations.get(index + 1);
858 let (_, consumes_store) = measurement_destination(&op.instruction, *result, next)?;
859 let remaining_start = index + 1 + usize::from(consumes_store);
860 let read_later = operations[remaining_start..]
861 .iter()
862 .any(|operation| operation.instruction.reads_value(*result));
863 if !read_later {
864 skipped.insert(*result);
865 }
866 if consumes_store {
867 index += 1;
868 }
869 }
870 Instruction::ClassicalControl(control) => {
871 collect_skipped_classical_values_in_control(control, skipped)?;
872 }
873 _ => {}
874 }
875 index += 1;
876 }
877 Ok(())
878}
879
880fn collect_skipped_classical_values_in_control(
881 control: &ClassicalControlOp,
882 skipped: &mut HashSet<ClassicalValue>,
883) -> Result<(), Qasm3DumpError> {
884 match control {
885 ClassicalControlOp::If(op) => {
886 collect_skipped_classical_values(op.then_body().operations(), skipped)?;
887 if let Some(body) = op.else_body() {
888 collect_skipped_classical_values(body.operations(), skipped)?;
889 }
890 }
891 ClassicalControlOp::Switch(op) => {
892 for case in op.cases() {
893 collect_skipped_classical_values(case.body().operations(), skipped)?;
894 }
895 if let Some(body) = op.default() {
896 collect_skipped_classical_values(body.operations(), skipped)?;
897 }
898 }
899 ClassicalControlOp::While(op) => {
900 collect_skipped_classical_values(op.body().operations(), skipped)?;
901 }
902 ClassicalControlOp::For(op) => {
903 collect_skipped_classical_values(op.body().operations(), skipped)?;
904 }
905 ClassicalControlOp::Break | ClassicalControlOp::Continue => {}
906 }
907 Ok(())
908}
909
910fn measurement_destination(
911 instruction: &Instruction,
912 result: ClassicalValue,
913 next: Option<&Operation>,
914) -> Result<(MeasurementDestination, bool), Qasm3DumpError> {
915 let Some(Operation {
916 instruction: Instruction::ClassicalData(ClassicalDataOp::Store { target, value }),
917 ..
918 }) = next
919 else {
920 return Ok((MeasurementDestination::Value(result), false));
921 };
922
923 if matches!(value.kind(), ClassicalExprKind::Value(value) if *value == result) {
924 return match instruction {
925 Instruction::ClassicalData(ClassicalDataOp::MeasureBit { .. })
926 if target.ty() == ClassicalType::Bit =>
927 {
928 Ok((MeasurementDestination::VarWhole(*target), true))
929 }
930 Instruction::ClassicalData(ClassicalDataOp::MeasureBits { .. })
931 if target.ty() == result.ty() =>
932 {
933 Ok((MeasurementDestination::VarWhole(*target), true))
934 }
935 _ => Err(Qasm3DumpError::UnsupportedClassicalData(
936 "measurement store has incompatible target type".to_string(),
937 )),
938 };
939 }
940
941 if matches!(
942 instruction,
943 Instruction::ClassicalData(ClassicalDataOp::MeasureBit { .. })
944 ) && let Some(bit) = value.measurement_store_bit(*target, result)
945 {
946 return Ok((MeasurementDestination::VarBit(*target, bit), true));
947 }
948
949 Err(Qasm3DumpError::UnsupportedClassicalData(
950 "measurement is followed by an unsupported store".to_string(),
951 ))
952}
953
954#[allow(clippy::too_many_arguments)]
955fn dump_measurement(
956 op: &Operation,
957 result: ClassicalValue,
958 destination: MeasurementDestination,
959 output: &mut String,
960 qubit_map: &HashMap<Qubit, String>,
961 qubit_register: &str,
962 names: &ClassicalNameMap,
963 auto_measurements: &AutoMeasurementMap,
964) -> Result<(), Qasm3DumpError> {
965 let qubits = map_qubits(op, qubit_map);
966 let (target, width) = match destination {
967 MeasurementDestination::Value(value) => {
968 (names.value(value)?.to_string(), value.ty().width())
969 }
970 MeasurementDestination::Discard(value) => {
971 let Some(target) = auto_measurements.target(value) else {
972 return Err(Qasm3DumpError::UnsupportedClassicalData(format!(
973 "discarded measurement value {} has no generated classical target",
974 value.index()
975 )));
976 };
977 (auto_measurements.name.clone(), target.width as u32)
978 }
979 MeasurementDestination::VarWhole(var) => (names.var(var)?.to_string(), var.ty().width()),
980 MeasurementDestination::VarBit(var, bit) => (format!("{}[{bit}]", names.var(var)?), 1),
981 };
982
983 if qubits.len() != width as usize || result.ty().width() != width {
984 return Err(Qasm3DumpError::UnsupportedClassicalData(format!(
985 "measurement width {} does not match destination width {width}",
986 qubits.len()
987 )));
988 }
989
990 if width == 1 {
991 let source = measurement_source(&qubits[0], qubit_map.len());
992 if let MeasurementDestination::Discard(value) = destination {
993 let target = auto_measurements.target(value).unwrap();
994 writeln!(
995 output,
996 "{}[{}] = measure {source};",
997 auto_measurements.name, target.start
998 )?;
999 } else {
1000 writeln!(output, "{target} = measure {source};")?;
1001 }
1002 } else if is_full_register_measurement(&qubits, qubit_register, qubit_map.len()) {
1003 if let MeasurementDestination::Discard(value) = destination {
1004 let target = auto_measurements.target(value).unwrap();
1005 for (index, qubit) in qubits.iter().enumerate() {
1006 let source = measurement_source(qubit, qubit_map.len());
1007 writeln!(
1008 output,
1009 "{}[{}] = measure {source};",
1010 auto_measurements.name,
1011 target.start + index
1012 )?;
1013 }
1014 } else {
1015 writeln!(output, "{target} = measure {qubit_register};")?;
1016 }
1017 } else {
1018 for (index, qubit) in qubits.iter().enumerate() {
1019 let source = measurement_source(qubit, qubit_map.len());
1020 if let MeasurementDestination::Discard(value) = destination {
1021 let target = auto_measurements.target(value).unwrap();
1022 writeln!(
1023 output,
1024 "{}[{}] = measure {source};",
1025 auto_measurements.name,
1026 target.start + index
1027 )?;
1028 } else {
1029 writeln!(output, "{target}[{index}] = measure {source};")?;
1030 }
1031 }
1032 }
1033 Ok(())
1034}
1035
1036fn measurement_source(qubit: &str, qubit_count: usize) -> &str {
1037 let _ = qubit_count;
1038 qubit
1039}
1040
1041fn is_full_register_measurement(
1042 qubits: &[String],
1043 qubit_register: &str,
1044 qubit_count: usize,
1045) -> bool {
1046 qubits.len() == qubit_count
1047 && qubits
1048 .iter()
1049 .enumerate()
1050 .all(|(index, qubit)| qubit == &format!("{qubit_register}[{index}]"))
1051}
1052
1053fn dump_standard_gate(
1054 gate: &StandardGate,
1055 op: &Operation,
1056 circuit: &Circuit,
1057 output: &mut String,
1058 qubit_map: &HashMap<Qubit, String>,
1059 param_map: &HashMap<String, Parameter>,
1060) -> Result<(), Qasm3DumpError> {
1061 let name = standard_gate_name(*gate)?;
1062 let params = format_params(op, circuit, param_map)?;
1063 let qubits = map_qubits(op, qubit_map);
1064 if *gate == StandardGate::GPhase {
1065 writeln!(output, "gphase{};", params)?;
1066 } else {
1067 writeln!(output, "{}{} {};", name, params, qubits.join(","))?;
1068 }
1069 Ok(())
1070}
1071
1072fn standard_gate_name(gate: StandardGate) -> Result<&'static str, Qasm3DumpError> {
1073 Ok(match gate {
1074 StandardGate::I => "id",
1075 StandardGate::X => "x",
1076 StandardGate::Y => "y",
1077 StandardGate::Z => "z",
1078 StandardGate::H => "h",
1079 StandardGate::S => "s",
1080 StandardGate::SDG => "sdg",
1081 StandardGate::T => "t",
1082 StandardGate::TDG => "tdg",
1083 StandardGate::X2P => "x2p",
1084 StandardGate::X2M => "x2m",
1085 StandardGate::Y2P => "y2p",
1086 StandardGate::Y2M => "y2m",
1087 StandardGate::RX => "rx",
1088 StandardGate::RY => "ry",
1089 StandardGate::RZ => "rz",
1090 StandardGate::RXX => "rxx",
1091 StandardGate::RYY => "ryy",
1092 StandardGate::RZZ => "rzz",
1093 StandardGate::RZX => "rzx",
1094 StandardGate::Phase => "p",
1095 StandardGate::GPhase => "gphase",
1096 StandardGate::U => "u3",
1097 StandardGate::CX => "cx",
1098 StandardGate::CY => "cy",
1099 StandardGate::CZ => "cz",
1100 StandardGate::CRX => "crx",
1101 StandardGate::CRY => "cry",
1102 StandardGate::CRZ => "crz",
1103 StandardGate::SWAP => "swap",
1104 StandardGate::CCX => "ccx",
1105 StandardGate::XY2P => "xy2p",
1106 StandardGate::XY2M => "xy2m",
1107 StandardGate::FSIM => "fsim",
1108 StandardGate::XY | StandardGate::RXY => {
1109 return Err(Qasm3DumpError::UnsupportedInstruction(format!(
1110 "standard gate {gate}"
1111 )));
1112 }
1113 })
1114}
1115
1116fn dump_mc_gate(
1117 gate: &crate::circuit::gate::MCGate,
1118 op: &Operation,
1119 circuit: &Circuit,
1120 output: &mut String,
1121 qubit_map: &HashMap<Qubit, String>,
1122 param_map: &HashMap<String, Parameter>,
1123) -> Result<(), Qasm3DumpError> {
1124 let name = match (gate.num_ctrl_qubits(), gate.base_gate()) {
1125 (1, StandardGate::X) => "cx",
1126 (1, StandardGate::Y) => "cy",
1127 (1, StandardGate::Z) => "cz",
1128 (1, StandardGate::H) => "ch",
1129 (1, StandardGate::RX) => "crx",
1130 (1, StandardGate::RY) => "cry",
1131 (1, StandardGate::RZ) => "crz",
1132 (1, StandardGate::Phase) => "cp",
1133 (1, StandardGate::CX) | (2, StandardGate::X) => "ccx",
1134 (1, StandardGate::SWAP) => "cswap",
1135 _ => {
1136 return Err(Qasm3DumpError::UnsupportedInstruction(format!(
1137 "multi-controlled {} with {} controls",
1138 gate.base_gate(),
1139 gate.num_ctrl_qubits()
1140 )));
1141 }
1142 };
1143 let params = format_params(op, circuit, param_map)?;
1144 let qubits = map_qubits(op, qubit_map);
1145 writeln!(output, "{}{} {};", name, params, qubits.join(","))?;
1146 Ok(())
1147}
1148
1149fn dump_directive(
1150 directive: &Directive,
1151 op: &Operation,
1152 output: &mut String,
1153 qubit_map: &HashMap<Qubit, String>,
1154) -> Result<(), Qasm3DumpError> {
1155 let qubits = map_qubits(op, qubit_map);
1156 match directive {
1157 Directive::Measure => Err(Qasm3DumpError::UnsupportedClassicalData(
1158 "legacy measurement has no classical destination".to_string(),
1159 )),
1160 Directive::Barrier => {
1161 writeln!(output, "barrier {};", qubits.join(","))?;
1162 Ok(())
1163 }
1164 Directive::Reset => {
1165 for qubit in qubits {
1166 writeln!(output, "reset {qubit};")?;
1167 }
1168 Ok(())
1169 }
1170 }
1171}
1172
1173#[allow(clippy::too_many_arguments)]
1174fn dump_control_flow(
1175 control: &ClassicalControlOp,
1176 circuit: &Circuit,
1177 output: &mut String,
1178 qubit_map: &HashMap<Qubit, String>,
1179 qubit_register: &str,
1180 param_map: &HashMap<String, Parameter>,
1181 classical_names: &ClassicalNameMap,
1182 auto_measurements: &AutoMeasurementMap,
1183) -> Result<(), Qasm3DumpError> {
1184 match control {
1185 ClassicalControlOp::If(op) => {
1186 let condition = classical_expr_to_qasm(op.condition(), classical_names)?;
1187 writeln!(output, "if ({condition}) {{")?;
1188 dump_operations(
1189 circuit,
1190 op.then_body().operations(),
1191 output,
1192 qubit_map,
1193 qubit_register,
1194 param_map,
1195 classical_names,
1196 auto_measurements,
1197 false,
1198 )?;
1199 if let Some(body) = op.else_body() {
1200 writeln!(output, "}} else {{")?;
1201 dump_operations(
1202 circuit,
1203 body.operations(),
1204 output,
1205 qubit_map,
1206 qubit_register,
1207 param_map,
1208 classical_names,
1209 auto_measurements,
1210 false,
1211 )?;
1212 }
1213 writeln!(output, "}}")?;
1214 }
1215 ClassicalControlOp::Switch(op) => {
1216 let target = classical_expr_to_qasm(op.target(), classical_names)?;
1217 writeln!(output, "switch ({target}) {{")?;
1218 for case in op.cases() {
1219 writeln!(output, "case {} {{", case.value())?;
1220 dump_operations(
1221 circuit,
1222 case.body().operations(),
1223 output,
1224 qubit_map,
1225 qubit_register,
1226 param_map,
1227 classical_names,
1228 auto_measurements,
1229 false,
1230 )?;
1231 writeln!(output, "}}")?;
1232 }
1233 if let Some(body) = op.default() {
1234 writeln!(output, "default {{")?;
1235 dump_operations(
1236 circuit,
1237 body.operations(),
1238 output,
1239 qubit_map,
1240 qubit_register,
1241 param_map,
1242 classical_names,
1243 auto_measurements,
1244 false,
1245 )?;
1246 writeln!(output, "}}")?;
1247 }
1248 writeln!(output, "}}")?;
1249 }
1250 ClassicalControlOp::While(_) => {
1251 return Err(Qasm3DumpError::UnsupportedClassicalControl(
1252 "while".to_string(),
1253 ));
1254 }
1255 ClassicalControlOp::For(_) => {
1256 return Err(Qasm3DumpError::UnsupportedClassicalControl(
1257 "for".to_string(),
1258 ));
1259 }
1260 ClassicalControlOp::Break | ClassicalControlOp::Continue => {
1261 return Err(Qasm3DumpError::UnsupportedClassicalControl(
1262 "break/continue".to_string(),
1263 ));
1264 }
1265 }
1266 Ok(())
1267}
1268
1269fn classical_expr_to_qasm(
1270 expr: &ClassicalExpr,
1271 names: &ClassicalNameMap,
1272) -> Result<String, Qasm3DumpError> {
1273 Ok(match expr.kind() {
1274 ClassicalExprKind::Var(var) => names.var(*var)?.to_string(),
1275 ClassicalExprKind::Value(value) => names.value(*value)?.to_string(),
1276 ClassicalExprKind::BoolLiteral(value) => value.to_string(),
1277 ClassicalExprKind::BitLiteral(value) => value.to_string(),
1278 ClassicalExprKind::UIntLiteral { value, .. } => value.to_string(),
1279 ClassicalExprKind::BitVecLiteral { width, value } => {
1280 format!("\"{value:0width$b}\"", width = width.get() as usize)
1281 }
1282 ClassicalExprKind::Unary {
1283 op: ClassicalUnaryOp::Not,
1284 expr,
1285 } => format!("!({})", classical_expr_to_qasm(expr, names)?),
1286 ClassicalExprKind::Binary { op, lhs, rhs } => {
1287 let op = match op {
1288 ClassicalBinaryOp::And => "&",
1289 ClassicalBinaryOp::Or => "|",
1290 ClassicalBinaryOp::Xor => "^",
1291 };
1292 format!(
1293 "({} {op} {})",
1294 classical_expr_to_qasm(lhs, names)?,
1295 classical_expr_to_qasm(rhs, names)?
1296 )
1297 }
1298 ClassicalExprKind::Compare { op, lhs, rhs } => {
1299 let op = match op {
1300 ClassicalCompareOp::Eq => "==",
1301 ClassicalCompareOp::Ne => "!=",
1302 ClassicalCompareOp::Lt => "<",
1303 ClassicalCompareOp::Le => "<=",
1304 ClassicalCompareOp::Gt => ">",
1305 ClassicalCompareOp::Ge => ">=",
1306 };
1307 format!(
1308 "({} {op} {})",
1309 classical_expr_to_qasm(lhs, names)?,
1310 classical_expr_to_qasm(rhs, names)?
1311 )
1312 }
1313 ClassicalExprKind::Cast { cast, expr } => match cast {
1314 ClassicalCast::BitToBool => format!("bool({})", classical_expr_to_qasm(expr, names)?),
1315 ClassicalCast::BitVecToUInt => {
1316 format!("uint({})", classical_expr_to_qasm(expr, names)?)
1317 }
1318 },
1319 ClassicalExprKind::ExtractBit { value, index } => {
1320 format!("{}[{index}]", classical_expr_to_qasm(value, names)?)
1321 }
1322 other => {
1323 return Err(Qasm3DumpError::UnsupportedClassicalControl(format!(
1324 "classical expression {other:?}"
1325 )));
1326 }
1327 })
1328}
1329
1330fn resolve_param(
1331 op: &Operation,
1332 param_index: usize,
1333 circuit: &Circuit,
1334 param_map: &HashMap<String, Parameter>,
1335) -> Result<Parameter, Qasm3DumpError> {
1336 let param = circuit
1337 .resolve_parameter(&op.params[param_index])
1338 .map_err(|error| {
1339 let error = match error {
1340 crate::circuit::CircuitError::InvalidParameterValue(_, value) => {
1341 crate::circuit::CircuitError::InvalidParameterValue(param_index, value)
1342 }
1343 error => error,
1344 };
1345 Qasm3DumpError::FormatError(format!(
1346 "invalid parameter {param_index} for {:?}: {error}",
1347 op.instruction
1348 ))
1349 })?;
1350 Ok(param.substitute_many_simultaneous(param_map))
1351}
1352
1353fn format_params(
1354 op: &Operation,
1355 circuit: &Circuit,
1356 param_map: &HashMap<String, Parameter>,
1357) -> Result<String, Qasm3DumpError> {
1358 if op.params.is_empty() {
1359 return Ok(String::new());
1360 }
1361 let params = (0..op.params.len())
1362 .map(|index| {
1363 resolve_param(op, index, circuit, param_map)
1364 .map(|param| param.to_string().replace("π", "pi"))
1365 })
1366 .collect::<Result<Vec<_>, _>>()?;
1367 Ok(format!("({})", params.join(",")))
1368}
1369
1370fn map_qubits(op: &Operation, qubit_map: &HashMap<Qubit, String>) -> Vec<String> {
1371 op.qubits
1372 .iter()
1373 .map(|qubit| {
1374 qubit_map
1375 .get(qubit)
1376 .cloned()
1377 .unwrap_or_else(|| format!("q[{}]", qubit.index()))
1378 })
1379 .collect()
1380}
1381
1382fn collect_used_standard_gates(operations: &[Operation], used: &mut HashSet<StandardGate>) {
1383 for op in operations {
1384 match &op.instruction {
1385 Instruction::Standard(gate) => {
1386 used.insert(*gate);
1387 }
1388 Instruction::CircuitGate(gate) => {
1389 collect_used_standard_gates(gate.circuit.circuit.operations(), used);
1390 }
1391 Instruction::UnitaryGate(gate) => {
1392 if let Some(circuit) = gate.circuit() {
1393 collect_used_standard_gates(circuit.circuit.operations(), used);
1394 }
1395 }
1396 Instruction::ClassicalControl(control) => match control {
1397 ClassicalControlOp::If(op) => {
1398 collect_used_standard_gates(op.then_body().operations(), used);
1399 if let Some(body) = op.else_body() {
1400 collect_used_standard_gates(body.operations(), used);
1401 }
1402 }
1403 ClassicalControlOp::Switch(op) => {
1404 for case in op.cases() {
1405 collect_used_standard_gates(case.body().operations(), used);
1406 }
1407 if let Some(body) = op.default() {
1408 collect_used_standard_gates(body.operations(), used);
1409 }
1410 }
1411 ClassicalControlOp::While(op) => {
1412 collect_used_standard_gates(op.body().operations(), used)
1413 }
1414 ClassicalControlOp::For(op) => {
1415 collect_used_standard_gates(op.body().operations(), used)
1416 }
1417 ClassicalControlOp::Break | ClassicalControlOp::Continue => {}
1418 },
1419 _ => {}
1420 }
1421 }
1422}
1423
1424fn extension_definition_order(used: &HashSet<StandardGate>) -> Vec<StandardGate> {
1425 let mut out = Vec::new();
1426 for gate in [
1427 StandardGate::X2P,
1428 StandardGate::X2M,
1429 StandardGate::Y2P,
1430 StandardGate::Y2M,
1431 StandardGate::XY2P,
1432 StandardGate::XY2M,
1433 StandardGate::RXX,
1434 StandardGate::RYY,
1435 StandardGate::RZZ,
1436 StandardGate::RZX,
1437 ] {
1438 if used.contains(&gate) {
1439 out.push(gate);
1440 }
1441 }
1442 if used.contains(&StandardGate::FSIM) {
1443 for gate in [StandardGate::RXX, StandardGate::RYY, StandardGate::RZZ] {
1444 if !out.contains(&gate) {
1445 out.push(gate);
1446 }
1447 }
1448 out.push(StandardGate::FSIM);
1449 }
1450 out
1451}
1452
1453fn extension_gate_definition(gate: StandardGate) -> Option<&'static str> {
1454 match gate {
1455 StandardGate::X2P => Some("gate x2p q { rx(pi/2) q; }"),
1456 StandardGate::X2M => Some("gate x2m q { rx(-pi/2) q; }"),
1457 StandardGate::Y2P => Some("gate y2p q { ry(pi/2) q; }"),
1458 StandardGate::Y2M => Some("gate y2m q { ry(-pi/2) q; }"),
1459 StandardGate::XY2P => Some("gate xy2p(phi) q { rz(-phi) q; x2p q; rz(phi) q; }"),
1460 StandardGate::XY2M => Some("gate xy2m(phi) q { rz(-phi) q; x2m q; rz(phi) q; }"),
1461 StandardGate::RXX => {
1462 Some("gate rxx(theta) a,b { h a; h b; cx a,b; rz(theta) b; cx a,b; h a; h b; }")
1463 }
1464 StandardGate::RYY => Some(
1465 "gate ryy(theta) a,b { rx(pi/2) a; rx(pi/2) b; cx a,b; rz(theta) b; cx a,b; rx(-pi/2) a; rx(-pi/2) b; }",
1466 ),
1467 StandardGate::RZZ => Some("gate rzz(theta) a,b { cx a,b; rz(theta) b; cx a,b; }"),
1468 StandardGate::RZX => Some("gate rzx(theta) a,b { h b; cx a,b; rz(theta) b; cx a,b; h b; }"),
1469 StandardGate::FSIM => Some(
1470 "gate fsim(theta,phi) a,b { rxx(theta) a,b; ryy(theta) a,b; gphase(-phi/4); rz(-phi/2) a; rz(-phi/2) b; rzz(phi/2) a,b; }",
1471 ),
1472 _ => None,
1473 }
1474}
1475
1476#[cfg(test)]
1477#[path = "./dump_test.rs"]
1478mod dump_test;