use super::*;
#[cfg(feature = "scirs2")]
use scirs2_graph::planarity::is_planar;
#[cfg(feature = "scirs2")]
use scirs2_graph::spectral::{laplacian, LaplacianType};
#[cfg(feature = "scirs2")]
use scirs2_graph::{
chromatic_number as scirs2_chromatic_number, connected_components, is_bipartite, modularity,
pagerank_centrality,
};
pub struct SciRS2QubitMapper {
config: SciRS2MappingConfig,
device_topology: HardwareTopology,
calibration: Option<DeviceCalibration>,
logical_graph: Option<Graph<usize, f64>>,
physical_graph: Option<Graph<usize, f64>>,
spectral_cache: Option<SpectralAnalysisResult>,
community_cache: Option<CommunityAnalysisResult>,
centrality_cache: Option<CentralityAnalysisResult>,
}
impl SciRS2QubitMapper {
pub fn new(
config: SciRS2MappingConfig,
device_topology: HardwareTopology,
calibration: Option<DeviceCalibration>,
) -> Self {
Self {
config,
device_topology,
calibration,
logical_graph: None,
physical_graph: None,
spectral_cache: None,
community_cache: None,
centrality_cache: None,
}
}
#[cfg(feature = "scirs2")]
pub fn map_circuit<const N: usize>(
&mut self,
circuit: &Circuit<N>,
) -> DeviceResult<SciRS2MappingResult> {
let start_time = std::time::Instant::now();
let logical_graph = self.build_logical_graph(circuit)?;
self.logical_graph = None;
let physical_graph = self.build_physical_graph()?;
self.physical_graph = None;
let graph_analysis = self.analyze_graphs(&logical_graph, &physical_graph)?;
let spectral_analysis = if self.config.enable_spectral_analysis {
Some(self.perform_spectral_analysis(&logical_graph, &physical_graph)?)
} else {
None
};
let community_analysis =
self.perform_community_analysis(&logical_graph, &physical_graph)?;
let centrality_analysis = if self.config.enable_centrality_optimization {
self.perform_centrality_analysis(&logical_graph, &physical_graph)?
} else {
CentralityAnalysisResult {
betweenness_centrality: HashMap::new(),
closeness_centrality: HashMap::new(),
eigenvector_centrality: HashMap::new(),
pagerank_centrality: HashMap::new(),
centrality_correlations: Array2::zeros((0, 0)),
centrality_statistics: CentralityStatistics {
max_betweenness: 0.0,
max_closeness: 0.0,
max_eigenvector: 0.0,
max_pagerank: 0.0,
mean_betweenness: 0.0,
mean_closeness: 0.0,
mean_eigenvector: 0.0,
mean_pagerank: 0.0,
},
}
};
let initial_mapping = self.generate_initial_mapping(
&logical_graph,
&physical_graph,
&graph_analysis,
spectral_analysis.as_ref(),
&community_analysis,
¢rality_analysis,
)?;
let (final_mapping, swap_operations, optimization_metrics) = self.optimize_mapping(
circuit,
initial_mapping.clone(),
&logical_graph,
&physical_graph,
)?;
let performance_predictions = if self.config.enable_ml_predictions {
Some(self.predict_performance(&final_mapping, circuit, &graph_analysis)?)
} else {
None
};
let realtime_analytics = self.generate_realtime_analytics(&optimization_metrics)?;
let ml_performance = if self.config.ml_config.enable_ml {
Some(self.analyze_ml_performance(&final_mapping, &optimization_metrics)?)
} else {
None
};
let adaptive_insights = self.generate_adaptive_insights(&optimization_metrics)?;
let optimization_recommendations = self.generate_optimization_recommendations(
&graph_analysis,
&optimization_metrics,
spectral_analysis.as_ref(),
&community_analysis,
)?;
Ok(SciRS2MappingResult {
initial_mapping,
final_mapping,
swap_operations,
graph_analysis,
spectral_analysis,
community_analysis,
centrality_analysis,
optimization_metrics,
performance_predictions,
realtime_analytics,
ml_performance,
adaptive_insights,
optimization_recommendations,
})
}
#[cfg(not(feature = "scirs2"))]
pub fn map_circuit<const N: usize>(
&mut self,
circuit: &Circuit<N>,
) -> DeviceResult<SciRS2MappingResult> {
let mut initial_mapping = HashMap::new();
let mut final_mapping = HashMap::new();
for i in 0..N.min(self.device_topology.num_qubits()) {
initial_mapping.insert(i, i);
final_mapping.insert(i, i);
}
Ok(SciRS2MappingResult {
initial_mapping,
final_mapping,
swap_operations: Vec::new(),
graph_analysis: GraphAnalysisResult {
density: 0.5,
clustering_coefficient: 0.3,
diameter: 4,
radius: 2,
average_path_length: 2.5,
connectivity_stats: ConnectivityStats {
edge_connectivity: 2,
vertex_connectivity: 1,
algebraic_connectivity: 0.5,
is_connected: true,
num_components: 1,
largest_component_size: N,
},
topological_properties: TopologicalProperties {
is_planar: true,
is_bipartite: false,
is_tree: false,
is_forest: false,
has_cycles: true,
girth: 3,
chromatic_number: 3,
independence_number: 5,
},
},
spectral_analysis: None,
community_analysis: CommunityAnalysisResult {
communities: HashMap::new(),
modularity: 0.4,
num_communities: 1,
community_sizes: vec![N],
inter_community_edges: 0,
quality_metrics: CommunityQualityMetrics {
silhouette_score: 0.7,
conductance: 0.3,
coverage: 0.8,
performance: 0.75,
},
},
centrality_analysis: CentralityAnalysisResult {
betweenness_centrality: HashMap::new(),
closeness_centrality: HashMap::new(),
eigenvector_centrality: HashMap::new(),
pagerank_centrality: HashMap::new(),
centrality_correlations: Array2::zeros((0, 0)),
centrality_statistics: CentralityStatistics {
max_betweenness: 0.0,
max_closeness: 0.0,
max_eigenvector: 0.0,
max_pagerank: 0.0,
mean_betweenness: 0.0,
mean_closeness: 0.0,
mean_eigenvector: 0.0,
mean_pagerank: 0.0,
},
},
optimization_metrics: OptimizationMetrics {
optimization_time: Duration::from_millis(1),
iterations: 1,
converged: true,
final_objective: 0.0,
best_objective: 0.0,
improvement_ratio: 0.0,
constraint_violations: 0.0,
algorithm_metrics: HashMap::new(),
resource_usage: ResourceUsageMetrics {
peak_memory: 1024,
average_cpu: 1.0,
energy_consumption: None,
network_overhead: None,
},
},
performance_predictions: None,
realtime_analytics: RealtimeAnalyticsResult {
current_metrics: HashMap::new(),
performance_trends: HashMap::new(),
anomalies: Vec::new(),
resource_utilization: ResourceUtilization {
cpu_usage: 1.0,
memory_usage: 5.0,
disk_io: 0.0,
network_usage: 0.0,
gpu_usage: None,
},
quality_assessments: Vec::new(),
},
ml_performance: None,
adaptive_insights: AdaptiveMappingInsights {
learning_progress: HashMap::new(),
adaptation_effectiveness: HashMap::new(),
performance_trends: HashMap::new(),
recommended_adjustments: Vec::new(),
},
optimization_recommendations: OptimizationRecommendations {
algorithm_recommendations: Vec::new(),
parameter_suggestions: Vec::new(),
hardware_optimizations: Vec::new(),
improvement_predictions: HashMap::new(),
},
})
}
#[cfg(feature = "scirs2")]
fn build_logical_graph<const N: usize>(
&self,
circuit: &Circuit<N>,
) -> DeviceResult<Graph<usize, f64>> {
let mut graph = Graph::new();
let mut node_map: HashMap<usize, usize> = HashMap::new();
for i in 0..N {
let node = graph.add_node(i);
node_map.insert(i, node.index());
}
for gate in circuit.gates() {
let qubits = gate.qubits();
if qubits.len() == 2 {
let q1 = qubits[0].id() as usize;
let q2 = qubits[1].id() as usize;
if let (Some(&node1), Some(&node2)) = (node_map.get(&q1), node_map.get(&q2)) {
let weight = self.calculate_gate_weight(gate.as_ref());
let _ = graph.add_edge(node1, node2, weight);
}
}
}
Ok(graph)
}
#[cfg(feature = "scirs2")]
fn build_physical_graph(&self) -> DeviceResult<Graph<usize, f64>> {
let mut graph = Graph::new();
let mut node_map: HashMap<usize, usize> = HashMap::new();
for i in 0..self.device_topology.num_qubits() {
let node = graph.add_node(i);
node_map.insert(i, node.index());
}
for (q1, q2) in self.device_topology.connectivity() {
if let (Some(&node1), Some(&node2)) = (node_map.get(&q1), node_map.get(&q2)) {
let weight = self.get_connection_weight(q1, q2);
let _ = graph.add_edge(node1, node2, weight);
}
}
Ok(graph)
}
fn calculate_gate_weight(&self, _gate: &dyn GateOp) -> f64 {
1.0
}
fn get_connection_weight(&self, q1: usize, q2: usize) -> f64 {
if let Some(calibration) = &self.calibration {
calibration.gate_fidelity(q1, q2).unwrap_or(1.0)
} else {
1.0
}
}
fn calculate_objective<const N: usize>(
&self,
mapping: &HashMap<usize, usize>,
circuit: &Circuit<N>,
) -> DeviceResult<f64> {
let mut objective = 0.0;
match self.config.optimization_objective {
OptimizationObjective::MinimizeSwaps => {
for gate in circuit.gates() {
let qubits = gate.qubits();
if qubits.len() == 2 {
let logical_q1 = qubits[0].id() as usize;
let logical_q2 = qubits[1].id() as usize;
if let (Some(&physical_q1), Some(&physical_q2)) =
(mapping.get(&logical_q1), mapping.get(&logical_q2))
{
if !self.device_topology.are_connected(physical_q1, physical_q2) {
objective += 1.0;
}
}
}
}
}
OptimizationObjective::MinimizeDepth => {
objective = circuit.gates().len() as f64;
}
OptimizationObjective::MaximizeFidelity => {
if let Some(calibration) = &self.calibration {
let mut total_fidelity = 0.0;
let mut gate_count = 0;
for gate in circuit.gates() {
let qubits = gate.qubits();
if qubits.len() == 1 {
let q = qubits[0].id() as usize;
if let Some(&physical_q) = mapping.get(&q) {
total_fidelity += calibration
.single_qubit_fidelity(physical_q)
.unwrap_or(0.99);
gate_count += 1;
}
} else if qubits.len() == 2 {
let q1 = qubits[0].id() as usize;
let q2 = qubits[1].id() as usize;
if let (Some(&pq1), Some(&pq2)) = (mapping.get(&q1), mapping.get(&q2)) {
total_fidelity +=
calibration.gate_fidelity(pq1, pq2).unwrap_or(0.95);
gate_count += 1;
}
}
}
objective = -(total_fidelity / gate_count.max(1) as f64); } else {
objective = -0.95; }
}
_ => {
objective = 0.0;
}
}
Ok(objective)
}
#[cfg(feature = "scirs2")]
fn analyze_graphs(
&self,
_logical_graph: &Graph<usize, f64>,
physical_graph: &Graph<usize, f64>,
) -> DeviceResult<GraphAnalysisResult> {
let n = physical_graph.node_count();
let density = if n >= 2 {
graph_density(physical_graph).unwrap_or(0.0)
} else {
0.0
};
let clustering_coefficient = clustering_coefficient(physical_graph)
.map(|per_node| {
if per_node.is_empty() {
0.0
} else {
per_node.values().sum::<f64>() / per_node.len() as f64
}
})
.unwrap_or(0.0);
let diameter_f = diameter(physical_graph).unwrap_or(0.0);
let radius_f = radius(physical_graph).unwrap_or(0.0);
let average_path_length = Self::average_shortest_path_length(physical_graph);
let components = connected_components(physical_graph);
let num_components = components.len();
let largest_component_size = components.iter().map(|c| c.len()).max().unwrap_or(0);
let is_connected = num_components <= 1;
let algebraic_connectivity = Self::algebraic_connectivity(physical_graph).unwrap_or(0.0);
let (edge_connectivity, vertex_connectivity) =
Self::min_degree_connectivity_bounds(physical_graph);
let bipartite_result = is_bipartite(physical_graph);
let edge_count = physical_graph.edge_count();
let is_forest = num_components > 0 && edge_count == n.saturating_sub(num_components);
let is_tree = is_connected && is_forest && n > 0;
let has_cycles = !is_forest;
let girth = Self::compute_girth(physical_graph).unwrap_or(0);
let max_degree = Self::max_degree(physical_graph);
let chromatic_number = if n == 0 {
0
} else if n <= 40 {
scirs2_chromatic_number(physical_graph, max_degree + 1).unwrap_or(max_degree + 1)
} else {
max_degree + 1
};
let independence_number = Self::greedy_independence_number(physical_graph);
let edges: Vec<(usize, usize)> = Self::edge_list(physical_graph);
let is_planar = n == 0 || is_planar(&edges, n);
Ok(GraphAnalysisResult {
density,
clustering_coefficient,
diameter: diameter_f.round().max(0.0) as usize,
radius: radius_f.round().max(0.0) as usize,
average_path_length,
connectivity_stats: ConnectivityStats {
edge_connectivity,
vertex_connectivity,
algebraic_connectivity,
is_connected,
num_components,
largest_component_size,
},
topological_properties: TopologicalProperties {
is_planar,
is_bipartite: bipartite_result.is_bipartite,
is_tree,
is_forest,
has_cycles,
girth,
chromatic_number,
independence_number,
},
})
}
fn perform_spectral_analysis(
&self,
_logical_graph: &Graph<usize, f64>,
physical_graph: &Graph<usize, f64>,
) -> DeviceResult<SpectralAnalysisResult> {
let n = physical_graph.node_count();
if n == 0 {
return Ok(SpectralAnalysisResult {
laplacian_eigenvalues: Array1::zeros(0),
embedding_vectors: Array2::zeros((0, 0)),
spectral_radius: 0.0,
algebraic_connectivity: 0.0,
spectral_gap: 0.0,
embedding_quality: EmbeddingQuality {
stress: 0.0,
distortion: 0.0,
preservation_ratio: 0.0,
embedding_dimension: 0,
},
});
}
let laplacian_matrix = laplacian(physical_graph, LaplacianType::Standard)
.map_err(|e| DeviceError::GraphAnalysisError(format!("laplacian failed: {e}")))?;
let (eigenvalues_complex, eigenvectors_complex) = eig(&laplacian_matrix.view(), None)
.map_err(|e| DeviceError::GraphAnalysisError(format!("eig failed: {e}")))?;
let mut order: Vec<usize> = (0..n).collect();
order.sort_by(|&a, &b| {
eigenvalues_complex[a]
.re
.partial_cmp(&eigenvalues_complex[b].re)
.unwrap_or(std::cmp::Ordering::Equal)
});
let sorted_eigenvalues: Vec<f64> =
order.iter().map(|&i| eigenvalues_complex[i].re).collect();
let laplacian_eigenvalues = Array1::from_vec(sorted_eigenvalues.clone());
let spectral_radius_value = sorted_eigenvalues
.iter()
.cloned()
.fold(0.0_f64, |acc, v| acc.max(v.abs()));
let algebraic_connectivity = if n >= 2 {
sorted_eigenvalues[1].max(0.0)
} else {
0.0
};
let spectral_gap = if n >= 2 {
(sorted_eigenvalues[n - 1] - sorted_eigenvalues[n.saturating_sub(2)]).abs()
} else {
0.0
};
let embedding_dimension = (n.saturating_sub(1)).min(2);
let mut embedding_vectors = Array2::<f64>::zeros((n, embedding_dimension));
for (col, &eig_idx) in order.iter().skip(1).take(embedding_dimension).enumerate() {
for row in 0..n {
embedding_vectors[[row, col]] = eigenvectors_complex[[row, eig_idx]].re;
}
}
let (stress, distortion, preservation_ratio) =
Self::embedding_quality_metrics(physical_graph, &embedding_vectors);
Ok(SpectralAnalysisResult {
laplacian_eigenvalues,
embedding_vectors,
spectral_radius: spectral_radius_value,
algebraic_connectivity,
spectral_gap,
embedding_quality: EmbeddingQuality {
stress,
distortion,
preservation_ratio,
embedding_dimension,
},
})
}
fn perform_community_analysis(
&self,
logical_graph: &Graph<usize, f64>,
physical_graph: &Graph<usize, f64>,
) -> DeviceResult<CommunityAnalysisResult> {
let graph = if logical_graph.node_count() > 0 {
logical_graph
} else {
physical_graph
};
let n = graph.node_count();
if n == 0 {
return Ok(CommunityAnalysisResult {
communities: HashMap::new(),
modularity: 0.0,
num_communities: 0,
community_sizes: Vec::new(),
inter_community_edges: 0,
quality_metrics: CommunityQualityMetrics {
silhouette_score: 0.0,
conductance: 0.0,
coverage: 0.0,
performance: 0.0,
},
});
}
let result = louvain_communities_result(graph);
let communities: HashMap<usize, usize> = result.node_communities.clone();
let community_sizes: Vec<usize> = result.communities.iter().map(|c| c.len()).collect();
let num_communities = result.num_communities;
let modularity_score = modularity(graph, &communities);
let mut inter_community_edges = 0usize;
let mut intra_community_edges = 0usize;
for node in graph.nodes() {
if let Ok(neighbors) = graph.neighbors(node) {
for neighbor in neighbors {
if communities.get(node) != communities.get(&neighbor) {
inter_community_edges += 1;
} else {
intra_community_edges += 1;
}
}
}
}
inter_community_edges /= 2;
intra_community_edges /= 2;
let total_edges = inter_community_edges + intra_community_edges;
let coverage = if total_edges > 0 {
intra_community_edges as f64 / total_edges as f64
} else {
0.0
};
let mut community_boundary: HashMap<usize, (usize, usize)> = HashMap::new();
for node in graph.nodes() {
let Some(&community) = communities.get(node) else {
continue;
};
let entry = community_boundary.entry(community).or_insert((0, 0));
if let Ok(neighbors) = graph.neighbors(node) {
for neighbor in neighbors {
entry.1 += 1;
if communities.get(&neighbor) != Some(&community) {
entry.0 += 1;
}
}
}
}
let conductance = if community_boundary.is_empty() {
0.0
} else {
community_boundary
.values()
.map(|&(boundary, total)| {
if total > 0 {
boundary as f64 / total as f64
} else {
0.0
}
})
.sum::<f64>()
/ community_boundary.len() as f64
};
let performance = Self::community_performance(graph, &communities);
let silhouette_score = Self::community_silhouette_proxy(graph, &communities);
Ok(CommunityAnalysisResult {
communities,
modularity: modularity_score,
num_communities,
community_sizes,
inter_community_edges,
quality_metrics: CommunityQualityMetrics {
silhouette_score,
conductance,
coverage,
performance,
},
})
}
fn perform_centrality_analysis(
&self,
_logical_graph: &Graph<usize, f64>,
physical_graph: &Graph<usize, f64>,
) -> DeviceResult<CentralityAnalysisResult> {
let n = physical_graph.node_count();
if n == 0 {
return Ok(CentralityAnalysisResult {
betweenness_centrality: HashMap::new(),
closeness_centrality: HashMap::new(),
eigenvector_centrality: HashMap::new(),
pagerank_centrality: HashMap::new(),
centrality_correlations: Array2::zeros((4, 4)),
centrality_statistics: CentralityStatistics {
max_betweenness: 0.0,
max_closeness: 0.0,
max_eigenvector: 0.0,
max_pagerank: 0.0,
mean_betweenness: 0.0,
mean_closeness: 0.0,
mean_eigenvector: 0.0,
mean_pagerank: 0.0,
},
});
}
let betweenness = betweenness_centrality(physical_graph, true);
let closeness = closeness_centrality(physical_graph, true);
let eigenvector = eigenvector_centrality(physical_graph, 200, 1e-9).unwrap_or_default();
let pagerank_map = pagerank_centrality(physical_graph, 0.85, 1e-9).unwrap_or_default();
let nodes: Vec<usize> = physical_graph.nodes().into_iter().cloned().collect();
let mut data = Array2::<f64>::zeros((nodes.len(), 4));
for (row, node) in nodes.iter().enumerate() {
data[[row, 0]] = *betweenness.get(node).unwrap_or(&0.0);
data[[row, 1]] = *closeness.get(node).unwrap_or(&0.0);
data[[row, 2]] = *eigenvector.get(node).unwrap_or(&0.0);
data[[row, 3]] = *pagerank_map.get(node).unwrap_or(&0.0);
}
let centrality_correlations = corrcoef(&data.view(), "pearson")
.unwrap_or_else(|_| Array2::zeros((4, 4)))
.mapv(|v: f64| if v.is_finite() { v } else { 0.0 });
let mean_or_zero = |values: &HashMap<usize, f64>| {
if values.is_empty() {
0.0
} else {
values.values().sum::<f64>() / values.len() as f64
}
};
let max_or_zero =
|values: &HashMap<usize, f64>| values.values().cloned().fold(0.0_f64, f64::max);
Ok(CentralityAnalysisResult {
betweenness_centrality: betweenness.clone(),
closeness_centrality: closeness.clone(),
eigenvector_centrality: eigenvector.clone(),
pagerank_centrality: pagerank_map.clone(),
centrality_correlations,
centrality_statistics: CentralityStatistics {
max_betweenness: max_or_zero(&betweenness),
max_closeness: max_or_zero(&closeness),
max_eigenvector: max_or_zero(&eigenvector),
max_pagerank: max_or_zero(&pagerank_map),
mean_betweenness: mean_or_zero(&betweenness),
mean_closeness: mean_or_zero(&closeness),
mean_eigenvector: mean_or_zero(&eigenvector),
mean_pagerank: mean_or_zero(&pagerank_map),
},
})
}
fn generate_initial_mapping(
&self,
logical_graph: &Graph<usize, f64>,
physical_graph: &Graph<usize, f64>,
_graph_analysis: &GraphAnalysisResult,
_spectral_analysis: Option<&SpectralAnalysisResult>,
_community_analysis: &CommunityAnalysisResult,
centrality_analysis: &CentralityAnalysisResult,
) -> DeviceResult<HashMap<usize, usize>> {
let num_physical = self.device_topology.num_qubits();
let num_logical = logical_graph.node_count().max(physical_graph.node_count());
if num_physical == 0 || num_logical == 0 {
return Ok(HashMap::new());
}
let mut logical_qubits: Vec<usize> = logical_graph.nodes().into_iter().cloned().collect();
if logical_qubits.is_empty() {
logical_qubits = (0..num_logical).collect();
}
logical_qubits.sort_by(|&a, &b| {
let deg_a = logical_graph.neighbors(&a).map(|v| v.len()).unwrap_or(0);
let deg_b = logical_graph.neighbors(&b).map(|v| v.len()).unwrap_or(0);
deg_b.cmp(°_a).then(a.cmp(&b))
});
let mut physical_qubits: Vec<usize> = (0..num_physical).collect();
physical_qubits.sort_by(|&a, &b| {
let ca = centrality_analysis
.betweenness_centrality
.get(&a)
.copied()
.unwrap_or(0.0);
let cb = centrality_analysis
.betweenness_centrality
.get(&b)
.copied()
.unwrap_or(0.0);
cb.partial_cmp(&ca)
.unwrap_or(std::cmp::Ordering::Equal)
.then(a.cmp(&b))
});
let mut mapping = HashMap::new();
for (logical, physical) in logical_qubits.into_iter().zip(physical_qubits) {
mapping.insert(logical, physical);
}
Ok(mapping)
}
fn optimize_mapping<const N: usize>(
&self,
circuit: &Circuit<N>,
initial_mapping: HashMap<usize, usize>,
_logical_graph: &Graph<usize, f64>,
_physical_graph: &Graph<usize, f64>,
) -> DeviceResult<(
HashMap<usize, usize>,
Vec<SwapOperation>,
OptimizationMetrics,
)> {
let start_time = Instant::now();
let mut router = crate::routing_advanced::AdvancedQubitRouter::new(
self.device_topology.clone(),
crate::routing_advanced::AdvancedRoutingStrategy::SABRE {
heuristic_weight: 0.5,
},
42,
);
let routing_result = router.route_circuit(circuit)?;
let optimization_time = start_time.elapsed();
let final_mapping = if routing_result.final_mapping.is_empty() {
initial_mapping.clone()
} else {
routing_result.final_mapping
};
let swap_operations = routing_result.swap_sequence;
let initial_objective = self.calculate_objective(&initial_mapping, circuit)?;
let objective_value = self.calculate_objective(&final_mapping, circuit)?;
let improvement_ratio = if initial_objective.abs() > f64::EPSILON {
((initial_objective - objective_value) / initial_objective.abs()).clamp(-1.0, 1.0)
} else {
0.0
};
let mut algorithm_metrics = HashMap::new();
algorithm_metrics.insert("swap_count".to_string(), swap_operations.len() as f64);
algorithm_metrics.insert(
"routing_time_ms".to_string(),
routing_result.routing_time as f64,
);
algorithm_metrics.insert(
"states_explored".to_string(),
routing_result.metrics.states_explored as f64,
);
algorithm_metrics.insert(
"depth_overhead".to_string(),
routing_result.depth_overhead as f64,
);
let metrics = OptimizationMetrics {
optimization_time,
iterations: routing_result.metrics.iterations.max(1),
converged: true,
final_objective: objective_value,
best_objective: objective_value,
improvement_ratio,
constraint_violations: 0.0,
algorithm_metrics,
resource_usage: ResourceUsageMetrics {
peak_memory: (final_mapping.len() + swap_operations.len()) * 64,
average_cpu: if optimization_time.as_micros() > 0 {
100.0
} else {
0.0
},
energy_consumption: None,
network_overhead: None,
},
};
Ok((final_mapping, swap_operations, metrics))
}
fn predict_performance<const N: usize>(
&self,
mapping: &HashMap<usize, usize>,
circuit: &Circuit<N>,
_graph_analysis: &GraphAnalysisResult,
) -> DeviceResult<PerformancePredictions> {
let mut predicted_swaps = 0.0;
let mut fidelity_total = 0.0;
let mut fidelity_count = 0usize;
for gate in circuit.gates() {
let qubits = gate.qubits();
if qubits.len() != 2 {
continue;
}
let logical_q1 = qubits[0].id() as usize;
let logical_q2 = qubits[1].id() as usize;
if let (Some(&physical_q1), Some(&physical_q2)) =
(mapping.get(&logical_q1), mapping.get(&logical_q2))
{
if !self.device_topology.are_connected(physical_q1, physical_q2) {
predicted_swaps += 1.0;
}
if let Some(calibration) = &self.calibration {
fidelity_total += calibration
.gate_fidelity(physical_q1, physical_q2)
.unwrap_or(0.95);
fidelity_count += 1;
}
}
}
let gate_count = circuit.gates().len() as f64;
let predicted_time = gate_count + predicted_swaps * 3.0;
let predicted_fidelity = if fidelity_count > 0 {
fidelity_total / fidelity_count as f64
} else {
0.95
};
Ok(PerformancePredictions {
predicted_swaps,
predicted_time,
predicted_fidelity,
confidence_intervals: HashMap::new(),
uncertainty_estimates: HashMap::new(),
})
}
fn generate_realtime_analytics(
&self,
_metrics: &OptimizationMetrics,
) -> DeviceResult<RealtimeAnalyticsResult> {
Ok(RealtimeAnalyticsResult {
current_metrics: HashMap::new(),
performance_trends: HashMap::new(),
anomalies: Vec::new(),
resource_utilization: ResourceUtilization {
cpu_usage: 25.0,
memory_usage: 40.0,
disk_io: 10.0,
network_usage: 5.0,
gpu_usage: None,
},
quality_assessments: Vec::new(),
})
}
fn analyze_ml_performance(
&self,
_mapping: &HashMap<usize, usize>,
_metrics: &OptimizationMetrics,
) -> DeviceResult<MLPerformanceResult> {
Ok(MLPerformanceResult {
model_accuracy: HashMap::new(),
feature_importance: HashMap::new(),
prediction_reliability: 0.9,
training_history: Vec::new(),
})
}
fn generate_adaptive_insights(
&self,
_metrics: &OptimizationMetrics,
) -> DeviceResult<AdaptiveMappingInsights> {
Ok(AdaptiveMappingInsights {
learning_progress: HashMap::new(),
adaptation_effectiveness: HashMap::new(),
performance_trends: HashMap::new(),
recommended_adjustments: Vec::new(),
})
}
fn generate_optimization_recommendations(
&self,
_graph_analysis: &GraphAnalysisResult,
_metrics: &OptimizationMetrics,
_spectral_analysis: Option<&SpectralAnalysisResult>,
_community_analysis: &CommunityAnalysisResult,
) -> DeviceResult<OptimizationRecommendations> {
Ok(OptimizationRecommendations {
algorithm_recommendations: Vec::new(),
parameter_suggestions: Vec::new(),
hardware_optimizations: Vec::new(),
improvement_predictions: HashMap::new(),
})
}
fn average_shortest_path_length(graph: &Graph<usize, f64>) -> f64 {
let nodes: Vec<usize> = graph.nodes().into_iter().cloned().collect();
let n = nodes.len();
if n < 2 {
return 0.0;
}
let mut total = 0.0;
let mut count = 0usize;
for i in 0..n {
for j in (i + 1)..n {
if let Ok(Some(path)) = dijkstra_path(graph, &nodes[i], &nodes[j]) {
total += path.total_weight;
count += 1;
}
}
}
if count > 0 {
total / count as f64
} else {
0.0
}
}
fn algebraic_connectivity(graph: &Graph<usize, f64>) -> Option<f64> {
let n = graph.node_count();
if n < 2 {
return Some(0.0);
}
let lap = laplacian(graph, LaplacianType::Standard).ok()?;
let (eigenvalues, _) = eig(&lap.view(), None).ok()?;
let mut vals: Vec<f64> = (0..n).map(|i| eigenvalues[i].re).collect();
vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
Some(vals[1].max(0.0))
}
fn min_degree_connectivity_bounds(graph: &Graph<usize, f64>) -> (usize, usize) {
let min_degree = graph
.nodes()
.into_iter()
.map(|node| graph.neighbors(node).map(|n| n.len()).unwrap_or(0))
.min()
.unwrap_or(0);
(min_degree, min_degree)
}
fn max_degree(graph: &Graph<usize, f64>) -> usize {
graph
.nodes()
.into_iter()
.map(|node| graph.neighbors(node).map(|n| n.len()).unwrap_or(0))
.max()
.unwrap_or(0)
}
fn compute_girth(graph: &Graph<usize, f64>) -> Option<usize> {
let nodes: Vec<usize> = graph.nodes().into_iter().cloned().collect();
let mut best: Option<usize> = None;
for &start in &nodes {
let mut dist: HashMap<usize, usize> = HashMap::new();
let mut parent: HashMap<usize, usize> = HashMap::new();
dist.insert(start, 0);
let mut queue = VecDeque::new();
queue.push_back(start);
while let Some(u) = queue.pop_front() {
let neighbors = graph.neighbors(&u).unwrap_or_default();
let dist_u = dist[&u];
for v in neighbors {
if let std::collections::hash_map::Entry::Vacant(e) = dist.entry(v) {
e.insert(dist_u + 1);
parent.insert(v, u);
queue.push_back(v);
} else if parent.get(&u) != Some(&v) {
let cycle_len = dist_u + dist[&v] + 1;
best = Some(best.map_or(cycle_len, |b| b.min(cycle_len)));
}
}
}
}
best
}
fn greedy_independence_number(graph: &Graph<usize, f64>) -> usize {
let mut remaining: HashSet<usize> = graph.nodes().into_iter().cloned().collect();
let mut count = 0usize;
while !remaining.is_empty() {
let pick = *remaining
.iter()
.min_by_key(|&&v| {
graph
.neighbors(&v)
.map(|n| n.into_iter().filter(|w| remaining.contains(w)).count())
.unwrap_or(0)
})
.expect("remaining is non-empty");
count += 1;
let neighbors: Vec<usize> = graph.neighbors(&pick).unwrap_or_default();
remaining.remove(&pick);
for neighbor in neighbors {
remaining.remove(&neighbor);
}
}
count
}
#[cfg(feature = "scirs2")]
fn edge_list(graph: &Graph<usize, f64>) -> Vec<(usize, usize)> {
use petgraph::visit::EdgeRef;
graph
.inner()
.edge_references()
.map(|edge| (graph.inner()[edge.source()], graph.inner()[edge.target()]))
.collect()
}
fn embedding_quality_metrics(
graph: &Graph<usize, f64>,
embedding: &Array2<f64>,
) -> (f64, f64, f64) {
let nodes: Vec<usize> = graph.nodes().into_iter().cloned().collect();
let n = nodes.len();
if n < 2 || embedding.ncols() == 0 {
return (0.0, 0.0, 0.0);
}
let mut sum_sq_diff = 0.0;
let mut sum_sq_graph = 0.0;
let mut sum_rel_diff = 0.0;
let mut count = 0usize;
for i in 0..n {
for j in (i + 1)..n {
let Ok(Some(path)) = dijkstra_path(graph, &nodes[i], &nodes[j]) else {
continue;
};
let d_graph = path.total_weight;
if d_graph <= 0.0 {
continue;
}
let row_i = embedding.row(i);
let row_j = embedding.row(j);
let d_embed = row_i
.iter()
.zip(row_j.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt();
sum_sq_diff += (d_graph - d_embed).powi(2);
sum_sq_graph += d_graph.powi(2);
sum_rel_diff += (d_embed - d_graph).abs() / d_graph;
count += 1;
}
}
if count == 0 || sum_sq_graph <= 0.0 {
return (0.0, 0.0, 0.0);
}
let stress = (sum_sq_diff / sum_sq_graph).sqrt();
let distortion = sum_rel_diff / count as f64;
let preservation_ratio = (1.0 - stress).clamp(0.0, 1.0);
(stress, distortion, preservation_ratio)
}
fn community_performance(
graph: &Graph<usize, f64>,
communities: &HashMap<usize, usize>,
) -> f64 {
let nodes: Vec<usize> = graph.nodes().into_iter().cloned().collect();
let n = nodes.len();
if n < 2 {
return 0.0;
}
let mut correct = 0u64;
let mut total = 0u64;
for i in 0..n {
for j in (i + 1)..n {
let same_community = communities.get(&nodes[i]) == communities.get(&nodes[j]);
let connected = graph
.neighbors(&nodes[i])
.map(|neighbors| neighbors.contains(&nodes[j]))
.unwrap_or(false);
if same_community == connected {
correct += 1;
}
total += 1;
}
}
if total > 0 {
correct as f64 / total as f64
} else {
0.0
}
}
fn community_silhouette_proxy(
graph: &Graph<usize, f64>,
communities: &HashMap<usize, usize>,
) -> f64 {
let mut scores = Vec::new();
for node in graph.nodes() {
let Some(&own_community) = communities.get(node) else {
continue;
};
let neighbors = graph.neighbors(node).unwrap_or_default();
if neighbors.is_empty() {
continue;
}
let intra = neighbors
.iter()
.filter(|neighbor| communities.get(*neighbor) == Some(&own_community))
.count() as f64;
let total = neighbors.len() as f64;
let a = intra / total;
let b = 1.0 - a;
let denom = a.max(b);
if denom > 0.0 {
scores.push((a - b) / denom);
}
}
if scores.is_empty() {
0.0
} else {
scores.iter().sum::<f64>() / scores.len() as f64
}
}
}
#[cfg(test)]
mod real_analysis_tests {
use super::*;
use crate::mapping_scirs2::utils::{create_standard_topology, generate_random_circuit};
fn mapper_for(topology_type: &str, num_qubits: usize) -> SciRS2QubitMapper {
let topology = create_standard_topology(topology_type, num_qubits)
.expect("standard topology should be constructible");
SciRS2QubitMapper::new(SciRS2MappingConfig::default(), topology, None)
}
#[test]
fn test_graph_analysis_reflects_real_topology_not_fixed_constants() {
let mut complete_mapper = mapper_for("complete", 5);
let mut linear_mapper = mapper_for("linear", 5);
let circuit = generate_random_circuit::<5>(6, 0.6);
let complete_result = complete_mapper
.map_circuit(&circuit)
.expect("mapping should succeed");
let linear_result = linear_mapper
.map_circuit(&circuit)
.expect("mapping should succeed");
assert!(
(complete_result.graph_analysis.density - 1.0).abs() < 1e-9,
"complete graph on 5 nodes must have density 1.0, got {}",
complete_result.graph_analysis.density
);
assert!(
linear_result.graph_analysis.density < complete_result.graph_analysis.density,
"linear chain density ({}) must be lower than complete graph density ({})",
linear_result.graph_analysis.density,
complete_result.graph_analysis.density
);
assert!((linear_result.graph_analysis.density - 0.5).abs() > 1e-9);
assert_eq!(complete_result.graph_analysis.diameter, 1);
assert_eq!(linear_result.graph_analysis.diameter, 4);
assert!(
!complete_result
.graph_analysis
.topological_properties
.is_planar,
"the complete graph on 5 nodes (K5) is not planar"
);
assert!(
linear_result
.graph_analysis
.topological_properties
.is_planar,
"a 5-node linear chain is a tree and therefore planar"
);
assert!(
complete_result
.graph_analysis
.connectivity_stats
.is_connected
);
}
#[test]
fn test_centrality_analysis_identifies_real_hub_qubit() {
let mut mapper = mapper_for("star", 5);
let circuit = generate_random_circuit::<5>(6, 0.6);
let result = mapper
.map_circuit(&circuit)
.expect("mapping should succeed");
let hub_centrality = *result
.centrality_analysis
.betweenness_centrality
.get(&0)
.unwrap_or(&0.0);
for leaf in 1..5 {
let leaf_centrality = *result
.centrality_analysis
.betweenness_centrality
.get(&leaf)
.unwrap_or(&0.0);
assert!(
hub_centrality > leaf_centrality,
"hub (qubit 0) centrality {hub_centrality} must exceed leaf {leaf} centrality {leaf_centrality}"
);
}
}
#[test]
fn test_optimize_mapping_actually_routes_disconnected_circuit() {
let mut mapper = mapper_for("linear", 5);
let mut circuit = Circuit::<5>::new();
let _ = circuit.cnot(QubitId(0), QubitId(4));
let result = mapper
.map_circuit(&circuit)
.expect("mapping should succeed");
let final_objective = result.optimization_metrics.final_objective;
assert!(
final_objective <= 1.0,
"final objective should reflect at most the one long-range interaction, got {final_objective}"
);
assert!(result
.optimization_metrics
.algorithm_metrics
.contains_key("swap_count"));
}
#[test]
fn test_community_analysis_varies_with_real_interaction_graph() {
let mut mapper_a = mapper_for("grid", 6);
let mut mapper_b = mapper_for("grid", 6);
let sparse_circuit = generate_random_circuit::<6>(2, 0.2);
let dense_circuit = generate_random_circuit::<6>(30, 0.9);
let result_a = mapper_a
.map_circuit(&sparse_circuit)
.expect("mapping should succeed");
let result_b = mapper_b
.map_circuit(&dense_circuit)
.expect("mapping should succeed");
assert!(
result_a.community_analysis.communities.len()
<= result_b.community_analysis.communities.len()
|| result_a.community_analysis.inter_community_edges
!= result_b.community_analysis.inter_community_edges
);
}
}