1use petgraph::algo::{connected_components, dijkstra, min_spanning_tree};
7use petgraph::data::FromElements;
8use petgraph::graph::{NodeIndex, UnGraph};
9use std::collections::{HashMap, HashSet};
10use thiserror::Error;
11
12#[derive(Error, Debug)]
14pub enum TopologyError {
15 #[error("Unsupported topology: {0}")]
16 UnsupportedTopology(String),
17
18 #[error("Invalid configuration: {0}")]
19 InvalidConfiguration(String),
20}
21
22#[derive(Debug, Clone)]
24pub struct HardwareTopology {
25 pub num_qubits: usize,
27 pub connectivity: UnGraph<u32, f64>,
29 pub qubit_properties: Vec<QubitProperties>,
31 pub gate_properties: HashMap<(u32, u32), GateProperties>,
33}
34
35#[derive(Debug, Clone)]
37pub struct QubitProperties {
38 pub id: u32,
40 pub index: u32,
42 pub t1: f64,
44 pub t2: f64,
46 pub single_qubit_gate_error: f64,
48 pub gate_error_1q: f64,
50 pub readout_error: f64,
52 pub frequency: f64,
54}
55
56#[derive(Debug, Clone)]
58pub struct GateProperties {
59 pub error_rate: f64,
61 pub duration: f64,
63 pub gate_type: String,
65}
66
67impl HardwareTopology {
68 pub fn new(num_qubits: usize) -> Self {
70 Self {
71 num_qubits,
72 connectivity: UnGraph::new_undirected(),
73 qubit_properties: Vec::new(),
74 gate_properties: HashMap::new(),
75 }
76 }
77
78 pub fn add_qubit(&mut self, properties: QubitProperties) -> NodeIndex {
80 let idx = self.connectivity.add_node(properties.index);
81 self.qubit_properties.push(properties);
82 idx
83 }
84
85 pub fn add_connection(&mut self, q1: u32, q2: u32, properties: GateProperties) {
87 let n1 = self
89 .connectivity
90 .node_indices()
91 .find(|&n| self.connectivity[n] == q1);
92 let n2 = self
93 .connectivity
94 .node_indices()
95 .find(|&n| self.connectivity[n] == q2);
96
97 if let (Some(n1), Some(n2)) = (n1, n2) {
98 self.connectivity.add_edge(n1, n2, properties.error_rate);
100 self.gate_properties.insert((q1, q2), properties.clone());
101 self.gate_properties.insert((q2, q1), properties);
102 }
103 }
104
105 pub fn load_standard(topology_type: &str) -> Result<Self, TopologyError> {
107 match topology_type {
108 "linear" => Ok(Self::linear_topology(5)),
109 "grid_2x3" => Ok(Self::grid_topology(2, 3)),
110 "ibm_5q" => Ok(Self::ibm_topology()),
111 "google_sycamore" => Ok(Self::create_sycamore_subset()),
112 _ => Err(TopologyError::UnsupportedTopology(format!(
113 "Unknown topology: {topology_type}"
114 ))),
115 }
116 }
117
118 pub fn linear_topology(n: usize) -> Self {
120 let mut topo = Self::new(n);
121
122 for i in 0..n {
124 topo.add_qubit(QubitProperties {
125 id: i as u32,
126 index: i as u32,
127 t1: 50.0,
128 t2: 70.0,
129 single_qubit_gate_error: 0.001,
130 gate_error_1q: 0.001,
131 readout_error: 0.01,
132 frequency: 0.1f64.mul_add(i as f64, 5.0),
133 });
134 }
135
136 for i in 0..n - 1 {
138 topo.add_connection(
139 i as u32,
140 (i + 1) as u32,
141 GateProperties {
142 error_rate: 0.01,
143 duration: 200.0,
144 gate_type: "CZ".to_string(),
145 },
146 );
147 }
148
149 topo
150 }
151
152 pub fn grid_topology(rows: usize, cols: usize) -> Self {
154 let n = rows * cols;
155 let mut topo = Self::new(n);
156
157 for i in 0..n {
159 topo.add_qubit(QubitProperties {
160 id: i as u32,
161 index: i as u32,
162 t1: 5.0f64.mul_add(fastrand::f64(), 45.0),
163 t2: 10.0f64.mul_add(fastrand::f64(), 60.0),
164 single_qubit_gate_error: 0.001 * 0.2f64.mul_add(fastrand::f64(), 1.0),
165 gate_error_1q: 0.001 * 0.2f64.mul_add(fastrand::f64(), 1.0),
166 readout_error: 0.01 * 0.3f64.mul_add(fastrand::f64(), 1.0),
167 frequency: 0.2f64.mul_add(fastrand::f64(), 5.0),
168 });
169 }
170
171 for r in 0..rows {
173 for c in 0..cols - 1 {
174 let q1 = (r * cols + c) as u32;
175 let q2 = (r * cols + c + 1) as u32;
176 topo.add_connection(
177 q1,
178 q2,
179 GateProperties {
180 error_rate: 0.01 * 0.2f64.mul_add(fastrand::f64(), 1.0),
181 duration: 40.0f64.mul_add(fastrand::f64(), 180.0),
182 gate_type: "CZ".to_string(),
183 },
184 );
185 }
186 }
187
188 for r in 0..rows - 1 {
190 for c in 0..cols {
191 let q1 = (r * cols + c) as u32;
192 let q2 = ((r + 1) * cols + c) as u32;
193 topo.add_connection(
194 q1,
195 q2,
196 GateProperties {
197 error_rate: 0.01 * 0.2f64.mul_add(fastrand::f64(), 1.0),
198 duration: 40.0f64.mul_add(fastrand::f64(), 180.0),
199 gate_type: "CZ".to_string(),
200 },
201 );
202 }
203 }
204
205 topo
206 }
207
208 pub fn ibm_topology() -> Self {
210 let mut topo = Self::new(5);
211
212 let qubit_props = vec![
214 (0, 52.3, 71.2, 0.0008, 0.015),
215 (1, 48.7, 68.5, 0.0011, 0.018),
216 (2, 55.1, 73.8, 0.0009, 0.012),
217 (3, 51.4, 69.9, 0.0010, 0.016),
218 (4, 49.8, 67.3, 0.0012, 0.020),
219 ];
220
221 for (i, (idx, t1, t2, err_1q, err_ro)) in qubit_props.into_iter().enumerate() {
222 topo.add_qubit(QubitProperties {
223 id: idx,
224 index: idx,
225 t1,
226 t2,
227 single_qubit_gate_error: err_1q,
228 gate_error_1q: err_1q,
229 readout_error: err_ro,
230 frequency: 0.05f64.mul_add(i as f64, 5.0),
231 });
232 }
233
234 let connections = vec![(0, 1, 0.008), (1, 2, 0.012), (1, 3, 0.010), (3, 4, 0.009)];
236
237 for (q1, q2, err) in connections {
238 topo.add_connection(
239 q1,
240 q2,
241 GateProperties {
242 error_rate: err,
243 duration: 20.0f64.mul_add(fastrand::f64(), 200.0),
244 gate_type: "CNOT".to_string(),
245 },
246 );
247 }
248
249 topo
250 }
251
252 pub fn google_topology() -> Self {
254 let mut topo = Self::new(6);
256
257 for i in 0..6 {
259 topo.add_qubit(QubitProperties {
260 id: i as u32,
261 index: i as u32,
262 t1: 2.0f64.mul_add(fastrand::f64(), 15.0),
263 t2: 3.0f64.mul_add(fastrand::f64(), 20.0),
264 single_qubit_gate_error: 0.0015 * 0.1f64.mul_add(fastrand::f64(), 1.0),
265 gate_error_1q: 0.0015 * 0.1f64.mul_add(fastrand::f64(), 1.0),
266 readout_error: 0.008 * 0.2f64.mul_add(fastrand::f64(), 1.0),
267 frequency: 0.1f64.mul_add(fastrand::f64(), 5.5),
268 });
269 }
270
271 let connections = vec![
273 (0, 1),
274 (1, 2), (3, 4),
276 (4, 5), (0, 3),
278 (1, 4),
279 (2, 5), (0, 4),
281 (1, 5), ];
283
284 for (q1, q2) in connections {
285 topo.add_connection(
286 q1,
287 q2,
288 GateProperties {
289 error_rate: 0.006 * 0.15f64.mul_add(fastrand::f64(), 1.0),
290 duration: 5.0f64.mul_add(fastrand::f64(), 25.0),
291 gate_type: "sqrt_ISWAP".to_string(),
292 },
293 );
294 }
295
296 topo
297 }
298
299 pub fn from_heavy_hex(num_qubits: usize) -> Self {
301 let mut topo = Self::new(num_qubits);
302
303 for i in 0..num_qubits {
305 topo.add_qubit(QubitProperties {
306 id: i as u32,
307 index: i as u32,
308 t1: 10.0f64.mul_add(fastrand::f64(), 50.0),
309 t2: 15.0f64.mul_add(fastrand::f64(), 70.0),
310 single_qubit_gate_error: 0.001 * 0.2f64.mul_add(fastrand::f64(), 1.0),
311 gate_error_1q: 0.001 * 0.2f64.mul_add(fastrand::f64(), 1.0),
312 readout_error: 0.015 * 0.3f64.mul_add(fastrand::f64(), 1.0),
313 frequency: 0.2f64.mul_add(fastrand::f64(), 5.0),
314 });
315 }
316
317 if num_qubits >= 27 {
320 for i in 0..4 {
323 topo.add_connection(
324 i,
325 i + 1,
326 GateProperties {
327 error_rate: 0.01 * 0.2f64.mul_add(fastrand::f64(), 1.0),
328 duration: 40.0f64.mul_add(fastrand::f64(), 200.0),
329 gate_type: "CNOT".to_string(),
330 },
331 );
332 }
333
334 for i in 5..9 {
336 topo.add_connection(
337 i,
338 i + 1,
339 GateProperties {
340 error_rate: 0.01 * 0.2f64.mul_add(fastrand::f64(), 1.0),
341 duration: 40.0f64.mul_add(fastrand::f64(), 200.0),
342 gate_type: "CNOT".to_string(),
343 },
344 );
345 }
346
347 for i in 10..14 {
349 topo.add_connection(
350 i,
351 i + 1,
352 GateProperties {
353 error_rate: 0.01 * 0.2f64.mul_add(fastrand::f64(), 1.0),
354 duration: 40.0f64.mul_add(fastrand::f64(), 200.0),
355 gate_type: "CNOT".to_string(),
356 },
357 );
358 }
359
360 for i in 15..19 {
362 topo.add_connection(
363 i,
364 i + 1,
365 GateProperties {
366 error_rate: 0.01 * 0.2f64.mul_add(fastrand::f64(), 1.0),
367 duration: 40.0f64.mul_add(fastrand::f64(), 200.0),
368 gate_type: "CNOT".to_string(),
369 },
370 );
371 }
372
373 for i in 20..26 {
375 topo.add_connection(
376 i,
377 i + 1,
378 GateProperties {
379 error_rate: 0.01 * 0.2f64.mul_add(fastrand::f64(), 1.0),
380 duration: 40.0f64.mul_add(fastrand::f64(), 200.0),
381 gate_type: "CNOT".to_string(),
382 },
383 );
384 }
385
386 let vertical_connections = vec![
388 (0, 5),
389 (2, 7),
390 (4, 9),
391 (5, 10),
392 (7, 12),
393 (9, 14),
394 (10, 15),
395 (12, 17),
396 (14, 19),
397 (15, 20),
398 (16, 21),
399 (17, 22),
400 (18, 23),
401 (19, 24),
402 ];
403
404 for (a, b) in vertical_connections {
405 if a < num_qubits && b < num_qubits {
406 topo.add_connection(
407 a as u32,
408 b as u32,
409 GateProperties {
410 error_rate: 0.01 * 0.2f64.mul_add(fastrand::f64(), 1.0),
411 duration: 40.0f64.mul_add(fastrand::f64(), 200.0),
412 gate_type: "CNOT".to_string(),
413 },
414 );
415 }
416 }
417
418 let diagonal_connections = vec![(1, 6), (3, 8), (6, 11), (8, 13), (11, 16), (13, 18)];
420
421 for (a, b) in diagonal_connections {
422 if a < num_qubits && b < num_qubits {
423 topo.add_connection(
424 a as u32,
425 b as u32,
426 GateProperties {
427 error_rate: 0.01 * 0.2f64.mul_add(fastrand::f64(), 1.0),
428 duration: 40.0f64.mul_add(fastrand::f64(), 200.0),
429 gate_type: "CNOT".to_string(),
430 },
431 );
432 }
433 }
434 }
435
436 topo
437 }
438
439 pub fn from_sycamore(num_qubits: usize) -> Self {
441 let mut topo = Self::new(num_qubits);
442
443 for i in 0..num_qubits {
445 topo.add_qubit(QubitProperties {
446 id: i as u32,
447 index: i as u32,
448 t1: 3.0f64.mul_add(fastrand::f64(), 15.0),
449 t2: 5.0f64.mul_add(fastrand::f64(), 20.0),
450 single_qubit_gate_error: 0.0015 * 0.2f64.mul_add(fastrand::f64(), 1.0),
451 gate_error_1q: 0.0015 * 0.2f64.mul_add(fastrand::f64(), 1.0),
452 readout_error: 0.008 * 0.2f64.mul_add(fastrand::f64(), 1.0),
453 frequency: 0.1f64.mul_add(fastrand::f64(), 5.5),
454 });
455 }
456
457 if num_qubits >= 20 {
460 let rows = 4;
462 let cols = 5;
463
464 for row in 0..rows {
466 for col in 0..cols - 1 {
467 let q1 = row * cols + col;
468 let q2 = row * cols + col + 1;
469 if q1 < num_qubits && q2 < num_qubits {
470 topo.add_connection(
471 q1 as u32,
472 q2 as u32,
473 GateProperties {
474 error_rate: 0.006 * 0.15f64.mul_add(fastrand::f64(), 1.0),
475 duration: 12.0,
476 gate_type: "sqrt_ISwap".to_string(),
477 },
478 );
479 }
480 }
481 }
482
483 for row in 0..rows - 1 {
485 for col in 0..cols {
486 if (row + col) % 2 == 0 {
488 let q1 = row * cols + col;
489 let q2 = (row + 1) * cols + col;
490 if q1 < num_qubits && q2 < num_qubits {
491 topo.add_connection(
492 q1 as u32,
493 q2 as u32,
494 GateProperties {
495 error_rate: 0.006 * 0.15f64.mul_add(fastrand::f64(), 1.0),
496 duration: 12.0,
497 gate_type: "sqrt_ISwap".to_string(),
498 },
499 );
500 }
501 }
502 }
503 }
504 }
505
506 topo
507 }
508
509 fn create_sycamore_subset() -> Self {
511 let mut topo = Self::new(9);
512
513 for i in 0..9 {
515 topo.add_qubit(QubitProperties {
516 id: i as u32,
517 index: i as u32,
518 t1: 3.0f64.mul_add(fastrand::f64(), 15.0),
519 t2: 5.0f64.mul_add(fastrand::f64(), 20.0),
520 single_qubit_gate_error: 0.0015 * 0.2f64.mul_add(fastrand::f64(), 1.0),
521 gate_error_1q: 0.0015 * 0.2f64.mul_add(fastrand::f64(), 1.0),
522 readout_error: 0.03 * 0.2f64.mul_add(fastrand::f64(), 1.0),
523 frequency: 0.1f64.mul_add(fastrand::f64(), 5.5),
524 });
525 }
526
527 let grid_conns = vec![
529 (0, 1),
530 (1, 2),
531 (3, 4),
532 (4, 5),
533 (6, 7),
534 (7, 8), (0, 3),
536 (3, 6),
537 (1, 4),
538 (4, 7),
539 (2, 5),
540 (5, 8), ];
542
543 for (q1, q2) in grid_conns {
544 topo.add_connection(
545 q1,
546 q2,
547 GateProperties {
548 error_rate: 0.006 * 0.3f64.mul_add(fastrand::f64(), 1.0),
549 duration: 12.0,
550 gate_type: "sqrt_ISwap".to_string(),
551 },
552 );
553 }
554
555 let diag_conns = vec![(0, 4), (4, 8), (2, 4), (4, 6)];
557
558 for (q1, q2) in diag_conns {
559 topo.add_connection(
560 q1,
561 q2,
562 GateProperties {
563 error_rate: 0.008 * 0.3f64.mul_add(fastrand::f64(), 1.0),
564 duration: 12.0,
565 gate_type: "sqrt_ISwap".to_string(),
566 },
567 );
568 }
569
570 topo
571 }
572
573 pub const fn num_qubits(&self) -> usize {
575 self.num_qubits
576 }
577
578 pub fn connectivity(&self) -> Vec<(usize, usize)> {
580 let mut connections = Vec::new();
581 for edge in self.connectivity.edge_indices() {
582 if let Some((a, b)) = self.connectivity.edge_endpoints(edge) {
583 let q1 = self.connectivity[a] as usize;
584 let q2 = self.connectivity[b] as usize;
585 connections.push((q1, q2));
586 }
587 }
588 connections
589 }
590
591 pub fn are_connected(&self, q1: usize, q2: usize) -> bool {
593 let node1 = self
595 .connectivity
596 .node_indices()
597 .find(|&n| self.connectivity[n] == q1 as u32);
598 let node2 = self
599 .connectivity
600 .node_indices()
601 .find(|&n| self.connectivity[n] == q2 as u32);
602
603 if let (Some(n1), Some(n2)) = (node1, node2) {
604 self.connectivity.contains_edge(n1, n2)
605 } else {
606 false
607 }
608 }
609
610 pub fn shortest_path_distance(&self, q1: usize, q2: usize) -> Option<f64> {
612 let node1 = self
614 .connectivity
615 .node_indices()
616 .find(|&n| self.connectivity[n] == q1 as u32)?;
617 let node2 = self
618 .connectivity
619 .node_indices()
620 .find(|&n| self.connectivity[n] == q2 as u32)?;
621
622 let distances = dijkstra(&self.connectivity, node1, Some(node2), |e| *e.weight());
624 distances.get(&node2).copied()
625 }
626
627 pub fn analyze_connectivity(&self) -> ConnectivityAnalysis {
629 let num_edges = self.connectivity.edge_count();
630 let mut min_conn = usize::MAX;
631 let mut max_conn = 0;
632 let mut total_conn = 0;
633
634 for node in self.connectivity.node_indices() {
635 let degree = self.connectivity.edges(node).count();
636 min_conn = min_conn.min(degree);
637 max_conn = max_conn.max(degree);
638 total_conn += degree;
639 }
640
641 let avg_conn = if self.num_qubits > 0 {
642 total_conn as f64 / self.num_qubits as f64
643 } else {
644 0.0
645 };
646
647 ConnectivityAnalysis {
648 num_qubits: self.num_qubits,
649 num_edges,
650 average_connectivity: avg_conn,
651 max_connectivity: max_conn,
652 min_connectivity: min_conn,
653 connected_components: connected_components(&self.connectivity),
654 }
655 }
656
657 pub fn analyze(&self) -> TopologyAnalysis {
659 let mut all_distances = vec![vec![None; self.num_qubits]; self.num_qubits];
661
662 for node in self.connectivity.node_indices() {
663 let distances = dijkstra(&self.connectivity, node, None, |e| *e.weight());
664 for (&target, &dist) in &distances {
665 let src_idx = self.connectivity[node] as usize;
666 let tgt_idx = self.connectivity[target] as usize;
667 all_distances[src_idx][tgt_idx] = Some(dist);
668 }
669 }
670
671 let mut total_dist = 0.0;
673 let mut count = 0;
674 for i in 0..self.num_qubits {
675 for j in i + 1..self.num_qubits {
676 if let Some(d) = all_distances[i][j] {
677 total_dist += d;
678 count += 1;
679 }
680 }
681 }
682 let avg_distance = if count > 0 {
683 total_dist / f64::from(count)
684 } else {
685 0.0
686 };
687
688 let mut degree_dist = HashMap::new();
690 for node in self.connectivity.node_indices() {
691 let degree = self.connectivity.edges(node).count();
692 *degree_dist.entry(degree).or_insert(0) += 1;
693 }
694
695 let most_connected = self
697 .connectivity
698 .node_indices()
699 .max_by_key(|&n| self.connectivity.edges(n).count())
700 .map(|n| self.connectivity[n]);
701
702 let clustering_coeff = self.calculate_clustering_coefficient();
704
705 let num_components = connected_components(&self.connectivity);
707
708 let mst = min_spanning_tree(&self.connectivity);
710 let mst_graph = UnGraph::<_, _>::from_elements(mst);
711 let mst_weight: f64 = mst_graph.edge_references().map(|e| *e.weight()).sum();
712
713 TopologyAnalysis {
714 num_qubits: self.num_qubits,
715 num_connections: self.connectivity.edge_count(),
716 average_distance: avg_distance,
717 max_distance: all_distances
718 .iter()
719 .flatten()
720 .filter_map(|&d| d)
721 .fold(0.0, f64::max),
722 degree_distribution: degree_dist,
723 most_connected_qubit: most_connected,
724 clustering_coefficient: clustering_coeff,
725 connected_components: num_components,
726 mst_weight,
727 avg_gate_error: self
728 .gate_properties
729 .values()
730 .map(|p| p.error_rate)
731 .sum::<f64>()
732 / self.gate_properties.len().max(1) as f64,
733 avg_coherence_time: self.qubit_properties.iter().map(|p| p.t2).sum::<f64>()
734 / self.qubit_properties.len().max(1) as f64,
735 }
736 }
737
738 fn calculate_clustering_coefficient(&self) -> f64 {
740 let mut total_coeff = 0.0;
741 let mut count = 0;
742
743 for node in self.connectivity.node_indices() {
744 let neighbors: Vec<_> = self.connectivity.neighbors(node).collect();
745 let k = neighbors.len();
746
747 if k >= 2 {
748 let mut triangles = 0;
749 for i in 0..k {
750 for j in i + 1..k {
751 if self.connectivity.contains_edge(neighbors[i], neighbors[j]) {
752 triangles += 1;
753 }
754 }
755 }
756
757 let possible = k * (k - 1) / 2;
758 total_coeff += f64::from(triangles) / possible as f64;
759 count += 1;
760 }
761 }
762
763 if count > 0 {
764 total_coeff / f64::from(count)
765 } else {
766 0.0
767 }
768 }
769
770 pub fn find_critical_qubits(&self) -> Vec<u32> {
772 let base_analysis = self.analyze();
773 let mut critical = Vec::new();
774
775 for node in self.connectivity.node_indices() {
776 let mut test_graph = self.connectivity.clone();
778 test_graph.remove_node(node);
779
780 if connected_components(&test_graph) > base_analysis.connected_components {
782 critical.push(self.connectivity[node]);
783 }
784 }
785
786 critical
787 }
788
789 pub fn find_optimal_subset(&self, required_qubits: usize) -> Result<Vec<u32>, TopologyError> {
791 if required_qubits > self.num_qubits {
792 return Err(TopologyError::InvalidConfiguration(format!(
793 "Required {} qubits but hardware has only {}",
794 required_qubits, self.num_qubits
795 )));
796 }
797
798 let score_subset = |subset: &[u32]| -> f64 {
800 let mut score = 0.0;
801 let mut connections = 0;
802
803 for &q in subset {
805 if let Some(props) = self.qubit_properties.get(q as usize) {
806 score += props.gate_error_1q + props.readout_error;
807 }
808 }
809
810 for i in 0..subset.len() {
812 for j in i + 1..subset.len() {
813 if let Some(props) = self
814 .gate_properties
815 .get(&(subset[i], subset[j]))
816 .or_else(|| self.gate_properties.get(&(subset[j], subset[i])))
817 {
818 score += props.error_rate;
819 connections += 1;
820 }
821 }
822 }
823
824 if connections < required_qubits - 1 {
826 score *= 10.0;
827 }
828
829 score
830 };
831
832 let mut best_subset = Vec::new();
834 let mut best_score = f64::INFINITY;
835
836 for start in 0..self.num_qubits.min(5) {
838 let mut subset = vec![start as u32];
839 let mut remaining: HashSet<_> = (0..self.num_qubits as u32).collect();
840 remaining.remove(&(start as u32));
841
842 while subset.len() < required_qubits {
843 let mut best_add = None;
845 let mut best_add_score = f64::INFINITY;
846
847 let mut found_connected = false;
849 for &candidate in &remaining {
850 let is_connected = subset.iter().any(|&q| {
852 self.gate_properties.contains_key(&(q, candidate))
853 || self.gate_properties.contains_key(&(candidate, q))
854 });
855
856 if is_connected {
857 found_connected = true;
858 let mut test_subset = subset.clone();
859 test_subset.push(candidate);
860 let score = score_subset(&test_subset);
861
862 if score < best_add_score {
863 best_add_score = score;
864 best_add = Some(candidate);
865 }
866 }
867 }
868
869 if !found_connected {
871 for &candidate in &remaining {
872 let mut test_subset = subset.clone();
873 test_subset.push(candidate);
874 let score = score_subset(&test_subset);
875
876 if score < best_add_score {
877 best_add_score = score;
878 best_add = Some(candidate);
879 }
880 }
881 }
882
883 if let Some(q) = best_add {
884 subset.push(q);
885 remaining.remove(&q);
886 } else {
887 break; }
889 }
890
891 let score = score_subset(&subset);
892 if score < best_score {
893 best_score = score;
894 best_subset = subset;
895 }
896 }
897
898 Ok(best_subset)
899 }
900}
901
902impl Default for HardwareTopology {
903 fn default() -> Self {
904 Self {
905 num_qubits: 0,
906 connectivity: UnGraph::new_undirected(),
907 qubit_properties: Vec::new(),
908 gate_properties: HashMap::new(),
909 }
910 }
911}
912
913#[derive(Debug)]
915pub struct ConnectivityAnalysis {
916 pub num_qubits: usize,
917 pub num_edges: usize,
918 pub average_connectivity: f64,
919 pub max_connectivity: usize,
920 pub min_connectivity: usize,
921 pub connected_components: usize,
922}
923
924#[derive(Debug)]
926pub struct TopologyAnalysis {
927 pub num_qubits: usize,
928 pub num_connections: usize,
929 pub average_distance: f64,
930 pub max_distance: f64,
931 pub degree_distribution: HashMap<usize, usize>,
932 pub most_connected_qubit: Option<u32>,
933 pub clustering_coefficient: f64,
934 pub connected_components: usize,
935 pub mst_weight: f64,
936 pub avg_gate_error: f64,
937 pub avg_coherence_time: f64,
938}
939
940impl TopologyAnalysis {
941 pub fn report(&self) -> String {
943 use std::fmt::Write;
944 let mut report = String::new();
945
946 report.push_str("=== Hardware Topology Analysis ===\n");
947 let _ = writeln!(report, "Number of qubits: {}", self.num_qubits);
948 let _ = writeln!(report, "Number of connections: {}", self.num_connections);
949 let _ = writeln!(
950 report,
951 "Connected components: {}",
952 self.connected_components
953 );
954 report.push_str("\nDistance metrics:\n");
955 let _ = writeln!(report, " Average distance: {:.2}", self.average_distance);
956 let _ = writeln!(report, " Maximum distance: {:.2}", self.max_distance);
957 report.push_str("\nConnectivity metrics:\n");
958 let _ = writeln!(
959 report,
960 " Clustering coefficient: {:.3}",
961 self.clustering_coefficient
962 );
963 let _ = writeln!(report, " MST weight: {:.3}", self.mst_weight);
964
965 if let Some(q) = self.most_connected_qubit {
966 let _ = writeln!(report, " Most connected qubit: {q}");
967 }
968
969 report.push_str("\nQuality metrics:\n");
970 let _ = writeln!(report, " Average gate error: {:.4}", self.avg_gate_error);
971 let _ = writeln!(
972 report,
973 " Average T2 time: {:.1} μs",
974 self.avg_coherence_time
975 );
976
977 report.push_str("\nDegree distribution:\n");
978 let mut degrees: Vec<_> = self.degree_distribution.iter().collect();
979 degrees.sort_by_key(|&(k, _)| k);
980 for (degree, count) in degrees {
981 let _ = writeln!(report, " {degree} connections: {count} qubits");
982 }
983
984 report
985 }
986}
987
988#[cfg(test)]
989mod tests {
990 use super::*;
991
992 #[test]
993 fn test_linear_topology() {
994 let topo = HardwareTopology::linear_topology(5);
995 let analysis = topo.analyze();
996
997 assert_eq!(analysis.num_qubits, 5);
998 assert_eq!(analysis.num_connections, 4);
999 assert_eq!(analysis.connected_components, 1);
1000 assert_eq!(analysis.max_distance, 4.0 * 0.01); }
1002
1003 #[test]
1004 fn test_grid_topology() {
1005 let topo = HardwareTopology::grid_topology(3, 3);
1006 let analysis = topo.analyze();
1007
1008 assert_eq!(analysis.num_qubits, 9);
1009 assert_eq!(analysis.connected_components, 1);
1010 assert!(analysis.average_distance > 0.0);
1011 }
1012
1013 #[test]
1014 fn test_ibm_topology() {
1015 let topo = HardwareTopology::ibm_topology();
1016 let analysis = topo.analyze();
1017
1018 assert_eq!(analysis.num_qubits, 5);
1019 assert_eq!(analysis.num_connections, 4);
1020 assert_eq!(analysis.most_connected_qubit, Some(1)); }
1022
1023 #[test]
1024 fn test_find_optimal_subset() {
1025 let topo = HardwareTopology::grid_topology(3, 3);
1026 let subset = topo
1027 .find_optimal_subset(4)
1028 .expect("should find valid subset for 3x3 grid");
1029
1030 assert_eq!(subset.len(), 4);
1031 let mut connected = false;
1033 for i in 0..subset.len() {
1034 for j in i + 1..subset.len() {
1035 if topo.gate_properties.contains_key(&(subset[i], subset[j]))
1036 || topo.gate_properties.contains_key(&(subset[j], subset[i]))
1037 {
1038 connected = true;
1039 break;
1040 }
1041 }
1042 if connected {
1043 break;
1044 }
1045 }
1046 assert!(connected, "Subset {subset:?} is not connected");
1047 }
1048}