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