Skip to main content

quantrs2_circuit/routing/
routing_pass.rs

1//! Routing pass implementation and result types
2
3use crate::builder::Circuit;
4use quantrs2_core::{error::QuantRS2Result, gate::GateOp};
5use std::collections::HashMap;
6
7/// Result of a routing operation
8#[derive(Debug, Clone)]
9pub struct RoutingResult {
10    /// Total number of SWAP gates inserted
11    pub total_swaps: usize,
12    /// Final circuit depth after routing
13    pub circuit_depth: usize,
14    /// Routing overhead as a fraction
15    pub routing_overhead: f64,
16}
17
18impl RoutingResult {
19    /// Calculate total cost (combination of swaps and depth)
20    #[must_use]
21    pub fn total_cost(&self) -> f64 {
22        (self.circuit_depth as f64).mul_add(0.1, self.total_swaps as f64)
23    }
24}
25
26/// A routed circuit with mapping information
27#[derive(Debug)]
28pub struct RoutedCircuit<const N: usize> {
29    /// The routed gates in execution order
30    pub gates: Vec<Box<dyn GateOp>>,
31    /// Final mapping from logical to physical qubits
32    pub logical_to_physical: HashMap<usize, usize>,
33    /// Routing statistics
34    pub result: RoutingResult,
35}
36
37impl<const N: usize> RoutedCircuit<N> {
38    /// Create a new routed circuit
39    #[must_use]
40    pub fn new(
41        gates: Vec<Box<dyn GateOp>>,
42        logical_to_physical: HashMap<usize, usize>,
43        result: RoutingResult,
44    ) -> Self {
45        Self {
46            gates,
47            logical_to_physical,
48            result,
49        }
50    }
51
52    /// Get total cost of the routed circuit
53    #[must_use]
54    pub fn total_cost(&self) -> f64 {
55        self.result.total_cost()
56    }
57
58    /// Get the number of gates in the routed circuit
59    #[must_use]
60    pub fn num_gates(&self) -> usize {
61        self.gates.len()
62    }
63
64    /// Get the number of SWAP gates
65    #[must_use]
66    pub fn num_swaps(&self) -> usize {
67        self.gates.iter().filter(|g| g.name() == "SWAP").count()
68    }
69
70    /// Get the routing overhead (SWAPs / total gates)
71    #[must_use]
72    pub fn routing_overhead(&self) -> f64 {
73        if self.gates.is_empty() {
74            0.0
75        } else {
76            self.num_swaps() as f64 / self.gates.len() as f64
77        }
78    }
79
80    /// Get gates by type
81    #[must_use]
82    pub fn gates_by_type(&self) -> HashMap<String, usize> {
83        let mut counts = HashMap::new();
84        for gate in &self.gates {
85            *counts.entry(gate.name().to_string()).or_insert(0) += 1;
86        }
87        counts
88    }
89
90    /// Convert back to a `Circuit<N>` by replaying the routed gate sequence.
91    ///
92    /// Each stored `Box<dyn GateOp>` is added via `Circuit::add_gate_box`, which
93    /// down-casts to the concrete gate type internally.  Gates whose concrete type
94    /// is not recognised by `add_gate_box` will produce an error.
95    pub fn to_circuit(&self) -> QuantRS2Result<Circuit<N>> {
96        let mut circuit = Circuit::<N>::new();
97
98        for gate in &self.gates {
99            // Clone the boxed gate so we can hand ownership to the circuit.
100            let gate_clone: Box<dyn quantrs2_core::gate::GateOp> = gate.clone_gate();
101            circuit.add_gate_box(gate_clone)?;
102        }
103
104        Ok(circuit)
105    }
106
107    /// Get the final qubit mapping
108    #[must_use]
109    pub const fn get_mapping(&self) -> &HashMap<usize, usize> {
110        &self.logical_to_physical
111    }
112
113    /// Get the inverse mapping (physical to logical)
114    #[must_use]
115    pub fn get_inverse_mapping(&self) -> HashMap<usize, usize> {
116        self.logical_to_physical
117            .iter()
118            .map(|(&logical, &physical)| (physical, logical))
119            .collect()
120    }
121
122    /// Calculate circuit statistics
123    #[must_use]
124    pub fn statistics(&self) -> RoutingStatistics {
125        let mut two_qubit_gates = 0;
126        let mut single_qubit_gates = 0;
127        let mut swap_gates = 0;
128
129        for gate in &self.gates {
130            match gate.qubits().len() {
131                1 => single_qubit_gates += 1,
132                2 => {
133                    if gate.name() == "SWAP" {
134                        swap_gates += 1;
135                    } else {
136                        two_qubit_gates += 1;
137                    }
138                }
139                _ => {}
140            }
141        }
142
143        RoutingStatistics {
144            total_gates: self.gates.len(),
145            single_qubit_gates,
146            two_qubit_gates,
147            swap_gates,
148            circuit_depth: self.result.circuit_depth,
149            routing_overhead: self.routing_overhead(),
150        }
151    }
152}
153
154/// Detailed statistics about a routed circuit
155#[derive(Debug, Clone)]
156pub struct RoutingStatistics {
157    pub total_gates: usize,
158    pub single_qubit_gates: usize,
159    pub two_qubit_gates: usize,
160    pub swap_gates: usize,
161    pub circuit_depth: usize,
162    pub routing_overhead: f64,
163}
164
165impl RoutingStatistics {
166    /// Calculate the improvement ratio compared to another statistic
167    #[must_use]
168    pub fn improvement_ratio(&self, other: &Self) -> f64 {
169        if other.total_gates == 0 {
170            return 0.0;
171        }
172
173        (other.total_gates as f64 - self.total_gates as f64) / other.total_gates as f64
174    }
175
176    /// Calculate SWAP efficiency (two-qubit gates / total gates)
177    #[must_use]
178    pub fn swap_efficiency(&self) -> f64 {
179        if self.total_gates == 0 {
180            0.0
181        } else {
182            self.two_qubit_gates as f64 / self.total_gates as f64
183        }
184    }
185}
186
187/// Routing pass enum for type-safe routing
188#[derive(Debug, Clone)]
189pub enum RoutingPassType {
190    Sabre {
191        coupling_map: super::CouplingMap,
192        config: super::SabreConfig,
193    },
194    Lookahead {
195        coupling_map: super::CouplingMap,
196        config: super::LookaheadConfig,
197    },
198}
199
200impl RoutingPassType {
201    /// Get the name of the routing pass
202    #[must_use]
203    pub const fn name(&self) -> &str {
204        match self {
205            Self::Sabre { .. } => "SABRE",
206            Self::Lookahead { .. } => "Lookahead",
207        }
208    }
209
210    /// Apply routing to a circuit
211    pub fn route<const N: usize>(&self, circuit: &Circuit<N>) -> QuantRS2Result<RoutedCircuit<N>> {
212        match self {
213            Self::Sabre {
214                coupling_map,
215                config,
216            } => {
217                let router = super::SabreRouter::new(coupling_map.clone(), config.clone());
218                router.route(circuit)
219            }
220            Self::Lookahead {
221                coupling_map,
222                config,
223            } => {
224                let router = super::LookaheadRouter::new(coupling_map.clone(), config.clone());
225                router.route(circuit)
226            }
227        }
228    }
229
230    /// Check if this pass should be applied
231    #[must_use]
232    pub const fn should_apply<const N: usize>(&self, _circuit: &Circuit<N>) -> bool {
233        true
234    }
235
236    /// Get pass configuration as string (for debugging)
237    #[must_use]
238    pub fn config_string(&self) -> String {
239        match self {
240            Self::Sabre { config, .. } => {
241                format!(
242                    "SABRE(depth={}, max_iter={}, stochastic={})",
243                    config.lookahead_depth, config.max_iterations, config.stochastic
244                )
245            }
246            Self::Lookahead { config, .. } => {
247                format!(
248                    "Lookahead(depth={}, candidates={})",
249                    config.lookahead_depth, config.max_swap_candidates
250                )
251            }
252        }
253    }
254}
255
256/// Routing pass manager for handling multiple routing strategies
257pub struct RoutingPassManager {
258    passes: Vec<RoutingPassType>,
259}
260
261impl RoutingPassManager {
262    /// Create a new routing pass manager
263    pub const fn new() -> Self {
264        Self { passes: Vec::new() }
265    }
266
267    /// Add a routing pass
268    pub fn add_pass(&mut self, pass: RoutingPassType) {
269        self.passes.push(pass);
270    }
271
272    /// Apply the best routing pass to a circuit
273    pub fn route_with_best_pass<const N: usize>(
274        &self,
275        circuit: &Circuit<N>,
276    ) -> QuantRS2Result<RoutedCircuit<N>> {
277        if self.passes.is_empty() {
278            return Err(quantrs2_core::error::QuantRS2Error::RoutingError(
279                "No routing passes configured".to_string(),
280            ));
281        }
282
283        let mut best_result = None;
284        let mut best_cost = f64::INFINITY;
285
286        for pass in &self.passes {
287            if pass.should_apply(circuit) {
288                match pass.route(circuit) {
289                    Ok(result) => {
290                        let cost = result.result.total_cost();
291                        if cost < best_cost {
292                            best_cost = cost;
293                            best_result = Some(result);
294                        }
295                    }
296                    Err(e) => {
297                        eprintln!("Routing pass {} failed: {}", pass.name(), e);
298                    }
299                }
300            }
301        }
302
303        best_result.ok_or_else(|| {
304            quantrs2_core::error::QuantRS2Error::RoutingError(
305                "All routing passes failed".to_string(),
306            )
307        })
308    }
309
310    /// Apply a specific routing pass by name
311    pub fn route_with_pass<const N: usize>(
312        &self,
313        circuit: &Circuit<N>,
314        pass_name: &str,
315    ) -> QuantRS2Result<RoutedCircuit<N>> {
316        for pass in &self.passes {
317            if pass.name() == pass_name {
318                return pass.route(circuit);
319            }
320        }
321
322        Err(quantrs2_core::error::QuantRS2Error::RoutingError(format!(
323            "Routing pass '{pass_name}' not found"
324        )))
325    }
326
327    /// Get available pass names
328    pub fn available_passes(&self) -> Vec<&str> {
329        self.passes.iter().map(RoutingPassType::name).collect()
330    }
331}
332
333impl Default for RoutingPassManager {
334    fn default() -> Self {
335        Self::new()
336    }
337}
338
339#[cfg(test)]
340mod tests {
341    use super::*;
342    use quantrs2_core::gate::{
343        multi::{CNOT, SWAP},
344        single::Hadamard,
345    };
346    use quantrs2_core::qubit::QubitId;
347
348    #[test]
349    fn test_routed_circuit_statistics() {
350        let gates: Vec<Box<dyn GateOp>> = vec![
351            Box::new(Hadamard { target: QubitId(0) }),
352            Box::new(CNOT {
353                control: QubitId(0),
354                target: QubitId(1),
355            }),
356            Box::new(SWAP {
357                qubit1: QubitId(1),
358                qubit2: QubitId(2),
359            }),
360        ];
361
362        let mapping = [(0, 0), (1, 1), (2, 2)].iter().copied().collect();
363        let result = RoutingResult {
364            total_swaps: 1,
365            circuit_depth: 3,
366            routing_overhead: 0.33,
367        };
368
369        let routed_circuit = RoutedCircuit::<3>::new(gates, mapping, result);
370        let stats = routed_circuit.statistics();
371
372        assert_eq!(stats.total_gates, 3);
373        assert_eq!(stats.single_qubit_gates, 1);
374        assert_eq!(stats.two_qubit_gates, 1);
375        assert_eq!(stats.swap_gates, 1);
376    }
377
378    #[test]
379    fn test_routing_pass_manager() {
380        let mut manager = RoutingPassManager::new();
381        let coupling_map = super::super::CouplingMap::linear(3);
382        let sabre_config = super::super::SabreConfig::default();
383
384        manager.add_pass(RoutingPassType::Sabre {
385            coupling_map,
386            config: sabre_config,
387        });
388
389        assert_eq!(manager.available_passes(), vec!["SABRE"]);
390    }
391}