quantrs2_circuit/qasm/
exporter.rs

1//! Export `QuantRS2` circuits to `OpenQASM` 3.0 format
2
3use super::ast::{
4    ClassicalRef, Declaration, Expression, GateDefinition, Literal, Measurement, QasmGate,
5    QasmProgram, QasmRegister, QasmStatement, QubitRef,
6};
7use crate::builder::Circuit;
8use quantrs2_core::{gate::GateOp, qubit::QubitId};
9use scirs2_core::Complex64;
10use std::collections::{HashMap, HashSet};
11use std::fmt::Write;
12use std::sync::Arc;
13use thiserror::Error;
14
15/// Export error types
16#[derive(Debug, Error)]
17pub enum ExportError {
18    #[error("Unsupported gate: {0}")]
19    UnsupportedGate(String),
20
21    #[error("Invalid circuit: {0}")]
22    InvalidCircuit(String),
23
24    #[error("Formatting error: {0}")]
25    FormattingError(#[from] std::fmt::Error),
26
27    #[error("Gate parameter error: {0}")]
28    ParameterError(String),
29}
30
31/// Options for controlling QASM export
32#[derive(Debug, Clone)]
33pub struct ExportOptions {
34    /// Include standard gate library
35    pub include_stdgates: bool,
36    /// Use gate decomposition for non-standard gates
37    pub decompose_custom: bool,
38    /// Add comments with gate matrix representations
39    pub include_gate_comments: bool,
40    /// Optimize gate sequences
41    pub optimize: bool,
42    /// Pretty print with indentation
43    pub pretty_print: bool,
44}
45
46impl Default for ExportOptions {
47    fn default() -> Self {
48        Self {
49            include_stdgates: true,
50            decompose_custom: true,
51            include_gate_comments: false,
52            optimize: false,
53            pretty_print: true,
54        }
55    }
56}
57
58/// QASM exporter
59pub struct QasmExporter {
60    options: ExportOptions,
61    /// Track which gates need custom definitions
62    custom_gates: HashMap<String, GateInfo>,
63    /// Track qubit usage
64    qubit_usage: HashSet<usize>,
65    /// Track if measurements are used
66    needs_classical_bits: bool,
67}
68
69#[derive(Clone)]
70struct GateInfo {
71    name: String,
72    num_qubits: usize,
73    num_params: usize,
74    matrix: Option<scirs2_core::ndarray::Array2<Complex64>>,
75}
76
77impl QasmExporter {
78    /// Create a new exporter with options
79    #[must_use]
80    pub fn new(options: ExportOptions) -> Self {
81        Self {
82            options,
83            custom_gates: HashMap::new(),
84            qubit_usage: HashSet::new(),
85            needs_classical_bits: false,
86        }
87    }
88
89    /// Export a circuit to QASM 3.0
90    pub fn export<const N: usize>(&mut self, circuit: &Circuit<N>) -> Result<String, ExportError> {
91        // Analyze circuit
92        self.analyze_circuit(circuit)?;
93
94        // Generate QASM program
95        let program = self.generate_program(circuit)?;
96
97        // Convert to string
98        Ok(program.to_string())
99    }
100
101    /// Analyze circuit to determine requirements
102    fn analyze_circuit<const N: usize>(&mut self, circuit: &Circuit<N>) -> Result<(), ExportError> {
103        self.qubit_usage.clear();
104        self.custom_gates.clear();
105        self.needs_classical_bits = false;
106
107        // Analyze each gate
108        for gate in circuit.gates() {
109            // Track qubit usage
110            for qubit in gate.qubits() {
111                self.qubit_usage.insert(qubit.id() as usize);
112            }
113
114            // Check if gate is standard or custom
115            if !self.is_standard_gate(gate.as_ref()) {
116                self.register_custom_gate(gate.as_ref())?;
117            }
118
119            // Check for measurements
120            if gate.name().contains("measure") {
121                self.needs_classical_bits = true;
122            }
123        }
124
125        Ok(())
126    }
127
128    /// Check if a gate is in the standard library
129    fn is_standard_gate(&self, gate: &dyn GateOp) -> bool {
130        let name = gate.name();
131        matches!(
132            name,
133            "I" | "X"
134                | "Y"
135                | "Z"
136                | "H"
137                | "S"
138                | "S†"
139                | "Sdg"
140                | "T"
141                | "T†"
142                | "Tdg"
143                | "√X"
144                | "√X†"
145                | "SX"
146                | "SXdg"
147                | "RX"
148                | "RY"
149                | "RZ"
150                | "P"
151                | "Phase"
152                | "U"
153                | "U1"
154                | "U2"
155                | "U3"
156                | "CX"
157                | "CNOT"
158                | "CY"
159                | "CZ"
160                | "CH"
161                | "CRX"
162                | "CRY"
163                | "CRZ"
164                | "CPhase"
165                | "SWAP"
166                | "iSWAP"
167                | "ECR"
168                | "DCX"
169                | "RXX"
170                | "RYY"
171                | "RZZ"
172                | "RZX"
173                | "CU"
174                | "CCX"
175                | "Toffoli"
176                | "Fredkin"
177                | "measure"
178                | "reset"
179                | "barrier"
180        )
181    }
182
183    /// Register a custom gate
184    fn register_custom_gate(&mut self, gate: &dyn GateOp) -> Result<(), ExportError> {
185        let name = self.gate_qasm_name(gate);
186
187        if !self.custom_gates.contains_key(&name) {
188            let info = GateInfo {
189                name: name.clone(),
190                num_qubits: gate.qubits().len(),
191                num_params: self.count_gate_params(gate),
192                matrix: None, // GateOp doesn't have matrix() method
193            };
194
195            self.custom_gates.insert(name, info);
196        }
197
198        Ok(())
199    }
200
201    /// Get QASM name for a gate
202    fn gate_qasm_name(&self, gate: &dyn GateOp) -> String {
203        let name = gate.name();
204        match name {
205            "I" => "id".to_string(),
206            "X" => "x".to_string(),
207            "Y" => "y".to_string(),
208            "Z" => "z".to_string(),
209            "H" => "h".to_string(),
210            "S" | "S†" => "s".to_string(),
211            "Sdg" => "sdg".to_string(),
212            "T" => "t".to_string(),
213            "T†" | "Tdg" => "tdg".to_string(),
214            "√X" | "SX" => "sx".to_string(),
215            "√X†" | "SXdg" => "sxdg".to_string(),
216            "RX" => "rx".to_string(),
217            "RY" => "ry".to_string(),
218            "RZ" => "rz".to_string(),
219            "P" | "Phase" => "p".to_string(),
220            "U" => "u".to_string(),
221            "CX" | "CNOT" => "cx".to_string(),
222            "CY" => "cy".to_string(),
223            "CZ" => "cz".to_string(),
224            "CH" => "ch".to_string(),
225            "CRX" => "crx".to_string(),
226            "CRY" => "cry".to_string(),
227            "CRZ" => "crz".to_string(),
228            "CPhase" => "cp".to_string(),
229            "SWAP" => "swap".to_string(),
230            "iSWAP" => "iswap".to_string(),
231            "ECR" => "ecr".to_string(),
232            "DCX" => "dcx".to_string(),
233            "RXX" => "rxx".to_string(),
234            "RYY" => "ryy".to_string(),
235            "RZZ" => "rzz".to_string(),
236            "RZX" => "rzx".to_string(),
237            "CCX" | "Toffoli" => "ccx".to_string(),
238            "Fredkin" => "cswap".to_string(),
239            _ => name.to_lowercase(),
240        }
241    }
242
243    /// Count gate parameters
244    fn count_gate_params(&self, gate: &dyn GateOp) -> usize {
245        // This is a simplified version - would need gate trait extension
246        let name = gate.name();
247        match name {
248            "RX" | "RY" | "RZ" | "P" | "Phase" | "U1" => 1,
249            "U2" => 2,
250            "U" | "U3" => 3,
251            "CRX" | "CRY" | "CRZ" | "CPhase" => 1,
252            "RXX" | "RYY" | "RZZ" | "RZX" => 1,
253            _ => 0,
254        }
255    }
256
257    /// Generate QASM program
258    fn generate_program<const N: usize>(
259        &self,
260        circuit: &Circuit<N>,
261    ) -> Result<QasmProgram, ExportError> {
262        let mut declarations = Vec::new();
263        let mut statements = Vec::new();
264
265        // Calculate required register size
266        let max_qubit = self.qubit_usage.iter().max().copied().unwrap_or(0);
267        let num_qubits = max_qubit + 1;
268
269        // Add quantum register
270        declarations.push(Declaration::QuantumRegister(QasmRegister {
271            name: "q".to_string(),
272            size: num_qubits,
273        }));
274
275        // Add classical register if needed
276        if self.needs_classical_bits {
277            declarations.push(Declaration::ClassicalRegister(QasmRegister {
278                name: "c".to_string(),
279                size: num_qubits,
280            }));
281        }
282
283        // Add custom gate definitions
284        if self.options.decompose_custom {
285            for gate_info in self.custom_gates.values() {
286                if let Some(def) = self.generate_gate_definition(gate_info)? {
287                    declarations.push(Declaration::GateDefinition(def));
288                }
289            }
290        }
291
292        // Convert gates to statements
293        for gate in circuit.gates() {
294            statements.push(self.convert_gate(gate)?);
295        }
296
297        // Build includes
298        let includes = if self.options.include_stdgates {
299            vec!["stdgates.inc".to_string()]
300        } else {
301            vec![]
302        };
303
304        Ok(QasmProgram {
305            version: "3.0".to_string(),
306            includes,
307            declarations,
308            statements,
309        })
310    }
311
312    /// Generate gate definition for custom gate
313    const fn generate_gate_definition(
314        &self,
315        gate_info: &GateInfo,
316    ) -> Result<Option<GateDefinition>, ExportError> {
317        // For now, return None - full implementation would decompose gates
318        // This would use gate synthesis algorithms
319        Ok(None)
320    }
321
322    /// Convert gate to QASM statement
323    fn convert_gate(
324        &self,
325        gate: &Arc<dyn GateOp + Send + Sync>,
326    ) -> Result<QasmStatement, ExportError> {
327        let gate_name = gate.name();
328
329        match gate_name {
330            "measure" => {
331                // Convert measurement
332                let qubits: Vec<QubitRef> = gate
333                    .qubits()
334                    .iter()
335                    .map(|q| QubitRef::Single {
336                        register: "q".to_string(),
337                        index: q.id() as usize,
338                    })
339                    .collect();
340
341                let targets: Vec<ClassicalRef> = gate
342                    .qubits()
343                    .iter()
344                    .map(|q| ClassicalRef::Single {
345                        register: "c".to_string(),
346                        index: q.id() as usize,
347                    })
348                    .collect();
349
350                Ok(QasmStatement::Measure(Measurement { qubits, targets }))
351            }
352            "reset" => {
353                let qubits: Vec<QubitRef> = gate
354                    .qubits()
355                    .iter()
356                    .map(|q| QubitRef::Single {
357                        register: "q".to_string(),
358                        index: q.id() as usize,
359                    })
360                    .collect();
361
362                Ok(QasmStatement::Reset(qubits))
363            }
364            "barrier" => {
365                let qubits: Vec<QubitRef> = gate
366                    .qubits()
367                    .iter()
368                    .map(|q| QubitRef::Single {
369                        register: "q".to_string(),
370                        index: q.id() as usize,
371                    })
372                    .collect();
373
374                Ok(QasmStatement::Barrier(qubits))
375            }
376            _ => {
377                // Regular gate
378                let name = self.gate_qasm_name(gate.as_ref());
379
380                let qubits: Vec<QubitRef> = gate
381                    .qubits()
382                    .iter()
383                    .map(|q| QubitRef::Single {
384                        register: "q".to_string(),
385                        index: q.id() as usize,
386                    })
387                    .collect();
388
389                // Extract parameters - this is simplified
390                let params = self.extract_gate_params(gate.as_ref())?;
391
392                Ok(QasmStatement::Gate(QasmGate {
393                    name,
394                    params,
395                    qubits,
396                    control: None,
397                    inverse: false,
398                    power: None,
399                }))
400            }
401        }
402    }
403
404    /// Extract gate parameters as expressions
405    fn extract_gate_params(&self, gate: &dyn GateOp) -> Result<Vec<Expression>, ExportError> {
406        use quantrs2_core::gate::multi::{CRX, CRY, CRZ};
407        use quantrs2_core::gate::single::{RotationX, RotationY, RotationZ};
408        use std::any::Any;
409
410        let any_gate = gate.as_any();
411
412        // Single-qubit rotation gates
413        if let Some(rx) = any_gate.downcast_ref::<RotationX>() {
414            return Ok(vec![Expression::Literal(Literal::Float(rx.theta))]);
415        }
416        if let Some(ry) = any_gate.downcast_ref::<RotationY>() {
417            return Ok(vec![Expression::Literal(Literal::Float(ry.theta))]);
418        }
419        if let Some(rz) = any_gate.downcast_ref::<RotationZ>() {
420            return Ok(vec![Expression::Literal(Literal::Float(rz.theta))]);
421        }
422
423        // Controlled rotation gates
424        if let Some(crx) = any_gate.downcast_ref::<CRX>() {
425            return Ok(vec![Expression::Literal(Literal::Float(crx.theta))]);
426        }
427        if let Some(cry) = any_gate.downcast_ref::<CRY>() {
428            return Ok(vec![Expression::Literal(Literal::Float(cry.theta))]);
429        }
430        if let Some(crz) = any_gate.downcast_ref::<CRZ>() {
431            return Ok(vec![Expression::Literal(Literal::Float(crz.theta))]);
432        }
433
434        // No parameters for other gates
435        Ok(vec![])
436    }
437}
438
439/// Export a circuit to QASM 3.0 with default options
440pub fn export_qasm3<const N: usize>(circuit: &Circuit<N>) -> Result<String, ExportError> {
441    let mut exporter = QasmExporter::new(ExportOptions::default());
442    exporter.export(circuit)
443}
444
445#[cfg(test)]
446mod tests {
447    use super::*;
448    use quantrs2_core::gate::multi::CNOT;
449    use quantrs2_core::gate::single::{Hadamard, PauliX};
450    use quantrs2_core::qubit::QubitId;
451
452    #[test]
453    fn test_export_simple_circuit() {
454        let mut circuit = Circuit::<2>::new();
455        circuit
456            .add_gate(Hadamard { target: QubitId(0) })
457            .expect("adding Hadamard gate should succeed");
458        circuit
459            .add_gate(CNOT {
460                control: QubitId(0),
461                target: QubitId(1),
462            })
463            .expect("adding CNOT gate should succeed");
464
465        let result = export_qasm3(&circuit);
466        assert!(result.is_ok());
467
468        let qasm = result.expect("export_qasm3 should succeed for valid circuit");
469        assert!(qasm.contains("OPENQASM 3.0"));
470        assert!(qasm.contains("qubit[2] q"));
471        assert!(qasm.contains("h q[0]"));
472        assert!(qasm.contains("cx q[0], q[1]"));
473    }
474
475    #[test]
476    fn test_export_with_measurements() {
477        let mut circuit = Circuit::<2>::new();
478        circuit
479            .add_gate(Hadamard { target: QubitId(0) })
480            .expect("adding Hadamard gate should succeed");
481        // Note: measure gate would need to be implemented
482
483        let result = export_qasm3(&circuit);
484        assert!(result.is_ok());
485
486        let qasm = result.expect("export_qasm3 should succeed for measurement test");
487        // Basic check
488        assert!(qasm.contains("OPENQASM 3.0"));
489    }
490}