oximedia_graph/
edge_weight.rs1#![allow(dead_code)]
7
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
12pub enum WeightType {
13 Bandwidth,
15 LatencyUs,
17 Cost,
19 LossProbability,
21}
22
23impl WeightType {
24 pub fn unit(&self) -> &'static str {
26 match self {
27 WeightType::Bandwidth => "bps",
28 WeightType::LatencyUs => "µs",
29 WeightType::Cost => "",
30 WeightType::LossProbability => "",
31 }
32 }
33}
34
35#[derive(Debug, Clone)]
37pub struct EdgeWeight {
38 weight_type: WeightType,
40 value: f64,
42 bottleneck_threshold: f64,
44}
45
46impl EdgeWeight {
47 pub fn new(weight_type: WeightType, value: f64, bottleneck_threshold: f64) -> Self {
49 Self {
50 weight_type,
51 value,
52 bottleneck_threshold,
53 }
54 }
55
56 pub fn weight_type(&self) -> &WeightType {
58 &self.weight_type
59 }
60
61 pub fn value(&self) -> f64 {
63 self.value
64 }
65
66 pub fn is_bottleneck(&self) -> bool {
68 self.value > self.bottleneck_threshold
69 }
70
71 pub fn set_value(&mut self, value: f64) {
73 self.value = value;
74 }
75}
76
77#[derive(Debug, Clone)]
79pub struct WeightedEdge {
80 from: u64,
82 to: u64,
84 bandwidth_bps: f64,
86 weights: Vec<EdgeWeight>,
88}
89
90impl WeightedEdge {
91 pub fn new(from: u64, to: u64, bandwidth_bps: f64) -> Self {
94 Self {
95 from,
96 to,
97 bandwidth_bps,
98 weights: Vec::new(),
99 }
100 }
101
102 pub fn from(&self) -> u64 {
104 self.from
105 }
106
107 pub fn to(&self) -> u64 {
109 self.to
110 }
111
112 pub fn bandwidth_bps(&self) -> f64 {
114 self.bandwidth_bps
115 }
116
117 pub fn bandwidth_ratio(&self, total_bps: f64) -> f64 {
120 if total_bps <= 0.0 {
121 return 0.0;
122 }
123 self.bandwidth_bps / total_bps
124 }
125
126 pub fn add_weight(&mut self, weight: EdgeWeight) {
128 self.weights.push(weight);
129 }
130
131 pub fn weights(&self) -> &[EdgeWeight] {
133 &self.weights
134 }
135
136 pub fn has_bottleneck(&self) -> bool {
138 self.weights.iter().any(|w| w.is_bottleneck())
139 }
140}
141
142#[derive(Debug, Clone, Default)]
144pub struct EdgeWeightMap {
145 edges: HashMap<(u64, u64), WeightedEdge>,
146}
147
148impl EdgeWeightMap {
149 pub fn new() -> Self {
151 Self {
152 edges: HashMap::new(),
153 }
154 }
155
156 pub fn insert(&mut self, edge: WeightedEdge) {
158 self.edges.insert((edge.from(), edge.to()), edge);
159 }
160
161 pub fn get(&self, from: u64, to: u64) -> Option<&WeightedEdge> {
163 self.edges.get(&(from, to))
164 }
165
166 pub fn min_weight(&self) -> Option<&WeightedEdge> {
168 self.edges.values().min_by(|a, b| {
169 a.bandwidth_bps()
170 .partial_cmp(&b.bandwidth_bps())
171 .unwrap_or(std::cmp::Ordering::Equal)
172 })
173 }
174
175 pub fn len(&self) -> usize {
177 self.edges.len()
178 }
179
180 pub fn is_empty(&self) -> bool {
182 self.edges.is_empty()
183 }
184
185 pub fn bottleneck_edges(&self) -> Vec<&WeightedEdge> {
187 self.edges.values().filter(|e| e.has_bottleneck()).collect()
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
196 fn test_weight_type_unit_bandwidth() {
197 assert_eq!(WeightType::Bandwidth.unit(), "bps");
198 }
199
200 #[test]
201 fn test_weight_type_unit_latency() {
202 assert_eq!(WeightType::LatencyUs.unit(), "µs");
203 }
204
205 #[test]
206 fn test_weight_type_unit_cost_empty() {
207 assert_eq!(WeightType::Cost.unit(), "");
208 }
209
210 #[test]
211 fn test_edge_weight_not_bottleneck() {
212 let w = EdgeWeight::new(WeightType::Bandwidth, 100.0, 1000.0);
213 assert!(!w.is_bottleneck());
214 }
215
216 #[test]
217 fn test_edge_weight_is_bottleneck() {
218 let w = EdgeWeight::new(WeightType::LatencyUs, 2000.0, 1000.0);
219 assert!(w.is_bottleneck());
220 }
221
222 #[test]
223 fn test_edge_weight_set_value() {
224 let mut w = EdgeWeight::new(WeightType::Cost, 5.0, 10.0);
225 w.set_value(15.0);
226 assert!(w.is_bottleneck());
227 }
228
229 #[test]
230 fn test_weighted_edge_bandwidth_ratio() {
231 let e = WeightedEdge::new(0, 1, 500_000.0);
232 assert!((e.bandwidth_ratio(1_000_000.0) - 0.5).abs() < 1e-9);
233 }
234
235 #[test]
236 fn test_weighted_edge_bandwidth_ratio_zero_total() {
237 let e = WeightedEdge::new(0, 1, 500_000.0);
238 assert_eq!(e.bandwidth_ratio(0.0), 0.0);
239 }
240
241 #[test]
242 fn test_weighted_edge_has_bottleneck_false() {
243 let e = WeightedEdge::new(0, 1, 1_000_000.0);
244 assert!(!e.has_bottleneck());
245 }
246
247 #[test]
248 fn test_weighted_edge_has_bottleneck_true() {
249 let mut e = WeightedEdge::new(0, 1, 1_000_000.0);
250 e.add_weight(EdgeWeight::new(WeightType::LatencyUs, 5000.0, 1000.0));
251 assert!(e.has_bottleneck());
252 }
253
254 #[test]
255 fn test_edge_weight_map_insert_and_get() {
256 let mut map = EdgeWeightMap::new();
257 map.insert(WeightedEdge::new(0, 1, 100_000.0));
258 assert!(map.get(0, 1).is_some());
259 assert!(map.get(1, 0).is_none());
260 }
261
262 #[test]
263 fn test_edge_weight_map_min_weight() {
264 let mut map = EdgeWeightMap::new();
265 map.insert(WeightedEdge::new(0, 1, 200_000.0));
266 map.insert(WeightedEdge::new(1, 2, 50_000.0));
267 let min = map.min_weight().expect("min_weight should succeed");
268 assert!((min.bandwidth_bps() - 50_000.0).abs() < 1.0);
269 }
270
271 #[test]
272 fn test_edge_weight_map_empty() {
273 let map = EdgeWeightMap::new();
274 assert!(map.is_empty());
275 assert!(map.min_weight().is_none());
276 }
277
278 #[test]
279 fn test_edge_weight_map_bottleneck_edges() {
280 let mut map = EdgeWeightMap::new();
281 let mut e = WeightedEdge::new(0, 1, 1_000_000.0);
282 e.add_weight(EdgeWeight::new(WeightType::Cost, 999.0, 100.0));
283 map.insert(e);
284 map.insert(WeightedEdge::new(2, 3, 500_000.0));
285 assert_eq!(map.bottleneck_edges().len(), 1);
286 }
287}