1use std::collections::VecDeque;
8
9use crate::circuit::QuantumCircuit;
10use crate::gate::Gate;
11
12use std::f64::consts::{FRAC_PI_2, FRAC_PI_4, PI};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum BasisGateSet {
21 IbmEagle,
23 IonQAria,
25 RigettiAspen,
27 Universal,
29}
30
31#[derive(Debug, Clone)]
33pub struct TranspilerConfig {
34 pub basis: BasisGateSet,
36 pub coupling_map: Option<Vec<(u32, u32)>>,
39 pub optimization_level: u8,
42}
43
44pub fn transpile(circuit: &QuantumCircuit, config: &TranspilerConfig) -> QuantumCircuit {
51 let decomposed = decompose(circuit, config.basis);
53
54 let routed = match &config.coupling_map {
56 Some(map) => route_circuit(&decomposed, map),
57 None => decomposed,
58 };
59
60 optimize_gates(&routed, config.optimization_level)
62}
63
64fn decompose(circuit: &QuantumCircuit, basis: BasisGateSet) -> QuantumCircuit {
69 if basis == BasisGateSet::Universal {
70 return circuit.clone();
71 }
72 let mut result = QuantumCircuit::new(circuit.num_qubits());
73 for gate in circuit.gates() {
74 let decomposed = match basis {
75 BasisGateSet::IbmEagle => decompose_to_ibm(gate),
76 BasisGateSet::IonQAria => decompose_to_ionq(gate),
77 BasisGateSet::RigettiAspen => decompose_to_rigetti(gate),
78 BasisGateSet::Universal => unreachable!(),
79 };
80 for g in decomposed {
81 result.add_gate(g);
82 }
83 }
84 result
85}
86
87pub fn decompose_to_ibm(gate: &Gate) -> Vec<Gate> {
97 match gate {
98 Gate::CNOT(c, t) => vec![Gate::CNOT(*c, *t)],
100 Gate::X(q) => vec![Gate::X(*q)],
101 Gate::Rz(q, theta) => vec![Gate::Rz(*q, *theta)],
102
103 Gate::H(q) => vec![
106 Gate::Rz(*q, PI),
107 Gate::Rx(*q, FRAC_PI_2), Gate::Rz(*q, PI),
109 ],
110
111 Gate::S(q) => vec![Gate::Rz(*q, FRAC_PI_2)],
113
114 Gate::Sdg(q) => vec![Gate::Rz(*q, -FRAC_PI_2)],
116
117 Gate::T(q) => vec![Gate::Rz(*q, FRAC_PI_4)],
119
120 Gate::Tdg(q) => vec![Gate::Rz(*q, -FRAC_PI_4)],
122
123 Gate::Y(q) => vec![Gate::X(*q), Gate::Rz(*q, PI)],
125
126 Gate::Z(q) => vec![Gate::Rz(*q, PI)],
128
129 Gate::Phase(q, theta) => vec![Gate::Rz(*q, *theta)],
131
132 Gate::Rx(q, theta) => {
138 if (*theta - FRAC_PI_2).abs() < 1e-12 {
139 vec![Gate::Rx(*q, FRAC_PI_2)]
141 } else {
142 vec![
144 Gate::Rz(*q, -FRAC_PI_2),
145 Gate::Rx(*q, FRAC_PI_2),
146 Gate::Rz(*q, PI - theta),
147 Gate::Rx(*q, FRAC_PI_2),
148 Gate::Rz(*q, -FRAC_PI_2),
149 ]
150 }
151 }
152
153 Gate::Ry(q, theta) => vec![
160 Gate::Rz(*q, -FRAC_PI_2),
161 Gate::Rx(*q, FRAC_PI_2),
162 Gate::Rz(*q, theta + PI),
163 Gate::Rx(*q, FRAC_PI_2),
164 Gate::Rz(*q, FRAC_PI_2),
165 ],
166
167 Gate::CZ(q1, q2) => {
170 let mut gates = Vec::new();
171 gates.extend(decompose_to_ibm(&Gate::H(*q2)));
172 gates.push(Gate::CNOT(*q1, *q2));
173 gates.extend(decompose_to_ibm(&Gate::H(*q2)));
174 gates
175 }
176
177 Gate::SWAP(a, b) => vec![
179 Gate::CNOT(*a, *b),
180 Gate::CNOT(*b, *a),
181 Gate::CNOT(*a, *b),
182 ],
183
184 Gate::Rzz(a, b, theta) => vec![
186 Gate::CNOT(*a, *b),
187 Gate::Rz(*b, *theta),
188 Gate::CNOT(*a, *b),
189 ],
190
191 Gate::Measure(q) => vec![Gate::Measure(*q)],
193 Gate::Reset(q) => vec![Gate::Reset(*q)],
194 Gate::Barrier => vec![Gate::Barrier],
195
196 Gate::Unitary1Q(q, m) => vec![Gate::Unitary1Q(*q, *m)],
200 }
201}
202
203pub fn decompose_to_rigetti(gate: &Gate) -> Vec<Gate> {
209 match gate {
210 Gate::CZ(q1, q2) => vec![Gate::CZ(*q1, *q2)],
212 Gate::Rx(q, theta) => vec![Gate::Rx(*q, *theta)],
213 Gate::Rz(q, theta) => vec![Gate::Rz(*q, *theta)],
214
215 Gate::H(q) => vec![Gate::Rz(*q, PI), Gate::Rx(*q, FRAC_PI_2)],
218
219 Gate::X(q) => vec![Gate::Rx(*q, PI)],
220 Gate::Y(q) => vec![Gate::Rx(*q, PI), Gate::Rz(*q, PI)],
221 Gate::Z(q) => vec![Gate::Rz(*q, PI)],
222 Gate::S(q) => vec![Gate::Rz(*q, FRAC_PI_2)],
223 Gate::Sdg(q) => vec![Gate::Rz(*q, -FRAC_PI_2)],
224 Gate::T(q) => vec![Gate::Rz(*q, FRAC_PI_4)],
225 Gate::Tdg(q) => vec![Gate::Rz(*q, -FRAC_PI_4)],
226 Gate::Phase(q, theta) => vec![Gate::Rz(*q, *theta)],
227
228 Gate::Ry(q, theta) => vec![
230 Gate::Rz(*q, -FRAC_PI_2),
231 Gate::Rx(*q, *theta),
232 Gate::Rz(*q, FRAC_PI_2),
233 ],
234
235 Gate::CNOT(c, t) => {
239 let mut gates = Vec::new();
240 gates.extend(decompose_to_rigetti(&Gate::H(*t)));
241 gates.push(Gate::CZ(*c, *t));
242 gates.extend(decompose_to_rigetti(&Gate::H(*t)));
243 gates
244 }
245
246 Gate::SWAP(a, b) => {
248 let mut gates = Vec::new();
249 gates.extend(decompose_to_rigetti(&Gate::CNOT(*a, *b)));
250 gates.extend(decompose_to_rigetti(&Gate::CNOT(*b, *a)));
251 gates.extend(decompose_to_rigetti(&Gate::CNOT(*a, *b)));
252 gates
253 }
254
255 Gate::Rzz(a, b, theta) => {
257 let mut gates = Vec::new();
258 gates.extend(decompose_to_rigetti(&Gate::CNOT(*a, *b)));
259 gates.push(Gate::Rz(*b, *theta));
260 gates.extend(decompose_to_rigetti(&Gate::CNOT(*a, *b)));
261 gates
262 }
263
264 Gate::Measure(q) => vec![Gate::Measure(*q)],
266 Gate::Reset(q) => vec![Gate::Reset(*q)],
267 Gate::Barrier => vec![Gate::Barrier],
268 Gate::Unitary1Q(q, m) => vec![Gate::Unitary1Q(*q, *m)],
269 }
270}
271
272pub fn decompose_to_ionq(gate: &Gate) -> Vec<Gate> {
281 match gate {
282 Gate::Rx(q, theta) => vec![Gate::Rx(*q, *theta)],
284 Gate::Ry(q, theta) => vec![Gate::Ry(*q, *theta)],
285 Gate::Rzz(a, b, theta) => vec![Gate::Rzz(*a, *b, *theta)],
286
287 Gate::H(q) => vec![Gate::Ry(*q, FRAC_PI_2), Gate::Rx(*q, PI)],
290
291 Gate::X(q) => vec![Gate::Rx(*q, PI)],
292 Gate::Y(q) => vec![Gate::Ry(*q, PI)],
293
294 Gate::Z(q) => vec![Gate::Rx(*q, PI), Gate::Ry(*q, PI)],
296
297 Gate::S(q) => vec![
299 Gate::Rx(*q, -FRAC_PI_2),
300 Gate::Ry(*q, FRAC_PI_2),
301 Gate::Rx(*q, FRAC_PI_2),
302 ],
303
304 Gate::Sdg(q) => vec![
306 Gate::Rx(*q, -FRAC_PI_2),
307 Gate::Ry(*q, -FRAC_PI_2),
308 Gate::Rx(*q, FRAC_PI_2),
309 ],
310
311 Gate::T(q) => vec![
313 Gate::Rx(*q, -FRAC_PI_2),
314 Gate::Ry(*q, FRAC_PI_4),
315 Gate::Rx(*q, FRAC_PI_2),
316 ],
317
318 Gate::Tdg(q) => vec![
320 Gate::Rx(*q, -FRAC_PI_2),
321 Gate::Ry(*q, -FRAC_PI_4),
322 Gate::Rx(*q, FRAC_PI_2),
323 ],
324
325 Gate::Rz(q, theta) => vec![
327 Gate::Rx(*q, -FRAC_PI_2),
328 Gate::Ry(*q, *theta),
329 Gate::Rx(*q, FRAC_PI_2),
330 ],
331
332 Gate::Phase(q, theta) => decompose_to_ionq(&Gate::Rz(*q, *theta)),
334
335 Gate::CNOT(c, t) => vec![
340 Gate::Ry(*t, -FRAC_PI_2),
341 Gate::Rzz(*c, *t, FRAC_PI_2),
342 Gate::Rx(*c, -FRAC_PI_2),
343 Gate::Rx(*t, -FRAC_PI_2),
344 Gate::Ry(*t, FRAC_PI_2),
345 ],
346
347 Gate::CZ(q1, q2) => {
349 let mut gates = Vec::new();
350 gates.extend(decompose_to_ionq(&Gate::H(*q2)));
351 gates.extend(decompose_to_ionq(&Gate::CNOT(*q1, *q2)));
352 gates.extend(decompose_to_ionq(&Gate::H(*q2)));
353 gates
354 }
355
356 Gate::SWAP(a, b) => {
358 let mut gates = Vec::new();
359 gates.extend(decompose_to_ionq(&Gate::CNOT(*a, *b)));
360 gates.extend(decompose_to_ionq(&Gate::CNOT(*b, *a)));
361 gates.extend(decompose_to_ionq(&Gate::CNOT(*a, *b)));
362 gates
363 }
364
365 Gate::Measure(q) => vec![Gate::Measure(*q)],
367 Gate::Reset(q) => vec![Gate::Reset(*q)],
368 Gate::Barrier => vec![Gate::Barrier],
369 Gate::Unitary1Q(q, m) => vec![Gate::Unitary1Q(*q, *m)],
370 }
371}
372
373pub fn route_circuit(circuit: &QuantumCircuit, coupling_map: &[(u32, u32)]) -> QuantumCircuit {
386 let n = circuit.num_qubits() as usize;
387
388 let adj = build_adjacency_list(coupling_map, n);
390
391 let mut log2phys: Vec<u32> = (0..n as u32).collect();
393 let mut phys2log: Vec<u32> = (0..n as u32).collect();
395
396 let mut result = QuantumCircuit::new(circuit.num_qubits());
397
398 for gate in circuit.gates() {
399 let qubits = gate.qubits();
400 if qubits.len() == 2 {
401 let logical_a = qubits[0];
402 let logical_b = qubits[1];
403 let mut phys_a = log2phys[logical_a as usize];
404 let mut phys_b = log2phys[logical_b as usize];
405
406 if !are_adjacent(&adj, phys_a, phys_b) {
408 let path = bfs_shortest_path(&adj, phys_a, phys_b, n);
410
411 for i in 0..path.len() - 2 {
416 let p1 = path[i];
417 let p2 = path[i + 1];
418
419 result.add_gate(Gate::SWAP(p1, p2));
421
422 let log1 = phys2log[p1 as usize];
424 let log2 = phys2log[p2 as usize];
425 log2phys[log1 as usize] = p2;
426 log2phys[log2 as usize] = p1;
427 phys2log[p1 as usize] = log2;
428 phys2log[p2 as usize] = log1;
429 }
430
431 phys_a = log2phys[logical_a as usize];
433 phys_b = log2phys[logical_b as usize];
434 }
435
436 result.add_gate(remap_gate(gate, &log2phys));
438
439 debug_assert!(
441 are_adjacent(&adj, phys_a, phys_b),
442 "routing failed: qubits {} and {} are not adjacent after SWAP insertion",
443 phys_a,
444 phys_b
445 );
446 } else if qubits.len() == 1 {
447 result.add_gate(remap_gate(gate, &log2phys));
449 } else {
450 result.add_gate(gate.clone());
452 }
453 }
454
455 result
456}
457
458fn build_adjacency_list(coupling_map: &[(u32, u32)], n: usize) -> Vec<Vec<u32>> {
460 let mut adj: Vec<Vec<u32>> = vec![Vec::new(); n];
461 for &(a, b) in coupling_map {
462 if (a as usize) < n && (b as usize) < n {
463 if !adj[a as usize].contains(&b) {
464 adj[a as usize].push(b);
465 }
466 if !adj[b as usize].contains(&a) {
467 adj[b as usize].push(a);
468 }
469 }
470 }
471 adj
472}
473
474fn are_adjacent(adj: &[Vec<u32>], a: u32, b: u32) -> bool {
476 adj.get(a as usize)
477 .map(|neighbors| neighbors.contains(&b))
478 .unwrap_or(false)
479}
480
481fn bfs_shortest_path(adj: &[Vec<u32>], start: u32, end: u32, n: usize) -> Vec<u32> {
485 if start == end {
486 return vec![start];
487 }
488
489 let mut visited = vec![false; n];
490 let mut parent: Vec<Option<u32>> = vec![None; n];
491 let mut queue = VecDeque::new();
492
493 visited[start as usize] = true;
494 queue.push_back(start);
495
496 while let Some(current) = queue.pop_front() {
497 if current == end {
498 break;
499 }
500 for &neighbor in &adj[current as usize] {
501 if !visited[neighbor as usize] {
502 visited[neighbor as usize] = true;
503 parent[neighbor as usize] = Some(current);
504 queue.push_back(neighbor);
505 }
506 }
507 }
508
509 let mut path = Vec::new();
511 let mut current = end;
512 path.push(current);
513 while let Some(p) = parent[current as usize] {
514 path.push(p);
515 current = p;
516 if current == start {
517 break;
518 }
519 }
520 path.reverse();
521 path
522}
523
524fn remap_gate(gate: &Gate, log2phys: &[u32]) -> Gate {
526 match gate {
527 Gate::H(q) => Gate::H(log2phys[*q as usize]),
528 Gate::X(q) => Gate::X(log2phys[*q as usize]),
529 Gate::Y(q) => Gate::Y(log2phys[*q as usize]),
530 Gate::Z(q) => Gate::Z(log2phys[*q as usize]),
531 Gate::S(q) => Gate::S(log2phys[*q as usize]),
532 Gate::Sdg(q) => Gate::Sdg(log2phys[*q as usize]),
533 Gate::T(q) => Gate::T(log2phys[*q as usize]),
534 Gate::Tdg(q) => Gate::Tdg(log2phys[*q as usize]),
535 Gate::Rx(q, theta) => Gate::Rx(log2phys[*q as usize], *theta),
536 Gate::Ry(q, theta) => Gate::Ry(log2phys[*q as usize], *theta),
537 Gate::Rz(q, theta) => Gate::Rz(log2phys[*q as usize], *theta),
538 Gate::Phase(q, theta) => Gate::Phase(log2phys[*q as usize], *theta),
539 Gate::CNOT(c, t) => Gate::CNOT(log2phys[*c as usize], log2phys[*t as usize]),
540 Gate::CZ(a, b) => Gate::CZ(log2phys[*a as usize], log2phys[*b as usize]),
541 Gate::SWAP(a, b) => Gate::SWAP(log2phys[*a as usize], log2phys[*b as usize]),
542 Gate::Rzz(a, b, theta) => {
543 Gate::Rzz(log2phys[*a as usize], log2phys[*b as usize], *theta)
544 }
545 Gate::Measure(q) => Gate::Measure(log2phys[*q as usize]),
546 Gate::Reset(q) => Gate::Reset(log2phys[*q as usize]),
547 Gate::Barrier => Gate::Barrier,
548 Gate::Unitary1Q(q, m) => Gate::Unitary1Q(log2phys[*q as usize], *m),
549 }
550}
551
552pub fn optimize_gates(circuit: &QuantumCircuit, level: u8) -> QuantumCircuit {
564 if level == 0 {
565 return circuit.clone();
566 }
567
568 let mut gates: Vec<Gate> = circuit.gates().to_vec();
569
570 let mut changed = true;
572 while changed {
573 changed = false;
574
575 let (new_gates, did_cancel) = cancel_inverse_pairs(&gates);
577 if did_cancel {
578 gates = new_gates;
579 changed = true;
580 }
581
582 if level >= 2 {
584 let (new_gates, did_merge) = merge_adjacent_rz(&gates);
585 if did_merge {
586 gates = new_gates;
587 changed = true;
588 }
589 }
590 }
591
592 let mut result = QuantumCircuit::new(circuit.num_qubits());
593 for g in gates {
594 result.add_gate(g);
595 }
596 result
597}
598
599fn cancel_inverse_pairs(gates: &[Gate]) -> (Vec<Gate>, bool) {
603 let mut result: Vec<Gate> = Vec::with_capacity(gates.len());
604 let mut changed = false;
605 let mut i = 0;
606
607 while i < gates.len() {
608 if i + 1 < gates.len() && is_inverse_pair(&gates[i], &gates[i + 1]) {
609 changed = true;
611 i += 2;
612 } else {
613 result.push(gates[i].clone());
614 i += 1;
615 }
616 }
617
618 (result, changed)
619}
620
621fn is_inverse_pair(a: &Gate, b: &Gate) -> bool {
623 match (a, b) {
624 (Gate::H(q1), Gate::H(q2)) if q1 == q2 => true,
626 (Gate::X(q1), Gate::X(q2)) if q1 == q2 => true,
627 (Gate::Y(q1), Gate::Y(q2)) if q1 == q2 => true,
628 (Gate::Z(q1), Gate::Z(q2)) if q1 == q2 => true,
629
630 (Gate::S(q1), Gate::Sdg(q2)) if q1 == q2 => true,
632 (Gate::Sdg(q1), Gate::S(q2)) if q1 == q2 => true,
633 (Gate::T(q1), Gate::Tdg(q2)) if q1 == q2 => true,
634 (Gate::Tdg(q1), Gate::T(q2)) if q1 == q2 => true,
635
636 (Gate::CNOT(c1, t1), Gate::CNOT(c2, t2)) if c1 == c2 && t1 == t2 => true,
638 (Gate::CZ(a1, b1), Gate::CZ(a2, b2))
639 if (a1 == a2 && b1 == b2) || (a1 == b2 && b1 == a2) =>
640 {
641 true
642 }
643 (Gate::SWAP(a1, b1), Gate::SWAP(a2, b2))
644 if (a1 == a2 && b1 == b2) || (a1 == b2 && b1 == a2) =>
645 {
646 true
647 }
648
649 _ => false,
650 }
651}
652
653fn merge_adjacent_rz(gates: &[Gate]) -> (Vec<Gate>, bool) {
660 let mut result: Vec<Gate> = Vec::with_capacity(gates.len());
661 let mut changed = false;
662 let mut i = 0;
663 let epsilon = 1e-12;
664
665 while i < gates.len() {
666 if let Gate::Rz(q1, a) = &gates[i] {
667 let mut total_angle = *a;
669 let qubit = *q1;
670 let mut count = 1;
671
672 while i + count < gates.len() {
673 if let Gate::Rz(q2, b) = &gates[i + count] {
674 if *q2 == qubit {
675 total_angle += b;
676 count += 1;
677 continue;
678 }
679 }
680 break;
681 }
682
683 if count > 1 {
684 changed = true;
685 if total_angle.abs() > epsilon {
686 result.push(Gate::Rz(qubit, total_angle));
687 }
688 } else {
690 result.push(gates[i].clone());
691 }
692 i += count;
693 } else {
694 result.push(gates[i].clone());
695 i += 1;
696 }
697 }
698
699 (result, changed)
700}
701
702#[cfg(test)]
707mod tests {
708 use super::*;
709 use std::f64::consts::{FRAC_PI_2, FRAC_PI_4, PI};
710
711 #[test]
714 fn test_decompose_h_to_ibm() {
715 let gates = decompose_to_ibm(&Gate::H(0));
716 assert_eq!(gates.len(), 3);
718 assert!(matches!(gates[0], Gate::Rz(0, _)));
719 assert!(matches!(gates[1], Gate::Rx(0, _)));
720 assert!(matches!(gates[2], Gate::Rz(0, _)));
721
722 if let Gate::Rx(_, theta) = &gates[1] {
724 assert!((theta - FRAC_PI_2).abs() < 1e-12);
725 } else {
726 panic!("expected Rx");
727 }
728 }
729
730 #[test]
731 fn test_decompose_s_to_ibm() {
732 let gates = decompose_to_ibm(&Gate::S(0));
733 assert_eq!(gates.len(), 1);
734 if let Gate::Rz(0, theta) = &gates[0] {
735 assert!((theta - FRAC_PI_2).abs() < 1e-12);
736 } else {
737 panic!("expected Rz(pi/2)");
738 }
739 }
740
741 #[test]
742 fn test_decompose_t_to_ibm() {
743 let gates = decompose_to_ibm(&Gate::T(0));
744 assert_eq!(gates.len(), 1);
745 if let Gate::Rz(0, theta) = &gates[0] {
746 assert!((theta - FRAC_PI_4).abs() < 1e-12);
747 } else {
748 panic!("expected Rz(pi/4)");
749 }
750 }
751
752 #[test]
753 fn test_decompose_swap_to_ibm() {
754 let gates = decompose_to_ibm(&Gate::SWAP(0, 1));
755 assert_eq!(gates.len(), 3);
757 assert!(gates.iter().all(|g| matches!(g, Gate::CNOT(_, _))));
758 }
759
760 #[test]
761 fn test_decompose_cz_to_ibm() {
762 let gates = decompose_to_ibm(&Gate::CZ(0, 1));
763 assert_eq!(gates.len(), 7);
765 assert!(matches!(gates[3], Gate::CNOT(0, 1)));
767 }
768
769 #[test]
770 fn test_decompose_cnot_to_rigetti_produces_cz() {
771 let gates = decompose_to_rigetti(&Gate::CNOT(0, 1));
772 assert_eq!(gates.len(), 5);
776 let cz_count = gates.iter().filter(|g| matches!(g, Gate::CZ(_, _))).count();
778 assert_eq!(cz_count, 1);
779 assert!(matches!(gates[2], Gate::CZ(0, 1)));
780 }
781
782 #[test]
783 fn test_decompose_h_to_rigetti() {
784 let gates = decompose_to_rigetti(&Gate::H(0));
785 assert_eq!(gates.len(), 2);
787 assert!(matches!(gates[0], Gate::Rz(0, _)));
788 assert!(matches!(gates[1], Gate::Rx(0, _)));
789 }
790
791 #[test]
792 fn test_decompose_cnot_to_ionq() {
793 let gates = decompose_to_ionq(&Gate::CNOT(0, 1));
794 let rzz_count = gates
796 .iter()
797 .filter(|g| matches!(g, Gate::Rzz(_, _, _)))
798 .count();
799 assert_eq!(rzz_count, 1);
800 assert_eq!(gates.len(), 5);
802 }
803
804 #[test]
805 fn test_decompose_preserves_non_unitary() {
806 let measure_ibm = decompose_to_ibm(&Gate::Measure(0));
807 assert_eq!(measure_ibm.len(), 1);
808 assert!(matches!(measure_ibm[0], Gate::Measure(0)));
809
810 let barrier_rigetti = decompose_to_rigetti(&Gate::Barrier);
811 assert_eq!(barrier_rigetti.len(), 1);
812 assert!(matches!(barrier_rigetti[0], Gate::Barrier));
813
814 let reset_ionq = decompose_to_ionq(&Gate::Reset(2));
815 assert_eq!(reset_ionq.len(), 1);
816 assert!(matches!(reset_ionq[0], Gate::Reset(2)));
817 }
818
819 #[test]
822 fn test_route_adjacent_cnot_no_swaps() {
823 let coupling = vec![(0, 1), (1, 2)];
825 let mut circuit = QuantumCircuit::new(3);
826 circuit.cnot(0, 1);
827
828 let routed = route_circuit(&circuit, &coupling);
829 let swap_count = routed
831 .gates()
832 .iter()
833 .filter(|g| matches!(g, Gate::SWAP(_, _)))
834 .count();
835 assert_eq!(swap_count, 0);
836 assert_eq!(routed.gates().len(), 1);
837 }
838
839 #[test]
840 fn test_route_non_adjacent_cnot_inserts_swaps() {
841 let coupling = vec![(0, 1), (1, 2)];
843 let mut circuit = QuantumCircuit::new(3);
844 circuit.cnot(0, 2); let routed = route_circuit(&circuit, &coupling);
847 let swap_count = routed
849 .gates()
850 .iter()
851 .filter(|g| matches!(g, Gate::SWAP(_, _)))
852 .count();
853 assert!(swap_count >= 1, "expected at least 1 SWAP, got {}", swap_count);
854 }
855
856 #[test]
857 fn test_route_single_qubit_gate_remapped() {
858 let coupling = vec![(0, 1), (1, 2)];
860 let mut circuit = QuantumCircuit::new(3);
861 circuit.h(0);
862
863 let routed = route_circuit(&circuit, &coupling);
864 assert_eq!(routed.gates().len(), 1);
867 assert!(matches!(routed.gates()[0], Gate::H(0)));
868 }
869
870 #[test]
871 fn test_bfs_shortest_path_linear() {
872 let coupling = vec![(0, 1), (1, 2), (2, 3)];
874 let adj = build_adjacency_list(&coupling, 4);
875 let path = bfs_shortest_path(&adj, 0, 3, 4);
876 assert_eq!(path, vec![0, 1, 2, 3]);
877 }
878
879 #[test]
880 fn test_bfs_shortest_path_branching() {
881 let coupling = vec![(0, 1), (0, 2), (0, 3)];
883 let adj = build_adjacency_list(&coupling, 4);
884 let path = bfs_shortest_path(&adj, 1, 3, 4);
885 assert_eq!(path.len(), 3);
887 assert_eq!(path[0], 1);
888 assert_eq!(*path.last().unwrap(), 3);
889 }
890
891 #[test]
892 fn test_bfs_same_node() {
893 let coupling = vec![(0, 1)];
894 let adj = build_adjacency_list(&coupling, 2);
895 let path = bfs_shortest_path(&adj, 0, 0, 2);
896 assert_eq!(path, vec![0]);
897 }
898
899 #[test]
902 fn test_cancel_hh_produces_empty() {
903 let mut circuit = QuantumCircuit::new(1);
904 circuit.h(0);
905 circuit.h(0);
906
907 let optimized = optimize_gates(&circuit, 1);
908 assert_eq!(optimized.gate_count(), 0);
909 }
910
911 #[test]
912 fn test_cancel_xx() {
913 let mut circuit = QuantumCircuit::new(1);
914 circuit.x(0);
915 circuit.x(0);
916
917 let optimized = optimize_gates(&circuit, 1);
918 assert_eq!(optimized.gate_count(), 0);
919 }
920
921 #[test]
922 fn test_cancel_zz() {
923 let mut circuit = QuantumCircuit::new(1);
924 circuit.z(0);
925 circuit.z(0);
926
927 let optimized = optimize_gates(&circuit, 1);
928 assert_eq!(optimized.gate_count(), 0);
929 }
930
931 #[test]
932 fn test_cancel_s_sdg() {
933 let mut circuit = QuantumCircuit::new(1);
934 circuit.s(0);
935 circuit.add_gate(Gate::Sdg(0));
936
937 let optimized = optimize_gates(&circuit, 1);
938 assert_eq!(optimized.gate_count(), 0);
939 }
940
941 #[test]
942 fn test_cancel_t_tdg() {
943 let mut circuit = QuantumCircuit::new(1);
944 circuit.t(0);
945 circuit.add_gate(Gate::Tdg(0));
946
947 let optimized = optimize_gates(&circuit, 1);
948 assert_eq!(optimized.gate_count(), 0);
949 }
950
951 #[test]
952 fn test_cancel_cnot_cnot() {
953 let mut circuit = QuantumCircuit::new(2);
954 circuit.cnot(0, 1);
955 circuit.cnot(0, 1);
956
957 let optimized = optimize_gates(&circuit, 1);
958 assert_eq!(optimized.gate_count(), 0);
959 }
960
961 #[test]
962 fn test_no_cancel_different_qubits() {
963 let mut circuit = QuantumCircuit::new(2);
964 circuit.h(0);
965 circuit.h(1);
966
967 let optimized = optimize_gates(&circuit, 1);
968 assert_eq!(optimized.gate_count(), 2);
969 }
970
971 #[test]
972 fn test_merge_rz_level2() {
973 let mut circuit = QuantumCircuit::new(1);
974 circuit.rz(0, FRAC_PI_4);
975 circuit.rz(0, FRAC_PI_4);
976
977 let optimized = optimize_gates(&circuit, 2);
978 assert_eq!(optimized.gate_count(), 1);
979 if let Gate::Rz(0, theta) = &optimized.gates()[0] {
980 assert!((theta - FRAC_PI_2).abs() < 1e-12);
981 } else {
982 panic!("expected merged Rz(pi/2)");
983 }
984 }
985
986 #[test]
987 fn test_merge_rz_to_zero_eliminates() {
988 let mut circuit = QuantumCircuit::new(1);
989 circuit.rz(0, PI);
990 circuit.rz(0, -PI);
991
992 let optimized = optimize_gates(&circuit, 2);
993 assert_eq!(optimized.gate_count(), 0);
994 }
995
996 #[test]
997 fn test_merge_three_rz() {
998 let mut circuit = QuantumCircuit::new(1);
999 circuit.rz(0, FRAC_PI_4);
1000 circuit.rz(0, FRAC_PI_4);
1001 circuit.rz(0, FRAC_PI_4);
1002
1003 let optimized = optimize_gates(&circuit, 2);
1004 assert_eq!(optimized.gate_count(), 1);
1005 if let Gate::Rz(0, theta) = &optimized.gates()[0] {
1006 assert!((theta - 3.0 * FRAC_PI_4).abs() < 1e-12);
1007 } else {
1008 panic!("expected merged Rz(3*pi/4)");
1009 }
1010 }
1011
1012 #[test]
1013 fn test_level0_no_optimization() {
1014 let mut circuit = QuantumCircuit::new(1);
1015 circuit.h(0);
1016 circuit.h(0);
1017
1018 let optimized = optimize_gates(&circuit, 0);
1019 assert_eq!(optimized.gate_count(), 2);
1020 }
1021
1022 #[test]
1023 fn test_level1_does_not_merge_rz() {
1024 let mut circuit = QuantumCircuit::new(1);
1025 circuit.rz(0, FRAC_PI_4);
1026 circuit.rz(0, FRAC_PI_4);
1027
1028 let optimized = optimize_gates(&circuit, 1);
1029 assert_eq!(optimized.gate_count(), 2);
1031 }
1032
1033 #[test]
1036 fn test_transpile_universal_passthrough() {
1037 let mut circuit = QuantumCircuit::new(2);
1038 circuit.h(0);
1039 circuit.cnot(0, 1);
1040
1041 let config = TranspilerConfig {
1042 basis: BasisGateSet::Universal,
1043 coupling_map: None,
1044 optimization_level: 0,
1045 };
1046
1047 let result = transpile(&circuit, &config);
1048 assert_eq!(result.gate_count(), 2);
1049 }
1050
1051 #[test]
1052 fn test_transpile_ibm_decomposes_then_optimizes() {
1053 let mut circuit = QuantumCircuit::new(1);
1054 circuit.h(0);
1056 circuit.h(0);
1057
1058 let config = TranspilerConfig {
1059 basis: BasisGateSet::IbmEagle,
1060 coupling_map: None,
1061 optimization_level: 2,
1062 };
1063
1064 let result = transpile(&circuit, &config);
1065 assert!(result.gate_count() < 6, "expected some optimization");
1070 }
1071
1072 #[test]
1073 fn test_transpile_with_routing() {
1074 let mut circuit = QuantumCircuit::new(3);
1076 circuit.cnot(0, 2);
1077
1078 let config = TranspilerConfig {
1079 basis: BasisGateSet::Universal,
1080 coupling_map: Some(vec![(0, 1), (1, 2)]),
1081 optimization_level: 0,
1082 };
1083
1084 let result = transpile(&circuit, &config);
1085 let swap_count = result
1087 .gates()
1088 .iter()
1089 .filter(|g| matches!(g, Gate::SWAP(_, _)))
1090 .count();
1091 assert!(swap_count >= 1);
1092 }
1093
1094 #[test]
1095 fn test_transpile_rigetti_bell_state() {
1096 let mut circuit = QuantumCircuit::new(2);
1098 circuit.h(0);
1099 circuit.cnot(0, 1);
1100
1101 let config = TranspilerConfig {
1102 basis: BasisGateSet::RigettiAspen,
1103 coupling_map: None,
1104 optimization_level: 0,
1105 };
1106
1107 let result = transpile(&circuit, &config);
1108 for gate in result.gates() {
1110 match gate {
1111 Gate::CZ(_, _) | Gate::Rx(_, _) | Gate::Rz(_, _) => {}
1112 Gate::Measure(_) | Gate::Reset(_) | Gate::Barrier => {}
1113 other => panic!("gate {:?} not in Rigetti basis", other),
1114 }
1115 }
1116 }
1117
1118 #[test]
1119 fn test_transpile_ionq_single_qubit() {
1120 let mut circuit = QuantumCircuit::new(1);
1121 circuit.h(0);
1122
1123 let config = TranspilerConfig {
1124 basis: BasisGateSet::IonQAria,
1125 coupling_map: None,
1126 optimization_level: 0,
1127 };
1128
1129 let result = transpile(&circuit, &config);
1130 for gate in result.gates() {
1132 match gate {
1133 Gate::Rx(_, _) | Gate::Ry(_, _) | Gate::Rzz(_, _, _) => {}
1134 Gate::Measure(_) | Gate::Reset(_) | Gate::Barrier => {}
1135 other => panic!("gate {:?} not in IonQ basis", other),
1136 }
1137 }
1138 }
1139
1140 #[test]
1141 fn test_iterative_cancellation() {
1142 let mut circuit = QuantumCircuit::new(1);
1145 circuit.x(0);
1146 circuit.h(0);
1147 circuit.h(0);
1148 circuit.x(0);
1149
1150 let optimized = optimize_gates(&circuit, 1);
1151 assert_eq!(optimized.gate_count(), 0);
1152 }
1153
1154 #[test]
1155 fn test_routing_updates_mapping_correctly() {
1156 let coupling = vec![(0, 1), (1, 2), (2, 3)];
1160 let mut circuit = QuantumCircuit::new(4);
1161 circuit.cnot(0, 3);
1162 circuit.h(0);
1163
1164 let routed = route_circuit(&circuit, &coupling);
1165 let swap_count = routed
1167 .gates()
1168 .iter()
1169 .filter(|g| matches!(g, Gate::SWAP(_, _)))
1170 .count();
1171 assert!(swap_count >= 1);
1172 let h_count = routed
1174 .gates()
1175 .iter()
1176 .filter(|g| matches!(g, Gate::H(_)))
1177 .count();
1178 assert_eq!(h_count, 1);
1179 }
1180
1181 #[test]
1182 fn test_decompose_rzz_to_ibm() {
1183 let gates = decompose_to_ibm(&Gate::Rzz(0, 1, FRAC_PI_4));
1184 assert_eq!(gates.len(), 3);
1186 assert!(matches!(gates[0], Gate::CNOT(0, 1)));
1187 assert!(matches!(gates[1], Gate::Rz(1, _)));
1188 assert!(matches!(gates[2], Gate::CNOT(0, 1)));
1189 }
1190
1191 #[test]
1192 fn test_basis_gate_set_variants() {
1193 let variants = [
1195 BasisGateSet::IbmEagle,
1196 BasisGateSet::IonQAria,
1197 BasisGateSet::RigettiAspen,
1198 BasisGateSet::Universal,
1199 ];
1200 for (i, a) in variants.iter().enumerate() {
1201 for (j, b) in variants.iter().enumerate() {
1202 if i == j {
1203 assert_eq!(a, b);
1204 } else {
1205 assert_ne!(a, b);
1206 }
1207 }
1208 }
1209 }
1210}