Skip to main content

cqlib_core/ir/qasm2/
dump.rs

1// This code is part of Cqlib.
2//
3// (C) Copyright China Telecom Quantum Group 2026
4//
5// This code is licensed under the Apache License, Version 2.0. You may
6// obtain a copy of this license in the LICENSE.txt file in the root directory
7// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8//
9// Any modifications or derivative works of this code must retain this
10// copyright notice, and modified files need to carry a notice indicating
11// that they have been altered from the originals.
12
13//! OpenQASM 2.0 Serializer Module
14//!
15//! This module provides functionality to serialize internal `Circuit` representations
16//! into OpenQASM 2.0 format.
17//!
18//! ## Features
19//!
20//! - **Standard Gates**: Outputs all standard quantum gates with correct QASM syntax
21//! - **Custom Gates**: Outputs user-defined gates as gate definitions followed by invocations
22//! - **Control Flow**: Handles the OpenQASM 2.0 `if (creg == integer)` subset
23//! - **Extended Gates**: Automatically includes definitions for non-standard gates (CRX, CRY, etc.)
24//! - **Parameter Formatting**: Formats floating-point parameters using common constants (pi, pi/2, etc.)
25//!
26//! ## Output Format
27//!
28//! The serializer generates valid OpenQASM 2.0 code:
29//! - Header with version and include statement
30//! - Quantum register declaration
31//! - Classical register declarations (for conditional operations)
32//! - Gate definitions (for custom gates)
33//! - Circuit operations
34//!
35//! ## Example
36//!
37//! ```rust
38//! use cqlib_core::ir::qasm2::dump::dumps;
39//! use cqlib_core::circuit::{Circuit, Qubit};
40//!
41//! let mut circuit = Circuit::new(2);
42//! circuit.h(Qubit::new(0)).unwrap();
43//! circuit.cx(Qubit::new(0), Qubit::new(1)).unwrap();
44//!
45//! let qasm = dumps(&circuit).unwrap();
46//! assert_eq!(
47//!     qasm,
48//!     concat!("// This file is auto-generated by Cqlib v", env!("CARGO_PKG_VERSION"), ".\n\
49//! OPENQASM 2.0;\n\
50//! include \"qelib1.inc\";\n\
51//! \n\
52//! qreg q[2];\n\
53//! \n\
54//! \n\
55//! \n\
56//! h q[0];\n\
57//! cx q[0],q[1];\n")
58//! );
59//! ```
60//!
61//! ## Limitations
62//!
63//! - General classical expressions and assignments are not supported by OpenQASM 2.0
64//! - Else branches, loops, switches, and nested control flow return an error
65//! - Measurements and resets inside gate definitions return an error
66//! - Classical register names are normalized to `c{id}` and `v{id}` because
67//!   [`Circuit`] does not retain source-format register names.
68//! - Non-zero global phase and `GPhase` instructions are emitted only as comments;
69//!   loading the generated OpenQASM 2.0 does not restore that phase.
70
71use 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/// Errors that can occur during OpenQASM serialization.
91///
92/// These errors indicate problems that prevent successful conversion
93/// from internal Circuit representation to OpenQASM 2.0 format.
94#[derive(Debug)]
95pub enum QasmDumpError {
96    /// File system or I/O error during file writing
97    IoError(io::Error),
98    /// Format error (invalid gate, parameter, or syntax generation failure)
99    FormatError(String),
100    /// Measure directives inside gate definitions are invalid in OpenQASM 2.0
101    MeasureInGateNotAllowed,
102    /// Reset directives inside gate definitions are invalid in OpenQASM 2.0
103    ResetInGateNotAllowed,
104    /// Invalid qubit index in quantum register
105    InvalidQubitIndex(String),
106    /// A classical type cannot be represented by an OpenQASM 2.0 `creg`.
107    UnsupportedClassicalType(String),
108    /// A classical data operation cannot be represented in OpenQASM 2.0.
109    UnsupportedClassicalData(String),
110    /// A classical control operation cannot be represented in OpenQASM 2.0.
111    UnsupportedClassicalControl(String),
112    /// Distinct custom gate definitions use the same OpenQASM gate name.
113    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
246/// Dumps a circuit to a file in OpenQASM 2.0 format.
247pub 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
254/// Write a circuit to an OpenQASM 2.0 file.
255///
256/// Rust-style alias for [`dump`]. The Python-style `dump` name is retained for
257/// compatibility with the rest of the IR module API.
258pub fn to_path<P: AsRef<Path>>(circuit: &Circuit, path: P) -> Result<(), QasmDumpError> {
259    dump(circuit, path)
260}
261
262/// Dumps a circuit to a string in OpenQASM 2.0 format.
263///
264/// Top-level qubits are mapped densely in ascending numeric identifier order,
265/// matching [`Circuit::to_matrix`] with its default qubit order.
266pub 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    // Output global phase as a comment (OpenQASM 2.0 does not have a standard directive for this)
281    let global_phase = circuit.global_phase();
282    // Simple check if it's non-zero (approx)
283    match global_phase.evaluate(&None) {
284        Ok(val) if val.abs() < 1e-10 => {} // Zero, ignore
285        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    // Pass 0: Collect actual output names, including those emitted by MCGate.
292    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    // Pass 1: Collect gate definitions (Dependencies first)
309    let mut defined_gates: IndexMap<String, CircuitGate> = IndexMap::new();
310    let mut unitary_gate_defs: IndexMap<String, UnitaryGate> = IndexMap::new();
311    // UnitaryGate with only matrix (no circuit) - will be declared as opaque
312    let mut opaque_unitary_gates: IndexMap<String, UnitaryGate> = IndexMap::new();
313    // Track if Delay is used
314    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    // A helper definition and a user definition cannot share a QASM name.
324    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    // Output CircuitGate definitions
337    for gate in defined_gates.values() {
338        dump_gate_definition(gate, &mut output)?;
339        writeln!(&mut output)?;
340    }
341
342    // Output UnitaryGate definitions (with circuit)
343    for gate in unitary_gate_defs.values() {
344        dump_unitary_gate_definition(gate, &mut output)?;
345        writeln!(&mut output)?;
346    }
347
348    // Output opaque declarations for UnitaryGate without circuit
349    for gate in opaque_unitary_gates.values() {
350        dump_opaque_declaration(gate, &mut output)?;
351        writeln!(&mut output)?;
352    }
353
354    // Output opaque declaration for Delay if used
355    if has_delay {
356        writeln!(&mut output, "opaque delay(t) q;")?;
357        writeln!(&mut output)?;
358    }
359
360    // Pass 2: Main circuit
361    // Numeric identifiers need not be contiguous or match storage order.
362    // Preserve the default matrix basis while keeping the register width N.
363    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    // Top-level parameter resolution is direct (Parameter objects are used as is)
371    let param_map: HashMap<String, Parameter> = HashMap::new();
372
373    process_circuit_operations(
374        circuit,
375        &mut output,
376        &qubit_map,
377        &param_map,
378        &classical_registers,
379    )?;
380
381    Ok(output)
382}
383
384/// Serialize a circuit to an OpenQASM 2.0 string.
385///
386/// Rust-style alias for [`dumps`].
387pub fn to_string(circuit: &Circuit) -> Result<String, QasmDumpError> {
388    dumps(circuit)
389}
390
391/// Collects all custom gate definitions from a circuit for output.
392///
393/// This is the first pass of serialization - it traverses the circuit recursively
394/// to find all CircuitGate and UnitaryGate definitions that need to be output
395/// as gate declarations in the QASM output.
396///
397/// Uses post-order traversal to ensure dependencies are collected before the gates
398/// that depend on them (important for correct gate ordering in output).
399fn 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                // Recurse first (Post-order traversal ensures dependencies are collected first)
429                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                    // Recurse into UnitaryGate's internal circuit first
454                    collect_gates(
455                        &fc.circuit,
456                        defined_gates,
457                        unitary_gate_defs,
458                        opaque_unitary_gates,
459                        has_delay,
460                    )?;
461
462                    // Register this UnitaryGate as a gate definition
463                    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                    // UnitaryGate without circuit (only matrix) - will be declared as opaque
469                    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    // Body context
559    // Map inner qubits to formal parameters: Qubit(i) -> "qi"
560    let mut body_qubit_map = HashMap::new();
561    let inner_circuit = &cg.circuit.circuit;
562    // We assume the inner circuit qubits are ordered 0..N corresponding to arguments
563    // Or we use the order in `inner_circuit.qubits()`
564    for (i, q) in inner_circuit.qubits().iter().enumerate() {
565        body_qubit_map.insert(*q, format!("q{}", i));
566    }
567
568    // Parameters in body are symbols, which match the formal parameters in name.
569    // No substitution needed.
570    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    // Body context
611    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                // Output gate call: name(params) qubits;
717
718                // Resolve arguments
719                let params_str = format_params(op, circuit, param_map)?;
720
721                // Map qubits
722                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                // Output gate call: label(params) qubits;
728                // (The gate definition or opaque declaration was already output in Pass 1)
729                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                // Output delay gate call: delay(value) q[i];
783                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(&param))
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
1313/// Dumps the subset of expression-based control flow representable by OpenQASM 2.0.
1314fn 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
1477/// Convert a single operation to QASM string (without semicolon)
1478fn 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
1573// Definitions use only the original qelib1.inc, so no implicit extension
1574// dependencies (or additional dependency ordering) are needed.
1575fn 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(theta,phi) = RXX(theta) RYY(theta) CP(-phi), inlined to
1590        // keep the definition independent of other extension definitions.
1591        "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;