1use crate::builder::Circuit;
7use crate::dag::{circuit_to_dag, CircuitDag, DagNode};
8use crate::routing::{CouplingMap, RoutedCircuit, RoutingResult};
9use quantrs2_core::{
10 error::{QuantRS2Error, QuantRS2Result},
11 gate::{
12 multi::SWAP,
13 single::{RotationX, RotationY, RotationZ},
14 GateOp,
15 },
16 qubit::QubitId,
17};
18use std::collections::{HashMap, HashSet, VecDeque};
19
20#[derive(Debug, Clone)]
22pub struct LookaheadConfig {
23 pub lookahead_depth: usize,
25 pub max_swap_candidates: usize,
27 pub distance_weight: f64,
29 pub future_weight: f64,
31 pub max_iterations: usize,
33}
34
35impl LookaheadConfig {
36 #[must_use]
38 pub const fn new(depth: usize) -> Self {
39 Self {
40 lookahead_depth: depth,
41 max_swap_candidates: 20,
42 distance_weight: 1.0,
43 future_weight: 0.5,
44 max_iterations: 1000,
45 }
46 }
47}
48
49impl Default for LookaheadConfig {
50 fn default() -> Self {
51 Self::new(10)
52 }
53}
54
55pub struct LookaheadRouter {
57 coupling_map: CouplingMap,
58 config: LookaheadConfig,
59}
60
61impl LookaheadRouter {
62 #[must_use]
64 pub const fn new(coupling_map: CouplingMap, config: LookaheadConfig) -> Self {
65 Self {
66 coupling_map,
67 config,
68 }
69 }
70
71 pub fn route<const N: usize>(&self, circuit: &Circuit<N>) -> QuantRS2Result<RoutedCircuit<N>> {
73 let dag = circuit_to_dag(circuit);
74 let mut logical_to_physical = self.initial_mapping(&dag);
75 let mut physical_to_logical: HashMap<usize, usize> = logical_to_physical
76 .iter()
77 .map(|(&logical, &physical)| (physical, logical))
78 .collect();
79
80 let mut routed_gates = Vec::new();
81 let mut remaining_gates: HashSet<usize> = (0..dag.nodes().len()).collect();
82 let mut iteration = 0;
83
84 while !remaining_gates.is_empty() && iteration < self.config.max_iterations {
85 iteration += 1;
86
87 let ready_gates = self.find_ready_gates(&dag, &remaining_gates, &logical_to_physical);
89
90 for gate_id in ready_gates {
91 let node = &dag.nodes()[gate_id];
92 let routed_gate = self.map_gate_to_physical(node, &logical_to_physical)?;
93 routed_gates.push(routed_gate);
94 remaining_gates.remove(&gate_id);
95 }
96
97 if !remaining_gates.is_empty() {
99 let best_swap =
100 self.find_best_lookahead_swap(&dag, &remaining_gates, &logical_to_physical)?;
101
102 if let Some((p1, p2)) = best_swap {
103 let swap_gate = Box::new(SWAP {
105 qubit1: QubitId::new(p1 as u32),
106 qubit2: QubitId::new(p2 as u32),
107 }) as Box<dyn GateOp>;
108 routed_gates.push(swap_gate);
109
110 let l1 = physical_to_logical[&p1];
112 let l2 = physical_to_logical[&p2];
113
114 logical_to_physical.insert(l1, p2);
115 logical_to_physical.insert(l2, p1);
116 physical_to_logical.insert(p1, l2);
117 physical_to_logical.insert(p2, l1);
118 } else {
119 return Err(QuantRS2Error::RoutingError(
120 "Cannot find valid SWAP operation".to_string(),
121 ));
122 }
123 }
124 }
125
126 if !remaining_gates.is_empty() {
127 return Err(QuantRS2Error::RoutingError(format!(
128 "Routing failed: {} gates remaining",
129 remaining_gates.len()
130 )));
131 }
132
133 let total_swaps = routed_gates.iter().filter(|g| g.name() == "SWAP").count();
134 let circuit_depth = self.calculate_depth(&routed_gates);
135
136 Ok(RoutedCircuit::new(
137 routed_gates,
138 logical_to_physical,
139 RoutingResult {
140 total_swaps,
141 circuit_depth,
142 routing_overhead: if circuit_depth > 0 {
143 total_swaps as f64 / circuit_depth as f64
144 } else {
145 0.0
146 },
147 },
148 ))
149 }
150
151 fn initial_mapping(&self, dag: &CircuitDag) -> HashMap<usize, usize> {
153 let logical_qubits = self.extract_logical_qubits(dag);
154 let mut mapping = HashMap::new();
155
156 if logical_qubits.is_empty() {
157 return mapping;
158 }
159
160 let interaction_graph = self.build_interaction_graph(dag);
162
163 let mut used_physical = HashSet::new();
165 let mut logical_priorities = self.calculate_logical_priorities(&interaction_graph);
166 logical_priorities
167 .sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
168
169 for (logical, _priority) in logical_priorities {
170 let best_physical = self.find_best_physical_qubit(
171 logical,
172 &interaction_graph,
173 &mapping,
174 &used_physical,
175 );
176 if let Some(physical) = best_physical {
177 mapping.insert(logical, physical);
178 used_physical.insert(physical);
179 }
180 }
181
182 for &logical in &logical_qubits {
184 if !mapping.contains_key(&logical) {
185 for physical in 0..self.coupling_map.num_qubits() {
186 if used_physical.insert(physical) {
187 mapping.insert(logical, physical);
188 break;
189 }
190 }
191 }
192 }
193
194 mapping
195 }
196
197 fn extract_logical_qubits(&self, dag: &CircuitDag) -> Vec<usize> {
199 let mut qubits = HashSet::new();
200
201 for node in dag.nodes() {
202 for qubit in node.gate.qubits() {
203 qubits.insert(qubit.id() as usize);
204 }
205 }
206
207 let mut qubit_vec: Vec<usize> = qubits.into_iter().collect();
208 qubit_vec.sort_unstable();
209 qubit_vec
210 }
211
212 fn build_interaction_graph(&self, dag: &CircuitDag) -> HashMap<(usize, usize), usize> {
214 let mut interactions = HashMap::new();
215
216 for node in dag.nodes() {
217 let qubits = node.gate.qubits();
218 if qubits.len() == 2 {
219 let q1 = qubits[0].id() as usize;
220 let q2 = qubits[1].id() as usize;
221 let key = (q1.min(q2), q1.max(q2));
222 *interactions.entry(key).or_insert(0) += 1;
223 }
224 }
225
226 interactions
227 }
228
229 fn calculate_logical_priorities(
231 &self,
232 interaction_graph: &HashMap<(usize, usize), usize>,
233 ) -> Vec<(usize, f64)> {
234 let mut priorities = HashMap::new();
235
236 for (&(q1, q2), &weight) in interaction_graph {
237 *priorities.entry(q1).or_insert(0.0) += weight as f64;
238 *priorities.entry(q2).or_insert(0.0) += weight as f64;
239 }
240
241 priorities.into_iter().collect()
242 }
243
244 fn find_best_physical_qubit(
246 &self,
247 logical: usize,
248 interaction_graph: &HashMap<(usize, usize), usize>,
249 current_mapping: &HashMap<usize, usize>,
250 used_physical: &HashSet<usize>,
251 ) -> Option<usize> {
252 let mut best_physical = None;
253 let mut best_score = f64::NEG_INFINITY;
254
255 for physical in 0..self.coupling_map.num_qubits() {
256 if used_physical.contains(&physical) {
257 continue;
258 }
259
260 let score = self.calculate_physical_score(
261 logical,
262 physical,
263 interaction_graph,
264 current_mapping,
265 );
266 if score > best_score {
267 best_score = score;
268 best_physical = Some(physical);
269 }
270 }
271
272 best_physical
273 }
274
275 fn calculate_physical_score(
277 &self,
278 logical: usize,
279 physical: usize,
280 interaction_graph: &HashMap<(usize, usize), usize>,
281 current_mapping: &HashMap<usize, usize>,
282 ) -> f64 {
283 let mut score = 0.0;
284
285 for (&other_logical, &other_physical) in current_mapping {
287 let interaction_key = (logical.min(other_logical), logical.max(other_logical));
288 if let Some(&weight) = interaction_graph.get(&interaction_key) {
289 let distance = self.coupling_map.distance(physical, other_physical);
290 score += weight as f64 / (1.0 + distance as f64);
291 }
292 }
293
294 score += self.coupling_map.neighbors(physical).len() as f64 * 0.1;
296
297 score
298 }
299
300 fn find_ready_gates(
302 &self,
303 dag: &CircuitDag,
304 remaining: &HashSet<usize>,
305 mapping: &HashMap<usize, usize>,
306 ) -> Vec<usize> {
307 let mut ready = Vec::new();
308
309 for &gate_id in remaining {
310 let node = &dag.nodes()[gate_id];
311
312 let deps_ready = node
314 .predecessors
315 .iter()
316 .all(|&pred| !remaining.contains(&pred));
317
318 if deps_ready && self.is_gate_executable(node, mapping) {
319 ready.push(gate_id);
320 }
321 }
322
323 ready
324 }
325
326 fn is_gate_executable(&self, node: &DagNode, mapping: &HashMap<usize, usize>) -> bool {
328 let qubits = node.gate.qubits();
329
330 if qubits.len() <= 1 {
331 return true;
332 }
333
334 if qubits.len() == 2 {
335 let q1 = qubits[0].id() as usize;
336 let q2 = qubits[1].id() as usize;
337
338 if let (Some(&p1), Some(&p2)) = (mapping.get(&q1), mapping.get(&q2)) {
339 return self.coupling_map.are_connected(p1, p2);
340 }
341 }
342
343 false
344 }
345
346 fn find_best_lookahead_swap(
348 &self,
349 dag: &CircuitDag,
350 remaining: &HashSet<usize>,
351 mapping: &HashMap<usize, usize>,
352 ) -> QuantRS2Result<Option<(usize, usize)>> {
353 let lookahead_layers = self.compute_lookahead_layers(dag, remaining);
354 let swap_candidates = self.generate_swap_candidates(mapping);
355
356 let mut best_swap = None;
357 let mut best_score = f64::NEG_INFINITY;
358
359 for &(p1, p2) in &swap_candidates {
360 let score = self.evaluate_swap_with_lookahead((p1, p2), &lookahead_layers, mapping);
361
362 if score > best_score {
363 best_score = score;
364 best_swap = Some((p1, p2));
365 }
366 }
367
368 Ok(best_swap)
369 }
370
371 fn compute_lookahead_layers(
373 &self,
374 dag: &CircuitDag,
375 remaining: &HashSet<usize>,
376 ) -> Vec<Vec<usize>> {
377 let mut layers = Vec::new();
378 let mut current_layer = HashSet::new();
379 let mut processed: HashSet<usize> = HashSet::new();
380
381 for &gate_id in remaining {
383 let node = &dag.nodes()[gate_id];
384 if node
385 .predecessors
386 .iter()
387 .all(|&pred| !remaining.contains(&pred))
388 {
389 current_layer.insert(gate_id);
390 }
391 }
392
393 for _ in 0..self.config.lookahead_depth {
394 if current_layer.is_empty() {
395 break;
396 }
397
398 layers.push(current_layer.iter().copied().collect());
399 processed.extend(¤t_layer);
400
401 let mut next_layer = HashSet::new();
402 for &gate_id in ¤t_layer {
403 let node = &dag.nodes()[gate_id];
404 for &succ in &node.successors {
405 if remaining.contains(&succ) && !processed.contains(&succ) {
406 let ready = dag.nodes()[succ]
408 .predecessors
409 .iter()
410 .all(|&pred| !remaining.contains(&pred) || processed.contains(&pred));
411
412 if ready {
413 next_layer.insert(succ);
414 }
415 }
416 }
417 }
418
419 current_layer = next_layer;
420 }
421
422 layers
423 }
424
425 fn generate_swap_candidates(&self, mapping: &HashMap<usize, usize>) -> Vec<(usize, usize)> {
427 let mut candidates = Vec::new();
428 let mapped_physical: HashSet<usize> = mapping.values().copied().collect();
429
430 for &p1 in &mapped_physical {
431 for &p2 in self.coupling_map.neighbors(p1) {
432 if mapped_physical.contains(&p2) && p1 < p2 {
433 candidates.push((p1, p2));
434 }
435 }
436 }
437
438 candidates.truncate(self.config.max_swap_candidates);
440 candidates
441 }
442
443 fn evaluate_swap_with_lookahead(
445 &self,
446 swap: (usize, usize),
447 lookahead_layers: &[Vec<usize>],
448 mapping: &HashMap<usize, usize>,
449 ) -> f64 {
450 let mut temp_mapping = mapping.clone();
452 let (p1, p2) = swap;
453
454 let mut l1_opt = None;
456 let mut l2_opt = None;
457
458 for (&logical, &physical) in mapping {
459 if physical == p1 {
460 l1_opt = Some(logical);
461 } else if physical == p2 {
462 l2_opt = Some(logical);
463 }
464 }
465
466 if let (Some(l1), Some(l2)) = (l1_opt, l2_opt) {
467 temp_mapping.insert(l1, p2);
468 temp_mapping.insert(l2, p1);
469 } else {
470 return f64::NEG_INFINITY;
471 }
472
473 let mut score = 0.0;
475 let mut layer_weight = 1.0;
476
477 for layer in lookahead_layers {
478 let mut layer_score = 0.0;
479
480 for &gate_id in layer {
481 layer_score += 1.0;
484 }
485
486 score += layer_score * layer_weight;
487 layer_weight *= 0.8; }
489
490 score
491 }
492
493 fn map_gate_to_physical(
495 &self,
496 node: &DagNode,
497 mapping: &HashMap<usize, usize>,
498 ) -> QuantRS2Result<Box<dyn GateOp>> {
499 let qubits = node.gate.qubits();
500 let mut physical_qubits = Vec::new();
501
502 for qubit in qubits {
503 let logical = qubit.id() as usize;
504 if let Some(&physical) = mapping.get(&logical) {
505 physical_qubits.push(QubitId::new(physical as u32));
506 } else {
507 return Err(QuantRS2Error::RoutingError(format!(
508 "Logical qubit {logical} not mapped"
509 )));
510 }
511 }
512
513 self.clone_gate_with_qubits(node.gate.as_ref(), &physical_qubits)
514 }
515
516 fn clone_gate_with_qubits(
518 &self,
519 gate: &dyn GateOp,
520 new_qubits: &[QubitId],
521 ) -> QuantRS2Result<Box<dyn GateOp>> {
522 use quantrs2_core::gate::{multi, single};
523
524 match (gate.name(), new_qubits.len()) {
525 ("H", 1) => Ok(Box::new(single::Hadamard {
526 target: new_qubits[0],
527 })),
528 ("X", 1) => Ok(Box::new(single::PauliX {
529 target: new_qubits[0],
530 })),
531 ("Y", 1) => Ok(Box::new(single::PauliY {
532 target: new_qubits[0],
533 })),
534 ("Z", 1) => Ok(Box::new(single::PauliZ {
535 target: new_qubits[0],
536 })),
537 ("S", 1) => Ok(Box::new(single::Phase {
538 target: new_qubits[0],
539 })),
540 ("T", 1) => Ok(Box::new(single::T {
541 target: new_qubits[0],
542 })),
543 ("CNOT", 2) => Ok(Box::new(multi::CNOT {
544 control: new_qubits[0],
545 target: new_qubits[1],
546 })),
547 ("CZ", 2) => Ok(Box::new(multi::CZ {
548 control: new_qubits[0],
549 target: new_qubits[1],
550 })),
551 ("SWAP", 2) => Ok(Box::new(multi::SWAP {
552 qubit1: new_qubits[0],
553 qubit2: new_qubits[1],
554 })),
555 ("RZ", 1) => {
556 let theta = gate
557 .as_any()
558 .downcast_ref::<RotationZ>()
559 .map(|g| g.theta)
560 .unwrap_or(0.0);
561 Ok(Box::new(RotationZ {
562 target: new_qubits[0],
563 theta,
564 }))
565 }
566 ("RY", 1) => {
567 let theta = gate
568 .as_any()
569 .downcast_ref::<RotationY>()
570 .map(|g| g.theta)
571 .unwrap_or(0.0);
572 Ok(Box::new(RotationY {
573 target: new_qubits[0],
574 theta,
575 }))
576 }
577 ("RX", 1) => {
578 let theta = gate
579 .as_any()
580 .downcast_ref::<RotationX>()
581 .map(|g| g.theta)
582 .unwrap_or(0.0);
583 Ok(Box::new(RotationX {
584 target: new_qubits[0],
585 theta,
586 }))
587 }
588 _ => Err(QuantRS2Error::UnsupportedOperation(format!(
589 "Cannot route gate {} with {} qubits",
590 gate.name(),
591 new_qubits.len()
592 ))),
593 }
594 }
595
596 fn calculate_depth(&self, _gates: &[Box<dyn GateOp>]) -> usize {
598 0
600 }
601}
602
603#[cfg(test)]
604mod tests {
605 use super::*;
606 use quantrs2_core::gate::{multi::CNOT, single::Hadamard};
607
608 #[test]
609 fn test_lookahead_basic() {
610 let coupling_map = CouplingMap::linear(4);
611 let config = LookaheadConfig::new(5);
612 let router = LookaheadRouter::new(coupling_map, config);
613
614 let mut circuit = Circuit::<4>::new();
615 circuit
616 .add_gate(Hadamard { target: QubitId(0) })
617 .expect("add H gate to circuit");
618 circuit
619 .add_gate(CNOT {
620 control: QubitId(0),
621 target: QubitId(3),
622 })
623 .expect("add CNOT gate to circuit");
624
625 let result = router.route(&circuit);
626 assert!(result.is_ok());
627 }
628
629 #[test]
630 fn test_interaction_graph() {
631 let coupling_map = CouplingMap::linear(3);
632 let config = LookaheadConfig::default();
633 let router = LookaheadRouter::new(coupling_map, config);
634
635 let mut circuit = Circuit::<3>::new();
636 circuit
637 .add_gate(CNOT {
638 control: QubitId(0),
639 target: QubitId(1),
640 })
641 .expect("add first CNOT gate to circuit");
642 circuit
643 .add_gate(CNOT {
644 control: QubitId(0),
645 target: QubitId(1),
646 })
647 .expect("add second CNOT gate to circuit");
648
649 let dag = circuit_to_dag(&circuit);
650 let graph = router.build_interaction_graph(&dag);
651
652 assert_eq!(graph.get(&(0, 1)), Some(&2));
653 }
654}