1use crate::topology::HardwareTopology;
7use crate::{DeviceError, DeviceResult};
8use petgraph::algo::astar;
9use petgraph::graph::{NodeIndex, UnGraph};
10use quantrs2_circuit::prelude::*;
11use std::collections::{HashMap, HashSet};
12
13#[derive(Debug, Clone, Copy)]
15pub enum RoutingStrategy {
16 NearestNeighbor,
18 SteinerTree,
20 Lookahead { depth: usize },
22 StochasticAnnealing,
24}
25
26#[derive(Debug, Clone)]
28pub struct RoutingResult {
29 pub initial_mapping: HashMap<usize, usize>,
31 pub final_mapping: HashMap<usize, usize>,
33 pub swap_gates: Vec<SwapGate>,
35 pub cost: usize,
37 pub depth_overhead: usize,
39}
40
41#[derive(Debug, Clone)]
43pub struct SwapGate {
44 pub qubit1: usize,
46 pub qubit2: usize,
47 pub position: usize,
49}
50
51pub struct QubitRouter {
53 topology: HardwareTopology,
55 strategy: RoutingStrategy,
57 seed: u64,
59}
60
61impl QubitRouter {
62 pub const fn new(topology: HardwareTopology, strategy: RoutingStrategy) -> Self {
64 Self {
65 topology,
66 strategy,
67 seed: 42,
68 }
69 }
70
71 pub fn route_circuit<const N: usize>(
73 &self,
74 circuit: &Circuit<N>,
75 ) -> DeviceResult<RoutingResult> {
76 let interactions = self.extract_interactions(circuit)?;
78
79 let initial_mapping = self.find_initial_mapping(&interactions, N)?;
81
82 match self.strategy {
84 RoutingStrategy::NearestNeighbor => {
85 self.route_nearest_neighbor(circuit, initial_mapping, interactions)
86 }
87 RoutingStrategy::SteinerTree => {
88 self.route_steiner_tree(circuit, initial_mapping, interactions)
89 }
90 RoutingStrategy::Lookahead { depth } => {
91 self.route_lookahead(circuit, initial_mapping, interactions, depth)
92 }
93 RoutingStrategy::StochasticAnnealing => {
94 self.route_simulated_annealing(circuit, initial_mapping, interactions)
95 }
96 }
97 }
98
99 fn extract_interactions<const N: usize>(
101 &self,
102 circuit: &Circuit<N>,
103 ) -> DeviceResult<Vec<(usize, usize)>> {
104 let mut interactions = Vec::new();
105
106 for gate in circuit.gates() {
107 if gate.qubits().len() == 2 {
108 let q0 = gate.qubits()[0].id() as usize;
109 let q1 = gate.qubits()[1].id() as usize;
110 interactions.push((q0, q1));
111 }
112 }
113
114 Ok(interactions)
115 }
116
117 fn find_initial_mapping(
119 &self,
120 interactions: &[(usize, usize)],
121 num_logical_qubits: usize,
122 ) -> DeviceResult<HashMap<usize, usize>> {
123 if num_logical_qubits > self.topology.num_qubits {
124 return Err(DeviceError::InsufficientQubits {
125 required: num_logical_qubits,
126 available: self.topology.num_qubits,
127 });
128 }
129
130 let mut interaction_graph = UnGraph::<(), ()>::new_undirected();
132 let mut nodes = HashMap::new();
133
134 for i in 0..num_logical_qubits {
135 let node = interaction_graph.add_node(());
136 nodes.insert(i, node);
137 }
138
139 for &(q0, q1) in interactions {
140 if let (Some(&n0), Some(&n1)) = (nodes.get(&q0), nodes.get(&q1)) {
141 interaction_graph.add_edge(n0, n1, ());
142 }
143 }
144
145 let mut mapping = HashMap::new();
148 let _available_physical: Vec<usize> = (0..self.topology.num_qubits).collect();
149
150 let mut logical_degrees: Vec<(usize, usize)> = nodes
152 .iter()
153 .map(|(&log_q, &node)| {
154 let degree = interaction_graph.edges(node).count();
155 (log_q, degree)
156 })
157 .collect();
158 logical_degrees.sort_by_key(|&(_, deg)| std::cmp::Reverse(deg));
159
160 let mut physical_degrees: Vec<(usize, usize)> = (0..self.topology.num_qubits)
161 .map(|p| {
162 let node = NodeIndex::new(p);
163 let degree = self.topology.connectivity.edges(node).count();
164 (p, degree)
165 })
166 .collect();
167 physical_degrees.sort_by_key(|&(_, deg)| std::cmp::Reverse(deg));
168
169 for (i, &(log_q, _)) in logical_degrees.iter().enumerate() {
170 if i < physical_degrees.len() {
171 mapping.insert(log_q, physical_degrees[i].0);
172 }
173 }
174
175 Ok(mapping)
176 }
177
178 fn route_nearest_neighbor<const N: usize>(
180 &self,
181 _circuit: &Circuit<N>,
182 initial_mapping: HashMap<usize, usize>,
183 interactions: Vec<(usize, usize)>,
184 ) -> DeviceResult<RoutingResult> {
185 let mut current_mapping = initial_mapping.clone();
186 let mut swap_gates = Vec::new();
187 let mut position = 0;
188
189 for (log_q0, log_q1) in interactions {
190 let phys_q0 = current_mapping[&log_q0];
191 let phys_q1 = current_mapping[&log_q1];
192
193 if !self.are_connected(phys_q0, phys_q1)? {
195 let path = self.find_shortest_path(phys_q0, phys_q1)?;
197
198 for i in 0..path.len() - 2 {
200 swap_gates.push(SwapGate {
201 qubit1: path[i],
202 qubit2: path[i + 1],
203 position,
204 });
205
206 self.apply_swap(&mut current_mapping, path[i], path[i + 1]);
208 }
209 }
210
211 position += 1;
212 }
213
214 Ok(RoutingResult {
215 initial_mapping,
216 final_mapping: current_mapping,
217 cost: swap_gates.len(),
218 depth_overhead: swap_gates.len() * 3, swap_gates,
220 })
221 }
222
223 fn route_steiner_tree<const N: usize>(
225 &self,
226 _circuit: &Circuit<N>,
227 initial_mapping: HashMap<usize, usize>,
228 interactions: Vec<(usize, usize)>,
229 ) -> DeviceResult<RoutingResult> {
230 self.route_nearest_neighbor(_circuit, initial_mapping, interactions)
233 }
234
235 fn route_lookahead<const N: usize>(
237 &self,
238 circuit: &Circuit<N>,
239 initial_mapping: HashMap<usize, usize>,
240 interactions: Vec<(usize, usize)>,
241 lookahead_depth: usize,
242 ) -> DeviceResult<RoutingResult> {
243 let mut current_mapping = initial_mapping.clone();
244 let mut swap_gates = Vec::new();
245 let mut position = 0;
246
247 for i in 0..interactions.len() {
248 let (log_q0, log_q1) = interactions[i];
249 let phys_q0 = current_mapping[&log_q0];
250 let phys_q1 = current_mapping[&log_q1];
251
252 if !self.are_connected(phys_q0, phys_q1)? {
253 let future_gates =
255 &interactions[i..std::cmp::min(i + lookahead_depth, interactions.len())];
256
257 let best_swap = self.find_best_swap_lookahead(
259 ¤t_mapping,
260 phys_q0,
261 phys_q1,
262 future_gates,
263 )?;
264
265 if let Some((swap_q0, swap_q1)) = best_swap {
266 swap_gates.push(SwapGate {
267 qubit1: swap_q0,
268 qubit2: swap_q1,
269 position,
270 });
271
272 self.apply_swap(&mut current_mapping, swap_q0, swap_q1);
273 }
274 }
275
276 position += 1;
277 }
278
279 Ok(RoutingResult {
280 initial_mapping,
281 final_mapping: current_mapping,
282 cost: swap_gates.len(),
283 depth_overhead: swap_gates.len() * 3,
284 swap_gates,
285 })
286 }
287
288 fn route_simulated_annealing<const N: usize>(
290 &self,
291 circuit: &Circuit<N>,
292 initial_mapping: HashMap<usize, usize>,
293 interactions: Vec<(usize, usize)>,
294 ) -> DeviceResult<RoutingResult> {
295 use fastrand::Rng;
296 let mut rng = Rng::with_seed(self.seed);
297
298 let mut best_mapping = initial_mapping;
299 let mut best_cost = self.evaluate_mapping(&best_mapping, &interactions)?;
300
301 let mut current_mapping = best_mapping.clone();
302 let mut current_cost = best_cost;
303
304 let mut temperature = 100.0;
306 let cooling_rate = 0.95;
307 let min_temperature = 0.01;
308
309 while temperature > min_temperature {
310 let mut neighbor_mapping = current_mapping.clone();
312 let logical_qubits: Vec<usize> = neighbor_mapping.keys().copied().collect();
313
314 if logical_qubits.len() >= 2 {
315 let idx1 = rng.usize(0..logical_qubits.len());
316 let idx2 = rng.usize(0..logical_qubits.len());
317
318 if idx1 != idx2 {
319 let log_q1 = logical_qubits[idx1];
320 let log_q2 = logical_qubits[idx2];
321
322 let phys_q1 = neighbor_mapping[&log_q1];
323 let phys_q2 = neighbor_mapping[&log_q2];
324
325 neighbor_mapping.insert(log_q1, phys_q2);
326 neighbor_mapping.insert(log_q2, phys_q1);
327 }
328 }
329
330 let neighbor_cost = self.evaluate_mapping(&neighbor_mapping, &interactions)?;
331 let delta = neighbor_cost as f64 - current_cost as f64;
332
333 if delta < 0.0 || rng.f64() < (-delta / temperature).exp() {
335 current_mapping = neighbor_mapping;
336 current_cost = neighbor_cost;
337
338 if current_cost < best_cost {
339 best_mapping.clone_from(¤t_mapping);
340 best_cost = current_cost;
341 }
342 }
343
344 temperature *= cooling_rate;
345 }
346
347 self.route_nearest_neighbor(circuit, best_mapping, interactions)
349 }
350
351 fn are_connected(&self, phys_q0: usize, phys_q1: usize) -> DeviceResult<bool> {
353 let n0 = NodeIndex::new(phys_q0);
354 let n1 = NodeIndex::new(phys_q1);
355 Ok(self.topology.connectivity.find_edge(n0, n1).is_some())
356 }
357
358 fn find_shortest_path(&self, start: usize, end: usize) -> DeviceResult<Vec<usize>> {
360 let start_node = NodeIndex::new(start);
361 let end_node = NodeIndex::new(end);
362
363 let result = astar(
364 &self.topology.connectivity,
365 start_node,
366 |n| n == end_node,
367 |e| *e.weight(),
368 |_| 0.0,
369 );
370
371 match result {
372 Some((_, path)) => Ok(path.into_iter().map(|n| n.index()).collect()),
373 None => Err(DeviceError::RoutingError(format!(
374 "No path found between qubits {start} and {end}"
375 ))),
376 }
377 }
378
379 fn apply_swap(&self, mapping: &mut HashMap<usize, usize>, phys_q0: usize, phys_q1: usize) {
381 let mut log_q0 = None;
383 let mut log_q1 = None;
384
385 for (&log_q, &phys_q) in mapping.iter() {
386 if phys_q == phys_q0 {
387 log_q0 = Some(log_q);
388 } else if phys_q == phys_q1 {
389 log_q1 = Some(log_q);
390 }
391 }
392
393 if let (Some(l0), Some(l1)) = (log_q0, log_q1) {
395 mapping.insert(l0, phys_q1);
396 mapping.insert(l1, phys_q0);
397 }
398 }
399
400 fn find_best_swap_lookahead(
402 &self,
403 mapping: &HashMap<usize, usize>,
404 target_phys_q0: usize,
405 target_phys_q1: usize,
406 future_gates: &[(usize, usize)],
407 ) -> DeviceResult<Option<(usize, usize)>> {
408 let mut best_swap = None;
409 let mut best_score = f64::MAX;
410
411 for edge in self.topology.connectivity.edge_indices() {
413 if let Some((n0, n1)) = self.topology.connectivity.edge_endpoints(edge) {
414 let phys_q0 = n0.index();
415 let phys_q1 = n1.index();
416
417 let mut test_mapping = mapping.clone();
419 self.apply_swap(&mut test_mapping, phys_q0, phys_q1);
420
421 let score = self.evaluate_swap_lookahead(
423 &test_mapping,
424 target_phys_q0,
425 target_phys_q1,
426 future_gates,
427 )?;
428
429 if score < best_score {
430 best_score = score;
431 best_swap = Some((phys_q0, phys_q1));
432 }
433 }
434 }
435
436 Ok(best_swap)
437 }
438
439 fn evaluate_swap_lookahead(
441 &self,
442 mapping: &HashMap<usize, usize>,
443 target_phys_q0: usize,
444 target_phys_q1: usize,
445 future_gates: &[(usize, usize)],
446 ) -> DeviceResult<f64> {
447 let mut score = 0.0;
448
449 if self.are_connected(target_phys_q0, target_phys_q1)? {
451 score -= 10.0; }
453
454 for (i, &(log_q0, log_q1)) in future_gates.iter().enumerate() {
456 if let (Some(&phys_q0), Some(&phys_q1)) = (mapping.get(&log_q0), mapping.get(&log_q1)) {
457 if self.are_connected(phys_q0, phys_q1)? {
458 score -= 1.0 / (i + 1) as f64; } else {
460 let path = self.find_shortest_path(phys_q0, phys_q1)?;
461 score += path.len() as f64 / (i + 1) as f64;
462 }
463 }
464 }
465
466 Ok(score)
467 }
468
469 fn evaluate_mapping(
471 &self,
472 mapping: &HashMap<usize, usize>,
473 interactions: &[(usize, usize)],
474 ) -> DeviceResult<usize> {
475 let mut total_cost = 0;
476
477 for &(log_q0, log_q1) in interactions {
478 if let (Some(&phys_q0), Some(&phys_q1)) = (mapping.get(&log_q0), mapping.get(&log_q1)) {
479 if !self.are_connected(phys_q0, phys_q1)? {
480 let path = self.find_shortest_path(phys_q0, phys_q1)?;
481 total_cost += path.len() - 1; }
483 }
484 }
485
486 Ok(total_cost)
487 }
488}
489
490pub struct LayoutSynthesis {
492 topology: HardwareTopology,
494}
495
496impl LayoutSynthesis {
497 pub const fn new(topology: HardwareTopology) -> Self {
499 Self { topology }
500 }
501
502 pub fn synthesize_layout(
504 &self,
505 interaction_graph: &UnGraph<(), f64>,
506 ) -> DeviceResult<HashMap<usize, usize>> {
507 let mut mapping = HashMap::new();
510
511 let mut logical_degrees: Vec<(usize, usize)> = interaction_graph
513 .node_indices()
514 .map(|n| {
515 let degree = interaction_graph.edges(n).count();
516 (n.index(), degree)
517 })
518 .collect();
519 logical_degrees.sort_by_key(|&(_, deg)| std::cmp::Reverse(deg));
520
521 let mut physical_degrees: Vec<(usize, usize)> = (0..self.topology.num_qubits)
523 .map(|p| {
524 let node = NodeIndex::new(p);
525 let degree = self.topology.connectivity.edges(node).count();
526 (p, degree)
527 })
528 .collect();
529 physical_degrees.sort_by_key(|&(_, deg)| std::cmp::Reverse(deg));
530
531 for (i, &(log_q, _)) in logical_degrees.iter().enumerate() {
533 if i < physical_degrees.len() {
534 mapping.insert(log_q, physical_degrees[i].0);
535 }
536 }
537
538 Ok(mapping)
539 }
540}
541
542#[cfg(test)]
543mod tests {
544 use super::*;
545 use quantrs2_core::prelude::QubitId;
546
547 #[test]
548 fn test_qubit_router_creation() {
549 let topology = HardwareTopology::linear_topology(5);
550 let router = QubitRouter::new(topology, RoutingStrategy::NearestNeighbor);
551 assert_eq!(router.topology.num_qubits, 5);
552 }
553
554 #[test]
555 fn test_routing_strategies() {
556 let topology = HardwareTopology::grid_topology(3, 3);
557
558 let strategies = vec![
560 RoutingStrategy::NearestNeighbor,
561 RoutingStrategy::SteinerTree,
562 RoutingStrategy::Lookahead { depth: 3 },
563 RoutingStrategy::StochasticAnnealing,
564 ];
565
566 for strategy in strategies {
567 let router = QubitRouter::new(topology.clone(), strategy);
568 assert_eq!(router.topology.num_qubits, 9);
569 }
570 }
571
572 #[test]
573 fn test_swap_application() {
574 let topology = HardwareTopology::linear_topology(4);
575 let router = QubitRouter::new(topology, RoutingStrategy::NearestNeighbor);
576
577 let mut mapping = HashMap::from([(0, 0), (1, 1), (2, 2), (3, 3)]);
578
579 router.apply_swap(&mut mapping, 1, 2);
580
581 assert_eq!(mapping[&1], 2);
582 assert_eq!(mapping[&2], 1);
583 }
584
585 #[test]
586 fn test_linear_topology_routing() {
587 let topology = HardwareTopology::linear_topology(5);
589 let router = QubitRouter::new(topology, RoutingStrategy::NearestNeighbor);
590
591 let mut circuit = Circuit::<5>::new();
593 circuit.h(QubitId::new(0)).expect("Failed to add H gate");
594 circuit
595 .cnot(QubitId::new(0), QubitId::new(4))
596 .expect("Failed to add CNOT gate"); circuit
598 .cnot(QubitId::new(1), QubitId::new(3))
599 .expect("Failed to add CNOT gate"); let result = router
602 .route_circuit(&circuit)
603 .expect("Failed to route circuit");
604
605 assert_eq!(result.initial_mapping.len(), 5);
611 assert_eq!(result.final_mapping.len(), 5);
612 }
613
614 #[test]
615 fn test_grid_topology_routing() {
616 let topology = HardwareTopology::grid_topology(3, 3);
618 let router = QubitRouter::new(topology, RoutingStrategy::Lookahead { depth: 3 });
619
620 let mut circuit = Circuit::<9>::new();
622 circuit.h(QubitId::new(0)).expect("Failed to add H gate");
623 circuit
624 .cnot(QubitId::new(0), QubitId::new(8))
625 .expect("Failed to add CNOT gate"); circuit
627 .cnot(QubitId::new(4), QubitId::new(2))
628 .expect("Failed to add CNOT gate"); let result = router
631 .route_circuit(&circuit)
632 .expect("Failed to route circuit");
633
634 assert!(result.swap_gates.len() > 0);
636 }
637
638 #[test]
639 fn test_heavy_hex_routing() {
640 let topology = HardwareTopology::from_heavy_hex(27);
642 let router = QubitRouter::new(topology, RoutingStrategy::StochasticAnnealing);
643
644 let mut circuit = Circuit::<10>::new();
645 circuit.h(QubitId::new(0)).expect("Failed to add H gate");
647 circuit
648 .cnot(QubitId::new(0), QubitId::new(5))
649 .expect("Failed to add CNOT gate");
650 circuit
651 .cnot(QubitId::new(2), QubitId::new(7))
652 .expect("Failed to add CNOT gate");
653 circuit
654 .cnot(QubitId::new(3), QubitId::new(9))
655 .expect("Failed to add CNOT gate");
656
657 let result = router
658 .route_circuit(&circuit)
659 .expect("Failed to route circuit");
660
661 assert!(result.initial_mapping.len() <= 27);
663 }
664
665 #[test]
666 fn test_insufficient_qubits() {
667 let topology = HardwareTopology::linear_topology(3);
668 let router = QubitRouter::new(topology, RoutingStrategy::NearestNeighbor);
669
670 let circuit = Circuit::<5>::new();
672
673 let result = router.route_circuit(&circuit);
674 assert!(result.is_err());
675 }
676
677 #[test]
678 fn test_different_strategies_comparison() {
679 let topology = HardwareTopology::grid_topology(4, 4);
680
681 let mut circuit = Circuit::<16>::new();
683 for i in 0..8 {
684 circuit
685 .h(QubitId::new(i as u32))
686 .expect("Failed to add H gate in loop");
687 circuit
688 .cnot(QubitId::new(i as u32), QubitId::new((i + 8) as u32))
689 .expect("Failed to add CNOT gate in loop");
690 }
691
692 let strategies = vec![
694 RoutingStrategy::NearestNeighbor,
695 RoutingStrategy::Lookahead { depth: 3 },
696 RoutingStrategy::Lookahead { depth: 5 },
697 RoutingStrategy::StochasticAnnealing,
698 ];
699
700 let mut results = Vec::new();
701 for strategy in strategies {
702 let router = QubitRouter::new(topology.clone(), strategy);
703 let result = router
704 .route_circuit(&circuit)
705 .expect("Failed to route with strategy");
706 results.push((strategy, result.cost));
707 }
708
709 }
712
713 #[test]
714 fn test_already_connected_qubits() {
715 let topology = HardwareTopology::linear_topology(3);
716 let router = QubitRouter::new(topology, RoutingStrategy::NearestNeighbor);
717
718 let mut circuit = Circuit::<3>::new();
720 circuit
721 .cnot(QubitId::new(0), QubitId::new(1))
722 .expect("Failed to add CNOT gate"); circuit
724 .cnot(QubitId::new(1), QubitId::new(2))
725 .expect("Failed to add CNOT gate"); let result = router
728 .route_circuit(&circuit)
729 .expect("Failed to route circuit");
730
731 assert_eq!(result.swap_gates.len(), 0);
733 assert_eq!(result.cost, 0);
734 }
735
736 #[test]
737 fn test_layout_synthesis() {
738 let topology = HardwareTopology::grid_topology(3, 3);
739 let synthesizer = LayoutSynthesis::new(topology);
740
741 let mut graph = UnGraph::<(), f64>::new_undirected();
743 let n0 = graph.add_node(());
744 let n1 = graph.add_node(());
745 let n2 = graph.add_node(());
746 let n3 = graph.add_node(());
747
748 graph.add_edge(n0, n1, 1.0);
749 graph.add_edge(n1, n2, 1.0);
750 graph.add_edge(n2, n3, 1.0);
751 graph.add_edge(n3, n0, 1.0); let layout = synthesizer
754 .synthesize_layout(&graph)
755 .expect("Failed to synthesize layout");
756
757 assert_eq!(layout.len(), 4);
759
760 let physical_qubits: HashSet<usize> = layout.values().copied().collect();
762 assert_eq!(physical_qubits.len(), 4);
763 }
764
765 #[test]
766 fn test_path_finding() {
767 let topology = HardwareTopology::linear_topology(5);
768 let router = QubitRouter::new(topology, RoutingStrategy::NearestNeighbor);
769
770 let path = router
772 .find_shortest_path(0, 4)
773 .expect("Failed to find path 0->4");
774 assert_eq!(path, vec![0, 1, 2, 3, 4]);
775
776 let path = router
778 .find_shortest_path(2, 0)
779 .expect("Failed to find path 2->0");
780 assert_eq!(path, vec![2, 1, 0]);
781 }
782
783 #[test]
784 fn test_connectivity_check() {
785 let topology = HardwareTopology::grid_topology(3, 3);
786 let router = QubitRouter::new(topology, RoutingStrategy::NearestNeighbor);
787
788 assert!(router
790 .are_connected(0, 1)
791 .expect("Connectivity check failed")); assert!(router
793 .are_connected(0, 3)
794 .expect("Connectivity check failed")); assert!(!router
798 .are_connected(0, 4)
799 .expect("Connectivity check failed"));
800
801 assert!(!router
803 .are_connected(0, 8)
804 .expect("Connectivity check failed"));
805 }
806}