1use quantrs2_circuit::prelude::Circuit;
7use quantrs2_core::gate::single::{RotationX, RotationY, RotationZ};
8use quantrs2_core::prelude::QubitId;
9use std::collections::{HashMap, VecDeque};
10
11use crate::DeviceError;
12use crate::DeviceResult;
13
14#[derive(Debug, Clone)]
16pub struct QasmCircuit {
17 pub content: String,
19 pub qubit_count: usize,
21 pub cbit_count: usize,
23 pub gate_counts: HashMap<String, usize>,
25}
26
27pub struct CircuitTranspiler;
47
48impl CircuitTranspiler {
49 pub fn circuit_to_qasm<const N: usize>(
51 circuit: &Circuit<N>,
52 qubit_mapping: Option<&HashMap<usize, usize>>,
53 ) -> DeviceResult<QasmCircuit> {
54 let mut qasm = String::from("OPENQASM 2.0;\ninclude \"qelib1.inc\";\n\n");
56
57 use std::fmt::Write;
59 let _ = writeln!(qasm, "qreg q[{N}];");
60 let _ = writeln!(qasm, "creg c[{N}];");
61
62 let mut gate_counts = HashMap::new();
64
65 for gate in circuit.gates() {
67 let gate_qasm = match gate.name() {
68 "X" => {
69 if gate.qubits().len() != 1 {
70 continue;
71 }
72 let qubit = gate.qubits()[0];
73 let q = map_qubit(qubit.id() as usize, &qubit_mapping);
74 *gate_counts.entry("x".to_string()).or_insert(0) += 1;
75 format!("x q[{q}];")
76 }
77 "Y" => {
78 if gate.qubits().len() != 1 {
79 continue;
80 }
81 let qubit = gate.qubits()[0];
82 let q = map_qubit(qubit.id() as usize, &qubit_mapping);
83 *gate_counts.entry("y".to_string()).or_insert(0) += 1;
84 format!("y q[{q}];")
85 }
86 "Z" => {
87 if gate.qubits().len() != 1 {
88 continue;
89 }
90 let qubit = gate.qubits()[0];
91 let q = map_qubit(qubit.id() as usize, &qubit_mapping);
92 *gate_counts.entry("z".to_string()).or_insert(0) += 1;
93 format!("z q[{q}];")
94 }
95 "H" => {
96 if gate.qubits().len() != 1 {
97 continue;
98 }
99 let qubit = gate.qubits()[0];
100 let q = map_qubit(qubit.id() as usize, &qubit_mapping);
101 *gate_counts.entry("h".to_string()).or_insert(0) += 1;
102 format!("h q[{q}];")
103 }
104 "S" => {
105 if gate.qubits().len() != 1 {
106 continue;
107 }
108 let qubit = gate.qubits()[0];
109 let q = map_qubit(qubit.id() as usize, &qubit_mapping);
110 *gate_counts.entry("s".to_string()).or_insert(0) += 1;
111 format!("s q[{q}];")
112 }
113 "S†" => {
114 if gate.qubits().len() != 1 {
115 continue;
116 }
117 let qubit = gate.qubits()[0];
118 let q = map_qubit(qubit.id() as usize, &qubit_mapping);
119 *gate_counts.entry("sdg".to_string()).or_insert(0) += 1;
120 format!("sdg q[{q}];")
121 }
122 "T" => {
123 if gate.qubits().len() != 1 {
124 continue;
125 }
126 let qubit = gate.qubits()[0];
127 let q = map_qubit(qubit.id() as usize, &qubit_mapping);
128 *gate_counts.entry("t".to_string()).or_insert(0) += 1;
129 format!("t q[{q}];")
130 }
131 "T†" => {
132 if gate.qubits().len() != 1 {
133 continue;
134 }
135 let qubit = gate.qubits()[0];
136 let q = map_qubit(qubit.id() as usize, &qubit_mapping);
137 *gate_counts.entry("tdg".to_string()).or_insert(0) += 1;
138 format!("tdg q[{q}];")
139 }
140 "CNOT" => {
141 if gate.qubits().len() != 2 {
142 continue;
143 }
144 let control = gate.qubits()[0];
145 let target = gate.qubits()[1];
146 let c = map_qubit(control.id() as usize, &qubit_mapping);
147 let t = map_qubit(target.id() as usize, &qubit_mapping);
148 *gate_counts.entry("cx".to_string()).or_insert(0) += 1;
149 format!("cx q[{c}], q[{t}];")
150 }
151 "CZ" => {
152 if gate.qubits().len() != 2 {
153 continue;
154 }
155 let control = gate.qubits()[0];
156 let target = gate.qubits()[1];
157 let c = map_qubit(control.id() as usize, &qubit_mapping);
158 let t = map_qubit(target.id() as usize, &qubit_mapping);
159 *gate_counts.entry("cz".to_string()).or_insert(0) += 1;
160 format!("cz q[{c}], q[{t}];")
161 }
162 "SWAP" => {
163 if gate.qubits().len() != 2 {
164 continue;
165 }
166 let q1 = gate.qubits()[0];
167 let q2 = gate.qubits()[1];
168 let q1_mapped = map_qubit(q1.id() as usize, &qubit_mapping);
169 let q2_mapped = map_qubit(q2.id() as usize, &qubit_mapping);
170 *gate_counts.entry("swap".to_string()).or_insert(0) += 1;
171 format!("swap q[{q1_mapped}], q[{q2_mapped}];")
172 }
173 "RX" => {
174 if gate.qubits().len() != 1 {
175 continue;
176 }
177 let qubit = gate.qubits()[0];
178 let q = map_qubit(qubit.id() as usize, &qubit_mapping);
179 let theta = gate
180 .as_any()
181 .downcast_ref::<RotationX>()
182 .map(|g| g.theta)
183 .unwrap_or(0.0);
184 *gate_counts.entry("rx".to_string()).or_insert(0) += 1;
185 format!("rx({theta}) q[{q}];")
186 }
187 "RY" => {
188 if gate.qubits().len() != 1 {
189 continue;
190 }
191 let qubit = gate.qubits()[0];
192 let q = map_qubit(qubit.id() as usize, &qubit_mapping);
193 let theta = gate
194 .as_any()
195 .downcast_ref::<RotationY>()
196 .map(|g| g.theta)
197 .unwrap_or(0.0);
198 *gate_counts.entry("ry".to_string()).or_insert(0) += 1;
199 format!("ry({theta}) q[{q}];")
200 }
201 "RZ" => {
202 if gate.qubits().len() != 1 {
203 continue;
204 }
205 let qubit = gate.qubits()[0];
206 let q = map_qubit(qubit.id() as usize, &qubit_mapping);
207 let theta = gate
208 .as_any()
209 .downcast_ref::<RotationZ>()
210 .map(|g| g.theta)
211 .unwrap_or(0.0);
212 *gate_counts.entry("rz".to_string()).or_insert(0) += 1;
213 format!("rz({theta}) q[{q}];")
214 }
215 _ => {
216 return Err(DeviceError::CircuitConversion(format!(
218 "Unsupported gate type for QASM conversion: {}",
219 gate.name()
220 )));
221 }
222 };
223
224 let _ = writeln!(qasm, "{gate_qasm}");
225 }
226
227 Ok(QasmCircuit {
228 content: qasm,
229 qubit_count: N,
230 cbit_count: N, gate_counts,
232 })
233 }
234
235 pub fn optimize_qubit_mapping<const N: usize>(
237 circuit: &Circuit<N>,
238 coupling_map: &[(usize, usize)],
239 ) -> HashMap<usize, usize> {
240 let mut qubit_interactions = HashMap::new();
246
247 for gate in circuit.gates() {
248 let qubits = gate.qubits();
249 if qubits.len() == 2 {
250 let q1 = qubits[0].id() as usize;
251 let q2 = qubits[1].id() as usize;
252 *qubit_interactions.entry((q1, q2)).or_insert(0) += 1;
253 }
254 }
255
256 let mut pairs: Vec<_> = qubit_interactions.iter().collect();
258 pairs.sort_by(|a, b| b.1.cmp(a.1));
259
260 let mut mapping = HashMap::new();
262 let mut used_device_qubits = std::collections::HashSet::new();
263
264 for (&(q1, q2), _) in &pairs {
266 if mapping.contains_key(&q1) && mapping.contains_key(&q2) {
267 continue;
268 }
269
270 for &(dev_q1, dev_q2) in coupling_map {
272 if (!mapping.contains_key(&q1) || mapping[&q1] == dev_q1)
273 && (!mapping.contains_key(&q2) || mapping[&q2] == dev_q2)
274 && !used_device_qubits.contains(&dev_q1)
275 && !used_device_qubits.contains(&dev_q2)
276 {
277 if let std::collections::hash_map::Entry::Vacant(e) = mapping.entry(q1) {
279 e.insert(dev_q1);
280 used_device_qubits.insert(dev_q1);
281 }
282 if let std::collections::hash_map::Entry::Vacant(e) = mapping.entry(q2) {
283 e.insert(dev_q2);
284 used_device_qubits.insert(dev_q2);
285 }
286 break;
287 }
288
289 if (!mapping.contains_key(&q1) || mapping[&q1] == dev_q2)
290 && (!mapping.contains_key(&q2) || mapping[&q2] == dev_q1)
291 && !used_device_qubits.contains(&dev_q1)
292 && !used_device_qubits.contains(&dev_q2)
293 {
294 if let std::collections::hash_map::Entry::Vacant(e) = mapping.entry(q1) {
296 e.insert(dev_q2);
297 used_device_qubits.insert(dev_q2);
298 }
299 if let std::collections::hash_map::Entry::Vacant(e) = mapping.entry(q2) {
300 e.insert(dev_q1);
301 used_device_qubits.insert(dev_q1);
302 }
303 break;
304 }
305 }
306 }
307
308 for q in 0..N {
310 if !mapping.contains_key(&q) {
311 for dev_q in 0..N {
313 if used_device_qubits.insert(dev_q) {
314 mapping.insert(q, dev_q);
315 break;
316 }
317 }
318 }
319 }
320
321 for q in 0..N {
324 mapping.entry(q).or_insert(q);
325 }
326
327 mapping
328 }
329
330 pub fn transpile_circuit<const N: usize>(
332 circuit: &Circuit<N>,
333 coupling_map: &[(usize, usize)],
334 ) -> DeviceResult<(Circuit<N>, HashMap<usize, usize>)> {
335 let mapping = Self::optimize_qubit_mapping(circuit, coupling_map);
337
338 let mut new_circuit = Circuit::<N>::new();
340
341 let mut used_gates = vec![false; circuit.gates().len()];
343
344 for (i, gate) in circuit.gates().iter().enumerate() {
346 if used_gates[i] {
347 continue;
348 }
349
350 let qubits = gate.qubits();
352 match gate.name() {
353 "CNOT" => {
354 if qubits.len() != 2 {
355 continue;
356 }
357 let control = qubits[0];
358 let target = qubits[1];
359 let c = control.id() as usize;
360 let t = target.id() as usize;
361 let mapped_c = mapping[&c];
362 let mapped_t = mapping[&t];
363
364 let directly_connected = coupling_map.contains(&(mapped_c, mapped_t))
366 || coupling_map.contains(&(mapped_t, mapped_c));
367
368 if directly_connected {
369 if coupling_map.contains(&(mapped_c, mapped_t)) {
371 let _ = new_circuit
372 .cnot(QubitId::new(mapped_c as u32), QubitId::new(mapped_t as u32));
373 } else {
374 let _ = new_circuit.h(QubitId::new(mapped_c as u32));
377 let _ = new_circuit.h(QubitId::new(mapped_t as u32));
378 let _ = new_circuit
379 .cnot(QubitId::new(mapped_t as u32), QubitId::new(mapped_c as u32));
380 let _ = new_circuit.h(QubitId::new(mapped_c as u32));
381 let _ = new_circuit.h(QubitId::new(mapped_t as u32));
382 }
383 } else {
384 let path = find_shortest_path(mapped_c, mapped_t, coupling_map);
388 if path.is_empty() {
389 return Err(DeviceError::CircuitConversion(format!(
390 "No path found between qubits {mapped_c} and {mapped_t}"
391 )));
392 }
393
394 for i in 0..path.len() - 2 {
396 let _ = new_circuit.swap(
397 QubitId::new(path[i] as u32),
398 QubitId::new(path[i + 1] as u32),
399 );
400 }
401
402 let final_c = path[path.len() - 2];
404 let final_t = path[path.len() - 1];
405 let _ = new_circuit
406 .cnot(QubitId::new(final_c as u32), QubitId::new(final_t as u32));
407
408 for i in (0..path.len() - 2).rev() {
410 let _ = new_circuit.swap(
411 QubitId::new(path[i] as u32),
412 QubitId::new(path[i + 1] as u32),
413 );
414 }
415 }
416
417 used_gates[i] = true;
418 }
419 "X" => {
421 if qubits.len() != 1 {
422 continue;
423 }
424 let qubit = qubits[0];
425 let q = qubit.id() as usize;
426 let mapped_q = mapping[&q];
427 let _ = new_circuit.x(QubitId::new(mapped_q as u32));
428 used_gates[i] = true;
429 }
430 "Y" => {
431 if qubits.len() != 1 {
432 continue;
433 }
434 let qubit = qubits[0];
435 let q = qubit.id() as usize;
436 let mapped_q = mapping[&q];
437 let _ = new_circuit.y(QubitId::new(mapped_q as u32));
438 used_gates[i] = true;
439 }
440 "Z" => {
441 if qubits.len() != 1 {
442 continue;
443 }
444 let qubit = qubits[0];
445 let q = qubit.id() as usize;
446 let mapped_q = mapping[&q];
447 let _ = new_circuit.z(QubitId::new(mapped_q as u32));
448 used_gates[i] = true;
449 }
450 "H" => {
451 if qubits.len() != 1 {
452 continue;
453 }
454 let qubit = qubits[0];
455 let q = qubit.id() as usize;
456 let mapped_q = mapping[&q];
457 let _ = new_circuit.h(QubitId::new(mapped_q as u32));
458 used_gates[i] = true;
459 }
460 _ => {
462 return Err(DeviceError::CircuitConversion(format!(
464 "Unsupported gate type for transpilation: {}",
465 gate.name()
466 )));
467 }
468 }
469 }
470
471 Ok((new_circuit, mapping))
472 }
473}
474
475fn map_qubit(qubit: usize, mapping: &Option<&HashMap<usize, usize>>) -> usize {
477 match mapping {
478 Some(map) => *map.get(&qubit).unwrap_or(&qubit),
479 None => qubit,
480 }
481}
482
483fn find_shortest_path(start: usize, end: usize, coupling_map: &[(usize, usize)]) -> Vec<usize> {
485 if start == end {
486 return vec![start];
487 }
488
489 let mut adj_list = HashMap::new();
491 for &(a, b) in coupling_map {
492 adj_list.entry(a).or_insert_with(Vec::new).push(b);
493 adj_list.entry(b).or_insert_with(Vec::new).push(a);
494 }
495
496 let mut queue = VecDeque::new();
498 let mut visited = std::collections::HashSet::new();
499 let mut parent = HashMap::new();
500
501 queue.push_back(start);
502 visited.insert(start);
503
504 while let Some(node) = queue.pop_front() {
505 if node == end {
506 let mut path = Vec::new();
508 let mut current = node;
509
510 while let Some(&p) = parent.get(¤t) {
511 path.push(current);
512 current = p;
513 if current == start {
514 path.push(current);
515 path.reverse();
516 return path;
517 }
518 }
519 }
520
521 if let Some(neighbors) = adj_list.get(&node) {
522 for &neighbor in neighbors {
523 if visited.insert(neighbor) {
524 queue.push_back(neighbor);
525 parent.insert(neighbor, node);
526 }
527 }
528 }
529 }
530
531 Vec::new()
533}
534
535#[cfg(test)]
536mod tests {
537 use super::*;
538 use quantrs2_circuit::builder::Circuit;
539
540 #[test]
541 fn test_qasm_export_rotation_angles() {
542 let mut circ: Circuit<3> = Circuit::new();
544 circ.rx(0u32, 0.5).expect("rx gate");
545 circ.ry(1u32, 1.0).expect("ry gate");
546 circ.rz(2u32, 1.5).expect("rz gate");
547
548 let qasm = CircuitTranspiler::circuit_to_qasm(&circ, None)
549 .expect("circuit_to_qasm should succeed");
550
551 assert!(
552 qasm.content.contains("rx(0.5)"),
553 "QASM should contain rx(0.5), got: {}",
554 qasm.content
555 );
556 assert!(
558 qasm.content.contains("ry(1)") || qasm.content.contains("ry(1.0)"),
559 "QASM should contain ry(1) or ry(1.0), got: {}",
560 qasm.content
561 );
562 assert!(
563 qasm.content.contains("rz(1.5)"),
564 "QASM should contain rz(1.5), got: {}",
565 qasm.content
566 );
567
568 assert!(
570 !qasm.content.contains("rx(0) "),
571 "QASM must not contain hardcoded placeholder rx(0), got: {}",
572 qasm.content
573 );
574 assert!(
575 !qasm.content.contains("ry(0) "),
576 "QASM must not contain hardcoded placeholder ry(0), got: {}",
577 qasm.content
578 );
579 assert!(
580 !qasm.content.contains("rz(0) "),
581 "QASM must not contain hardcoded placeholder rz(0), got: {}",
582 qasm.content
583 );
584 }
585}