rust_igraph/algorithms/properties/
flow_ratios.rs1#![allow(
13 clippy::cast_lossless,
14 clippy::cast_possible_truncation,
15 clippy::cast_precision_loss,
16 clippy::many_single_char_names,
17 clippy::needless_range_loop,
18 clippy::similar_names,
19 clippy::too_many_lines
20)]
21
22use crate::core::{Graph, IgraphResult};
23
24pub fn max_flow_efficiency(graph: &Graph) -> IgraphResult<f64> {
46 let n = graph.vcount() as usize;
47 if n < 2 {
48 return Ok(0.0);
49 }
50
51 let mut degrees = Vec::with_capacity(n);
52 let mut min_deg = usize::MAX;
53 for v in 0..n {
54 let d = graph.degree(v as u32)?;
55 degrees.push(d);
56 if d < min_deg {
57 min_deg = d;
58 }
59 }
60
61 if min_deg == 0 {
62 return Ok(0.0);
63 }
64
65 let mut sum_min_deg = 0_u64;
66 let mut pairs = 0_u64;
67 for v in 0..n {
68 for u in (v + 1)..n {
69 sum_min_deg += degrees[v].min(degrees[u]) as u64;
70 pairs += 1;
71 }
72 }
73
74 if pairs == 0 {
75 return Ok(0.0);
76 }
77
78 let avg_min_deg = sum_min_deg as f64 / pairs as f64;
79 Ok(avg_min_deg / min_deg as f64)
80}
81
82pub fn bottleneck_ratio(graph: &Graph) -> IgraphResult<f64> {
100 let n = graph.vcount() as usize;
101 if n < 2 {
102 return Ok(0.0);
103 }
104
105 let m = graph.ecount();
106 if m == 0 {
107 return Ok(0.0);
108 }
109
110 let betweenness = edge_betweenness(graph, n)?;
111 if betweenness.is_empty() {
112 return Ok(0.0);
113 }
114
115 let mut min_b = f64::MAX;
116 let mut max_b = 0.0_f64;
117 for &b in &betweenness {
118 if b < min_b {
119 min_b = b;
120 }
121 if b > max_b {
122 max_b = b;
123 }
124 }
125
126 if max_b < 1e-30 {
127 return Ok(0.0);
128 }
129
130 Ok(min_b / max_b)
131}
132
133pub fn flow_hierarchy_ratio(graph: &Graph) -> IgraphResult<f64> {
151 let n = graph.vcount() as usize;
152 if n < 2 {
153 return Ok(0.0);
154 }
155
156 let m = graph.ecount();
157 if m == 0 {
158 return Ok(0.0);
159 }
160
161 let mut visited = vec![false; n];
163 visited[0] = true;
164 let mut queue = std::collections::VecDeque::new();
165 queue.push_back(0_usize);
166 let mut visit_count = 1_usize;
167
168 while let Some(v) = queue.pop_front() {
169 let nbrs = graph.neighbors(v as u32)?;
170 for &u in &nbrs {
171 let ui = u as usize;
172 if !visited[ui] {
173 visited[ui] = true;
174 visit_count += 1;
175 queue.push_back(ui);
176 }
177 }
178 }
179
180 if visit_count < n {
181 return Ok(0.0);
182 }
183
184 Ok((n - 1) as f64 / m as f64)
186}
187
188fn edge_betweenness(graph: &Graph, n: usize) -> IgraphResult<Vec<f64>> {
190 let mut bet_map: std::collections::HashMap<(u32, u32), f64> = std::collections::HashMap::new();
192
193 for s in 0..n {
194 let mut dist = vec![u32::MAX; n];
196 let mut sigma = vec![0_u64; n]; let mut pred: Vec<Vec<usize>> = vec![Vec::new(); n];
198 let mut order = Vec::new();
199
200 dist[s] = 0;
201 sigma[s] = 1;
202 let mut queue = std::collections::VecDeque::new();
203 queue.push_back(s);
204
205 while let Some(v) = queue.pop_front() {
206 order.push(v);
207 let nbrs = graph.neighbors(v as u32)?;
208 for &u in &nbrs {
209 let ui = u as usize;
210 if dist[ui] == u32::MAX {
211 dist[ui] = dist[v] + 1;
212 queue.push_back(ui);
213 }
214 if dist[ui] == dist[v] + 1 {
215 sigma[ui] += sigma[v];
216 pred[ui].push(v);
217 }
218 }
219 }
220
221 let mut delta = vec![0.0_f64; n];
223 for &w in order.iter().rev() {
224 for &v in &pred[w] {
225 let coeff = (sigma[v] as f64 / sigma[w] as f64) * (1.0 + delta[w]);
226 let e = (v.min(w) as u32, v.max(w) as u32);
227 *bet_map.entry(e).or_insert(0.0) += coeff;
228 delta[v] += coeff;
229 }
230 }
231 }
232
233 Ok(bet_map.into_values().collect())
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239
240 fn empty() -> Graph {
241 Graph::with_vertices(0)
242 }
243
244 fn single() -> Graph {
245 Graph::with_vertices(1)
246 }
247
248 fn single_edge() -> Graph {
249 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
250 }
251
252 fn path3() -> Graph {
253 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
254 }
255
256 fn path4() -> Graph {
257 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
258 }
259
260 fn k3() -> Graph {
261 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
262 }
263
264 fn k4() -> Graph {
265 Graph::from_edges(
266 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
267 false,
268 Some(4),
269 )
270 .unwrap()
271 }
272
273 fn cycle4() -> Graph {
274 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
275 }
276
277 fn star5() -> Graph {
278 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
279 }
280
281 fn paw() -> Graph {
282 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
283 }
284
285 #[test]
288 fn mfe_empty() {
289 assert!(max_flow_efficiency(&empty()).unwrap().abs() < 1e-10);
290 }
291
292 #[test]
293 fn mfe_single() {
294 assert!(max_flow_efficiency(&single()).unwrap().abs() < 1e-10);
295 }
296
297 #[test]
298 fn mfe_k3() {
299 assert!((max_flow_efficiency(&k3()).unwrap() - 1.0).abs() < 1e-10);
301 }
302
303 #[test]
304 fn mfe_k4() {
305 assert!((max_flow_efficiency(&k4()).unwrap() - 1.0).abs() < 1e-10);
306 }
307
308 #[test]
309 fn mfe_cycle4() {
310 assert!((max_flow_efficiency(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
311 }
312
313 #[test]
314 fn mfe_star5() {
315 assert!((max_flow_efficiency(&star5()).unwrap() - 1.0).abs() < 1e-10);
319 }
320
321 #[test]
322 fn mfe_ge_1() {
323 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
324 let r = max_flow_efficiency(g).unwrap();
325 assert!(r >= 1.0 - 1e-10);
326 }
327 }
328
329 #[test]
332 fn br_empty() {
333 assert!(bottleneck_ratio(&empty()).unwrap().abs() < 1e-10);
334 }
335
336 #[test]
337 fn br_single() {
338 assert!(bottleneck_ratio(&single()).unwrap().abs() < 1e-10);
339 }
340
341 #[test]
342 fn br_k3() {
343 assert!((bottleneck_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
345 }
346
347 #[test]
348 fn br_k4() {
349 assert!((bottleneck_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
350 }
351
352 #[test]
353 fn br_cycle4() {
354 assert!((bottleneck_ratio(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
356 }
357
358 #[test]
359 fn br_in_01() {
360 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
361 let r = bottleneck_ratio(g).unwrap();
362 assert!(r >= -1e-10);
363 assert!(r <= 1.0 + 1e-10);
364 }
365 }
366
367 #[test]
370 fn fhr_empty() {
371 assert!(flow_hierarchy_ratio(&empty()).unwrap().abs() < 1e-10);
372 }
373
374 #[test]
375 fn fhr_single() {
376 assert!(flow_hierarchy_ratio(&single()).unwrap().abs() < 1e-10);
377 }
378
379 #[test]
380 fn fhr_path3() {
381 assert!((flow_hierarchy_ratio(&path3()).unwrap() - 1.0).abs() < 1e-10);
383 }
384
385 #[test]
386 fn fhr_path4() {
387 assert!((flow_hierarchy_ratio(&path4()).unwrap() - 1.0).abs() < 1e-10);
388 }
389
390 #[test]
391 fn fhr_star5() {
392 assert!((flow_hierarchy_ratio(&star5()).unwrap() - 1.0).abs() < 1e-10);
394 }
395
396 #[test]
397 fn fhr_k3() {
398 assert!((flow_hierarchy_ratio(&k3()).unwrap() - 2.0 / 3.0).abs() < 1e-10);
400 }
401
402 #[test]
403 fn fhr_k4() {
404 assert!((flow_hierarchy_ratio(&k4()).unwrap() - 0.5).abs() < 1e-10);
406 }
407
408 #[test]
409 fn fhr_cycle4() {
410 assert!((flow_hierarchy_ratio(&cycle4()).unwrap() - 0.75).abs() < 1e-10);
412 }
413
414 #[test]
415 fn fhr_in_01() {
416 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
417 let r = flow_hierarchy_ratio(g).unwrap();
418 assert!(r >= -1e-10);
419 assert!(r <= 1.0 + 1e-10);
420 }
421 }
422
423 #[test]
426 fn regular_unit_bottleneck() {
427 assert!((bottleneck_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
428 assert!((bottleneck_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
429 assert!((bottleneck_ratio(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
430 }
431
432 #[test]
433 fn trees_unit_hierarchy() {
434 assert!((flow_hierarchy_ratio(&path3()).unwrap() - 1.0).abs() < 1e-10);
435 assert!((flow_hierarchy_ratio(&path4()).unwrap() - 1.0).abs() < 1e-10);
436 assert!((flow_hierarchy_ratio(&star5()).unwrap() - 1.0).abs() < 1e-10);
437 }
438}