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>> {
96 let mut circuit = Circuit::<N>::new();
97
98 for gate in &self.gates {
99 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 #[must_use]
109 pub const fn get_mapping(&self) -> &HashMap<usize, usize> {
110 &self.logical_to_physical
111 }
112
113 #[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 #[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#[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 #[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 #[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#[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 #[must_use]
203 pub const fn name(&self) -> &str {
204 match self {
205 Self::Sabre { .. } => "SABRE",
206 Self::Lookahead { .. } => "Lookahead",
207 }
208 }
209
210 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 #[must_use]
232 pub const fn should_apply<const N: usize>(&self, _circuit: &Circuit<N>) -> bool {
233 true
234 }
235
236 #[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
256pub struct RoutingPassManager {
258 passes: Vec<RoutingPassType>,
259}
260
261impl RoutingPassManager {
262 pub const fn new() -> Self {
264 Self { passes: Vec::new() }
265 }
266
267 pub fn add_pass(&mut self, pass: RoutingPassType) {
269 self.passes.push(pass);
270 }
271
272 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 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 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}