quantrs2_device/
transpiler.rs1use quantrs2_circuit::prelude::Circuit;
2use quantrs2_core::prelude::QubitId;
3use std::collections::{HashMap, VecDeque};
4
5use crate::DeviceError;
6use crate::DeviceResult;
7
8#[derive(Debug, Clone)]
10pub struct QasmCircuit {
11 pub content: String,
13 pub qubit_count: usize,
15 pub cbit_count: usize,
17 pub gate_counts: HashMap<String, usize>,
19}
20
21pub struct CircuitTranspiler;
23
24impl CircuitTranspiler {
25 pub fn circuit_to_qasm<const N: usize>(
27 circuit: &Circuit<N>,
28 qubit_mapping: Option<&HashMap<usize, usize>>,
29 ) -> DeviceResult<QasmCircuit> {
30 let mut qasm = String::from("OPENQASM 2.0;\ninclude \"qelib1.inc\";\n\n");
32
33 use std::fmt::Write;
35 let _ = writeln!(qasm, "qreg q[{N}];");
36 let _ = writeln!(qasm, "creg c[{N}];");
37
38 let mut gate_counts = HashMap::new();
40
41 for gate in circuit.gates() {
43 let gate_qasm = match gate.name() {
44 "X" => {
45 if gate.qubits().len() != 1 {
46 continue;
47 }
48 let qubit = gate.qubits()[0];
49 let q = map_qubit(qubit.id() as usize, &qubit_mapping);
50 *gate_counts.entry("x".to_string()).or_insert(0) += 1;
51 format!("x q[{q}];")
52 }
53 "Y" => {
54 if gate.qubits().len() != 1 {
55 continue;
56 }
57 let qubit = gate.qubits()[0];
58 let q = map_qubit(qubit.id() as usize, &qubit_mapping);
59 *gate_counts.entry("y".to_string()).or_insert(0) += 1;
60 format!("y q[{q}];")
61 }
62 "Z" => {
63 if gate.qubits().len() != 1 {
64 continue;
65 }
66 let qubit = gate.qubits()[0];
67 let q = map_qubit(qubit.id() as usize, &qubit_mapping);
68 *gate_counts.entry("z".to_string()).or_insert(0) += 1;
69 format!("z q[{q}];")
70 }
71 "H" => {
72 if gate.qubits().len() != 1 {
73 continue;
74 }
75 let qubit = gate.qubits()[0];
76 let q = map_qubit(qubit.id() as usize, &qubit_mapping);
77 *gate_counts.entry("h".to_string()).or_insert(0) += 1;
78 format!("h q[{q}];")
79 }
80 "S" => {
81 if gate.qubits().len() != 1 {
82 continue;
83 }
84 let qubit = gate.qubits()[0];
85 let q = map_qubit(qubit.id() as usize, &qubit_mapping);
86 *gate_counts.entry("s".to_string()).or_insert(0) += 1;
87 format!("s q[{q}];")
88 }
89 "S†" => {
90 if gate.qubits().len() != 1 {
91 continue;
92 }
93 let qubit = gate.qubits()[0];
94 let q = map_qubit(qubit.id() as usize, &qubit_mapping);
95 *gate_counts.entry("sdg".to_string()).or_insert(0) += 1;
96 format!("sdg q[{q}];")
97 }
98 "T" => {
99 if gate.qubits().len() != 1 {
100 continue;
101 }
102 let qubit = gate.qubits()[0];
103 let q = map_qubit(qubit.id() as usize, &qubit_mapping);
104 *gate_counts.entry("t".to_string()).or_insert(0) += 1;
105 format!("t q[{q}];")
106 }
107 "T†" => {
108 if gate.qubits().len() != 1 {
109 continue;
110 }
111 let qubit = gate.qubits()[0];
112 let q = map_qubit(qubit.id() as usize, &qubit_mapping);
113 *gate_counts.entry("tdg".to_string()).or_insert(0) += 1;
114 format!("tdg q[{q}];")
115 }
116 "CNOT" => {
117 if gate.qubits().len() != 2 {
118 continue;
119 }
120 let control = gate.qubits()[0];
121 let target = gate.qubits()[1];
122 let c = map_qubit(control.id() as usize, &qubit_mapping);
123 let t = map_qubit(target.id() as usize, &qubit_mapping);
124 *gate_counts.entry("cx".to_string()).or_insert(0) += 1;
125 format!("cx q[{c}], q[{t}];")
126 }
127 "CZ" => {
128 if gate.qubits().len() != 2 {
129 continue;
130 }
131 let control = gate.qubits()[0];
132 let target = gate.qubits()[1];
133 let c = map_qubit(control.id() as usize, &qubit_mapping);
134 let t = map_qubit(target.id() as usize, &qubit_mapping);
135 *gate_counts.entry("cz".to_string()).or_insert(0) += 1;
136 format!("cz q[{c}], q[{t}];")
137 }
138 "SWAP" => {
139 if gate.qubits().len() != 2 {
140 continue;
141 }
142 let q1 = gate.qubits()[0];
143 let q2 = gate.qubits()[1];
144 let q1_mapped = map_qubit(q1.id() as usize, &qubit_mapping);
145 let q2_mapped = map_qubit(q2.id() as usize, &qubit_mapping);
146 *gate_counts.entry("swap".to_string()).or_insert(0) += 1;
147 format!("swap q[{q1_mapped}], q[{q2_mapped}];")
148 }
149 "RX" => {
150 if gate.qubits().len() != 1 {
151 continue;
152 }
153 let qubit = gate.qubits()[0];
156 let q = map_qubit(qubit.id() as usize, &qubit_mapping);
157 *gate_counts.entry("rx".to_string()).or_insert(0) += 1;
158 format!("rx(0) q[{q}];") }
160 "RY" => {
161 if gate.qubits().len() != 1 {
162 continue;
163 }
164 let qubit = gate.qubits()[0];
165 let q = map_qubit(qubit.id() as usize, &qubit_mapping);
166 *gate_counts.entry("ry".to_string()).or_insert(0) += 1;
167 format!("ry(0) q[{q}];") }
169 "RZ" => {
170 if gate.qubits().len() != 1 {
171 continue;
172 }
173 let qubit = gate.qubits()[0];
174 let q = map_qubit(qubit.id() as usize, &qubit_mapping);
175 *gate_counts.entry("rz".to_string()).or_insert(0) += 1;
176 format!("rz(0) q[{q}];") }
178 _ => {
179 return Err(DeviceError::CircuitConversion(format!(
181 "Unsupported gate type for QASM conversion: {}",
182 gate.name()
183 )));
184 }
185 };
186
187 let _ = writeln!(qasm, "{gate_qasm}");
188 }
189
190 Ok(QasmCircuit {
191 content: qasm,
192 qubit_count: N,
193 cbit_count: N, gate_counts,
195 })
196 }
197
198 pub fn optimize_qubit_mapping<const N: usize>(
200 circuit: &Circuit<N>,
201 coupling_map: &[(usize, usize)],
202 ) -> HashMap<usize, usize> {
203 let mut qubit_interactions = HashMap::new();
209
210 for gate in circuit.gates() {
211 let qubits = gate.qubits();
212 if qubits.len() == 2 {
213 let q1 = qubits[0].id() as usize;
214 let q2 = qubits[1].id() as usize;
215 *qubit_interactions.entry((q1, q2)).or_insert(0) += 1;
216 }
217 }
218
219 let mut pairs: Vec<_> = qubit_interactions.iter().collect();
221 pairs.sort_by(|a, b| b.1.cmp(a.1));
222
223 let mut mapping = HashMap::new();
225 let mut used_device_qubits = std::collections::HashSet::new();
226
227 for (&(q1, q2), _) in &pairs {
229 if mapping.contains_key(&q1) && mapping.contains_key(&q2) {
230 continue;
231 }
232
233 for &(dev_q1, dev_q2) in coupling_map {
235 if (!mapping.contains_key(&q1) || mapping[&q1] == dev_q1)
236 && (!mapping.contains_key(&q2) || mapping[&q2] == dev_q2)
237 && !used_device_qubits.contains(&dev_q1)
238 && !used_device_qubits.contains(&dev_q2)
239 {
240 if let std::collections::hash_map::Entry::Vacant(e) = mapping.entry(q1) {
242 e.insert(dev_q1);
243 used_device_qubits.insert(dev_q1);
244 }
245 if let std::collections::hash_map::Entry::Vacant(e) = mapping.entry(q2) {
246 e.insert(dev_q2);
247 used_device_qubits.insert(dev_q2);
248 }
249 break;
250 }
251
252 if (!mapping.contains_key(&q1) || mapping[&q1] == dev_q2)
253 && (!mapping.contains_key(&q2) || mapping[&q2] == dev_q1)
254 && !used_device_qubits.contains(&dev_q1)
255 && !used_device_qubits.contains(&dev_q2)
256 {
257 if let std::collections::hash_map::Entry::Vacant(e) = mapping.entry(q1) {
259 e.insert(dev_q2);
260 used_device_qubits.insert(dev_q2);
261 }
262 if let std::collections::hash_map::Entry::Vacant(e) = mapping.entry(q2) {
263 e.insert(dev_q1);
264 used_device_qubits.insert(dev_q1);
265 }
266 break;
267 }
268 }
269 }
270
271 for q in 0..N {
273 if !mapping.contains_key(&q) {
274 for dev_q in 0..N {
276 if used_device_qubits.insert(dev_q) {
277 mapping.insert(q, dev_q);
278 break;
279 }
280 }
281 }
282 }
283
284 for q in 0..N {
287 mapping.entry(q).or_insert(q);
288 }
289
290 mapping
291 }
292
293 pub fn transpile_circuit<const N: usize>(
295 circuit: &Circuit<N>,
296 coupling_map: &[(usize, usize)],
297 ) -> DeviceResult<(Circuit<N>, HashMap<usize, usize>)> {
298 let mapping = Self::optimize_qubit_mapping(circuit, coupling_map);
300
301 let mut new_circuit = Circuit::<N>::new();
303
304 let mut used_gates = vec![false; circuit.gates().len()];
306
307 for (i, gate) in circuit.gates().iter().enumerate() {
309 if used_gates[i] {
310 continue;
311 }
312
313 let qubits = gate.qubits();
315 match gate.name() {
316 "CNOT" => {
317 if qubits.len() != 2 {
318 continue;
319 }
320 let control = qubits[0];
321 let target = qubits[1];
322 let c = control.id() as usize;
323 let t = target.id() as usize;
324 let mapped_c = mapping[&c];
325 let mapped_t = mapping[&t];
326
327 let directly_connected = coupling_map.contains(&(mapped_c, mapped_t))
329 || coupling_map.contains(&(mapped_t, mapped_c));
330
331 if directly_connected {
332 if coupling_map.contains(&(mapped_c, mapped_t)) {
334 let _ = new_circuit
335 .cnot(QubitId::new(mapped_c as u32), QubitId::new(mapped_t as u32));
336 } else {
337 let _ = new_circuit.h(QubitId::new(mapped_c as u32));
340 let _ = new_circuit.h(QubitId::new(mapped_t as u32));
341 let _ = new_circuit
342 .cnot(QubitId::new(mapped_t as u32), QubitId::new(mapped_c as u32));
343 let _ = new_circuit.h(QubitId::new(mapped_c as u32));
344 let _ = new_circuit.h(QubitId::new(mapped_t as u32));
345 }
346 } else {
347 let path = find_shortest_path(mapped_c, mapped_t, coupling_map);
351 if path.is_empty() {
352 return Err(DeviceError::CircuitConversion(format!(
353 "No path found between qubits {mapped_c} and {mapped_t}"
354 )));
355 }
356
357 for i in 0..path.len() - 2 {
359 let _ = new_circuit.swap(
360 QubitId::new(path[i] as u32),
361 QubitId::new(path[i + 1] as u32),
362 );
363 }
364
365 let final_c = path[path.len() - 2];
367 let final_t = path[path.len() - 1];
368 let _ = new_circuit
369 .cnot(QubitId::new(final_c as u32), QubitId::new(final_t as u32));
370
371 for i in (0..path.len() - 2).rev() {
373 let _ = new_circuit.swap(
374 QubitId::new(path[i] as u32),
375 QubitId::new(path[i + 1] as u32),
376 );
377 }
378 }
379
380 used_gates[i] = true;
381 }
382 "X" => {
384 if qubits.len() != 1 {
385 continue;
386 }
387 let qubit = qubits[0];
388 let q = qubit.id() as usize;
389 let mapped_q = mapping[&q];
390 let _ = new_circuit.x(QubitId::new(mapped_q as u32));
391 used_gates[i] = true;
392 }
393 "Y" => {
394 if qubits.len() != 1 {
395 continue;
396 }
397 let qubit = qubits[0];
398 let q = qubit.id() as usize;
399 let mapped_q = mapping[&q];
400 let _ = new_circuit.y(QubitId::new(mapped_q as u32));
401 used_gates[i] = true;
402 }
403 "Z" => {
404 if qubits.len() != 1 {
405 continue;
406 }
407 let qubit = qubits[0];
408 let q = qubit.id() as usize;
409 let mapped_q = mapping[&q];
410 let _ = new_circuit.z(QubitId::new(mapped_q as u32));
411 used_gates[i] = true;
412 }
413 "H" => {
414 if qubits.len() != 1 {
415 continue;
416 }
417 let qubit = qubits[0];
418 let q = qubit.id() as usize;
419 let mapped_q = mapping[&q];
420 let _ = new_circuit.h(QubitId::new(mapped_q as u32));
421 used_gates[i] = true;
422 }
423 _ => {
425 return Err(DeviceError::CircuitConversion(format!(
427 "Unsupported gate type for transpilation: {}",
428 gate.name()
429 )));
430 }
431 }
432 }
433
434 Ok((new_circuit, mapping))
435 }
436}
437
438fn map_qubit(qubit: usize, mapping: &Option<&HashMap<usize, usize>>) -> usize {
440 match mapping {
441 Some(map) => *map.get(&qubit).unwrap_or(&qubit),
442 None => qubit,
443 }
444}
445
446fn find_shortest_path(start: usize, end: usize, coupling_map: &[(usize, usize)]) -> Vec<usize> {
448 if start == end {
449 return vec![start];
450 }
451
452 let mut adj_list = HashMap::new();
454 for &(a, b) in coupling_map {
455 adj_list.entry(a).or_insert_with(Vec::new).push(b);
456 adj_list.entry(b).or_insert_with(Vec::new).push(a);
457 }
458
459 let mut queue = VecDeque::new();
461 let mut visited = std::collections::HashSet::new();
462 let mut parent = HashMap::new();
463
464 queue.push_back(start);
465 visited.insert(start);
466
467 while let Some(node) = queue.pop_front() {
468 if node == end {
469 let mut path = Vec::new();
471 let mut current = node;
472
473 while let Some(&p) = parent.get(¤t) {
474 path.push(current);
475 current = p;
476 if current == start {
477 path.push(current);
478 path.reverse();
479 return path;
480 }
481 }
482 }
483
484 if let Some(neighbors) = adj_list.get(&node) {
485 for &neighbor in neighbors {
486 if visited.insert(neighbor) {
487 queue.push_back(neighbor);
488 parent.insert(neighbor, node);
489 }
490 }
491 }
492 }
493
494 Vec::new()
496}