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