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 (if possible)
91    pub fn to_circuit(&self) -> QuantRS2Result<Circuit<N>> {
92        let mut circuit = Circuit::<N>::new();
93
94        // Note: This is a simplified conversion - in practice we'd need proper gate conversion
95        // For now, we'll just return an empty circuit as this is mainly for demonstration
96        // TODO: Implement proper gate conversion from boxed gates back to circuit
97
98        Ok(circuit)
99    }
100
101    /// Get the final qubit mapping
102    #[must_use]
103    pub const fn get_mapping(&self) -> &HashMap<usize, usize> {
104        &self.logical_to_physical
105    }
106
107    /// Get the inverse mapping (physical to logical)
108    #[must_use]
109    pub fn get_inverse_mapping(&self) -> HashMap<usize, usize> {
110        self.logical_to_physical
111            .iter()
112            .map(|(&logical, &physical)| (physical, logical))
113            .collect()
114    }
115
116    /// Calculate circuit statistics
117    #[must_use]
118    pub fn statistics(&self) -> RoutingStatistics {
119        let mut two_qubit_gates = 0;
120        let mut single_qubit_gates = 0;
121        let mut swap_gates = 0;
122
123        for gate in &self.gates {
124            match gate.qubits().len() {
125                1 => single_qubit_gates += 1,
126                2 => {
127                    if gate.name() == "SWAP" {
128                        swap_gates += 1;
129                    } else {
130                        two_qubit_gates += 1;
131                    }
132                }
133                _ => {}
134            }
135        }
136
137        RoutingStatistics {
138            total_gates: self.gates.len(),
139            single_qubit_gates,
140            two_qubit_gates,
141            swap_gates,
142            circuit_depth: self.result.circuit_depth,
143            routing_overhead: self.routing_overhead(),
144        }
145    }
146}
147
148/// Detailed statistics about a routed circuit
149#[derive(Debug, Clone)]
150pub struct RoutingStatistics {
151    pub total_gates: usize,
152    pub single_qubit_gates: usize,
153    pub two_qubit_gates: usize,
154    pub swap_gates: usize,
155    pub circuit_depth: usize,
156    pub routing_overhead: f64,
157}
158
159impl RoutingStatistics {
160    /// Calculate the improvement ratio compared to another statistic
161    #[must_use]
162    pub fn improvement_ratio(&self, other: &Self) -> f64 {
163        if other.total_gates == 0 {
164            return 0.0;
165        }
166
167        (other.total_gates as f64 - self.total_gates as f64) / other.total_gates as f64
168    }
169
170    /// Calculate SWAP efficiency (two-qubit gates / total gates)
171    #[must_use]
172    pub fn swap_efficiency(&self) -> f64 {
173        if self.total_gates == 0 {
174            0.0
175        } else {
176            self.two_qubit_gates as f64 / self.total_gates as f64
177        }
178    }
179}
180
181/// Routing pass enum for type-safe routing
182#[derive(Debug, Clone)]
183pub enum RoutingPassType {
184    Sabre {
185        coupling_map: super::CouplingMap,
186        config: super::SabreConfig,
187    },
188    Lookahead {
189        coupling_map: super::CouplingMap,
190        config: super::LookaheadConfig,
191    },
192}
193
194impl RoutingPassType {
195    /// Get the name of the routing pass
196    #[must_use]
197    pub const fn name(&self) -> &str {
198        match self {
199            Self::Sabre { .. } => "SABRE",
200            Self::Lookahead { .. } => "Lookahead",
201        }
202    }
203
204    /// Apply routing to a circuit
205    pub fn route<const N: usize>(&self, circuit: &Circuit<N>) -> QuantRS2Result<RoutedCircuit<N>> {
206        match self {
207            Self::Sabre {
208                coupling_map,
209                config,
210            } => {
211                let router = super::SabreRouter::new(coupling_map.clone(), config.clone());
212                router.route(circuit)
213            }
214            Self::Lookahead {
215                coupling_map,
216                config,
217            } => {
218                let router = super::LookaheadRouter::new(coupling_map.clone(), config.clone());
219                router.route(circuit)
220            }
221        }
222    }
223
224    /// Check if this pass should be applied
225    #[must_use]
226    pub const fn should_apply<const N: usize>(&self, _circuit: &Circuit<N>) -> bool {
227        true
228    }
229
230    /// Get pass configuration as string (for debugging)
231    #[must_use]
232    pub fn config_string(&self) -> String {
233        match self {
234            Self::Sabre { config, .. } => {
235                format!(
236                    "SABRE(depth={}, max_iter={}, stochastic={})",
237                    config.lookahead_depth, config.max_iterations, config.stochastic
238                )
239            }
240            Self::Lookahead { config, .. } => {
241                format!(
242                    "Lookahead(depth={}, candidates={})",
243                    config.lookahead_depth, config.max_swap_candidates
244                )
245            }
246        }
247    }
248}
249
250/// Routing pass manager for handling multiple routing strategies
251pub struct RoutingPassManager {
252    passes: Vec<RoutingPassType>,
253}
254
255impl RoutingPassManager {
256    /// Create a new routing pass manager
257    pub const fn new() -> Self {
258        Self { passes: Vec::new() }
259    }
260
261    /// Add a routing pass
262    pub fn add_pass(&mut self, pass: RoutingPassType) {
263        self.passes.push(pass);
264    }
265
266    /// Apply the best routing pass to a circuit
267    pub fn route_with_best_pass<const N: usize>(
268        &self,
269        circuit: &Circuit<N>,
270    ) -> QuantRS2Result<RoutedCircuit<N>> {
271        if self.passes.is_empty() {
272            return Err(quantrs2_core::error::QuantRS2Error::RoutingError(
273                "No routing passes configured".to_string(),
274            ));
275        }
276
277        let mut best_result = None;
278        let mut best_cost = f64::INFINITY;
279
280        for pass in &self.passes {
281            if pass.should_apply(circuit) {
282                match pass.route(circuit) {
283                    Ok(result) => {
284                        let cost = result.result.total_cost();
285                        if cost < best_cost {
286                            best_cost = cost;
287                            best_result = Some(result);
288                        }
289                    }
290                    Err(e) => {
291                        eprintln!("Routing pass {} failed: {}", pass.name(), e);
292                    }
293                }
294            }
295        }
296
297        best_result.ok_or_else(|| {
298            quantrs2_core::error::QuantRS2Error::RoutingError(
299                "All routing passes failed".to_string(),
300            )
301        })
302    }
303
304    /// Apply a specific routing pass by name
305    pub fn route_with_pass<const N: usize>(
306        &self,
307        circuit: &Circuit<N>,
308        pass_name: &str,
309    ) -> QuantRS2Result<RoutedCircuit<N>> {
310        for pass in &self.passes {
311            if pass.name() == pass_name {
312                return pass.route(circuit);
313            }
314        }
315
316        Err(quantrs2_core::error::QuantRS2Error::RoutingError(format!(
317            "Routing pass '{pass_name}' not found"
318        )))
319    }
320
321    /// Get available pass names
322    pub fn available_passes(&self) -> Vec<&str> {
323        self.passes.iter().map(RoutingPassType::name).collect()
324    }
325}
326
327impl Default for RoutingPassManager {
328    fn default() -> Self {
329        Self::new()
330    }
331}
332
333#[cfg(test)]
334mod tests {
335    use super::*;
336    use quantrs2_core::gate::{
337        multi::{CNOT, SWAP},
338        single::Hadamard,
339    };
340    use quantrs2_core::qubit::QubitId;
341
342    #[test]
343    fn test_routed_circuit_statistics() {
344        let gates: Vec<Box<dyn GateOp>> = vec![
345            Box::new(Hadamard { target: QubitId(0) }),
346            Box::new(CNOT {
347                control: QubitId(0),
348                target: QubitId(1),
349            }),
350            Box::new(SWAP {
351                qubit1: QubitId(1),
352                qubit2: QubitId(2),
353            }),
354        ];
355
356        let mapping = [(0, 0), (1, 1), (2, 2)].iter().copied().collect();
357        let result = RoutingResult {
358            total_swaps: 1,
359            circuit_depth: 3,
360            routing_overhead: 0.33,
361        };
362
363        let routed_circuit = RoutedCircuit::<3>::new(gates, mapping, result);
364        let stats = routed_circuit.statistics();
365
366        assert_eq!(stats.total_gates, 3);
367        assert_eq!(stats.single_qubit_gates, 1);
368        assert_eq!(stats.two_qubit_gates, 1);
369        assert_eq!(stats.swap_gates, 1);
370    }
371
372    #[test]
373    fn test_routing_pass_manager() {
374        let mut manager = RoutingPassManager::new();
375        let coupling_map = super::super::CouplingMap::linear(3);
376        let sabre_config = super::super::SabreConfig::default();
377
378        manager.add_pass(RoutingPassType::Sabre {
379            coupling_map,
380            config: sabre_config,
381        });
382
383        assert_eq!(manager.available_passes(), vec!["SABRE"]);
384    }
385}