#![allow(dead_code)]
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum WeightType {
Bandwidth,
LatencyUs,
Cost,
LossProbability,
}
impl WeightType {
pub fn unit(&self) -> &'static str {
match self {
WeightType::Bandwidth => "bps",
WeightType::LatencyUs => "µs",
WeightType::Cost => "",
WeightType::LossProbability => "",
}
}
}
#[derive(Debug, Clone)]
pub struct EdgeWeight {
weight_type: WeightType,
value: f64,
bottleneck_threshold: f64,
}
impl EdgeWeight {
pub fn new(weight_type: WeightType, value: f64, bottleneck_threshold: f64) -> Self {
Self {
weight_type,
value,
bottleneck_threshold,
}
}
pub fn weight_type(&self) -> &WeightType {
&self.weight_type
}
pub fn value(&self) -> f64 {
self.value
}
pub fn is_bottleneck(&self) -> bool {
self.value > self.bottleneck_threshold
}
pub fn set_value(&mut self, value: f64) {
self.value = value;
}
}
#[derive(Debug, Clone)]
pub struct WeightedEdge {
from: u64,
to: u64,
bandwidth_bps: f64,
weights: Vec<EdgeWeight>,
}
impl WeightedEdge {
pub fn new(from: u64, to: u64, bandwidth_bps: f64) -> Self {
Self {
from,
to,
bandwidth_bps,
weights: Vec::new(),
}
}
pub fn from(&self) -> u64 {
self.from
}
pub fn to(&self) -> u64 {
self.to
}
pub fn bandwidth_bps(&self) -> f64 {
self.bandwidth_bps
}
pub fn bandwidth_ratio(&self, total_bps: f64) -> f64 {
if total_bps <= 0.0 {
return 0.0;
}
self.bandwidth_bps / total_bps
}
pub fn add_weight(&mut self, weight: EdgeWeight) {
self.weights.push(weight);
}
pub fn weights(&self) -> &[EdgeWeight] {
&self.weights
}
pub fn has_bottleneck(&self) -> bool {
self.weights.iter().any(|w| w.is_bottleneck())
}
}
#[derive(Debug, Clone, Default)]
pub struct EdgeWeightMap {
edges: HashMap<(u64, u64), WeightedEdge>,
}
impl EdgeWeightMap {
pub fn new() -> Self {
Self {
edges: HashMap::new(),
}
}
pub fn insert(&mut self, edge: WeightedEdge) {
self.edges.insert((edge.from(), edge.to()), edge);
}
pub fn get(&self, from: u64, to: u64) -> Option<&WeightedEdge> {
self.edges.get(&(from, to))
}
pub fn min_weight(&self) -> Option<&WeightedEdge> {
self.edges.values().min_by(|a, b| {
a.bandwidth_bps()
.partial_cmp(&b.bandwidth_bps())
.unwrap_or(std::cmp::Ordering::Equal)
})
}
pub fn len(&self) -> usize {
self.edges.len()
}
pub fn is_empty(&self) -> bool {
self.edges.is_empty()
}
pub fn bottleneck_edges(&self) -> Vec<&WeightedEdge> {
self.edges.values().filter(|e| e.has_bottleneck()).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_weight_type_unit_bandwidth() {
assert_eq!(WeightType::Bandwidth.unit(), "bps");
}
#[test]
fn test_weight_type_unit_latency() {
assert_eq!(WeightType::LatencyUs.unit(), "µs");
}
#[test]
fn test_weight_type_unit_cost_empty() {
assert_eq!(WeightType::Cost.unit(), "");
}
#[test]
fn test_edge_weight_not_bottleneck() {
let w = EdgeWeight::new(WeightType::Bandwidth, 100.0, 1000.0);
assert!(!w.is_bottleneck());
}
#[test]
fn test_edge_weight_is_bottleneck() {
let w = EdgeWeight::new(WeightType::LatencyUs, 2000.0, 1000.0);
assert!(w.is_bottleneck());
}
#[test]
fn test_edge_weight_set_value() {
let mut w = EdgeWeight::new(WeightType::Cost, 5.0, 10.0);
w.set_value(15.0);
assert!(w.is_bottleneck());
}
#[test]
fn test_weighted_edge_bandwidth_ratio() {
let e = WeightedEdge::new(0, 1, 500_000.0);
assert!((e.bandwidth_ratio(1_000_000.0) - 0.5).abs() < 1e-9);
}
#[test]
fn test_weighted_edge_bandwidth_ratio_zero_total() {
let e = WeightedEdge::new(0, 1, 500_000.0);
assert_eq!(e.bandwidth_ratio(0.0), 0.0);
}
#[test]
fn test_weighted_edge_has_bottleneck_false() {
let e = WeightedEdge::new(0, 1, 1_000_000.0);
assert!(!e.has_bottleneck());
}
#[test]
fn test_weighted_edge_has_bottleneck_true() {
let mut e = WeightedEdge::new(0, 1, 1_000_000.0);
e.add_weight(EdgeWeight::new(WeightType::LatencyUs, 5000.0, 1000.0));
assert!(e.has_bottleneck());
}
#[test]
fn test_edge_weight_map_insert_and_get() {
let mut map = EdgeWeightMap::new();
map.insert(WeightedEdge::new(0, 1, 100_000.0));
assert!(map.get(0, 1).is_some());
assert!(map.get(1, 0).is_none());
}
#[test]
fn test_edge_weight_map_min_weight() {
let mut map = EdgeWeightMap::new();
map.insert(WeightedEdge::new(0, 1, 200_000.0));
map.insert(WeightedEdge::new(1, 2, 50_000.0));
let min = map.min_weight().expect("min_weight should succeed");
assert!((min.bandwidth_bps() - 50_000.0).abs() < 1.0);
}
#[test]
fn test_edge_weight_map_empty() {
let map = EdgeWeightMap::new();
assert!(map.is_empty());
assert!(map.min_weight().is_none());
}
#[test]
fn test_edge_weight_map_bottleneck_edges() {
let mut map = EdgeWeightMap::new();
let mut e = WeightedEdge::new(0, 1, 1_000_000.0);
e.add_weight(EdgeWeight::new(WeightType::Cost, 999.0, 100.0));
map.insert(e);
map.insert(WeightedEdge::new(2, 3, 500_000.0));
assert_eq!(map.bottleneck_edges().len(), 1);
}
}