quantrs2_device/
topology.rs

1//! Hardware topology analysis using `SciRS2` graph algorithms
2//!
3//! This module provides tools for analyzing quantum hardware topologies,
4//! finding optimal qubit mappings, and identifying connectivity constraints.
5
6use 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/// Error type for topology operations
13#[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/// Represents a quantum hardware topology
23#[derive(Debug, Clone)]
24pub struct HardwareTopology {
25    /// Number of physical qubits
26    pub num_qubits: usize,
27    /// Connectivity graph (undirected for most hardware)
28    pub connectivity: UnGraph<u32, f64>,
29    /// Qubit properties (T1, T2, gate errors, etc.)
30    pub qubit_properties: Vec<QubitProperties>,
31    /// Two-qubit gate properties (gate errors, gate times)
32    pub gate_properties: HashMap<(u32, u32), GateProperties>,
33}
34
35/// Properties of a physical qubit
36#[derive(Debug, Clone)]
37pub struct QubitProperties {
38    /// Qubit index
39    pub id: u32,
40    /// Qubit index (same as id, for compatibility)
41    pub index: u32,
42    /// T1 coherence time (microseconds)
43    pub t1: f64,
44    /// T2 coherence time (microseconds)
45    pub t2: f64,
46    /// Single-qubit gate error rate
47    pub single_qubit_gate_error: f64,
48    /// Single-qubit gate error rate (same as above, for compatibility)
49    pub gate_error_1q: f64,
50    /// Readout error rate
51    pub readout_error: f64,
52    /// Frequency (GHz)
53    pub frequency: f64,
54}
55
56/// Properties of a two-qubit gate between qubits
57#[derive(Debug, Clone)]
58pub struct GateProperties {
59    /// Gate error rate
60    pub error_rate: f64,
61    /// Gate duration (nanoseconds)
62    pub duration: f64,
63    /// Gate type (e.g., "CZ", "CNOT")
64    pub gate_type: String,
65}
66
67impl HardwareTopology {
68    /// Create a new hardware topology
69    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    /// Add a qubit with properties
79    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    /// Add a connection between two qubits
86    pub fn add_connection(&mut self, q1: u32, q2: u32, properties: GateProperties) {
87        // Find node indices
88        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            // Use error rate as edge weight (lower is better)
99            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    /// Load standard hardware topologies
106    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    /// Create a linear topology (chain of qubits)
119    pub fn linear_topology(n: usize) -> Self {
120        let mut topo = Self::new(n);
121
122        // Add qubits
123        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        // Add connections
137        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    /// Create a 2D grid topology
153    pub fn grid_topology(rows: usize, cols: usize) -> Self {
154        let n = rows * cols;
155        let mut topo = Self::new(n);
156
157        // Add qubits
158        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        // Add horizontal connections
172        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        // Add vertical connections
189        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    /// Create IBM 5-qubit topology (bow-tie shape)
209    pub fn ibm_topology() -> Self {
210        let mut topo = Self::new(5);
211
212        // Add qubits with realistic properties
213        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        // Add connections (bow-tie pattern)
235        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    /// Create Google Sycamore-like topology (subset)
253    pub fn google_topology() -> Self {
254        // Create a 6-qubit subset of Sycamore topology
255        let mut topo = Self::new(6);
256
257        // Add qubits
258        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        // Add connections in a 2x3 grid pattern with diagonals
272        let connections = vec![
273            (0, 1),
274            (1, 2), // Top row
275            (3, 4),
276            (4, 5), // Bottom row
277            (0, 3),
278            (1, 4),
279            (2, 5), // Vertical
280            (0, 4),
281            (1, 5), // Diagonals
282        ];
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    /// Create IBM Heavy-Hex topology
300    pub fn from_heavy_hex(num_qubits: usize) -> Self {
301        let mut topo = Self::new(num_qubits);
302
303        // Add qubits with IBM-like properties
304        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        // Heavy-hex pattern for IBM quantum computers
318        // This is a simplified version - real IBM topologies vary
319        if num_qubits >= 27 {
320            // Create a heavy-hex lattice pattern
321            // Row 0: 0, 1, 2, 3, 4
322            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            // Row 1: 5, 6, 7, 8, 9
335            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            // Row 2: 10, 11, 12, 13, 14
348            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            // Row 3: 15, 16, 17, 18, 19
361            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            // Row 4: 20, 21, 22, 23, 24, 25, 26
374            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            // Vertical connections (heavy edges)
387            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            // Diagonal connections (hex pattern)
419            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    /// Create Google Sycamore-like topology
440    pub fn from_sycamore(num_qubits: usize) -> Self {
441        let mut topo = Self::new(num_qubits);
442
443        // Add qubits with Google-like properties
444        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        // Sycamore has a 2D grid with couplers removed
458        // This is a simplified version
459        if num_qubits >= 20 {
460            // Create a 4x5 grid-like structure
461            let rows = 4;
462            let cols = 5;
463
464            // Horizontal connections
465            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            // Vertical connections (with some removed for Sycamore pattern)
484            for row in 0..rows - 1 {
485                for col in 0..cols {
486                    // Skip some connections to create Sycamore pattern
487                    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    /// Create a subset of Google Sycamore topology (3x3 grid with diagonal connections)
510    fn create_sycamore_subset() -> Self {
511        let mut topo = Self::new(9);
512
513        // Add qubits
514        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        // Add grid connections
528        let grid_conns = vec![
529            (0, 1),
530            (1, 2),
531            (3, 4),
532            (4, 5),
533            (6, 7),
534            (7, 8), // horizontal
535            (0, 3),
536            (3, 6),
537            (1, 4),
538            (4, 7),
539            (2, 5),
540            (5, 8), // vertical
541        ];
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        // Add some diagonal connections (Sycamore feature)
556        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    /// Get the number of qubits
574    pub const fn num_qubits(&self) -> usize {
575        self.num_qubits
576    }
577
578    /// Get all connected pairs of qubits
579    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    /// Check if two qubits are connected
592    pub fn are_connected(&self, q1: usize, q2: usize) -> bool {
593        // Find node indices for these qubits
594        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    /// Get shortest path distance between two qubits
611    pub fn shortest_path_distance(&self, q1: usize, q2: usize) -> Option<f64> {
612        // Find node indices for these qubits
613        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        // Use Dijkstra to find shortest path
623        let distances = dijkstra(&self.connectivity, node1, Some(node2), |e| *e.weight());
624        distances.get(&node2).copied()
625    }
626
627    /// Simple connectivity analysis
628    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    /// Analyze topology properties
658    pub fn analyze(&self) -> TopologyAnalysis {
659        // Calculate shortest paths between all pairs
660        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        // Calculate average distance
672        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        // Calculate degree distribution
689        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        // Find most connected qubit
696        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        // Calculate clustering coefficient
703        let clustering_coeff = self.calculate_clustering_coefficient();
704
705        // Count connected components
706        let num_components = connected_components(&self.connectivity);
707
708        // Find minimum spanning tree weight
709        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    /// Calculate clustering coefficient
739    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    /// Find critical qubits (removal increases distances significantly)
771    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            // Create topology without this qubit
777            let mut test_graph = self.connectivity.clone();
778            test_graph.remove_node(node);
779
780            // Check if it disconnects the graph
781            if connected_components(&test_graph) > base_analysis.connected_components {
782                critical.push(self.connectivity[node]);
783            }
784        }
785
786        critical
787    }
788
789    /// Find optimal qubit subset for a given circuit size
790    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        // Score function: minimize average error and maximize connectivity
799        let score_subset = |subset: &[u32]| -> f64 {
800            let mut score = 0.0;
801            let mut connections = 0;
802
803            // Add qubit quality scores
804            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            // Add connection scores
811            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            // Penalize low connectivity
825            if connections < required_qubits - 1 {
826                score *= 10.0;
827            }
828
829            score
830        };
831
832        // Use greedy algorithm to find good subset
833        let mut best_subset = Vec::new();
834        let mut best_score = f64::INFINITY;
835
836        // Try different starting points
837        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                // Find best qubit to add - prefer connected qubits
844                let mut best_add = None;
845                let mut best_add_score = f64::INFINITY;
846
847                // First, try to add only connected qubits
848                let mut found_connected = false;
849                for &candidate in &remaining {
850                    // Check if candidate is connected to any qubit in subset
851                    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 no connected qubit found, consider all remaining qubits
870                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; // No more qubits to add
888                }
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/// Simple connectivity analysis results
914#[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/// Analysis results for a hardware topology
925#[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    /// Generate a report of the analysis
942    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); // 4 hops with error 0.01 each
1001    }
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)); // Qubit 1 is central
1021    }
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        // Should pick connected qubits
1032        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}