use crate::topology::{GateProperties, HardwareTopology, QubitProperties};
use crate::DeviceResult;
use petgraph::algo::{bellman_ford, floyd_warshall};
use petgraph::graph::{NodeIndex, UnGraph};
use petgraph::visit::EdgeRef;
use std::collections::{HashMap, HashSet, VecDeque};
pub struct TopologyAnalyzer {
topology: HardwareTopology,
distance_matrix: Option<Vec<Vec<f64>>>,
betweenness: Option<HashMap<u32, f64>>,
clustering: Option<HashMap<u32, f64>>,
}
#[derive(Debug, Clone)]
pub struct TopologyAnalysis {
pub diameter: usize,
pub average_path_length: f64,
pub density: f64,
pub components: usize,
pub clustering_coefficient: f64,
pub central_qubits: Vec<u32>,
pub peripheral_qubits: Vec<u32>,
pub communities: Vec<HashSet<u32>>,
pub hardware_metrics: HardwareMetrics,
}
#[derive(Debug, Clone)]
pub struct HardwareMetrics {
pub avg_gate_error: f64,
pub avg_t1: f64,
pub avg_t2: f64,
pub qubit_scores: HashMap<u32, f64>,
pub connection_scores: HashMap<(u32, u32), f64>,
pub premium_qubits: Vec<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AllocationStrategy {
MinimizeDistance,
MaximizeQuality,
Balanced,
CentralFirst,
MinimizeCrosstalk,
}
impl TopologyAnalyzer {
pub const fn new(topology: HardwareTopology) -> Self {
Self {
topology,
distance_matrix: None,
betweenness: None,
clustering: None,
}
}
pub fn analyze(&mut self) -> DeviceResult<TopologyAnalysis> {
if self.distance_matrix.is_none() {
self.calculate_distance_matrix();
}
let diameter = self.calculate_diameter();
let avg_path_length = self.calculate_average_path_length();
let density = self.calculate_density();
let components = self.count_components();
self.calculate_betweenness_centrality();
let central_qubits = self.find_central_qubits(5);
let peripheral_qubits = self.find_peripheral_qubits(5);
self.calculate_clustering_coefficients();
let clustering_coefficient = self.global_clustering_coefficient();
let communities = self.detect_communities();
let hardware_metrics = self.calculate_hardware_metrics();
Ok(TopologyAnalysis {
diameter,
average_path_length: avg_path_length,
density,
components,
clustering_coefficient,
central_qubits,
peripheral_qubits,
communities,
hardware_metrics,
})
}
fn calculate_distance_matrix(&mut self) {
let n = self.topology.num_qubits;
let mut matrix = vec![vec![f64::INFINITY; n]; n];
for i in 0..n {
matrix[i][i] = 0.0;
}
for edge in self.topology.connectivity.edge_references() {
let (a, b) = (edge.source(), edge.target());
let q1_id = self.topology.connectivity[a];
let q2_id = self.topology.connectivity[b];
let q1 = self
.topology
.qubit_properties
.iter()
.position(|q| q.id == q1_id)
.unwrap_or(q1_id as usize);
let q2 = self
.topology
.qubit_properties
.iter()
.position(|q| q.id == q2_id)
.unwrap_or(q2_id as usize);
let weight = *edge.weight();
matrix[q1][q2] = weight;
matrix[q2][q1] = weight;
}
for k in 0..n {
for i in 0..n {
for j in 0..n {
if matrix[i][k] + matrix[k][j] < matrix[i][j] {
matrix[i][j] = matrix[i][k] + matrix[k][j];
}
}
}
}
self.distance_matrix = Some(matrix);
}
fn calculate_diameter(&self) -> usize {
let n = self.topology.num_qubits;
let mut hop_matrix = vec![vec![usize::MAX; n]; n];
for i in 0..n {
hop_matrix[i][i] = 0;
}
for edge in self.topology.connectivity.edge_references() {
let (a, b) = (edge.source(), edge.target());
let q1_id = self.topology.connectivity[a];
let q2_id = self.topology.connectivity[b];
let q1 = self
.topology
.qubit_properties
.iter()
.position(|q| q.id == q1_id)
.unwrap_or(q1_id as usize);
let q2 = self
.topology
.qubit_properties
.iter()
.position(|q| q.id == q2_id)
.unwrap_or(q2_id as usize);
hop_matrix[q1][q2] = 1;
hop_matrix[q2][q1] = 1;
}
for k in 0..n {
for i in 0..n {
for j in 0..n {
if hop_matrix[i][k] != usize::MAX && hop_matrix[k][j] != usize::MAX {
let new_dist = hop_matrix[i][k] + hop_matrix[k][j];
if new_dist < hop_matrix[i][j] {
hop_matrix[i][j] = new_dist;
}
}
}
}
}
hop_matrix
.iter()
.flat_map(|row| row.iter())
.filter(|&&d| d != usize::MAX)
.copied()
.max()
.unwrap_or(0)
}
fn calculate_average_path_length(&self) -> f64 {
let matrix = self.distance_matrix.as_ref().expect(
"distance_matrix must be calculated before calling calculate_average_path_length",
);
let n = self.topology.num_qubits;
let mut sum = 0.0;
let mut count = 0;
for i in 0..n {
for j in i + 1..n {
if matrix[i][j] != f64::INFINITY {
sum += matrix[i][j];
count += 1;
}
}
}
if count > 0 {
sum / count as f64
} else {
0.0
}
}
fn calculate_density(&self) -> f64 {
let n = self.topology.num_qubits as f64;
let m = self.topology.connectivity.edge_count() as f64;
if n > 1.0 {
2.0 * m / (n * (n - 1.0))
} else {
0.0
}
}
fn count_components(&self) -> usize {
use petgraph::algo::connected_components;
connected_components(&self.topology.connectivity)
}
fn calculate_betweenness_centrality(&mut self) {
let n = self.topology.num_qubits;
let mut betweenness = HashMap::new();
for i in 0..n {
betweenness.insert(i as u32, 0.0);
}
for s in 0..n {
let (distances, predecessors) = self.single_source_shortest_paths(s);
let mut delta = vec![0.0; n];
let mut nodes_by_distance: Vec<_> =
(0..n).filter(|&v| distances[v] != f64::INFINITY).collect();
nodes_by_distance.sort_by(|&a, &b| {
distances[b]
.partial_cmp(&distances[a])
.unwrap_or(std::cmp::Ordering::Equal)
});
for &w in &nodes_by_distance {
for &v in &predecessors[w] {
let num_paths = if distances[v] + 1.0 == distances[w] {
1.0
} else {
0.0
};
delta[v] += num_paths * (1.0 + delta[w]);
}
if w != s {
if let Some(value) = betweenness.get_mut(&(w as u32)) {
*value += delta[w];
}
}
}
}
let norm = if n > 2 {
2.0 / ((n - 1) * (n - 2)) as f64
} else {
1.0
};
for value in betweenness.values_mut() {
*value *= norm;
}
self.betweenness = Some(betweenness);
}
fn single_source_shortest_paths(&self, source: usize) -> (Vec<f64>, Vec<Vec<usize>>) {
let n = self.topology.num_qubits;
let mut distances = vec![f64::INFINITY; n];
let mut predecessors = vec![Vec::new(); n];
let mut queue = VecDeque::new();
distances[source] = 0.0;
queue.push_back(source);
while let Some(u) = queue.pop_front() {
if let Some(node_u) = self
.topology
.connectivity
.node_indices()
.find(|&n| self.topology.connectivity[n] == u as u32)
{
for neighbor in self.topology.connectivity.neighbors(node_u) {
let v = self.topology.connectivity[neighbor] as usize;
if distances[v] == f64::INFINITY {
distances[v] = distances[u] + 1.0;
predecessors[v].push(u);
queue.push_back(v);
} else if distances[v] == distances[u] + 1.0 {
predecessors[v].push(u);
}
}
}
}
(distances, predecessors)
}
fn find_central_qubits(&self, count: usize) -> Vec<u32> {
let betweenness = match self.betweenness.as_ref() {
Some(b) => b,
None => return Vec::new(),
};
let mut qubits: Vec<_> = betweenness.iter().map(|(&q, &b)| (q, b)).collect();
qubits.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
qubits.into_iter().take(count).map(|(q, _)| q).collect()
}
fn find_peripheral_qubits(&self, count: usize) -> Vec<u32> {
let betweenness = match self.betweenness.as_ref() {
Some(b) => b,
None => return Vec::new(),
};
let mut qubits: Vec<_> = betweenness.iter().map(|(&q, &b)| (q, b)).collect();
qubits.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
qubits.into_iter().take(count).map(|(q, _)| q).collect()
}
fn calculate_clustering_coefficients(&mut self) {
let mut clustering = HashMap::new();
for node in self.topology.connectivity.node_indices() {
let qubit = self.topology.connectivity[node];
let neighbors: Vec<_> = self.topology.connectivity.neighbors(node).collect();
let k = neighbors.len();
if k < 2 {
clustering.insert(qubit, 0.0);
continue;
}
let mut edges = 0;
for i in 0..neighbors.len() {
for j in i + 1..neighbors.len() {
if self
.topology
.connectivity
.contains_edge(neighbors[i], neighbors[j])
{
edges += 1;
}
}
}
let coefficient = 2.0 * edges as f64 / (k * (k - 1)) as f64;
clustering.insert(qubit, coefficient);
}
self.clustering = Some(clustering);
}
fn global_clustering_coefficient(&self) -> f64 {
let coefficients = match self.clustering.as_ref() {
Some(c) => c,
None => return 0.0,
};
if coefficients.is_empty() {
return 0.0;
}
coefficients.values().sum::<f64>() / coefficients.len() as f64
}
fn detect_communities(&self) -> Vec<HashSet<u32>> {
use petgraph::algo::tarjan_scc;
let mut communities = Vec::new();
let sccs = tarjan_scc(&self.topology.connectivity);
for scc in sccs {
let community: HashSet<u32> = scc
.into_iter()
.map(|n| self.topology.connectivity[n])
.collect();
communities.push(community);
}
communities
}
fn calculate_hardware_metrics(&self) -> HardwareMetrics {
let mut total_gate_error = 0.0;
let mut gate_count = 0;
for props in self.topology.gate_properties.values() {
total_gate_error += props.error_rate;
gate_count += 1;
}
let avg_gate_error = if gate_count > 0 {
total_gate_error / gate_count as f64
} else {
0.0
};
let avg_t1 = self
.topology
.qubit_properties
.iter()
.map(|q| q.t1)
.sum::<f64>()
/ self.topology.qubit_properties.len() as f64;
let avg_t2 = self
.topology
.qubit_properties
.iter()
.map(|q| q.t2)
.sum::<f64>()
/ self.topology.qubit_properties.len() as f64;
let mut qubit_scores = HashMap::new();
for qubit in &self.topology.qubit_properties {
let score = self.calculate_qubit_score(qubit);
qubit_scores.insert(qubit.id, score);
}
let mut connection_scores = HashMap::new();
for ((q1, q2), props) in &self.topology.gate_properties {
let score = 1.0 - props.error_rate; connection_scores.insert((*q1, *q2), score);
}
let mut sorted_qubits: Vec<_> = qubit_scores
.iter()
.map(|(&id, &score)| (id, score))
.collect();
sorted_qubits.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
let premium_count = (sorted_qubits.len() as f64 * 0.2).ceil() as usize;
let premium_qubits = sorted_qubits
.into_iter()
.take(premium_count)
.map(|(id, _)| id)
.collect();
HardwareMetrics {
avg_gate_error,
avg_t1,
avg_t2,
qubit_scores,
connection_scores,
premium_qubits,
}
}
fn calculate_qubit_score(&self, qubit: &QubitProperties) -> f64 {
let t1_score = (qubit.t1 / 100.0).min(1.0); let t2_score = (qubit.t2 / 100.0).min(1.0);
let gate_score = 1.0 - qubit.single_qubit_gate_error;
let readout_score = 1.0 - qubit.readout_error;
0.2_f64.mul_add(
readout_score,
0.2_f64.mul_add(gate_score, 0.3_f64.mul_add(t1_score, 0.3 * t2_score)),
)
}
pub fn allocate_qubits(
&mut self,
num_logical_qubits: usize,
strategy: AllocationStrategy,
) -> DeviceResult<Vec<u32>> {
if num_logical_qubits > self.topology.num_qubits {
return Err(crate::DeviceError::InsufficientQubits {
required: num_logical_qubits,
available: self.topology.num_qubits,
});
}
let analysis = self.analyze()?;
match strategy {
AllocationStrategy::MinimizeDistance => {
self.allocate_minimize_distance(num_logical_qubits)
}
AllocationStrategy::MaximizeQuality => {
self.allocate_maximize_quality(num_logical_qubits, &analysis)
}
AllocationStrategy::Balanced => self.allocate_balanced(num_logical_qubits, &analysis),
AllocationStrategy::CentralFirst => Ok(analysis
.central_qubits
.into_iter()
.take(num_logical_qubits)
.collect()),
AllocationStrategy::MinimizeCrosstalk => {
self.allocate_minimize_crosstalk(num_logical_qubits)
}
}
}
fn allocate_minimize_distance(&self, num_qubits: usize) -> DeviceResult<Vec<u32>> {
let matrix = self.distance_matrix.as_ref().ok_or_else(|| {
crate::DeviceError::DeviceNotInitialized("Distance matrix not calculated".to_string())
})?;
let n = self.topology.num_qubits;
let mut best_allocation = Vec::new();
let mut best_score = f64::INFINITY;
for start in 0..n {
let mut allocation = vec![start as u32];
let mut remaining: HashSet<_> = (0..n).filter(|&i| i != start).collect();
while allocation.len() < num_qubits && !remaining.is_empty() {
let mut best_next = None;
let mut best_dist = f64::INFINITY;
for &candidate in &remaining {
let total_dist: f64 = allocation
.iter()
.map(|&q| matrix[q as usize][candidate])
.sum();
if total_dist < best_dist {
best_dist = total_dist;
best_next = Some(candidate);
}
}
if let Some(next) = best_next {
allocation.push(next as u32);
remaining.remove(&next);
}
}
let mut total_dist = 0.0;
let mut count = 0;
for i in 0..allocation.len() {
for j in i + 1..allocation.len() {
total_dist += matrix[allocation[i] as usize][allocation[j] as usize];
count += 1;
}
}
let avg_dist = if count > 0 {
total_dist / count as f64
} else {
0.0
};
if avg_dist < best_score {
best_score = avg_dist;
best_allocation = allocation;
}
}
Ok(best_allocation)
}
fn allocate_maximize_quality(
&self,
num_qubits: usize,
analysis: &TopologyAnalysis,
) -> DeviceResult<Vec<u32>> {
let mut qubits: Vec<_> = analysis
.hardware_metrics
.qubit_scores
.iter()
.map(|(&id, &score)| (id, score))
.collect();
qubits.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
Ok(qubits
.into_iter()
.take(num_qubits)
.map(|(id, _)| id)
.collect())
}
fn allocate_balanced(
&self,
num_qubits: usize,
analysis: &TopologyAnalysis,
) -> DeviceResult<Vec<u32>> {
let mut scores = HashMap::new();
let betweenness = self.betweenness.as_ref();
for (&qubit, &quality) in &analysis.hardware_metrics.qubit_scores {
let centrality = betweenness
.and_then(|b| b.get(&qubit))
.copied()
.unwrap_or(0.0);
let score = 0.6f64.mul_add(quality, 0.4 * centrality);
scores.insert(qubit, score);
}
let mut sorted: Vec<_> = scores.into_iter().collect();
sorted.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
Ok(sorted
.into_iter()
.take(num_qubits)
.map(|(id, _)| id)
.collect())
}
fn allocate_minimize_crosstalk(&self, num_qubits: usize) -> DeviceResult<Vec<u32>> {
let matrix = self.distance_matrix.as_ref().ok_or_else(|| {
crate::DeviceError::DeviceNotInitialized("Distance matrix not calculated".to_string())
})?;
let n = self.topology.num_qubits;
let mut allocation = Vec::new();
let mut remaining: HashSet<_> = (0..n).collect();
let start = 0;
allocation.push(start as u32);
remaining.remove(&start);
while allocation.len() < num_qubits && !remaining.is_empty() {
let mut best_candidate = None;
let mut best_min_dist = 0.0;
for &candidate in &remaining {
let min_dist = allocation
.iter()
.map(|&q| matrix[q as usize][candidate])
.fold(f64::INFINITY, f64::min);
if min_dist > best_min_dist {
best_min_dist = min_dist;
best_candidate = Some(candidate);
}
}
if let Some(candidate) = best_candidate {
allocation.push(candidate as u32);
remaining.remove(&candidate);
}
}
Ok(allocation)
}
pub fn recommend_swap_paths(&self, source: u32, target: u32) -> Vec<Vec<u32>> {
let n = self.topology.num_qubits;
let mut paths = Vec::new();
let mut queue = VecDeque::new();
queue.push_back((vec![source], HashSet::new()));
while let Some((path, mut visited)) = queue.pop_front() {
let current = match path.last() {
Some(&c) => c,
None => continue,
};
if current == target {
paths.push(path);
if paths.len() >= 3 {
break;
}
continue;
}
visited.insert(current);
if let Some(node) = self
.topology
.connectivity
.node_indices()
.find(|&n| self.topology.connectivity[n] == current)
{
for neighbor in self.topology.connectivity.neighbors(node) {
let next = self.topology.connectivity[neighbor];
if !visited.contains(&next) {
let mut new_path = path.clone();
new_path.push(next);
queue.push_back((new_path, visited.clone()));
}
}
}
}
paths
}
}
pub fn create_standard_topology(
topology_type: &str,
size: usize,
) -> DeviceResult<HardwareTopology> {
match topology_type {
"linear" => create_linear_topology(size),
"grid" => create_grid_topology(size),
"heavy_hex" => create_heavy_hex_topology(size),
"star" => create_star_topology(size),
_ => Err(crate::DeviceError::UnsupportedDevice(format!(
"Unknown topology type: {topology_type}"
))),
}
}
fn create_linear_topology(size: usize) -> DeviceResult<HardwareTopology> {
let mut topology = HardwareTopology::new(size);
for i in 0..size {
let props = QubitProperties {
id: i as u32,
index: i as u32,
t1: (i as f64).mul_add(2.0, 50.0), t2: (i as f64).mul_add(1.5, 30.0), single_qubit_gate_error: 0.001,
gate_error_1q: 0.001,
readout_error: 0.01,
frequency: (i as f64).mul_add(0.01, 5.0),
};
topology.add_qubit(props);
}
for i in 0..size - 1 {
let props = GateProperties {
error_rate: 0.01,
duration: 200.0,
gate_type: "CZ".to_string(),
};
topology.add_connection(i as u32, (i + 1) as u32, props);
}
Ok(topology)
}
fn create_grid_topology(size: usize) -> DeviceResult<HardwareTopology> {
let side = (size as f64).sqrt().ceil() as usize;
let mut topology = HardwareTopology::new(size);
for i in 0..size {
let props = QubitProperties {
id: i as u32,
index: i as u32,
t1: (i as f64).mul_add(1.5, 40.0),
t2: (i as f64).mul_add(1.0, 25.0),
single_qubit_gate_error: 0.001,
gate_error_1q: 0.001,
readout_error: 0.01,
frequency: (i as f64).mul_add(0.01, 5.0),
};
topology.add_qubit(props);
}
for row in 0..side {
for col in 0..side {
let idx = row * side + col;
if idx >= size {
break;
}
if col + 1 < side && idx + 1 < size {
let props = GateProperties {
error_rate: 0.01,
duration: 200.0,
gate_type: "CZ".to_string(),
};
topology.add_connection(idx as u32, (idx + 1) as u32, props);
}
if row + 1 < side && idx + side < size {
let props = GateProperties {
error_rate: 0.01,
duration: 200.0,
gate_type: "CZ".to_string(),
};
topology.add_connection(idx as u32, (idx + side) as u32, props);
}
}
}
Ok(topology)
}
fn create_heavy_hex_topology(size: usize) -> DeviceResult<HardwareTopology> {
let mut topology = HardwareTopology::new(size);
for i in 0..size {
let props = QubitProperties {
id: i as u32,
index: i as u32,
t1: (i as f64).mul_add(3.0, 30.0),
t2: (i as f64).mul_add(2.0, 20.0),
single_qubit_gate_error: (i as f64).mul_add(0.0001, 0.001),
gate_error_1q: (i as f64).mul_add(0.0001, 0.001),
readout_error: (i as f64).mul_add(0.001, 0.01),
frequency: (i as f64).mul_add(0.01, 5.0),
};
topology.add_qubit(props);
}
for i in 0..size {
let connections = match i % 6 {
0 => vec![1, 5],
1 => vec![0, 2],
2 => vec![1, 3],
3 => vec![2, 4],
4 => vec![3, 5],
5 => vec![4, 0],
_ => vec![],
};
for &j in &connections {
if i < j && j < size {
let props = GateProperties {
error_rate: ((i + j) as f64).mul_add(0.0001, 0.01),
duration: 200.0,
gate_type: "CZ".to_string(),
};
topology.add_connection(i as u32, j as u32, props);
}
}
}
Ok(topology)
}
fn create_star_topology(size: usize) -> DeviceResult<HardwareTopology> {
if size < 2 {
return Err(crate::DeviceError::UnsupportedDevice(
"Star topology requires at least 2 qubits".to_string(),
));
}
let mut topology = HardwareTopology::new(size);
for i in 0..size {
let props = QubitProperties {
id: i as u32,
index: i as u32,
t1: (i as f64).mul_add(2.0, 50.0),
t2: (i as f64).mul_add(1.5, 30.0),
single_qubit_gate_error: 0.001,
gate_error_1q: 0.001,
readout_error: 0.01,
frequency: (i as f64).mul_add(0.01, 5.0),
};
topology.add_qubit(props);
}
for i in 1..size {
let props = GateProperties {
error_rate: 0.01,
duration: 200.0,
gate_type: "CZ".to_string(),
};
topology.add_connection(0, i as u32, props);
}
Ok(topology)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_linear_topology_analysis() {
let topology = create_linear_topology(5).expect("Failed to create linear topology");
let mut analyzer = TopologyAnalyzer::new(topology);
let analysis = analyzer.analyze().expect("Failed to analyze topology");
assert_eq!(analysis.diameter, 4); assert_eq!(analysis.components, 1); assert!(analysis.density < 0.5); }
#[test]
fn test_grid_topology_analysis() {
let topology = create_grid_topology(9).expect("Failed to create grid topology"); let mut analyzer = TopologyAnalyzer::new(topology);
let analysis = analyzer.analyze().expect("Failed to analyze topology");
assert_eq!(analysis.components, 1);
assert!(analysis.diameter <= 4); assert_eq!(analysis.clustering_coefficient, 0.0);
}
#[test]
fn test_qubit_allocation() {
let topology = create_grid_topology(9).expect("Failed to create grid topology");
let mut analyzer = TopologyAnalyzer::new(topology);
let alloc1 = analyzer
.allocate_qubits(4, AllocationStrategy::MinimizeDistance)
.expect("Failed to allocate qubits with MinimizeDistance strategy");
assert_eq!(alloc1.len(), 4);
let alloc2 = analyzer
.allocate_qubits(4, AllocationStrategy::MaximizeQuality)
.expect("Failed to allocate qubits with MaximizeQuality strategy");
assert_eq!(alloc2.len(), 4);
let alloc3 = analyzer
.allocate_qubits(4, AllocationStrategy::CentralFirst)
.expect("Failed to allocate qubits with CentralFirst strategy");
assert_eq!(alloc3.len(), 4);
}
#[test]
fn test_swap_paths() {
let topology = create_linear_topology(5).expect("Failed to create linear topology");
let analyzer = TopologyAnalyzer::new(topology);
let paths = analyzer.recommend_swap_paths(0, 4);
assert!(!paths.is_empty());
assert_eq!(paths[0].len(), 5); }
}