quantrs2_circuit/routing/
routing_pass.rs1use crate::builder::Circuit;
4use quantrs2_core::{error::QuantRS2Result, gate::GateOp};
5use std::collections::HashMap;
6
7#[derive(Debug, Clone)]
9pub struct RoutingResult {
10 pub total_swaps: usize,
12 pub circuit_depth: usize,
14 pub routing_overhead: f64,
16}
17
18impl RoutingResult {
19 #[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#[derive(Debug)]
28pub struct RoutedCircuit<const N: usize> {
29 pub gates: Vec<Box<dyn GateOp>>,
31 pub logical_to_physical: HashMap<usize, usize>,
33 pub result: RoutingResult,
35}
36
37impl<const N: usize> RoutedCircuit<N> {
38 #[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 #[must_use]
54 pub fn total_cost(&self) -> f64 {
55 self.result.total_cost()
56 }
57
58 #[must_use]
60 pub fn num_gates(&self) -> usize {
61 self.gates.len()
62 }
63
64 #[must_use]
66 pub fn num_swaps(&self) -> usize {
67 self.gates.iter().filter(|g| g.name() == "SWAP").count()
68 }
69
70 #[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 #[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 pub fn to_circuit(&self) -> QuantRS2Result<Circuit<N>> {
92 let mut circuit = Circuit::<N>::new();
93
94 Ok(circuit)
99 }
100
101 #[must_use]
103 pub const fn get_mapping(&self) -> &HashMap<usize, usize> {
104 &self.logical_to_physical
105 }
106
107 #[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 #[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#[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 #[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 #[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#[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 #[must_use]
197 pub const fn name(&self) -> &str {
198 match self {
199 Self::Sabre { .. } => "SABRE",
200 Self::Lookahead { .. } => "Lookahead",
201 }
202 }
203
204 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 #[must_use]
226 pub const fn should_apply<const N: usize>(&self, _circuit: &Circuit<N>) -> bool {
227 true
228 }
229
230 #[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
250pub struct RoutingPassManager {
252 passes: Vec<RoutingPassType>,
253}
254
255impl RoutingPassManager {
256 pub const fn new() -> Self {
258 Self { passes: Vec::new() }
259 }
260
261 pub fn add_pass(&mut self, pass: RoutingPassType) {
263 self.passes.push(pass);
264 }
265
266 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 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 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}