quantrs2_device/
topology_analysis.rs

1//! Enhanced hardware topology analysis with SciRS2-style graph algorithms.
2//!
3//! This module provides advanced topology analysis capabilities including
4//! graph metrics, community detection, and optimal qubit allocation strategies.
5
6use crate::topology::{GateProperties, HardwareTopology, QubitProperties};
7use crate::DeviceResult;
8use petgraph::algo::{bellman_ford, floyd_warshall};
9use petgraph::graph::{NodeIndex, UnGraph};
10use petgraph::visit::EdgeRef;
11use std::collections::{HashMap, HashSet, VecDeque};
12
13/// Advanced topology analyzer with SciRS2-style algorithms
14pub struct TopologyAnalyzer {
15    /// The hardware topology to analyze
16    topology: HardwareTopology,
17    /// Cached distance matrix
18    distance_matrix: Option<Vec<Vec<f64>>>,
19    /// Cached betweenness centrality
20    betweenness: Option<HashMap<u32, f64>>,
21    /// Cached clustering coefficients
22    clustering: Option<HashMap<u32, f64>>,
23}
24
25/// Analysis results for hardware topology
26#[derive(Debug, Clone)]
27pub struct TopologyAnalysis {
28    /// Graph diameter (maximum shortest path)
29    pub diameter: usize,
30    /// Average shortest path length
31    pub average_path_length: f64,
32    /// Graph density
33    pub density: f64,
34    /// Number of connected components
35    pub components: usize,
36    /// Clustering coefficient
37    pub clustering_coefficient: f64,
38    /// Most central qubits (by betweenness)
39    pub central_qubits: Vec<u32>,
40    /// Peripheral qubits (far from center)
41    pub peripheral_qubits: Vec<u32>,
42    /// Qubit communities/clusters
43    pub communities: Vec<HashSet<u32>>,
44    /// Hardware-specific metrics
45    pub hardware_metrics: HardwareMetrics,
46}
47
48/// Hardware-specific performance metrics
49#[derive(Debug, Clone)]
50pub struct HardwareMetrics {
51    /// Average gate error rate
52    pub avg_gate_error: f64,
53    /// Average T1 time
54    pub avg_t1: f64,
55    /// Average T2 time
56    pub avg_t2: f64,
57    /// Qubit quality scores (0-1)
58    pub qubit_scores: HashMap<u32, f64>,
59    /// Connection quality scores
60    pub connection_scores: HashMap<(u32, u32), f64>,
61    /// Recommended qubits for critical operations
62    pub premium_qubits: Vec<u32>,
63}
64
65/// Qubit allocation strategy
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum AllocationStrategy {
68    /// Minimize average distance
69    MinimizeDistance,
70    /// Maximize qubit quality
71    MaximizeQuality,
72    /// Balance distance and quality
73    Balanced,
74    /// Use central qubits
75    CentralFirst,
76    /// Minimize crosstalk
77    MinimizeCrosstalk,
78}
79
80impl TopologyAnalyzer {
81    /// Create a new topology analyzer
82    pub const fn new(topology: HardwareTopology) -> Self {
83        Self {
84            topology,
85            distance_matrix: None,
86            betweenness: None,
87            clustering: None,
88        }
89    }
90
91    /// Perform comprehensive topology analysis
92    pub fn analyze(&mut self) -> DeviceResult<TopologyAnalysis> {
93        // Calculate distance matrix if not cached
94        if self.distance_matrix.is_none() {
95            self.calculate_distance_matrix();
96        }
97
98        // Calculate graph metrics
99        let diameter = self.calculate_diameter();
100        let avg_path_length = self.calculate_average_path_length();
101        let density = self.calculate_density();
102        let components = self.count_components();
103
104        // Calculate centrality measures
105        self.calculate_betweenness_centrality();
106        let central_qubits = self.find_central_qubits(5);
107        let peripheral_qubits = self.find_peripheral_qubits(5);
108
109        // Calculate clustering
110        self.calculate_clustering_coefficients();
111        let clustering_coefficient = self.global_clustering_coefficient();
112
113        // Find communities
114        let communities = self.detect_communities();
115
116        // Calculate hardware metrics
117        let hardware_metrics = self.calculate_hardware_metrics();
118
119        Ok(TopologyAnalysis {
120            diameter,
121            average_path_length: avg_path_length,
122            density,
123            components,
124            clustering_coefficient,
125            central_qubits,
126            peripheral_qubits,
127            communities,
128            hardware_metrics,
129        })
130    }
131
132    /// Calculate all-pairs shortest path distances
133    fn calculate_distance_matrix(&mut self) {
134        let n = self.topology.num_qubits;
135        let mut matrix = vec![vec![f64::INFINITY; n]; n];
136
137        // Initialize diagonal
138        for i in 0..n {
139            matrix[i][i] = 0.0;
140        }
141
142        // Initialize edges
143        for edge in self.topology.connectivity.edge_references() {
144            let (a, b) = (edge.source(), edge.target());
145            let q1_id = self.topology.connectivity[a];
146            let q2_id = self.topology.connectivity[b];
147
148            // Find the index in the qubit properties list
149            let q1 = self
150                .topology
151                .qubit_properties
152                .iter()
153                .position(|q| q.id == q1_id)
154                .unwrap_or(q1_id as usize);
155            let q2 = self
156                .topology
157                .qubit_properties
158                .iter()
159                .position(|q| q.id == q2_id)
160                .unwrap_or(q2_id as usize);
161
162            let weight = *edge.weight();
163
164            matrix[q1][q2] = weight;
165            matrix[q2][q1] = weight;
166        }
167
168        // Floyd-Warshall algorithm
169        for k in 0..n {
170            for i in 0..n {
171                for j in 0..n {
172                    if matrix[i][k] + matrix[k][j] < matrix[i][j] {
173                        matrix[i][j] = matrix[i][k] + matrix[k][j];
174                    }
175                }
176            }
177        }
178
179        self.distance_matrix = Some(matrix);
180    }
181
182    /// Calculate graph diameter
183    fn calculate_diameter(&self) -> usize {
184        // For graph diameter, we need hop count, not weighted distance
185        // Create an unweighted version of the graph
186        let n = self.topology.num_qubits;
187        let mut hop_matrix = vec![vec![usize::MAX; n]; n];
188
189        // Initialize diagonal
190        for i in 0..n {
191            hop_matrix[i][i] = 0;
192        }
193
194        // Initialize direct edges with hop count 1
195        for edge in self.topology.connectivity.edge_references() {
196            let (a, b) = (edge.source(), edge.target());
197            let q1_id = self.topology.connectivity[a];
198            let q2_id = self.topology.connectivity[b];
199
200            // Find the index in the qubit properties list
201            let q1 = self
202                .topology
203                .qubit_properties
204                .iter()
205                .position(|q| q.id == q1_id)
206                .unwrap_or(q1_id as usize);
207            let q2 = self
208                .topology
209                .qubit_properties
210                .iter()
211                .position(|q| q.id == q2_id)
212                .unwrap_or(q2_id as usize);
213
214            hop_matrix[q1][q2] = 1;
215            hop_matrix[q2][q1] = 1;
216        }
217
218        // Floyd-Warshall for hop counts
219        for k in 0..n {
220            for i in 0..n {
221                for j in 0..n {
222                    if hop_matrix[i][k] != usize::MAX && hop_matrix[k][j] != usize::MAX {
223                        let new_dist = hop_matrix[i][k] + hop_matrix[k][j];
224                        if new_dist < hop_matrix[i][j] {
225                            hop_matrix[i][j] = new_dist;
226                        }
227                    }
228                }
229            }
230        }
231
232        // Find maximum hop count
233        hop_matrix
234            .iter()
235            .flat_map(|row| row.iter())
236            .filter(|&&d| d != usize::MAX)
237            .copied()
238            .max()
239            .unwrap_or(0)
240    }
241
242    /// Calculate average shortest path length
243    fn calculate_average_path_length(&self) -> f64 {
244        let matrix = self.distance_matrix.as_ref().expect(
245            "distance_matrix must be calculated before calling calculate_average_path_length",
246        );
247        let n = self.topology.num_qubits;
248        let mut sum = 0.0;
249        let mut count = 0;
250
251        for i in 0..n {
252            for j in i + 1..n {
253                if matrix[i][j] != f64::INFINITY {
254                    sum += matrix[i][j];
255                    count += 1;
256                }
257            }
258        }
259
260        if count > 0 {
261            sum / count as f64
262        } else {
263            0.0
264        }
265    }
266
267    /// Calculate graph density
268    fn calculate_density(&self) -> f64 {
269        let n = self.topology.num_qubits as f64;
270        let m = self.topology.connectivity.edge_count() as f64;
271
272        if n > 1.0 {
273            2.0 * m / (n * (n - 1.0))
274        } else {
275            0.0
276        }
277    }
278
279    /// Count connected components
280    fn count_components(&self) -> usize {
281        use petgraph::algo::connected_components;
282        connected_components(&self.topology.connectivity)
283    }
284
285    /// Calculate betweenness centrality for all nodes
286    fn calculate_betweenness_centrality(&mut self) {
287        let n = self.topology.num_qubits;
288        let mut betweenness = HashMap::new();
289
290        // Initialize
291        for i in 0..n {
292            betweenness.insert(i as u32, 0.0);
293        }
294
295        // For each pair of nodes
296        for s in 0..n {
297            // Single-source shortest paths
298            let (distances, predecessors) = self.single_source_shortest_paths(s);
299
300            // Accumulate betweenness
301            let mut delta = vec![0.0; n];
302
303            // Process nodes in reverse topological order (by distance)
304            let mut nodes_by_distance: Vec<_> =
305                (0..n).filter(|&v| distances[v] != f64::INFINITY).collect();
306            nodes_by_distance.sort_by(|&a, &b| {
307                distances[b]
308                    .partial_cmp(&distances[a])
309                    .unwrap_or(std::cmp::Ordering::Equal)
310            });
311
312            for &w in &nodes_by_distance {
313                for &v in &predecessors[w] {
314                    let num_paths = if distances[v] + 1.0 == distances[w] {
315                        1.0
316                    } else {
317                        0.0
318                    };
319                    delta[v] += num_paths * (1.0 + delta[w]);
320                }
321
322                if w != s {
323                    if let Some(value) = betweenness.get_mut(&(w as u32)) {
324                        *value += delta[w];
325                    }
326                }
327            }
328        }
329
330        // Normalize
331        let norm = if n > 2 {
332            2.0 / ((n - 1) * (n - 2)) as f64
333        } else {
334            1.0
335        };
336        for value in betweenness.values_mut() {
337            *value *= norm;
338        }
339
340        self.betweenness = Some(betweenness);
341    }
342
343    /// Single-source shortest paths using BFS
344    fn single_source_shortest_paths(&self, source: usize) -> (Vec<f64>, Vec<Vec<usize>>) {
345        let n = self.topology.num_qubits;
346        let mut distances = vec![f64::INFINITY; n];
347        let mut predecessors = vec![Vec::new(); n];
348        let mut queue = VecDeque::new();
349
350        distances[source] = 0.0;
351        queue.push_back(source);
352
353        while let Some(u) = queue.pop_front() {
354            // Find node in graph
355            if let Some(node_u) = self
356                .topology
357                .connectivity
358                .node_indices()
359                .find(|&n| self.topology.connectivity[n] == u as u32)
360            {
361                // Check all neighbors
362                for neighbor in self.topology.connectivity.neighbors(node_u) {
363                    let v = self.topology.connectivity[neighbor] as usize;
364
365                    if distances[v] == f64::INFINITY {
366                        distances[v] = distances[u] + 1.0;
367                        predecessors[v].push(u);
368                        queue.push_back(v);
369                    } else if distances[v] == distances[u] + 1.0 {
370                        predecessors[v].push(u);
371                    }
372                }
373            }
374        }
375
376        (distances, predecessors)
377    }
378
379    /// Find most central qubits
380    fn find_central_qubits(&self, count: usize) -> Vec<u32> {
381        let betweenness = match self.betweenness.as_ref() {
382            Some(b) => b,
383            None => return Vec::new(),
384        };
385
386        let mut qubits: Vec<_> = betweenness.iter().map(|(&q, &b)| (q, b)).collect();
387
388        qubits.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
389        qubits.into_iter().take(count).map(|(q, _)| q).collect()
390    }
391
392    /// Find peripheral qubits
393    fn find_peripheral_qubits(&self, count: usize) -> Vec<u32> {
394        let betweenness = match self.betweenness.as_ref() {
395            Some(b) => b,
396            None => return Vec::new(),
397        };
398
399        let mut qubits: Vec<_> = betweenness.iter().map(|(&q, &b)| (q, b)).collect();
400
401        qubits.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
402        qubits.into_iter().take(count).map(|(q, _)| q).collect()
403    }
404
405    /// Calculate clustering coefficient for each node
406    fn calculate_clustering_coefficients(&mut self) {
407        let mut clustering = HashMap::new();
408
409        for node in self.topology.connectivity.node_indices() {
410            let qubit = self.topology.connectivity[node];
411            let neighbors: Vec<_> = self.topology.connectivity.neighbors(node).collect();
412
413            let k = neighbors.len();
414            if k < 2 {
415                clustering.insert(qubit, 0.0);
416                continue;
417            }
418
419            // Count edges between neighbors
420            let mut edges = 0;
421            for i in 0..neighbors.len() {
422                for j in i + 1..neighbors.len() {
423                    if self
424                        .topology
425                        .connectivity
426                        .contains_edge(neighbors[i], neighbors[j])
427                    {
428                        edges += 1;
429                    }
430                }
431            }
432
433            let coefficient = 2.0 * edges as f64 / (k * (k - 1)) as f64;
434            clustering.insert(qubit, coefficient);
435        }
436
437        self.clustering = Some(clustering);
438    }
439
440    /// Calculate global clustering coefficient
441    fn global_clustering_coefficient(&self) -> f64 {
442        let coefficients = match self.clustering.as_ref() {
443            Some(c) => c,
444            None => return 0.0,
445        };
446        if coefficients.is_empty() {
447            return 0.0;
448        }
449
450        coefficients.values().sum::<f64>() / coefficients.len() as f64
451    }
452
453    /// Detect communities using spectral clustering
454    fn detect_communities(&self) -> Vec<HashSet<u32>> {
455        // Simple community detection using connected components
456        // In a real implementation, we'd use more sophisticated algorithms
457        use petgraph::algo::tarjan_scc;
458
459        let mut communities = Vec::new();
460        let sccs = tarjan_scc(&self.topology.connectivity);
461
462        for scc in sccs {
463            let community: HashSet<u32> = scc
464                .into_iter()
465                .map(|n| self.topology.connectivity[n])
466                .collect();
467            communities.push(community);
468        }
469
470        communities
471    }
472
473    /// Calculate hardware-specific metrics
474    fn calculate_hardware_metrics(&self) -> HardwareMetrics {
475        let mut total_gate_error = 0.0;
476        let mut gate_count = 0;
477
478        for props in self.topology.gate_properties.values() {
479            total_gate_error += props.error_rate;
480            gate_count += 1;
481        }
482
483        let avg_gate_error = if gate_count > 0 {
484            total_gate_error / gate_count as f64
485        } else {
486            0.0
487        };
488
489        // Calculate average coherence times
490        let avg_t1 = self
491            .topology
492            .qubit_properties
493            .iter()
494            .map(|q| q.t1)
495            .sum::<f64>()
496            / self.topology.qubit_properties.len() as f64;
497
498        let avg_t2 = self
499            .topology
500            .qubit_properties
501            .iter()
502            .map(|q| q.t2)
503            .sum::<f64>()
504            / self.topology.qubit_properties.len() as f64;
505
506        // Calculate qubit quality scores
507        let mut qubit_scores = HashMap::new();
508        for qubit in &self.topology.qubit_properties {
509            let score = self.calculate_qubit_score(qubit);
510            qubit_scores.insert(qubit.id, score);
511        }
512
513        // Calculate connection quality scores
514        let mut connection_scores = HashMap::new();
515        for ((q1, q2), props) in &self.topology.gate_properties {
516            let score = 1.0 - props.error_rate; // Simple scoring
517            connection_scores.insert((*q1, *q2), score);
518        }
519
520        // Find premium qubits (top 20%)
521        let mut sorted_qubits: Vec<_> = qubit_scores
522            .iter()
523            .map(|(&id, &score)| (id, score))
524            .collect();
525        sorted_qubits.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
526
527        let premium_count = (sorted_qubits.len() as f64 * 0.2).ceil() as usize;
528        let premium_qubits = sorted_qubits
529            .into_iter()
530            .take(premium_count)
531            .map(|(id, _)| id)
532            .collect();
533
534        HardwareMetrics {
535            avg_gate_error,
536            avg_t1,
537            avg_t2,
538            qubit_scores,
539            connection_scores,
540            premium_qubits,
541        }
542    }
543
544    /// Calculate quality score for a qubit
545    fn calculate_qubit_score(&self, qubit: &QubitProperties) -> f64 {
546        // Weighted scoring based on various metrics
547        let t1_score = (qubit.t1 / 100.0).min(1.0); // Normalize to 100μs
548        let t2_score = (qubit.t2 / 100.0).min(1.0);
549        let gate_score = 1.0 - qubit.single_qubit_gate_error;
550        let readout_score = 1.0 - qubit.readout_error;
551
552        // Weighted average
553        0.2_f64.mul_add(
554            readout_score,
555            0.2_f64.mul_add(gate_score, 0.3_f64.mul_add(t1_score, 0.3 * t2_score)),
556        )
557    }
558
559    /// Find optimal qubit allocation for a given strategy
560    pub fn allocate_qubits(
561        &mut self,
562        num_logical_qubits: usize,
563        strategy: AllocationStrategy,
564    ) -> DeviceResult<Vec<u32>> {
565        if num_logical_qubits > self.topology.num_qubits {
566            return Err(crate::DeviceError::InsufficientQubits {
567                required: num_logical_qubits,
568                available: self.topology.num_qubits,
569            });
570        }
571
572        // Ensure analysis is performed
573        let analysis = self.analyze()?;
574
575        match strategy {
576            AllocationStrategy::MinimizeDistance => {
577                self.allocate_minimize_distance(num_logical_qubits)
578            }
579            AllocationStrategy::MaximizeQuality => {
580                self.allocate_maximize_quality(num_logical_qubits, &analysis)
581            }
582            AllocationStrategy::Balanced => self.allocate_balanced(num_logical_qubits, &analysis),
583            AllocationStrategy::CentralFirst => Ok(analysis
584                .central_qubits
585                .into_iter()
586                .take(num_logical_qubits)
587                .collect()),
588            AllocationStrategy::MinimizeCrosstalk => {
589                self.allocate_minimize_crosstalk(num_logical_qubits)
590            }
591        }
592    }
593
594    /// Allocate qubits to minimize average distance
595    fn allocate_minimize_distance(&self, num_qubits: usize) -> DeviceResult<Vec<u32>> {
596        // Find connected subgraph with minimum average distance
597        let matrix = self.distance_matrix.as_ref().ok_or_else(|| {
598            crate::DeviceError::DeviceNotInitialized("Distance matrix not calculated".to_string())
599        })?;
600        let n = self.topology.num_qubits;
601
602        let mut best_allocation = Vec::new();
603        let mut best_score = f64::INFINITY;
604
605        // Try different starting points
606        for start in 0..n {
607            let mut allocation = vec![start as u32];
608            let mut remaining: HashSet<_> = (0..n).filter(|&i| i != start).collect();
609
610            // Greedy selection
611            while allocation.len() < num_qubits && !remaining.is_empty() {
612                let mut best_next = None;
613                let mut best_dist = f64::INFINITY;
614
615                for &candidate in &remaining {
616                    let total_dist: f64 = allocation
617                        .iter()
618                        .map(|&q| matrix[q as usize][candidate])
619                        .sum();
620
621                    if total_dist < best_dist {
622                        best_dist = total_dist;
623                        best_next = Some(candidate);
624                    }
625                }
626
627                if let Some(next) = best_next {
628                    allocation.push(next as u32);
629                    remaining.remove(&next);
630                }
631            }
632
633            // Calculate average distance
634            let mut total_dist = 0.0;
635            let mut count = 0;
636            for i in 0..allocation.len() {
637                for j in i + 1..allocation.len() {
638                    total_dist += matrix[allocation[i] as usize][allocation[j] as usize];
639                    count += 1;
640                }
641            }
642
643            let avg_dist = if count > 0 {
644                total_dist / count as f64
645            } else {
646                0.0
647            };
648
649            if avg_dist < best_score {
650                best_score = avg_dist;
651                best_allocation = allocation;
652            }
653        }
654
655        Ok(best_allocation)
656    }
657
658    /// Allocate qubits to maximize quality
659    fn allocate_maximize_quality(
660        &self,
661        num_qubits: usize,
662        analysis: &TopologyAnalysis,
663    ) -> DeviceResult<Vec<u32>> {
664        // Simply take the highest quality qubits
665        let mut qubits: Vec<_> = analysis
666            .hardware_metrics
667            .qubit_scores
668            .iter()
669            .map(|(&id, &score)| (id, score))
670            .collect();
671
672        qubits.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
673
674        Ok(qubits
675            .into_iter()
676            .take(num_qubits)
677            .map(|(id, _)| id)
678            .collect())
679    }
680
681    /// Allocate qubits with balanced strategy
682    fn allocate_balanced(
683        &self,
684        num_qubits: usize,
685        analysis: &TopologyAnalysis,
686    ) -> DeviceResult<Vec<u32>> {
687        // Score qubits by quality and centrality
688        let mut scores = HashMap::new();
689
690        let betweenness = self.betweenness.as_ref();
691        for (&qubit, &quality) in &analysis.hardware_metrics.qubit_scores {
692            let centrality = betweenness
693                .and_then(|b| b.get(&qubit))
694                .copied()
695                .unwrap_or(0.0);
696            // Balanced scoring
697            let score = 0.6f64.mul_add(quality, 0.4 * centrality);
698            scores.insert(qubit, score);
699        }
700
701        let mut sorted: Vec<_> = scores.into_iter().collect();
702        sorted.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
703
704        Ok(sorted
705            .into_iter()
706            .take(num_qubits)
707            .map(|(id, _)| id)
708            .collect())
709    }
710
711    /// Allocate qubits to minimize crosstalk
712    fn allocate_minimize_crosstalk(&self, num_qubits: usize) -> DeviceResult<Vec<u32>> {
713        // Select qubits that are well-separated
714        let matrix = self.distance_matrix.as_ref().ok_or_else(|| {
715            crate::DeviceError::DeviceNotInitialized("Distance matrix not calculated".to_string())
716        })?;
717        let n = self.topology.num_qubits;
718
719        let mut allocation = Vec::new();
720        let mut remaining: HashSet<_> = (0..n).collect();
721
722        // Start with a random qubit
723        let start = 0;
724        allocation.push(start as u32);
725        remaining.remove(&start);
726
727        // Greedily select qubits that maximize minimum distance
728        while allocation.len() < num_qubits && !remaining.is_empty() {
729            let mut best_candidate = None;
730            let mut best_min_dist = 0.0;
731
732            for &candidate in &remaining {
733                let min_dist = allocation
734                    .iter()
735                    .map(|&q| matrix[q as usize][candidate])
736                    .fold(f64::INFINITY, f64::min);
737
738                if min_dist > best_min_dist {
739                    best_min_dist = min_dist;
740                    best_candidate = Some(candidate);
741                }
742            }
743
744            if let Some(candidate) = best_candidate {
745                allocation.push(candidate as u32);
746                remaining.remove(&candidate);
747            }
748        }
749
750        Ok(allocation)
751    }
752
753    /// Get recommended paths between qubits
754    pub fn recommend_swap_paths(&self, source: u32, target: u32) -> Vec<Vec<u32>> {
755        // Find k shortest paths for SWAP operations
756        // Note: This doesn't use the distance matrix, it finds actual paths
757        let n = self.topology.num_qubits;
758
759        // Simple BFS for shortest paths
760        let mut paths = Vec::new();
761        let mut queue = VecDeque::new();
762        queue.push_back((vec![source], HashSet::new()));
763
764        while let Some((path, mut visited)) = queue.pop_front() {
765            let current = match path.last() {
766                Some(&c) => c,
767                None => continue,
768            };
769
770            if current == target {
771                paths.push(path);
772                if paths.len() >= 3 {
773                    // Return up to 3 paths
774                    break;
775                }
776                continue;
777            }
778
779            visited.insert(current);
780
781            // Find neighbors
782            if let Some(node) = self
783                .topology
784                .connectivity
785                .node_indices()
786                .find(|&n| self.topology.connectivity[n] == current)
787            {
788                for neighbor in self.topology.connectivity.neighbors(node) {
789                    let next = self.topology.connectivity[neighbor];
790                    if !visited.contains(&next) {
791                        let mut new_path = path.clone();
792                        new_path.push(next);
793                        queue.push_back((new_path, visited.clone()));
794                    }
795                }
796            }
797        }
798
799        paths
800    }
801}
802
803/// Create standard hardware topologies
804pub fn create_standard_topology(
805    topology_type: &str,
806    size: usize,
807) -> DeviceResult<HardwareTopology> {
808    match topology_type {
809        "linear" => create_linear_topology(size),
810        "grid" => create_grid_topology(size),
811        "heavy_hex" => create_heavy_hex_topology(size),
812        "star" => create_star_topology(size),
813        _ => Err(crate::DeviceError::UnsupportedDevice(format!(
814            "Unknown topology type: {topology_type}"
815        ))),
816    }
817}
818
819/// Create a linear (chain) topology
820fn create_linear_topology(size: usize) -> DeviceResult<HardwareTopology> {
821    let mut topology = HardwareTopology::new(size);
822
823    // Add qubits
824    for i in 0..size {
825        let props = QubitProperties {
826            id: i as u32,
827            index: i as u32,
828            t1: (i as f64).mul_add(2.0, 50.0), // Varying T1
829            t2: (i as f64).mul_add(1.5, 30.0), // Varying T2
830            single_qubit_gate_error: 0.001,
831            gate_error_1q: 0.001,
832            readout_error: 0.01,
833            frequency: (i as f64).mul_add(0.01, 5.0),
834        };
835        topology.add_qubit(props);
836    }
837
838    // Add connections
839    for i in 0..size - 1 {
840        let props = GateProperties {
841            error_rate: 0.01,
842            duration: 200.0,
843            gate_type: "CZ".to_string(),
844        };
845        topology.add_connection(i as u32, (i + 1) as u32, props);
846    }
847
848    Ok(topology)
849}
850
851/// Create a 2D grid topology
852fn create_grid_topology(size: usize) -> DeviceResult<HardwareTopology> {
853    let side = (size as f64).sqrt().ceil() as usize;
854    let mut topology = HardwareTopology::new(size);
855
856    // Add qubits
857    for i in 0..size {
858        let props = QubitProperties {
859            id: i as u32,
860            index: i as u32,
861            t1: (i as f64).mul_add(1.5, 40.0),
862            t2: (i as f64).mul_add(1.0, 25.0),
863            single_qubit_gate_error: 0.001,
864            gate_error_1q: 0.001,
865            readout_error: 0.01,
866            frequency: (i as f64).mul_add(0.01, 5.0),
867        };
868        topology.add_qubit(props);
869    }
870
871    // Add grid connections
872    for row in 0..side {
873        for col in 0..side {
874            let idx = row * side + col;
875            if idx >= size {
876                break;
877            }
878
879            // Connect to right neighbor
880            if col + 1 < side && idx + 1 < size {
881                let props = GateProperties {
882                    error_rate: 0.01,
883                    duration: 200.0,
884                    gate_type: "CZ".to_string(),
885                };
886                topology.add_connection(idx as u32, (idx + 1) as u32, props);
887            }
888
889            // Connect to bottom neighbor
890            if row + 1 < side && idx + side < size {
891                let props = GateProperties {
892                    error_rate: 0.01,
893                    duration: 200.0,
894                    gate_type: "CZ".to_string(),
895                };
896                topology.add_connection(idx as u32, (idx + side) as u32, props);
897            }
898        }
899    }
900
901    Ok(topology)
902}
903
904/// Create a heavy-hexagon topology (IBM-style)
905fn create_heavy_hex_topology(size: usize) -> DeviceResult<HardwareTopology> {
906    // Simplified heavy-hex for demonstration
907    let mut topology = HardwareTopology::new(size);
908
909    // Add qubits with varying quality
910    for i in 0..size {
911        let props = QubitProperties {
912            id: i as u32,
913            index: i as u32,
914            t1: (i as f64).mul_add(3.0, 30.0),
915            t2: (i as f64).mul_add(2.0, 20.0),
916            single_qubit_gate_error: (i as f64).mul_add(0.0001, 0.001),
917            gate_error_1q: (i as f64).mul_add(0.0001, 0.001),
918            readout_error: (i as f64).mul_add(0.001, 0.01),
919            frequency: (i as f64).mul_add(0.01, 5.0),
920        };
921        topology.add_qubit(props);
922    }
923
924    // Create hexagonal connections (simplified)
925    for i in 0..size {
926        // Connect in a pattern that creates hexagons
927        let connections = match i % 6 {
928            0 => vec![1, 5],
929            1 => vec![0, 2],
930            2 => vec![1, 3],
931            3 => vec![2, 4],
932            4 => vec![3, 5],
933            5 => vec![4, 0],
934            _ => vec![],
935        };
936
937        for &j in &connections {
938            if i < j && j < size {
939                let props = GateProperties {
940                    error_rate: ((i + j) as f64).mul_add(0.0001, 0.01),
941                    duration: 200.0,
942                    gate_type: "CZ".to_string(),
943                };
944                topology.add_connection(i as u32, j as u32, props);
945            }
946        }
947    }
948
949    Ok(topology)
950}
951
952/// Create a star topology (one central node connected to all others)
953fn create_star_topology(size: usize) -> DeviceResult<HardwareTopology> {
954    if size < 2 {
955        return Err(crate::DeviceError::UnsupportedDevice(
956            "Star topology requires at least 2 qubits".to_string(),
957        ));
958    }
959
960    let mut topology = HardwareTopology::new(size);
961
962    // Add qubits
963    for i in 0..size {
964        let props = QubitProperties {
965            id: i as u32,
966            index: i as u32,
967            t1: (i as f64).mul_add(2.0, 50.0),
968            t2: (i as f64).mul_add(1.5, 30.0),
969            single_qubit_gate_error: 0.001,
970            gate_error_1q: 0.001,
971            readout_error: 0.01,
972            frequency: (i as f64).mul_add(0.01, 5.0),
973        };
974        topology.add_qubit(props);
975    }
976
977    // Connect center node (qubit 0) to all other nodes
978    for i in 1..size {
979        let props = GateProperties {
980            error_rate: 0.01,
981            duration: 200.0,
982            gate_type: "CZ".to_string(),
983        };
984        topology.add_connection(0, i as u32, props);
985    }
986
987    Ok(topology)
988}
989
990#[cfg(test)]
991mod tests {
992    use super::*;
993
994    #[test]
995    fn test_linear_topology_analysis() {
996        let topology = create_linear_topology(5).expect("Failed to create linear topology");
997        let mut analyzer = TopologyAnalyzer::new(topology);
998        let analysis = analyzer.analyze().expect("Failed to analyze topology");
999
1000        assert_eq!(analysis.diameter, 4); // Linear chain of 5 qubits
1001        assert_eq!(analysis.components, 1); // Fully connected
1002        assert!(analysis.density < 0.5); // Sparse connectivity
1003    }
1004
1005    #[test]
1006    fn test_grid_topology_analysis() {
1007        let topology = create_grid_topology(9).expect("Failed to create grid topology"); // 3x3 grid
1008        let mut analyzer = TopologyAnalyzer::new(topology);
1009        let analysis = analyzer.analyze().expect("Failed to analyze topology");
1010
1011        assert_eq!(analysis.components, 1);
1012        assert!(analysis.diameter <= 4); // Maximum distance in 3x3 grid
1013                                         // Grid topology has no triangles, so clustering coefficient is 0
1014        assert_eq!(analysis.clustering_coefficient, 0.0);
1015    }
1016
1017    #[test]
1018    fn test_qubit_allocation() {
1019        let topology = create_grid_topology(9).expect("Failed to create grid topology");
1020        let mut analyzer = TopologyAnalyzer::new(topology);
1021
1022        // Test different allocation strategies
1023        let alloc1 = analyzer
1024            .allocate_qubits(4, AllocationStrategy::MinimizeDistance)
1025            .expect("Failed to allocate qubits with MinimizeDistance strategy");
1026        assert_eq!(alloc1.len(), 4);
1027
1028        let alloc2 = analyzer
1029            .allocate_qubits(4, AllocationStrategy::MaximizeQuality)
1030            .expect("Failed to allocate qubits with MaximizeQuality strategy");
1031        assert_eq!(alloc2.len(), 4);
1032
1033        let alloc3 = analyzer
1034            .allocate_qubits(4, AllocationStrategy::CentralFirst)
1035            .expect("Failed to allocate qubits with CentralFirst strategy");
1036        assert_eq!(alloc3.len(), 4);
1037    }
1038
1039    #[test]
1040    fn test_swap_paths() {
1041        let topology = create_linear_topology(5).expect("Failed to create linear topology");
1042        let analyzer = TopologyAnalyzer::new(topology);
1043
1044        let paths = analyzer.recommend_swap_paths(0, 4);
1045        assert!(!paths.is_empty());
1046        assert_eq!(paths[0].len(), 5); // 0 -> 1 -> 2 -> 3 -> 4
1047    }
1048}