1use 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#[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#[derive(Debug, Clone)]
33pub struct ExportOptions {
34 pub include_stdgates: bool,
36 pub decompose_custom: bool,
38 pub include_gate_comments: bool,
40 pub optimize: bool,
42 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
58pub struct QasmExporter {
60 options: ExportOptions,
61 custom_gates: HashMap<String, GateInfo>,
63 qubit_usage: HashSet<usize>,
65 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 #[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 pub fn export<const N: usize>(&mut self, circuit: &Circuit<N>) -> Result<String, ExportError> {
91 self.analyze_circuit(circuit)?;
93
94 let program = self.generate_program(circuit)?;
96
97 Ok(program.to_string())
99 }
100
101 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 for gate in circuit.gates() {
109 for qubit in gate.qubits() {
111 self.qubit_usage.insert(qubit.id() as usize);
112 }
113
114 if !self.is_standard_gate(gate.as_ref()) {
116 self.register_custom_gate(gate.as_ref())?;
117 }
118
119 if gate.name().contains("measure") {
121 self.needs_classical_bits = true;
122 }
123 }
124
125 Ok(())
126 }
127
128 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 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, };
194
195 self.custom_gates.insert(name, info);
196 }
197
198 Ok(())
199 }
200
201 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 fn count_gate_params(&self, gate: &dyn GateOp) -> usize {
245 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 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 let max_qubit = self.qubit_usage.iter().max().copied().unwrap_or(0);
267 let num_qubits = max_qubit + 1;
268
269 declarations.push(Declaration::QuantumRegister(QasmRegister {
271 name: "q".to_string(),
272 size: num_qubits,
273 }));
274
275 if self.needs_classical_bits {
277 declarations.push(Declaration::ClassicalRegister(QasmRegister {
278 name: "c".to_string(),
279 size: num_qubits,
280 }));
281 }
282
283 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 for gate in circuit.gates() {
294 statements.push(self.convert_gate(gate)?);
295 }
296
297 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 const fn generate_gate_definition(
314 &self,
315 gate_info: &GateInfo,
316 ) -> Result<Option<GateDefinition>, ExportError> {
317 Ok(None)
320 }
321
322 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 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 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 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 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 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 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 Ok(vec![])
436 }
437}
438
439pub 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 let result = export_qasm3(&circuit);
484 assert!(result.is_ok());
485
486 let qasm = result.expect("export_qasm3 should succeed for measurement test");
487 assert!(qasm.contains("OPENQASM 3.0"));
489 }
490}