use super::{
ApplicationError, ApplicationResult, IndustryConstraint, IndustryObjective, IndustrySolution,
OptimizationProblem,
};
use crate::ising::IsingModel;
use crate::qubo::{QuboBuilder, QuboFormulation};
use crate::simulator::{AnnealingParams, ClassicalAnnealingSimulator};
use std::collections::HashMap;
use std::fmt::Write;
#[derive(Debug, Clone)]
pub struct NetworkTopologyOptimization {
pub num_nodes: usize,
pub potential_connections: Vec<(usize, usize)>,
pub connection_costs: Vec<f64>,
pub connection_capacities: Vec<f64>,
pub traffic_demands: Vec<Vec<f64>>,
pub reliability_requirements: Vec<f64>,
pub max_latency: f64,
pub redundancy_level: usize,
pub qos_constraints: Vec<IndustryConstraint>,
}
impl NetworkTopologyOptimization {
pub fn new(
num_nodes: usize,
potential_connections: Vec<(usize, usize)>,
connection_costs: Vec<f64>,
traffic_demands: Vec<Vec<f64>>,
) -> ApplicationResult<Self> {
if connection_costs.len() != potential_connections.len() {
return Err(ApplicationError::InvalidConfiguration(
"Connection costs must match number of potential connections".to_string(),
));
}
if traffic_demands.len() != num_nodes {
return Err(ApplicationError::InvalidConfiguration(
"Traffic demand matrix dimension mismatch".to_string(),
));
}
for row in &traffic_demands {
if row.len() != num_nodes {
return Err(ApplicationError::InvalidConfiguration(
"Traffic demand matrix is not square".to_string(),
));
}
}
Ok(Self {
num_nodes,
potential_connections: potential_connections.clone(),
connection_costs,
connection_capacities: vec![100.0; potential_connections.len()], traffic_demands,
reliability_requirements: vec![0.99; potential_connections.len()], max_latency: 100.0, redundancy_level: 2, qos_constraints: Vec::new(),
})
}
pub fn set_connection_capacities(&mut self, capacities: Vec<f64>) -> ApplicationResult<()> {
if capacities.len() != self.potential_connections.len() {
return Err(ApplicationError::InvalidConfiguration(
"Capacities must match number of potential connections".to_string(),
));
}
self.connection_capacities = capacities;
Ok(())
}
#[must_use]
pub fn calculate_connectivity(&self, topology: &NetworkTopology) -> f64 {
let mut connectivity_matrix = vec![vec![false; self.num_nodes]; self.num_nodes];
for (i, &active) in topology.active_connections.iter().enumerate() {
if active {
let (u, v) = self.potential_connections[i];
connectivity_matrix[u][v] = true;
connectivity_matrix[v][u] = true;
}
}
for k in 0..self.num_nodes {
for i in 0..self.num_nodes {
for j in 0..self.num_nodes {
connectivity_matrix[i][j] |=
connectivity_matrix[i][k] && connectivity_matrix[k][j];
}
}
}
let mut connected_pairs = 0;
let total_pairs = self.num_nodes * (self.num_nodes - 1);
for i in 0..self.num_nodes {
for j in 0..self.num_nodes {
if i != j && connectivity_matrix[i][j] {
connected_pairs += 1;
}
}
}
f64::from(connected_pairs) / total_pairs as f64
}
#[must_use]
pub fn calculate_network_latency(&self, topology: &NetworkTopology) -> f64 {
let mut distance_matrix = vec![vec![f64::INFINITY; self.num_nodes]; self.num_nodes];
for i in 0..self.num_nodes {
distance_matrix[i][i] = 0.0;
}
for (i, &active) in topology.active_connections.iter().enumerate() {
if active {
let (u, v) = self.potential_connections[i];
let latency = 1.0; distance_matrix[u][v] = latency;
distance_matrix[v][u] = latency;
}
}
for k in 0..self.num_nodes {
for i in 0..self.num_nodes {
for j in 0..self.num_nodes {
if distance_matrix[i][k] + distance_matrix[k][j] < distance_matrix[i][j] {
distance_matrix[i][j] = distance_matrix[i][k] + distance_matrix[k][j];
}
}
}
}
let mut total_latency = 0.0;
let mut connected_pairs = 0;
for i in 0..self.num_nodes {
for j in 0..self.num_nodes {
if i != j && distance_matrix[i][j] < f64::INFINITY {
total_latency += distance_matrix[i][j];
connected_pairs += 1;
}
}
}
if connected_pairs > 0 {
total_latency / f64::from(connected_pairs)
} else {
f64::INFINITY
}
}
#[must_use]
pub fn calculate_total_cost(&self, topology: &NetworkTopology) -> f64 {
topology
.active_connections
.iter()
.enumerate()
.filter(|(_, &active)| active)
.map(|(i, _)| self.connection_costs[i])
.sum()
}
}
impl OptimizationProblem for NetworkTopologyOptimization {
type Solution = NetworkTopology;
type ObjectiveValue = f64;
fn description(&self) -> String {
format!(
"Network topology optimization with {} nodes and {} potential connections",
self.num_nodes,
self.potential_connections.len()
)
}
fn size_metrics(&self) -> HashMap<String, usize> {
let mut metrics = HashMap::new();
metrics.insert("num_nodes".to_string(), self.num_nodes);
metrics.insert(
"num_potential_connections".to_string(),
self.potential_connections.len(),
);
metrics.insert(
"num_qos_constraints".to_string(),
self.qos_constraints.len(),
);
metrics
}
fn validate(&self) -> ApplicationResult<()> {
if self.num_nodes < 2 {
return Err(ApplicationError::DataValidationError(
"At least 2 nodes required".to_string(),
));
}
if self.potential_connections.is_empty() {
return Err(ApplicationError::DataValidationError(
"At least one potential connection required".to_string(),
));
}
for &(u, v) in &self.potential_connections {
if u >= self.num_nodes || v >= self.num_nodes {
return Err(ApplicationError::DataValidationError(
"Connection references invalid node index".to_string(),
));
}
}
for &cost in &self.connection_costs {
if cost < 0.0 {
return Err(ApplicationError::DataValidationError(
"Connection costs must be non-negative".to_string(),
));
}
}
for &capacity in &self.connection_capacities {
if capacity <= 0.0 {
return Err(ApplicationError::DataValidationError(
"Connection capacities must be positive".to_string(),
));
}
}
Ok(())
}
fn to_qubo(&self) -> ApplicationResult<(crate::ising::QuboModel, HashMap<String, usize>)> {
let mut builder = QuboBuilder::new();
let num_connections = self.potential_connections.len();
let mut string_var_map = HashMap::new();
for i in 0..num_connections {
string_var_map.insert(format!("x_{i}"), i);
}
for i in 0..num_connections {
builder.add_bias(i, self.connection_costs[i]);
let connectivity_benefit = -50.0; builder.add_bias(i, connectivity_benefit);
}
let redundancy_penalty = 1000.0;
for node in 0..self.num_nodes {
let mut adjacent_connections = Vec::new();
for (conn_idx, &(u, v)) in self.potential_connections.iter().enumerate() {
if u == node || v == node {
adjacent_connections.push(conn_idx);
}
}
if adjacent_connections.len() >= self.redundancy_level {
for i in 0..adjacent_connections.len() {
for j in (i + 1)..adjacent_connections.len() {
let var1 = adjacent_connections[i];
let var2 = adjacent_connections[j];
builder.add_coupling(var1, var2, -redundancy_penalty / 2.0);
}
}
}
}
let capacity_penalty = 500.0;
for (conn_idx, &(u, v)) in self.potential_connections.iter().enumerate() {
let traffic_demand = self.traffic_demands[u][v] + self.traffic_demands[v][u];
let capacity = self.connection_capacities[conn_idx];
if traffic_demand > capacity {
builder.add_bias(conn_idx, capacity_penalty * (traffic_demand - capacity));
}
}
Ok((builder.build(), string_var_map))
}
fn evaluate_solution(
&self,
solution: &Self::Solution,
) -> ApplicationResult<Self::ObjectiveValue> {
let total_cost = self.calculate_total_cost(solution);
let connectivity = self.calculate_connectivity(solution);
let latency = self.calculate_network_latency(solution);
let connectivity_weight = 100.0;
let latency_penalty = 10.0;
Ok(total_cost + latency_penalty * latency - connectivity_weight * connectivity)
}
fn is_feasible(&self, solution: &Self::Solution) -> bool {
let connectivity = self.calculate_connectivity(solution);
if connectivity < 0.8 {
return false;
}
let latency = self.calculate_network_latency(solution);
if latency > self.max_latency {
return false;
}
for (conn_idx, &active) in solution.active_connections.iter().enumerate() {
if active {
let (u, v) = self.potential_connections[conn_idx];
let traffic_demand = self.traffic_demands[u][v] + self.traffic_demands[v][u];
let capacity = self.connection_capacities[conn_idx];
if traffic_demand > capacity * 1.1 {
return false;
}
}
}
true
}
}
#[derive(Debug, Clone)]
pub struct NetworkTopology {
pub active_connections: Vec<bool>,
pub total_cost: f64,
pub connectivity: f64,
pub average_latency: f64,
pub performance_metrics: TelecomMetrics,
}
#[derive(Debug, Clone)]
pub struct TelecomMetrics {
pub throughput: f64,
pub packet_loss_rate: f64,
pub jitter: f64,
pub availability: f64,
pub mtbf: f64,
pub coverage_area: f64,
}
impl IndustrySolution for NetworkTopology {
type Problem = NetworkTopologyOptimization;
fn from_binary(problem: &Self::Problem, binary_solution: &[i8]) -> ApplicationResult<Self> {
let num_connections = problem.potential_connections.len();
let mut active_connections = vec![false; num_connections];
for i in 0..num_connections.min(binary_solution.len()) {
active_connections[i] = binary_solution[i] == 1;
}
let total_cost = problem.calculate_total_cost(&Self {
active_connections: active_connections.clone(),
total_cost: 0.0,
connectivity: 0.0,
average_latency: 0.0,
performance_metrics: TelecomMetrics {
throughput: 0.0,
packet_loss_rate: 0.0,
jitter: 0.0,
availability: 0.0,
mtbf: 0.0,
coverage_area: 0.0,
},
});
let connectivity = problem.calculate_connectivity(&Self {
active_connections: active_connections.clone(),
total_cost: 0.0,
connectivity: 0.0,
average_latency: 0.0,
performance_metrics: TelecomMetrics {
throughput: 0.0,
packet_loss_rate: 0.0,
jitter: 0.0,
availability: 0.0,
mtbf: 0.0,
coverage_area: 0.0,
},
});
let average_latency = problem.calculate_network_latency(&Self {
active_connections: active_connections.clone(),
total_cost: 0.0,
connectivity: 0.0,
average_latency: 0.0,
performance_metrics: TelecomMetrics {
throughput: 0.0,
packet_loss_rate: 0.0,
jitter: 0.0,
availability: 0.0,
mtbf: 0.0,
coverage_area: 0.0,
},
});
let num_active = active_connections.iter().filter(|&&x| x).count();
let performance_metrics = TelecomMetrics {
throughput: num_active as f64 * 10.0, packet_loss_rate: 0.001, jitter: 2.0, availability: 0.999, mtbf: 8760.0, coverage_area: num_active as f64 * 100.0, };
Ok(Self {
active_connections,
total_cost,
connectivity,
average_latency,
performance_metrics,
})
}
fn summary(&self) -> HashMap<String, String> {
let mut summary = HashMap::new();
summary.insert("type".to_string(), "Network Topology".to_string());
summary.insert("total_cost".to_string(), format!("${:.2}", self.total_cost));
summary.insert(
"connectivity".to_string(),
format!("{:.1}%", self.connectivity * 100.0),
);
summary.insert(
"average_latency".to_string(),
format!("{:.1} ms", self.average_latency),
);
summary.insert(
"throughput".to_string(),
format!("{:.1} Gbps", self.performance_metrics.throughput),
);
summary.insert(
"availability".to_string(),
format!("{:.3}%", self.performance_metrics.availability * 100.0),
);
let num_active = self.active_connections.iter().filter(|&&x| x).count();
summary.insert("active_connections".to_string(), num_active.to_string());
summary
}
fn metrics(&self) -> HashMap<String, f64> {
let mut metrics = HashMap::new();
metrics.insert("total_cost".to_string(), self.total_cost);
metrics.insert("connectivity".to_string(), self.connectivity);
metrics.insert("average_latency".to_string(), self.average_latency);
metrics.insert(
"throughput".to_string(),
self.performance_metrics.throughput,
);
metrics.insert(
"packet_loss_rate".to_string(),
self.performance_metrics.packet_loss_rate,
);
metrics.insert("jitter".to_string(), self.performance_metrics.jitter);
metrics.insert(
"availability".to_string(),
self.performance_metrics.availability,
);
metrics.insert("mtbf".to_string(), self.performance_metrics.mtbf);
metrics.insert(
"coverage_area".to_string(),
self.performance_metrics.coverage_area,
);
let num_active = self.active_connections.iter().filter(|&&x| x).count();
metrics.insert("active_connections".to_string(), num_active as f64);
metrics
}
fn export_format(&self) -> ApplicationResult<String> {
let mut output = String::new();
output.push_str("# Network Topology Optimization Report\n\n");
output.push_str("## Network Summary\n");
writeln!(output, "Total Cost: ${:.2}", self.total_cost)
.expect("writing to String is infallible");
writeln!(output, "Connectivity: {:.1}%", self.connectivity * 100.0)
.expect("writing to String is infallible");
writeln!(output, "Average Latency: {:.1} ms", self.average_latency)
.expect("writing to String is infallible");
output.push_str("\n## Performance Metrics\n");
write!(
output,
"Throughput: {:.1} Gbps\n",
self.performance_metrics.throughput
)
.expect("writing to String is infallible");
write!(
output,
"Packet Loss Rate: {:.3}%\n",
self.performance_metrics.packet_loss_rate * 100.0
)
.expect("writing to String is infallible");
write!(
output,
"Jitter: {:.1} ms\n",
self.performance_metrics.jitter
)
.expect("writing to String is infallible");
write!(
output,
"Availability: {:.3}%\n",
self.performance_metrics.availability * 100.0
)
.expect("writing to String is infallible");
writeln!(output, "MTBF: {:.1} hours", self.performance_metrics.mtbf)
.expect("writing to String is infallible");
write!(
output,
"Coverage Area: {:.1} km²\n",
self.performance_metrics.coverage_area
)
.expect("writing to String is infallible");
output.push_str("\n## Active Connections\n");
for (i, &active) in self.active_connections.iter().enumerate() {
if active {
writeln!(output, "Connection {}: Active", i + 1)
.expect("writing to String is infallible");
}
}
Ok(output)
}
}
#[derive(Debug, Clone)]
pub struct SpectrumAllocation {
pub num_bands: usize,
pub num_regions: usize,
pub interference_matrix: Vec<Vec<f64>>,
pub service_demands: Vec<Vec<f64>>,
pub band_availability: Vec<Vec<bool>>,
pub regulatory_constraints: Vec<IndustryConstraint>,
pub qos_requirements: Vec<f64>,
}
impl SpectrumAllocation {
pub fn new(
num_bands: usize,
num_regions: usize,
interference_matrix: Vec<Vec<f64>>,
service_demands: Vec<Vec<f64>>,
) -> ApplicationResult<Self> {
if interference_matrix.len() != num_bands {
return Err(ApplicationError::InvalidConfiguration(
"Interference matrix dimension mismatch".to_string(),
));
}
Ok(Self {
num_bands,
num_regions,
interference_matrix,
service_demands,
band_availability: vec![vec![true; num_bands]; num_regions],
regulatory_constraints: Vec::new(),
qos_requirements: vec![0.95; num_regions],
})
}
#[must_use]
pub fn calculate_interference(&self, allocation: &SpectrumSolution) -> f64 {
let mut total_interference = 0.0;
for region in 0..self.num_regions {
for band1 in 0..self.num_bands {
for band2 in 0..self.num_bands {
if band1 != band2
&& allocation.band_assignments[region][band1]
&& allocation.band_assignments[region][band2]
{
total_interference += self.interference_matrix[band1][band2];
}
}
}
}
total_interference
}
}
#[derive(Debug, Clone)]
pub struct SpectrumSolution {
pub band_assignments: Vec<Vec<bool>>,
pub total_interference: f64,
pub service_coverage: Vec<f64>,
pub spectrum_efficiency: f64,
}
#[derive(Debug, Clone)]
pub struct BinaryNetworkTopologyOptimization {
inner: NetworkTopologyOptimization,
}
impl BinaryNetworkTopologyOptimization {
#[must_use]
pub const fn new(inner: NetworkTopologyOptimization) -> Self {
Self { inner }
}
}
impl OptimizationProblem for BinaryNetworkTopologyOptimization {
type Solution = Vec<i8>;
type ObjectiveValue = f64;
fn description(&self) -> String {
self.inner.description()
}
fn size_metrics(&self) -> HashMap<String, usize> {
self.inner.size_metrics()
}
fn validate(&self) -> ApplicationResult<()> {
self.inner.validate()
}
fn to_qubo(&self) -> ApplicationResult<(crate::ising::QuboModel, HashMap<String, usize>)> {
self.inner.to_qubo()
}
fn evaluate_solution(
&self,
solution: &Self::Solution,
) -> ApplicationResult<Self::ObjectiveValue> {
let topology_solution = NetworkTopology::from_binary(&self.inner, solution)?;
self.inner.evaluate_solution(&topology_solution)
}
fn is_feasible(&self, solution: &Self::Solution) -> bool {
if let Ok(topology_solution) = NetworkTopology::from_binary(&self.inner, solution) {
self.inner.is_feasible(&topology_solution)
} else {
false
}
}
}
pub fn create_benchmark_problems(
size: usize,
) -> ApplicationResult<Vec<Box<dyn OptimizationProblem<Solution = Vec<i8>, ObjectiveValue = f64>>>>
{
let mut problems = Vec::new();
let potential_connections = vec![(0, 1), (0, 2), (1, 2), (1, 3), (2, 3)];
let connection_costs = vec![10.0, 15.0, 8.0, 12.0, 9.0];
let traffic_demands = vec![
vec![0.0, 5.0, 3.0, 2.0],
vec![5.0, 0.0, 4.0, 6.0],
vec![3.0, 4.0, 0.0, 3.0],
vec![2.0, 6.0, 3.0, 0.0],
];
let network_problem = NetworkTopologyOptimization::new(
4, potential_connections,
connection_costs,
traffic_demands,
)?;
problems.push(
Box::new(BinaryNetworkTopologyOptimization::new(network_problem))
as Box<dyn OptimizationProblem<Solution = Vec<i8>, ObjectiveValue = f64>>,
);
if size >= 6 {
let mut large_connections = Vec::new();
let mut large_costs = Vec::new();
for i in 0..size {
for j in (i + 1)..size {
large_connections.push((i, j));
large_costs.push(((i + j) as f64).mul_add(1.5, 5.0));
}
}
let large_demands = vec![vec![2.0; size]; size];
let large_network =
NetworkTopologyOptimization::new(size, large_connections, large_costs, large_demands)?;
problems.push(
Box::new(BinaryNetworkTopologyOptimization::new(large_network))
as Box<dyn OptimizationProblem<Solution = Vec<i8>, ObjectiveValue = f64>>,
);
}
Ok(problems)
}
pub fn solve_network_topology(
problem: &NetworkTopologyOptimization,
params: Option<AnnealingParams>,
) -> ApplicationResult<NetworkTopology> {
let (qubo, _var_map) = problem.to_qubo()?;
let ising = IsingModel::from_qubo(&qubo);
let annealing_params = params.unwrap_or_else(|| {
let mut p = AnnealingParams::default();
p.num_sweeps = 25_000;
p.num_repetitions = 40;
p.initial_temperature = 5.0;
p.final_temperature = 0.001;
p
});
let simulator = ClassicalAnnealingSimulator::new(annealing_params)
.map_err(|e| ApplicationError::OptimizationError(e.to_string()))?;
let result = simulator
.solve(&ising)
.map_err(|e| ApplicationError::OptimizationError(e.to_string()))?;
NetworkTopology::from_binary(problem, &result.best_spins)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_network_topology_creation() {
let connections = vec![(0, 1), (1, 2), (0, 2)];
let costs = vec![10.0, 15.0, 12.0];
let demands = vec![
vec![0.0, 5.0, 3.0],
vec![5.0, 0.0, 4.0],
vec![3.0, 4.0, 0.0],
];
let network = NetworkTopologyOptimization::new(3, connections, costs, demands)
.expect("failed to create network topology in test");
assert_eq!(network.num_nodes, 3);
assert_eq!(network.potential_connections.len(), 3);
}
#[test]
fn test_network_connectivity_calculation() {
let connections = vec![(0, 1), (1, 2)];
let costs = vec![10.0, 15.0];
let demands = vec![
vec![0.0, 1.0, 0.0],
vec![1.0, 0.0, 1.0],
vec![0.0, 1.0, 0.0],
];
let network = NetworkTopologyOptimization::new(3, connections, costs, demands)
.expect("failed to create network topology in test");
let topology = NetworkTopology {
active_connections: vec![true, true],
total_cost: 25.0,
connectivity: 0.0,
average_latency: 0.0,
performance_metrics: TelecomMetrics {
throughput: 0.0,
packet_loss_rate: 0.0,
jitter: 0.0,
availability: 0.0,
mtbf: 0.0,
coverage_area: 0.0,
},
};
let connectivity = network.calculate_connectivity(&topology);
assert_eq!(connectivity, 1.0); }
#[test]
fn test_network_cost_calculation() {
let connections = vec![(0, 1), (1, 2)];
let costs = vec![10.0, 15.0];
let demands = vec![vec![0.0; 3]; 3];
let network = NetworkTopologyOptimization::new(3, connections, costs, demands)
.expect("failed to create network topology in test");
let topology = NetworkTopology {
active_connections: vec![true, false],
total_cost: 0.0,
connectivity: 0.0,
average_latency: 0.0,
performance_metrics: TelecomMetrics {
throughput: 0.0,
packet_loss_rate: 0.0,
jitter: 0.0,
availability: 0.0,
mtbf: 0.0,
coverage_area: 0.0,
},
};
let cost = network.calculate_total_cost(&topology);
assert_eq!(cost, 10.0);
}
#[test]
fn test_spectrum_allocation_creation() {
let interference_matrix = vec![
vec![0.0, 0.3, 0.1],
vec![0.3, 0.0, 0.4],
vec![0.1, 0.4, 0.0],
];
let demands = vec![vec![1.0, 2.0, 3.0], vec![2.0, 1.0, 2.0]];
let spectrum = SpectrumAllocation::new(3, 2, interference_matrix, demands)
.expect("failed to create spectrum allocation in test");
assert_eq!(spectrum.num_bands, 3);
assert_eq!(spectrum.num_regions, 2);
}
#[test]
fn test_network_validation() {
let connections = vec![(0, 1), (1, 2)];
let costs = vec![10.0, 15.0];
let demands = vec![vec![0.0; 3]; 3];
let network = NetworkTopologyOptimization::new(3, connections, costs, demands.clone())
.expect("failed to create network topology in test");
assert!(network.validate().is_ok());
let invalid_connections = vec![(0, 1), (1, 5)]; let invalid_costs = vec![10.0, 15.0];
let invalid_network =
NetworkTopologyOptimization::new(3, invalid_connections, invalid_costs, demands);
assert!(invalid_network.is_ok()); assert!(invalid_network
.expect("failed to create invalid network in test")
.validate()
.is_err()); }
#[test]
fn test_benchmark_problems() {
let problems =
create_benchmark_problems(6).expect("failed to create benchmark problems in test");
assert_eq!(problems.len(), 2);
for problem in &problems {
assert!(problem.validate().is_ok());
}
}
}