Skip to main content

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