1use crate::circuit::gate::{
72 CircuitGate, ClassicalDataOp, Directive, Instruction, MCGate, StandardGate, UnitaryGate,
73};
74use crate::circuit::operation::Operation;
75use crate::circuit::parameter::Parameter;
76use crate::circuit::{
77 Circuit, ClassicalCast, ClassicalCompareOp, ClassicalControlOp, ClassicalExpr,
78 ClassicalExprKind, ClassicalType, ClassicalUnaryOp, ClassicalValue, ClassicalVar, Qubit,
79};
80use indexmap::IndexMap;
81use std::collections::{HashMap, HashSet};
82use std::fmt::Write;
83use std::fs::File;
84use std::io::{self, Write as IoWrite};
85use std::path::Path;
86use std::sync::Arc;
87
88const QELIB1_CUSTOM_GATES: &[&str] = &["ch", "cu1", "cu3"];
89
90#[derive(Debug)]
95pub enum QasmDumpError {
96 IoError(io::Error),
98 FormatError(String),
100 MeasureInGateNotAllowed,
102 ResetInGateNotAllowed,
104 InvalidQubitIndex(String),
106 UnsupportedClassicalType(String),
108 UnsupportedClassicalData(String),
110 UnsupportedClassicalControl(String),
112 ConflictingGateDefinition {
114 name: String,
115 existing_qubits: usize,
116 existing_params: usize,
117 conflicting_qubits: usize,
118 conflicting_params: usize,
119 },
120}
121
122impl std::fmt::Display for QasmDumpError {
123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124 match self {
125 QasmDumpError::IoError(s) => write!(f, "IO error: {}", s),
126 QasmDumpError::FormatError(s) => write!(f, "Format error: {}", s),
127 QasmDumpError::MeasureInGateNotAllowed => {
128 write!(f, "Measure inside gate definition is not allowed")
129 }
130 QasmDumpError::ResetInGateNotAllowed => {
131 write!(f, "Reset inside gate definition is not allowed")
132 }
133 QasmDumpError::InvalidQubitIndex(s) => write!(f, "Invalid qubit index: {}", s),
134 QasmDumpError::UnsupportedClassicalType(s) => {
135 write!(f, "Unsupported OpenQASM 2.0 classical type: {s}")
136 }
137 QasmDumpError::UnsupportedClassicalData(s) => {
138 write!(f, "Unsupported OpenQASM 2.0 classical data operation: {s}")
139 }
140 QasmDumpError::UnsupportedClassicalControl(s) => {
141 write!(
142 f,
143 "Unsupported OpenQASM 2.0 classical control operation: {s}"
144 )
145 }
146 QasmDumpError::ConflictingGateDefinition {
147 name,
148 existing_qubits,
149 existing_params,
150 conflicting_qubits,
151 conflicting_params,
152 } => write!(
153 f,
154 "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"
155 ),
156 }
157 }
158}
159
160#[derive(Debug, Default)]
161struct ClassicalRegisterMap {
162 vars: HashMap<ClassicalVar, String>,
163 values: HashMap<ClassicalValue, String>,
164}
165
166impl ClassicalRegisterMap {
167 fn new(
168 circuit: &Circuit,
169 output: &mut String,
170 skipped_values: &HashSet<ClassicalValue>,
171 ) -> Result<Self, QasmDumpError> {
172 let mut registers = Self::default();
173
174 for (id, ty) in circuit.classical_vars().iter().copied().enumerate() {
175 let width = qasm_register_width(ty)?;
176 let name = format!("c{id}");
177 writeln!(output, "creg {name}[{width}];")?;
178 registers
179 .vars
180 .insert(ClassicalVar::new(circuit.id(), id as u32, ty), name);
181 }
182 for (id, ty) in circuit.classical_values().iter().copied().enumerate() {
183 let value = ClassicalValue::new(circuit.id(), id as u32, ty);
184 if skipped_values.contains(&value) {
185 continue;
186 }
187 let width = qasm_register_width(ty)?;
188 let name = format!("v{id}");
189 writeln!(output, "creg {name}[{width}];")?;
190 registers.values.insert(value, name);
191 }
192
193 Ok(registers)
194 }
195
196 fn var(&self, var: ClassicalVar) -> Result<&str, QasmDumpError> {
197 self.vars.get(&var).map(String::as_str).ok_or_else(|| {
198 QasmDumpError::UnsupportedClassicalData(format!(
199 "classical variable {} is not owned by the circuit",
200 var.index()
201 ))
202 })
203 }
204
205 fn value(&self, value: ClassicalValue) -> Result<&str, QasmDumpError> {
206 self.values.get(&value).map(String::as_str).ok_or_else(|| {
207 QasmDumpError::UnsupportedClassicalData(format!(
208 "classical value {} is not owned by the circuit",
209 value.index()
210 ))
211 })
212 }
213}
214
215fn qasm_register_width(ty: ClassicalType) -> Result<u32, QasmDumpError> {
216 match ty {
217 ClassicalType::Bit => Ok(1),
218 ClassicalType::BitVec(width) => Ok(width.get()),
219 ClassicalType::Bool | ClassicalType::UInt(_) => {
220 Err(QasmDumpError::UnsupportedClassicalType(format!("{ty:?}")))
221 }
222 }
223}
224
225impl std::error::Error for QasmDumpError {
226 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
227 match self {
228 QasmDumpError::IoError(error) => Some(error),
229 _ => None,
230 }
231 }
232}
233
234impl From<std::fmt::Error> for QasmDumpError {
235 fn from(e: std::fmt::Error) -> Self {
236 QasmDumpError::FormatError(e.to_string())
237 }
238}
239
240impl From<io::Error> for QasmDumpError {
241 fn from(e: io::Error) -> Self {
242 QasmDumpError::IoError(e)
243 }
244}
245
246pub fn dump<P: AsRef<Path>>(circuit: &Circuit, path: P) -> Result<(), QasmDumpError> {
248 let qasm = dumps(circuit)?;
249 let mut file = File::create(path)?;
250 file.write_all(qasm.as_bytes())?;
251 Ok(())
252}
253
254pub fn to_path<P: AsRef<Path>>(circuit: &Circuit, path: P) -> Result<(), QasmDumpError> {
259 dump(circuit, path)
260}
261
262pub fn dumps(circuit: &Circuit) -> Result<String, QasmDumpError> {
267 let mut output = String::new();
268 writeln!(
269 &mut output,
270 "// This file is auto-generated by Cqlib v{}.",
271 env!("CARGO_PKG_VERSION")
272 )?;
273 writeln!(&mut output, "OPENQASM 2.0;")?;
274 writeln!(&mut output, "include \"qelib1.inc\";\n")?;
275 writeln!(&mut output, "qreg q[{}];", circuit.num_qubits())?;
276 let skipped_values = skipped_classical_value_declarations(circuit.operations())?;
277 let classical_registers = ClassicalRegisterMap::new(circuit, &mut output, &skipped_values)?;
278 writeln!(&mut output)?;
279
280 let global_phase = circuit.global_phase();
282 match global_phase.evaluate(&None) {
284 Ok(val) if val.abs() < 1e-10 => {} Ok(val) => writeln!(&mut output, "// Global Phase: {}\n", val)?,
286 Err(_) => writeln!(&mut output, "// Global Phase: {}\n", global_phase)?,
287 }
288
289 writeln!(&mut output)?;
290
291 let mut used_gate_names = HashSet::new();
293 collect_used_gate_names(circuit, &mut used_gate_names);
294
295 let extended_gates = [
296 "crx", "cry", "rzz", "rxx", "ryy", "rzx", "swap", "fsim", "cswap",
297 ];
298
299 for gate in extended_gates {
300 if used_gate_names.contains(gate)
301 && let Some(def) = get_extended_gate_definition(gate)
302 {
303 writeln!(&mut output, "{}", def)?;
304 }
305 }
306 writeln!(&mut output)?;
307
308 let mut defined_gates: IndexMap<String, CircuitGate> = IndexMap::new();
310 let mut unitary_gate_defs: IndexMap<String, UnitaryGate> = IndexMap::new();
311 let mut opaque_unitary_gates: IndexMap<String, UnitaryGate> = IndexMap::new();
313 let mut has_delay = false;
315 collect_gates(
316 circuit,
317 &mut defined_gates,
318 &mut unitary_gate_defs,
319 &mut opaque_unitary_gates,
320 &mut has_delay,
321 )?;
322
323 for name in extended_gates {
325 if used_gate_names.contains(name)
326 && (defined_gates.contains_key(name)
327 || unitary_gate_defs.contains_key(name)
328 || opaque_unitary_gates.contains_key(name))
329 {
330 return Err(QasmDumpError::FormatError(format!(
331 "Gate name '{name}' conflicts with a required OpenQASM 2.0 extension definition"
332 )));
333 }
334 }
335
336 for gate in defined_gates.values() {
338 dump_gate_definition(gate, &mut output)?;
339 writeln!(&mut output)?;
340 }
341
342 for gate in unitary_gate_defs.values() {
344 dump_unitary_gate_definition(gate, &mut output)?;
345 writeln!(&mut output)?;
346 }
347
348 for gate in opaque_unitary_gates.values() {
350 dump_opaque_declaration(gate, &mut output)?;
351 writeln!(&mut output)?;
352 }
353
354 if has_delay {
356 writeln!(&mut output, "opaque delay(t) q;")?;
357 writeln!(&mut output)?;
358 }
359
360 let mut qubit_map: HashMap<Qubit, String> = HashMap::new();
364 let mut qubits = circuit.qubits();
365 qubits.sort_unstable();
366 for (index, q) in qubits.into_iter().enumerate() {
367 qubit_map.insert(q, format!("q[{index}]"));
368 }
369
370 let param_map: HashMap<String, Parameter> = HashMap::new();
372
373 process_circuit_operations(
374 circuit,
375 &mut output,
376 &qubit_map,
377 ¶m_map,
378 &classical_registers,
379 )?;
380
381 Ok(output)
382}
383
384pub fn to_string(circuit: &Circuit) -> Result<String, QasmDumpError> {
388 dumps(circuit)
389}
390
391fn collect_gates(
400 circuit: &Circuit,
401 defined_gates: &mut IndexMap<String, CircuitGate>,
402 unitary_gate_defs: &mut IndexMap<String, UnitaryGate>,
403 opaque_unitary_gates: &mut IndexMap<String, UnitaryGate>,
404 has_delay: &mut bool,
405) -> Result<(), QasmDumpError> {
406 collect_gates_from_operations(
407 circuit.operations(),
408 defined_gates,
409 unitary_gate_defs,
410 opaque_unitary_gates,
411 has_delay,
412 )
413}
414
415fn collect_gates_from_operations(
416 operations: &[Operation],
417 defined_gates: &mut IndexMap<String, CircuitGate>,
418 unitary_gate_defs: &mut IndexMap<String, UnitaryGate>,
419 opaque_unitary_gates: &mut IndexMap<String, UnitaryGate>,
420 has_delay: &mut bool,
421) -> Result<(), QasmDumpError> {
422 for op in operations {
423 match &op.instruction {
424 Instruction::CircuitGate(cg) => {
425 if QELIB1_CUSTOM_GATES.contains(&cg.name.as_str()) {
426 continue;
427 }
428 collect_gates(
430 &cg.circuit.circuit,
431 defined_gates,
432 unitary_gate_defs,
433 opaque_unitary_gates,
434 has_delay,
435 )?;
436
437 if let Some(existing) = defined_gates.get(cg.name.as_str()) {
438 if !Arc::ptr_eq(&existing.circuit, &cg.circuit) {
439 return Err(QasmDumpError::ConflictingGateDefinition {
440 name: cg.name.to_string(),
441 existing_qubits: existing.num_qubits(),
442 existing_params: existing.num_params(),
443 conflicting_qubits: cg.num_qubits(),
444 conflicting_params: cg.num_params(),
445 });
446 }
447 } else {
448 defined_gates.insert(cg.name.to_string(), *cg.clone());
449 }
450 }
451 Instruction::UnitaryGate(ug) => {
452 if let Some(fc) = ug.circuit() {
453 collect_gates(
455 &fc.circuit,
456 defined_gates,
457 unitary_gate_defs,
458 opaque_unitary_gates,
459 has_delay,
460 )?;
461
462 let label = ug.label().to_string();
464 if !unitary_gate_defs.contains_key(&label) {
465 unitary_gate_defs.insert(label, ug.as_ref().clone());
466 }
467 } else {
468 let label = ug.label().to_string();
470 if !opaque_unitary_gates.contains_key(&label) {
471 opaque_unitary_gates.insert(label, ug.as_ref().clone());
472 }
473 }
474 }
475 Instruction::Delay => {
476 *has_delay = true;
477 }
478 Instruction::ClassicalControl(control) => match control {
479 ClassicalControlOp::If(if_op) => {
480 collect_gates_from_operations(
481 if_op.then_body().operations(),
482 defined_gates,
483 unitary_gate_defs,
484 opaque_unitary_gates,
485 has_delay,
486 )?;
487 if let Some(body) = if_op.else_body() {
488 collect_gates_from_operations(
489 body.operations(),
490 defined_gates,
491 unitary_gate_defs,
492 opaque_unitary_gates,
493 has_delay,
494 )?;
495 }
496 }
497 ClassicalControlOp::While(while_op) => {
498 collect_gates_from_operations(
499 while_op.body().operations(),
500 defined_gates,
501 unitary_gate_defs,
502 opaque_unitary_gates,
503 has_delay,
504 )?;
505 }
506 ClassicalControlOp::For(for_op) => {
507 collect_gates_from_operations(
508 for_op.body().operations(),
509 defined_gates,
510 unitary_gate_defs,
511 opaque_unitary_gates,
512 has_delay,
513 )?;
514 }
515 ClassicalControlOp::Switch(switch_op) => {
516 for case in switch_op.cases() {
517 collect_gates_from_operations(
518 case.body().operations(),
519 defined_gates,
520 unitary_gate_defs,
521 opaque_unitary_gates,
522 has_delay,
523 )?;
524 }
525 if let Some(body) = switch_op.default() {
526 collect_gates_from_operations(
527 body.operations(),
528 defined_gates,
529 unitary_gate_defs,
530 opaque_unitary_gates,
531 has_delay,
532 )?;
533 }
534 }
535 ClassicalControlOp::Break | ClassicalControlOp::Continue => {}
536 },
537 _ => {}
538 }
539 }
540 Ok(())
541}
542
543fn dump_gate_definition(cg: &CircuitGate, output: &mut String) -> Result<(), QasmDumpError> {
544 validate_gate_body_operations(cg.circuit.circuit.operations())?;
545
546 let params: Vec<String> = cg.signature_params().iter().cloned().collect();
547 let params_str = if params.is_empty() {
548 String::new()
549 } else {
550 format!("({})", params.join(","))
551 };
552
553 let qubits: Vec<String> = (0..cg.num_qubits()).map(|i| format!("q{}", i)).collect();
554 let qubits_str = qubits.join(",");
555
556 writeln!(output, "gate {}{} {} {{", cg.name, params_str, qubits_str)?;
557
558 let mut body_qubit_map = HashMap::new();
561 let inner_circuit = &cg.circuit.circuit;
562 for (i, q) in inner_circuit.qubits().iter().enumerate() {
565 body_qubit_map.insert(*q, format!("q{}", i));
566 }
567
568 let body_param_map = HashMap::new();
571
572 let classical_registers = ClassicalRegisterMap::default();
573 process_circuit_operations(
574 inner_circuit,
575 output,
576 &body_qubit_map,
577 &body_param_map,
578 &classical_registers,
579 )?;
580
581 writeln!(output, "}}")?;
582 Ok(())
583}
584
585fn dump_unitary_gate_definition(
586 gate: &UnitaryGate,
587 output: &mut String,
588) -> Result<(), QasmDumpError> {
589 let frozen_circuit = gate.circuit().as_ref().ok_or_else(|| {
590 QasmDumpError::FormatError(format!(
591 "Unitary gate '{}' has no circuit definition",
592 gate.label()
593 ))
594 })?;
595 validate_gate_body_operations(frozen_circuit.circuit.operations())?;
596
597 let num_qubits = frozen_circuit.circuit.qubits().len();
598 let qubits: Vec<String> = (0..num_qubits).map(|i| format!("q{}", i)).collect();
599 let qubits_str = qubits.join(",");
600 let params_str = format_parameter_signature(&unitary_gate_parameter_names(gate));
601
602 writeln!(
603 output,
604 "gate {}{} {} {{",
605 gate.label(),
606 params_str,
607 qubits_str
608 )?;
609
610 let mut body_qubit_map = HashMap::new();
612 let inner_circuit = &frozen_circuit.circuit;
613 for (i, q) in frozen_circuit.qubits_in_numeric_order().iter().enumerate() {
614 body_qubit_map.insert(*q, format!("q{}", i));
615 }
616
617 let body_param_map = HashMap::new();
618 let classical_registers = ClassicalRegisterMap::default();
619 process_circuit_operations(
620 inner_circuit,
621 output,
622 &body_qubit_map,
623 &body_param_map,
624 &classical_registers,
625 )?;
626
627 writeln!(output, "}}")?;
628 Ok(())
629}
630
631fn validate_gate_body_operations(operations: &[Operation]) -> Result<(), QasmDumpError> {
632 if operations
633 .iter()
634 .any(|operation| operation.instruction.has_measurement())
635 {
636 return Err(QasmDumpError::MeasureInGateNotAllowed);
637 }
638 if operations.iter().any(|operation| {
639 matches!(
640 &operation.instruction,
641 Instruction::Directive(Directive::Reset)
642 )
643 }) {
644 return Err(QasmDumpError::ResetInGateNotAllowed);
645 }
646 Ok(())
647}
648
649fn unitary_gate_parameter_names(gate: &UnitaryGate) -> Vec<String> {
650 if let Some(circuit) = gate.circuit() {
651 return circuit.circuit().symbols().iter().cloned().collect();
652 }
653 if let Some(params) = gate.matrix_params() {
654 return params.to_vec();
655 }
656 (0..gate.num_params())
657 .map(|index| format!("p{index}"))
658 .collect()
659}
660
661fn format_parameter_signature(params: &[String]) -> String {
662 if params.is_empty() {
663 String::new()
664 } else {
665 format!("({})", params.join(","))
666 }
667}
668
669fn dump_opaque_declaration(gate: &UnitaryGate, output: &mut String) -> Result<(), QasmDumpError> {
670 let qubits: Vec<String> = (0..gate.num_qubits()).map(|i| format!("q{}", i)).collect();
671 let qubits_str = qubits.join(",");
672 let params_str = format_parameter_signature(&unitary_gate_parameter_names(gate));
673
674 writeln!(
675 output,
676 "opaque {}{} {};",
677 gate.label(),
678 params_str,
679 qubits_str
680 )?;
681 Ok(())
682}
683
684fn process_circuit_operations(
685 circuit: &Circuit,
686 output: &mut String,
687 qubit_map: &HashMap<Qubit, String>,
688 param_map: &HashMap<String, Parameter>,
689 classical_registers: &ClassicalRegisterMap,
690) -> Result<(), QasmDumpError> {
691 let operations = circuit.operations();
692 let mut index = 0;
693 let mut accepting_initializers = true;
694 while index < operations.len() {
695 let op = &operations[index];
696 if let Instruction::ClassicalData(ClassicalDataOp::Store { target, value }) =
697 &op.instruction
698 && accepting_initializers
699 && is_zero_initializer(*target, value)
700 {
701 index += 1;
702 continue;
703 }
704 accepting_initializers = false;
705 match &op.instruction {
706 Instruction::Standard(gate) => {
707 dump_standard_gate(gate, op, circuit, output, qubit_map, param_map)?;
708 }
709 Instruction::McGate(mc_gate) => {
710 dump_mc_gate(mc_gate, op, circuit, output, qubit_map, param_map)?;
711 }
712 Instruction::Directive(directive) => {
713 dump_directive(directive, op, output, qubit_map)?;
714 }
715 Instruction::CircuitGate(cg) => {
716 let params_str = format_params(op, circuit, param_map)?;
720
721 let mapped_qs = map_qubits(op, qubit_map);
723
724 writeln!(output, "{}{} {};", cg.name, params_str, mapped_qs.join(","))?;
725 }
726 Instruction::UnitaryGate(unitary_gate) => {
727 let params_str = format_params(op, circuit, param_map)?;
730 let mapped_qs = map_qubits(op, qubit_map);
731 writeln!(
732 output,
733 "{}{} {};",
734 unitary_gate.label(),
735 params_str,
736 mapped_qs.join(",")
737 )?;
738 }
739 Instruction::ClassicalData(ClassicalDataOp::MeasureBit { result })
740 | Instruction::ClassicalData(ClassicalDataOp::MeasureBits { result }) => {
741 let next = operations.get(index + 1);
742 let (destination, consumes_store) =
743 measurement_destination(&op.instruction, *result, next)?;
744 if consumes_store
745 && operations[index + 2..]
746 .iter()
747 .any(|operation| operation.instruction.reads_value(*result))
748 {
749 return Err(QasmDumpError::UnsupportedClassicalData(format!(
750 "measurement value {} is read after being stored into a mutable register",
751 result.index()
752 )));
753 }
754 dump_classical_measurement(
755 op,
756 *result,
757 destination,
758 output,
759 qubit_map,
760 classical_registers,
761 )?;
762 if consumes_store {
763 index += 1;
764 }
765 }
766 Instruction::ClassicalData(ClassicalDataOp::Store { .. }) => {
767 return Err(QasmDumpError::UnsupportedClassicalData(
768 "general store assignment".to_string(),
769 ));
770 }
771 Instruction::ClassicalControl(control) => {
772 dump_control_flow(
773 control,
774 circuit,
775 output,
776 qubit_map,
777 param_map,
778 classical_registers,
779 )?;
780 }
781 Instruction::Delay => {
782 let params_str = format_params(op, circuit, param_map)?;
784 let mapped_qs = map_qubits(op, qubit_map);
785
786 writeln!(output, "delay({}) {};", params_str, mapped_qs[0])?;
787 }
788 }
789 index += 1;
790 }
791 Ok(())
792}
793
794fn is_zero_initializer(target: ClassicalVar, value: &ClassicalExpr) -> bool {
795 match (target.ty(), value.kind()) {
796 (ClassicalType::Bit, ClassicalExprKind::BitLiteral(false)) => true,
797 (
798 ClassicalType::BitVec(target_width),
799 ClassicalExprKind::BitVecLiteral { width, value: 0 },
800 ) => target_width == *width,
801 (ClassicalType::BitVec(target_width), ClassicalExprKind::PackBits { bits }) => {
802 bits.len() == target_width.get() as usize
803 && bits
804 .iter()
805 .all(|bit| matches!(bit.kind(), ClassicalExprKind::BitLiteral(false)))
806 }
807 _ => false,
808 }
809}
810
811#[derive(Debug, Clone, Copy)]
812enum MeasurementDestination {
813 Value(ClassicalValue),
814 VarWhole(ClassicalVar),
815 VarBit(ClassicalVar, u32),
816}
817
818fn skipped_classical_value_declarations(
819 operations: &[Operation],
820) -> Result<HashSet<ClassicalValue>, QasmDumpError> {
821 let mut skipped = HashSet::new();
822 collect_skipped_classical_values(operations, &mut skipped)?;
823 Ok(skipped)
824}
825
826fn collect_skipped_classical_values(
827 operations: &[Operation],
828 skipped: &mut HashSet<ClassicalValue>,
829) -> Result<(), QasmDumpError> {
830 let mut index = 0;
831 while index < operations.len() {
832 let op = &operations[index];
833 match &op.instruction {
834 Instruction::ClassicalData(ClassicalDataOp::MeasureBit { result })
835 | Instruction::ClassicalData(ClassicalDataOp::MeasureBits { result }) => {
836 let next = operations.get(index + 1);
837 let (_, consumes_store) = measurement_destination(&op.instruction, *result, next)?;
838 if consumes_store {
839 let read_later = operations[index + 2..]
840 .iter()
841 .any(|operation| operation.instruction.reads_value(*result));
842 if !read_later {
843 skipped.insert(*result);
844 }
845 index += 1;
846 }
847 }
848 Instruction::ClassicalControl(control) => {
849 collect_skipped_classical_values_in_control(control, skipped)?;
850 }
851 _ => {}
852 }
853 index += 1;
854 }
855 Ok(())
856}
857
858fn collect_skipped_classical_values_in_control(
859 control: &ClassicalControlOp,
860 skipped: &mut HashSet<ClassicalValue>,
861) -> Result<(), QasmDumpError> {
862 match control {
863 ClassicalControlOp::If(if_op) => {
864 collect_skipped_classical_values(if_op.then_body().operations(), skipped)?;
865 if let Some(body) = if_op.else_body() {
866 collect_skipped_classical_values(body.operations(), skipped)?;
867 }
868 }
869 ClassicalControlOp::While(while_op) => {
870 collect_skipped_classical_values(while_op.body().operations(), skipped)?;
871 }
872 ClassicalControlOp::For(for_op) => {
873 collect_skipped_classical_values(for_op.body().operations(), skipped)?;
874 }
875 ClassicalControlOp::Switch(switch_op) => {
876 for case in switch_op.cases() {
877 collect_skipped_classical_values(case.body().operations(), skipped)?;
878 }
879 if let Some(body) = switch_op.default() {
880 collect_skipped_classical_values(body.operations(), skipped)?;
881 }
882 }
883 ClassicalControlOp::Break | ClassicalControlOp::Continue => {}
884 }
885 Ok(())
886}
887
888fn measurement_destination(
889 instruction: &Instruction,
890 result: ClassicalValue,
891 next: Option<&Operation>,
892) -> Result<(MeasurementDestination, bool), QasmDumpError> {
893 let Some(Operation {
894 instruction: Instruction::ClassicalData(ClassicalDataOp::Store { target, value }),
895 ..
896 }) = next
897 else {
898 return Ok((MeasurementDestination::Value(result), false));
899 };
900
901 if matches!(value.kind(), ClassicalExprKind::Value(value) if *value == result) {
902 return match instruction {
903 Instruction::ClassicalData(ClassicalDataOp::MeasureBit { .. })
904 if target.ty() == ClassicalType::Bit =>
905 {
906 Ok((MeasurementDestination::VarWhole(*target), true))
907 }
908 Instruction::ClassicalData(ClassicalDataOp::MeasureBits { .. })
909 if target.ty() == result.ty() =>
910 {
911 Ok((MeasurementDestination::VarWhole(*target), true))
912 }
913 _ => Err(QasmDumpError::UnsupportedClassicalData(
914 "measurement store has incompatible target type".to_string(),
915 )),
916 };
917 }
918
919 if matches!(
920 instruction,
921 Instruction::ClassicalData(ClassicalDataOp::MeasureBit { .. })
922 ) && let Some(bit) = value.measurement_store_bit(*target, result)
923 {
924 return Ok((MeasurementDestination::VarBit(*target, bit), true));
925 }
926
927 Err(QasmDumpError::UnsupportedClassicalData(
928 "measurement is followed by a store that cannot be represented by OpenQASM 2.0".to_string(),
929 ))
930}
931
932fn dump_classical_measurement(
933 op: &Operation,
934 result: ClassicalValue,
935 destination: MeasurementDestination,
936 output: &mut String,
937 qubit_map: &HashMap<Qubit, String>,
938 registers: &ClassicalRegisterMap,
939) -> Result<(), QasmDumpError> {
940 for line in measurement_qasm_lines(op, result, destination, qubit_map, registers)? {
941 writeln!(output, "{line};")?;
942 }
943 Ok(())
944}
945
946fn measurement_qasm_lines(
947 op: &Operation,
948 result: ClassicalValue,
949 destination: MeasurementDestination,
950 qubit_map: &HashMap<Qubit, String>,
951 registers: &ClassicalRegisterMap,
952) -> Result<Vec<String>, QasmDumpError> {
953 let mapped_qubits = map_qubits(op, qubit_map);
954 let (register, first_bit, width) = match destination {
955 MeasurementDestination::Value(value) => (registers.value(value)?, 0, value.ty().width()),
956 MeasurementDestination::VarWhole(var) => (registers.var(var)?, 0, var.ty().width()),
957 MeasurementDestination::VarBit(var, bit) => (registers.var(var)?, bit, 1),
958 };
959
960 if mapped_qubits.len() != width as usize || result.ty().width() != width {
961 return Err(QasmDumpError::UnsupportedClassicalData(format!(
962 "measurement width {} does not match destination width {width}",
963 mapped_qubits.len()
964 )));
965 }
966 Ok(mapped_qubits
967 .iter()
968 .enumerate()
969 .map(|(offset, qubit)| {
970 format!(
971 "measure {qubit} -> {register}[{}]",
972 first_bit + offset as u32
973 )
974 })
975 .collect())
976}
977
978fn resolve_param(
979 op: &Operation,
980 param_index: usize,
981 circuit: &Circuit,
982 param_map: &HashMap<String, Parameter>,
983) -> Result<Parameter, QasmDumpError> {
984 let mut param = circuit
985 .resolve_parameter(&op.params[param_index])
986 .map_err(|error| {
987 let error = match error {
988 crate::circuit::CircuitError::InvalidParameterValue(_, value) => {
989 crate::circuit::CircuitError::InvalidParameterValue(param_index, value)
990 }
991 error => error,
992 };
993 QasmDumpError::FormatError(format!(
994 "invalid parameter {param_index} for {:?}: {error}",
995 op.instruction
996 ))
997 })?;
998 for (symbol, replacement) in param_map {
999 param = param.replace(symbol, replacement.clone());
1000 }
1001 Ok(param)
1002}
1003
1004fn format_parameter(param: &Parameter) -> String {
1005 param.to_string().replace('Ï€', "pi")
1006}
1007
1008fn format_params(
1009 op: &Operation,
1010 circuit: &Circuit,
1011 param_map: &HashMap<String, Parameter>,
1012) -> Result<String, QasmDumpError> {
1013 if op.params.is_empty() {
1014 return Ok(String::new());
1015 }
1016 let params = (0..op.params.len())
1017 .map(|index| {
1018 resolve_param(op, index, circuit, param_map).map(|param| format_parameter(¶m))
1019 })
1020 .collect::<Result<Vec<_>, _>>()?;
1021 Ok(format!("({})", params.join(",")))
1022}
1023
1024fn map_qubits(op: &Operation, qubit_map: &HashMap<Qubit, String>) -> Vec<String> {
1025 op.qubits
1026 .iter()
1027 .map(|q| {
1028 qubit_map
1029 .get(q)
1030 .cloned()
1031 .unwrap_or_else(|| format!("unknown[{}]", q.index()))
1032 })
1033 .collect()
1034}
1035
1036fn qasm2_builtin_gate_name(gate: StandardGate) -> Option<&'static str> {
1037 match gate {
1038 StandardGate::I => Some("id"),
1039 StandardGate::X => Some("x"),
1040 StandardGate::Y => Some("y"),
1041 StandardGate::Z => Some("z"),
1042 StandardGate::H => Some("h"),
1043 StandardGate::S => Some("s"),
1044 StandardGate::SDG => Some("sdg"),
1045 StandardGate::T => Some("t"),
1046 StandardGate::TDG => Some("tdg"),
1047 StandardGate::RX => Some("rx"),
1048 StandardGate::RY => Some("ry"),
1049 StandardGate::RZ => Some("rz"),
1050 StandardGate::Phase => Some("u1"),
1051 StandardGate::CX => Some("cx"),
1052 StandardGate::CY => Some("cy"),
1053 StandardGate::CZ => Some("cz"),
1054 StandardGate::SWAP => Some("swap"),
1055 StandardGate::CCX => Some("ccx"),
1056 StandardGate::U => Some("u3"),
1057 StandardGate::CRZ => Some("crz"),
1058 StandardGate::FSIM => Some("fsim"),
1059 StandardGate::CRX => Some("crx"),
1060 StandardGate::CRY => Some("cry"),
1061 StandardGate::RXX => Some("rxx"),
1062 StandardGate::RYY => Some("ryy"),
1063 StandardGate::RZZ => Some("rzz"),
1064 StandardGate::RZX => Some("rzx"),
1065 _ => None,
1066 }
1067}
1068
1069fn dump_standard_gate(
1070 gate: &StandardGate,
1071 op: &Operation,
1072 circuit: &Circuit,
1073 output: &mut String,
1074 qubit_map: &HashMap<Qubit, String>,
1075 param_map: &HashMap<String, Parameter>,
1076) -> Result<(), QasmDumpError> {
1077 let mapped_qs = map_qubits(op, qubit_map);
1078
1079 match gate {
1080 StandardGate::X2P => {
1081 writeln!(output, "// x2p {}", mapped_qs[0])?;
1082 writeln!(output, "rx(pi/2) {};", mapped_qs[0])?;
1083 return Ok(());
1084 }
1085 StandardGate::X2M => {
1086 writeln!(output, "// x2m {}", mapped_qs[0])?;
1087 writeln!(output, "rx(-pi/2) {};", mapped_qs[0])?;
1088 return Ok(());
1089 }
1090 StandardGate::Y2P => {
1091 writeln!(output, "// y2p {}", mapped_qs[0])?;
1092 writeln!(output, "ry(pi/2) {};", mapped_qs[0])?;
1093 return Ok(());
1094 }
1095 StandardGate::Y2M => {
1096 writeln!(output, "// y2m {}", mapped_qs[0])?;
1097 writeln!(output, "ry(-pi/2) {};", mapped_qs[0])?;
1098 return Ok(());
1099 }
1100 StandardGate::XY2P => {
1101 let theta = resolve_param(op, 0, circuit, param_map)?;
1102 let p1 = Parameter::pi() / 2.0 - theta.clone();
1103 let p3 = theta.clone() - Parameter::pi() / 2.0;
1104 let q_str = &mapped_qs[0];
1105
1106 writeln!(output, "// xy2p({}) {}", format_parameter(&theta), q_str)?;
1107 writeln!(output, "rz({}) {};", format_parameter(&p1), q_str)?;
1108 writeln!(output, "ry(pi/2) {};", q_str)?;
1109 writeln!(output, "rz({}) {};", format_parameter(&p3), q_str)?;
1110 return Ok(());
1111 }
1112 StandardGate::XY2M => {
1113 let theta = resolve_param(op, 0, circuit, param_map)?;
1114 let m1 = Parameter::from(0.0) - (Parameter::pi() / 2.0 + theta.clone());
1115 let m3 = theta.clone() + Parameter::pi() / 2.0;
1116 let q_str = &mapped_qs[0];
1117
1118 writeln!(output, "// xy2m({}) {}", format_parameter(&theta), q_str)?;
1119 writeln!(output, "rz({}) {};", format_parameter(&m1), q_str)?;
1120 writeln!(output, "ry(pi/2) {};", q_str)?;
1121 writeln!(output, "rz({}) {};", format_parameter(&m3), q_str)?;
1122 return Ok(());
1123 }
1124 StandardGate::XY => {
1125 let theta = resolve_param(op, 0, circuit, param_map)?;
1126 let phase: Parameter = theta.clone() - Parameter::pi() / 2.0;
1127 let lambda: Parameter = Parameter::pi() / 2.0 - theta.clone();
1128 let q_str = &mapped_qs[0];
1129
1130 writeln!(output, "// xy({}) {}", format_parameter(&theta), q_str)?;
1131 writeln!(
1132 output,
1133 "u3({},{},{}) {};",
1134 format_parameter(&Parameter::pi()),
1135 format_parameter(&phase),
1136 format_parameter(&lambda),
1137 q_str
1138 )?;
1139 return Ok(());
1140 }
1141 StandardGate::RXY => {
1142 let theta = resolve_param(op, 0, circuit, param_map)?;
1143 let phi = resolve_param(op, 1, circuit, param_map)?;
1144 let phase: Parameter = phi.clone() - Parameter::pi() / 2.0;
1145 let lambda: Parameter = Parameter::pi() / 2.0 - phi.clone();
1146 let q_str = &mapped_qs[0];
1147
1148 writeln!(
1149 output,
1150 "// rxy({},{}) {}",
1151 format_parameter(&theta),
1152 format_parameter(&phi),
1153 q_str
1154 )?;
1155 writeln!(
1156 output,
1157 "u3({},{},{}) {};",
1158 format_parameter(&theta),
1159 format_parameter(&phase),
1160 format_parameter(&lambda),
1161 q_str
1162 )?;
1163 return Ok(());
1164 }
1165 StandardGate::GPhase => {
1166 let param = resolve_param(op, 0, circuit, param_map)?;
1167 writeln!(output, "// gphase({})", param)?;
1168 return Ok(());
1169 }
1170 _ => {}
1171 }
1172
1173 let name = qasm2_builtin_gate_name(*gate).ok_or_else(|| {
1174 QasmDumpError::FormatError(format!(
1175 "Gate {gate:?} is not supported by the OpenQASM 2.0 dumper"
1176 ))
1177 })?;
1178
1179 let params_str = format_params(op, circuit, param_map)?;
1180 writeln!(output, "{}{} {};", name, params_str, mapped_qs.join(","))?;
1181 Ok(())
1182}
1183
1184fn dump_directive(
1185 directive: &Directive,
1186 op: &Operation,
1187 output: &mut String,
1188 qubit_map: &HashMap<Qubit, String>,
1189) -> Result<(), QasmDumpError> {
1190 let mapped_qs = map_qubits(op, qubit_map);
1191
1192 match directive {
1193 Directive::Measure => {
1194 return Err(QasmDumpError::UnsupportedClassicalData(
1195 "legacy measurement has no classical destination".to_string(),
1196 ));
1197 }
1198 Directive::Barrier => {
1199 writeln!(output, "barrier {};", mapped_qs.join(","))?;
1200 }
1201 Directive::Reset => {
1202 for q in mapped_qs {
1203 writeln!(output, "reset {};", q)?;
1204 }
1205 }
1206 }
1207 Ok(())
1208}
1209
1210fn qasm2_mc_gate_name(mc_gate: &MCGate) -> Option<&'static str> {
1211 match (mc_gate.num_ctrl_qubits(), mc_gate.base_gate()) {
1212 (1, StandardGate::X) => Some("cx"),
1213 (1, StandardGate::Z) => Some("cz"),
1214 (1, StandardGate::Y) => Some("cy"),
1215 (1, StandardGate::H) => Some("ch"),
1216 (1, StandardGate::RX) => Some("crx"),
1217 (1, StandardGate::RY) => Some("cry"),
1218 (1, StandardGate::RZ) => Some("crz"),
1219 (1, StandardGate::Phase) => Some("cu1"),
1220 (1, StandardGate::CX) => Some("ccx"),
1221 (1, StandardGate::SWAP) => Some("cswap"),
1222 (2, StandardGate::X) => Some("ccx"),
1223 _ => None,
1224 }
1225}
1226
1227fn dump_mc_gate(
1228 mc_gate: &MCGate,
1229 op: &Operation,
1230 circuit: &Circuit,
1231 output: &mut String,
1232 qubit_map: &HashMap<Qubit, String>,
1233 param_map: &HashMap<String, Parameter>,
1234) -> Result<(), QasmDumpError> {
1235 let num_ctrl = mc_gate.num_ctrl_qubits();
1236 let base = mc_gate.base_gate();
1237
1238 let name = qasm2_mc_gate_name(mc_gate).ok_or_else(|| {
1239 QasmDumpError::FormatError(format!(
1240 "Unsupported MCGate: {} controls on {}",
1241 num_ctrl, base
1242 ))
1243 })?;
1244
1245 let params_str = format_params(op, circuit, param_map)?;
1246 let mapped_qs = map_qubits(op, qubit_map);
1247
1248 writeln!(output, "{}{} {};", name, params_str, mapped_qs.join(","))?;
1249 Ok(())
1250}
1251
1252fn collect_used_gate_names(circuit: &Circuit, used_gates: &mut HashSet<&'static str>) {
1253 collect_used_gate_names_from_operations(circuit.operations(), used_gates);
1254}
1255
1256fn collect_used_gate_names_from_operations(
1257 operations: &[Operation],
1258 used_gates: &mut HashSet<&'static str>,
1259) {
1260 for op in operations {
1261 match &op.instruction {
1262 Instruction::Standard(sg) => {
1263 used_gates.extend(qasm2_builtin_gate_name(*sg));
1264 }
1265 Instruction::McGate(mc_gate) => {
1266 used_gates.extend(qasm2_mc_gate_name(mc_gate));
1267 }
1268 Instruction::CircuitGate(cg) => {
1269 collect_used_gate_names(&cg.circuit.circuit, used_gates);
1270 }
1271 Instruction::UnitaryGate(ug) => {
1272 if let Some(fc) = ug.circuit() {
1273 collect_used_gate_names(&fc.circuit, used_gates);
1274 }
1275 }
1276 Instruction::ClassicalControl(control) => match control {
1277 ClassicalControlOp::If(if_op) => {
1278 collect_used_gate_names_from_operations(
1279 if_op.then_body().operations(),
1280 used_gates,
1281 );
1282 if let Some(body) = if_op.else_body() {
1283 collect_used_gate_names_from_operations(body.operations(), used_gates);
1284 }
1285 }
1286 ClassicalControlOp::While(while_op) => {
1287 collect_used_gate_names_from_operations(
1288 while_op.body().operations(),
1289 used_gates,
1290 );
1291 }
1292 ClassicalControlOp::For(for_op) => {
1293 collect_used_gate_names_from_operations(for_op.body().operations(), used_gates);
1294 }
1295 ClassicalControlOp::Switch(switch_op) => {
1296 for case in switch_op.cases() {
1297 collect_used_gate_names_from_operations(
1298 case.body().operations(),
1299 used_gates,
1300 );
1301 }
1302 if let Some(body) = switch_op.default() {
1303 collect_used_gate_names_from_operations(body.operations(), used_gates);
1304 }
1305 }
1306 ClassicalControlOp::Break | ClassicalControlOp::Continue => {}
1307 },
1308 _ => {}
1309 }
1310 }
1311}
1312
1313fn dump_control_flow(
1315 control: &ClassicalControlOp,
1316 circuit: &Circuit,
1317 output: &mut String,
1318 qubit_map: &HashMap<Qubit, String>,
1319 param_map: &HashMap<String, Parameter>,
1320 registers: &ClassicalRegisterMap,
1321) -> Result<(), QasmDumpError> {
1322 match control {
1323 ClassicalControlOp::If(if_op) => {
1324 if if_op.else_body().is_some() {
1325 return Err(QasmDumpError::UnsupportedClassicalControl(
1326 "if/else has no lossless OpenQASM 2.0 representation".to_string(),
1327 ));
1328 }
1329 let (register, value) = qasm_condition(if_op.condition(), registers)?;
1330 let body_ops = if_op.then_body().operations();
1331 let mut index = 0;
1332 while index < body_ops.len() {
1333 let (body_qasms, consumed) = conditional_operation_qasms(
1334 body_ops, index, circuit, qubit_map, param_map, registers,
1335 )?;
1336 for body_qasm in body_qasms {
1337 writeln!(output, "if ({register} == {value}) {body_qasm};")?;
1338 }
1339 index += consumed;
1340 }
1341 }
1342 ClassicalControlOp::While(_) => return unsupported_control("while"),
1343 ClassicalControlOp::For(_) => return unsupported_control("for"),
1344 ClassicalControlOp::Switch(_) => return unsupported_control("switch"),
1345 ClassicalControlOp::Break => return unsupported_control("break"),
1346 ClassicalControlOp::Continue => return unsupported_control("continue"),
1347 }
1348 Ok(())
1349}
1350
1351fn conditional_operation_qasms(
1352 operations: &[Operation],
1353 index: usize,
1354 circuit: &Circuit,
1355 qubit_map: &HashMap<Qubit, String>,
1356 param_map: &HashMap<String, Parameter>,
1357 registers: &ClassicalRegisterMap,
1358) -> Result<(Vec<String>, usize), QasmDumpError> {
1359 let op = &operations[index];
1360 match &op.instruction {
1361 Instruction::ClassicalData(ClassicalDataOp::MeasureBit { result })
1362 | Instruction::ClassicalData(ClassicalDataOp::MeasureBits { result }) => {
1363 let next = operations.get(index + 1);
1364 let (destination, consumes_store) =
1365 measurement_destination(&op.instruction, *result, next)?;
1366 if consumes_store
1367 && operations[index + 2..]
1368 .iter()
1369 .any(|operation| operation.instruction.reads_value(*result))
1370 {
1371 return Err(QasmDumpError::UnsupportedClassicalData(format!(
1372 "measurement value {} is read after being stored into a mutable register",
1373 result.index()
1374 )));
1375 }
1376 let lines = measurement_qasm_lines(op, *result, destination, qubit_map, registers)?;
1377 Ok((lines, if consumes_store { 2 } else { 1 }))
1378 }
1379 _ => Ok((
1380 vec![operation_to_qasm(op, circuit, qubit_map, param_map)?],
1381 1,
1382 )),
1383 }
1384}
1385
1386fn unsupported_control<T>(name: &str) -> Result<T, QasmDumpError> {
1387 Err(QasmDumpError::UnsupportedClassicalControl(format!(
1388 "{name} is not part of OpenQASM 2.0"
1389 )))
1390}
1391
1392fn qasm_condition<'a>(
1393 expression: &ClassicalExpr,
1394 registers: &'a ClassicalRegisterMap,
1395) -> Result<(&'a str, u128), QasmDumpError> {
1396 match expression.kind() {
1397 ClassicalExprKind::Cast {
1398 cast: ClassicalCast::BitToBool,
1399 expr,
1400 } => Ok((classical_source(expr, registers, ClassicalType::Bit)?, 1)),
1401 ClassicalExprKind::Unary {
1402 op: ClassicalUnaryOp::Not,
1403 expr,
1404 } => match expr.kind() {
1405 ClassicalExprKind::Cast {
1406 cast: ClassicalCast::BitToBool,
1407 expr,
1408 } => Ok((classical_source(expr, registers, ClassicalType::Bit)?, 0)),
1409 _ => unsupported_condition(expression),
1410 },
1411 ClassicalExprKind::Compare {
1412 op: ClassicalCompareOp::Eq,
1413 lhs,
1414 rhs,
1415 } => qasm_equality_condition(lhs, rhs, registers)
1416 .or_else(|_| qasm_equality_condition(rhs, lhs, registers)),
1417 _ => unsupported_condition(expression),
1418 }
1419}
1420
1421fn qasm_equality_condition<'a>(
1422 source: &ClassicalExpr,
1423 literal: &ClassicalExpr,
1424 registers: &'a ClassicalRegisterMap,
1425) -> Result<(&'a str, u128), QasmDumpError> {
1426 match (source.kind(), literal.kind()) {
1427 (
1428 ClassicalExprKind::Cast {
1429 cast: ClassicalCast::BitVecToUInt,
1430 expr,
1431 },
1432 ClassicalExprKind::UIntLiteral { width, value },
1433 ) if expr.ty().width() == width.get() => {
1434 Ok((classical_source(expr, registers, expr.ty())?, *value))
1435 }
1436 (
1437 ClassicalExprKind::Var(_) | ClassicalExprKind::Value(_),
1438 ClassicalExprKind::BitLiteral(value),
1439 ) if source.ty() == ClassicalType::Bit => Ok((
1440 classical_source(source, registers, ClassicalType::Bit)?,
1441 u128::from(*value),
1442 )),
1443 (ClassicalExprKind::ExtractBit { value, index: 0 }, ClassicalExprKind::BitLiteral(bit))
1444 if value.ty().width() == 1 =>
1445 {
1446 Ok((
1447 classical_source(value, registers, value.ty())?,
1448 u128::from(*bit),
1449 ))
1450 }
1451 _ => unsupported_condition(source),
1452 }
1453}
1454
1455fn classical_source<'a>(
1456 expression: &ClassicalExpr,
1457 registers: &'a ClassicalRegisterMap,
1458 expected_type: ClassicalType,
1459) -> Result<&'a str, QasmDumpError> {
1460 if expression.ty() != expected_type {
1461 return unsupported_condition(expression);
1462 }
1463 match expression.kind() {
1464 ClassicalExprKind::Var(var) => registers.var(*var),
1465 ClassicalExprKind::Value(value) => registers.value(*value),
1466 _ => unsupported_condition(expression),
1467 }
1468}
1469
1470fn unsupported_condition<T>(expression: &ClassicalExpr) -> Result<T, QasmDumpError> {
1471 Err(QasmDumpError::UnsupportedClassicalControl(format!(
1472 "condition {:?} cannot be reduced to 'creg == integer'",
1473 expression.kind()
1474 )))
1475}
1476
1477fn operation_to_qasm(
1479 op: &Operation,
1480 circuit: &Circuit,
1481 qubit_map: &HashMap<Qubit, String>,
1482 param_map: &HashMap<String, Parameter>,
1483) -> Result<String, QasmDumpError> {
1484 match &op.instruction {
1485 Instruction::Standard(gate) => {
1486 let mapped_qs = map_qubits(op, qubit_map);
1487 match gate {
1488 StandardGate::XY => {
1489 let theta = resolve_param(op, 0, circuit, param_map)?;
1490 let phase: Parameter = theta.clone() - Parameter::pi() / 2.0;
1491 let lambda: Parameter = Parameter::pi() / 2.0 - theta;
1492 return Ok(format!(
1493 "u3({},{},{}) {}",
1494 format_parameter(&Parameter::pi()),
1495 format_parameter(&phase),
1496 format_parameter(&lambda),
1497 mapped_qs[0]
1498 ));
1499 }
1500 StandardGate::RXY => {
1501 let theta = resolve_param(op, 0, circuit, param_map)?;
1502 let phi = resolve_param(op, 1, circuit, param_map)?;
1503 let phase: Parameter = phi.clone() - Parameter::pi() / 2.0;
1504 let lambda: Parameter = Parameter::pi() / 2.0 - phi;
1505 return Ok(format!(
1506 "u3({},{},{}) {}",
1507 format_parameter(&theta),
1508 format_parameter(&phase),
1509 format_parameter(&lambda),
1510 mapped_qs[0]
1511 ));
1512 }
1513 _ => {}
1514 }
1515 let name = qasm2_builtin_gate_name(*gate).ok_or_else(|| {
1516 QasmDumpError::UnsupportedClassicalControl(format!(
1517 "gate {gate:?} cannot be used in an OpenQASM 2.0 conditional body"
1518 ))
1519 })?;
1520 let params_str = format_params(op, circuit, param_map)?;
1521 Ok(format!("{}{} {}", name, params_str, mapped_qs.join(",")))
1522 }
1523 Instruction::McGate(mc_gate) => {
1524 let num_ctrl = mc_gate.num_ctrl_qubits();
1525 let base = mc_gate.base_gate();
1526 let name = qasm2_mc_gate_name(mc_gate).ok_or_else(|| {
1527 QasmDumpError::FormatError(format!(
1528 "Unsupported multi-controlled gate: {}-controlled {:?}",
1529 num_ctrl, base
1530 ))
1531 })?;
1532 let params_str = format_params(op, circuit, param_map)?;
1533 let mapped_qs = map_qubits(op, qubit_map);
1534 Ok(format!("{}{} {}", name, params_str, mapped_qs.join(",")))
1535 }
1536 Instruction::Directive(directive) => match directive {
1537 Directive::Barrier => Err(QasmDumpError::UnsupportedClassicalControl(
1538 "barrier cannot be conditional in OpenQASM 2.0".to_string(),
1539 )),
1540 Directive::Reset => {
1541 let mapped_qs = map_qubits(op, qubit_map);
1542 Ok(format!("reset {}", mapped_qs[0]))
1543 }
1544 Directive::Measure => Err(QasmDumpError::MeasureInGateNotAllowed),
1545 },
1546 Instruction::CircuitGate(cg) => {
1547 let params_str = format_params(op, circuit, param_map)?;
1548 let mapped_qs = map_qubits(op, qubit_map);
1549 Ok(format!("{}{} {}", cg.name, params_str, mapped_qs.join(",")))
1550 }
1551 Instruction::UnitaryGate(unitary_gate) => {
1552 let params_str = format_params(op, circuit, param_map)?;
1553 let mapped_qs = map_qubits(op, qubit_map);
1554 Ok(format!(
1555 "{}{} {}",
1556 unitary_gate.label(),
1557 params_str,
1558 mapped_qs.join(",")
1559 ))
1560 }
1561 Instruction::ClassicalData(_) => Err(QasmDumpError::UnsupportedClassicalData(
1562 "classical data operation inside conditional body".to_string(),
1563 )),
1564 Instruction::ClassicalControl(_) => Err(QasmDumpError::UnsupportedClassicalControl(
1565 "nested control flow".to_string(),
1566 )),
1567 Instruction::Delay => Err(QasmDumpError::UnsupportedClassicalControl(
1568 "delay cannot be conditional in OpenQASM 2.0".to_string(),
1569 )),
1570 }
1571}
1572
1573fn get_extended_gate_definition(gate: &str) -> Option<&'static str> {
1576 match gate {
1577 "crx" => {
1578 Some("gate crx(theta) a,b { h b;rz(theta/2) b; cx a,b; rz(-theta/2) b; cx a,b; h b;}")
1579 }
1580 "cry" => Some("gate cry(theta) a,b { ry(theta/2) b; cx a,b; ry(-theta/2) b; cx a,b; }"),
1581 "rzz" => Some("gate rzz(theta) a,b { cx a,b; rz(theta) b; cx a,b; }"),
1582 "rxx" => Some("gate rxx(theta) a,b { h a; h b; cx a,b; rz(theta) b; cx a,b; h a; h b; }"),
1583 "ryy" => Some(
1584 "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; }",
1585 ),
1586 "rzx" => Some("gate rzx(theta) a,b { h b; cx a,b; rz(theta) b; cx a,b; h b; }"),
1587 "swap" => Some("gate swap a,b { cx a,b; cx b,a; cx a,b; }"),
1588 "cswap" => Some("gate cswap c,a,b { cx b,a; ccx c,a,b; cx b,a; }"),
1589 "fsim" => Some(concat!(
1592 "gate fsim(theta,phi) a,b { ",
1593 "h a; h b; cx a,b; rz(theta) b; cx a,b; h a; h b; ",
1594 "rx(pi/2) a; rx(pi/2) b; cx a,b; rz(theta) b; cx a,b; ",
1595 "rx(-pi/2) a; rx(-pi/2) b; cu1(-phi) a,b; }"
1596 )),
1597 _ => None,
1598 }
1599}
1600
1601#[cfg(test)]
1602#[path = "./dump_test.rs"]
1603mod dump_test;