rust_igraph/algorithms/properties/
resilience_ratios.rs1#![allow(
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 vertex_conn_ratio(graph: &Graph) -> IgraphResult<f64> {
42 let n = graph.vcount() as usize;
43 if n < 2 {
44 return Ok(0.0);
45 }
46
47 if !is_connected_bfs(graph)? {
48 return Ok(0.0);
49 }
50
51 let mut min_deg = usize::MAX;
52 for v in 0..n {
53 let d = graph.degree(v as u32)?;
54 if d < min_deg {
55 min_deg = d;
56 }
57 }
58
59 Ok(min_deg as f64 / (n - 1) as f64)
60}
61
62pub fn edge_conn_ratio(graph: &Graph) -> IgraphResult<f64> {
81 let n = graph.vcount() as usize;
82 if n < 2 {
83 return Ok(0.0);
84 }
85
86 if !is_connected_bfs(graph)? {
87 return Ok(0.0);
88 }
89
90 let mut min_deg = usize::MAX;
91 for v in 0..n {
92 let d = graph.degree(v as u32)?;
93 if d < min_deg {
94 min_deg = d;
95 }
96 }
97
98 Ok(min_deg as f64 / (n - 1) as f64)
99}
100
101pub fn diameter_vulnerability(graph: &Graph) -> IgraphResult<f64> {
118 let n = graph.vcount() as usize;
119 if n < 3 {
120 return Ok(0.0);
121 }
122
123 let orig_diam = compute_diameter(graph)?;
124 if orig_diam == 0 || orig_diam == u32::MAX {
125 return Ok(0.0);
126 }
127
128 let mut max_increase = 0_i64;
129
130 for removed in 0..n {
131 let sub_diam = compute_diameter_without(graph, removed)?;
132 if sub_diam == u32::MAX {
133 let increase = i64::from(orig_diam);
136 if increase > max_increase {
137 max_increase = increase;
138 }
139 } else {
140 let increase = i64::from(sub_diam) - i64::from(orig_diam);
141 if increase > max_increase {
142 max_increase = increase;
143 }
144 }
145 }
146
147 if max_increase <= 0 {
148 return Ok(0.0);
149 }
150
151 Ok(max_increase as f64 / f64::from(orig_diam))
152}
153
154pub fn neighbor_degree_disparity(graph: &Graph) -> IgraphResult<f64> {
172 let n = graph.vcount() as usize;
173 if n == 0 {
174 return Ok(0.0);
175 }
176
177 let mut degrees = Vec::with_capacity(n);
178 for v in 0..n {
179 degrees.push(graph.degree(v as u32)?);
180 }
181
182 let mut sum = 0.0_f64;
183 let mut count = 0_usize;
184
185 for v in 0..n {
186 let dv = degrees[v];
187 if dv == 0 {
188 continue;
189 }
190
191 let neighbors = graph.neighbors(v as u32)?;
192 let knn: f64 = neighbors
193 .iter()
194 .map(|&u| degrees[u as usize] as f64)
195 .sum::<f64>()
196 / dv as f64;
197 sum += knn / dv as f64;
198 count += 1;
199 }
200
201 if count == 0 {
202 return Ok(0.0);
203 }
204
205 Ok(sum / count as f64)
206}
207
208fn is_connected_bfs(graph: &Graph) -> IgraphResult<bool> {
209 let n = graph.vcount() as usize;
210 if n <= 1 {
211 return Ok(true);
212 }
213
214 let mut visited = vec![false; n];
215 visited[0] = true;
216 let mut queue = std::collections::VecDeque::new();
217 queue.push_back(0_usize);
218 let mut seen = 1_usize;
219
220 while let Some(v) = queue.pop_front() {
221 let neighbors = graph.neighbors(v as u32)?;
222 for &u in &neighbors {
223 let ui = u as usize;
224 if !visited[ui] {
225 visited[ui] = true;
226 seen += 1;
227 queue.push_back(ui);
228 }
229 }
230 }
231
232 Ok(seen == n)
233}
234
235fn compute_diameter(graph: &Graph) -> IgraphResult<u32> {
236 let n = graph.vcount() as usize;
237 if n < 2 {
238 return Ok(0);
239 }
240
241 let mut max_d = 0_u32;
242 for s in 0..n {
243 let mut dist = vec![u32::MAX; n];
244 dist[s] = 0;
245 let mut queue = std::collections::VecDeque::new();
246 queue.push_back(s);
247 while let Some(v) = queue.pop_front() {
248 let d = dist[v];
249 let neighbors = graph.neighbors(v as u32)?;
250 for &u in &neighbors {
251 let ui = u as usize;
252 if dist[ui] == u32::MAX {
253 dist[ui] = d + 1;
254 queue.push_back(ui);
255 }
256 }
257 }
258 for u in 0..n {
259 if dist[u] == u32::MAX {
260 return Ok(u32::MAX);
261 }
262 if dist[u] > max_d {
263 max_d = dist[u];
264 }
265 }
266 }
267
268 Ok(max_d)
269}
270
271fn compute_diameter_without(graph: &Graph, removed: usize) -> IgraphResult<u32> {
272 let n = graph.vcount() as usize;
273 if n < 3 {
274 return Ok(0);
275 }
276
277 let mut max_d = 0_u32;
278 for s in 0..n {
279 if s == removed {
280 continue;
281 }
282 let mut dist = vec![u32::MAX; n];
283 dist[s] = 0;
284 let mut queue = std::collections::VecDeque::new();
285 queue.push_back(s);
286 while let Some(v) = queue.pop_front() {
287 let d = dist[v];
288 let neighbors = graph.neighbors(v as u32)?;
289 for &u in &neighbors {
290 let ui = u as usize;
291 if ui == removed {
292 continue;
293 }
294 if dist[ui] == u32::MAX {
295 dist[ui] = d + 1;
296 queue.push_back(ui);
297 }
298 }
299 }
300 for u in 0..n {
301 if u == removed {
302 continue;
303 }
304 if dist[u] == u32::MAX {
305 return Ok(u32::MAX);
306 }
307 if dist[u] > max_d {
308 max_d = dist[u];
309 }
310 }
311 }
312
313 Ok(max_d)
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319
320 fn single_edge() -> Graph {
321 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
322 }
323
324 fn path3() -> Graph {
325 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
326 }
327
328 fn k3() -> Graph {
329 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
330 }
331
332 fn k4() -> Graph {
333 Graph::from_edges(
334 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
335 false,
336 Some(4),
337 )
338 .unwrap()
339 }
340
341 fn cycle4() -> Graph {
342 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
343 }
344
345 fn star5() -> Graph {
346 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
347 }
348
349 fn paw() -> Graph {
350 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
351 }
352
353 #[test]
356 fn vcr_empty() {
357 let g = Graph::with_vertices(0);
358 assert!(vertex_conn_ratio(&g).unwrap().abs() < 1e-10);
359 }
360
361 #[test]
362 fn vcr_single() {
363 let g = Graph::with_vertices(1);
364 assert!(vertex_conn_ratio(&g).unwrap().abs() < 1e-10);
365 }
366
367 #[test]
368 fn vcr_k3() {
369 assert!((vertex_conn_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
371 }
372
373 #[test]
374 fn vcr_k4() {
375 assert!((vertex_conn_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
377 }
378
379 #[test]
380 fn vcr_cycle4() {
381 assert!((vertex_conn_ratio(&cycle4()).unwrap() - 2.0 / 3.0).abs() < 1e-10);
383 }
384
385 #[test]
386 fn vcr_star5() {
387 assert!((vertex_conn_ratio(&star5()).unwrap() - 0.25).abs() < 1e-10);
389 }
390
391 #[test]
392 fn vcr_disconnected() {
393 let g = Graph::from_edges(&[(0, 1), (2, 3)], false, Some(4)).unwrap();
394 assert!(vertex_conn_ratio(&g).unwrap().abs() < 1e-10);
395 }
396
397 #[test]
398 fn vcr_in_01() {
399 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
400 let r = vertex_conn_ratio(g).unwrap();
401 assert!(r >= -1e-10);
402 assert!(r <= 1.0 + 1e-10);
403 }
404 }
405
406 #[test]
409 fn ecr_empty() {
410 let g = Graph::with_vertices(0);
411 assert!(edge_conn_ratio(&g).unwrap().abs() < 1e-10);
412 }
413
414 #[test]
415 fn ecr_single() {
416 let g = Graph::with_vertices(1);
417 assert!(edge_conn_ratio(&g).unwrap().abs() < 1e-10);
418 }
419
420 #[test]
421 fn ecr_k3() {
422 assert!((edge_conn_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
423 }
424
425 #[test]
426 fn ecr_k4() {
427 assert!((edge_conn_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
428 }
429
430 #[test]
431 fn ecr_cycle4() {
432 assert!((edge_conn_ratio(&cycle4()).unwrap() - 2.0 / 3.0).abs() < 1e-10);
433 }
434
435 #[test]
436 fn ecr_star5() {
437 assert!((edge_conn_ratio(&star5()).unwrap() - 0.25).abs() < 1e-10);
438 }
439
440 #[test]
441 fn ecr_disconnected() {
442 let g = Graph::from_edges(&[(0, 1), (2, 3)], false, Some(4)).unwrap();
443 assert!(edge_conn_ratio(&g).unwrap().abs() < 1e-10);
444 }
445
446 #[test]
449 fn dv_empty() {
450 let g = Graph::with_vertices(0);
451 assert!(diameter_vulnerability(&g).unwrap().abs() < 1e-10);
452 }
453
454 #[test]
455 fn dv_single() {
456 let g = Graph::with_vertices(1);
457 assert!(diameter_vulnerability(&g).unwrap().abs() < 1e-10);
458 }
459
460 #[test]
461 fn dv_two() {
462 assert!(diameter_vulnerability(&single_edge()).unwrap().abs() < 1e-10);
463 }
464
465 #[test]
466 fn dv_k3() {
467 assert!(diameter_vulnerability(&k3()).unwrap().abs() < 1e-10);
469 }
470
471 #[test]
472 fn dv_k4() {
473 assert!(diameter_vulnerability(&k4()).unwrap().abs() < 1e-10);
475 }
476
477 #[test]
478 fn dv_path3() {
479 assert!((diameter_vulnerability(&path3()).unwrap() - 1.0).abs() < 1e-10);
481 }
482
483 #[test]
484 fn dv_cycle4() {
485 assert!(diameter_vulnerability(&cycle4()).unwrap().abs() < 1e-10);
487 }
488
489 #[test]
490 fn dv_star5() {
491 assert!((diameter_vulnerability(&star5()).unwrap() - 1.0).abs() < 1e-10);
493 }
494
495 #[test]
496 fn dv_nonneg() {
497 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
498 assert!(diameter_vulnerability(g).unwrap() >= -1e-10);
499 }
500 }
501
502 #[test]
505 fn andr_empty() {
506 let g = Graph::with_vertices(0);
507 assert!(neighbor_degree_disparity(&g).unwrap().abs() < 1e-10);
508 }
509
510 #[test]
511 fn andr_single() {
512 let g = Graph::with_vertices(1);
513 assert!(neighbor_degree_disparity(&g).unwrap().abs() < 1e-10);
514 }
515
516 #[test]
517 fn andr_no_edges() {
518 let g = Graph::with_vertices(5);
519 assert!(neighbor_degree_disparity(&g).unwrap().abs() < 1e-10);
520 }
521
522 #[test]
523 fn andr_k3() {
524 assert!((neighbor_degree_disparity(&k3()).unwrap() - 1.0).abs() < 1e-10);
526 }
527
528 #[test]
529 fn andr_k4() {
530 assert!((neighbor_degree_disparity(&k4()).unwrap() - 1.0).abs() < 1e-10);
532 }
533
534 #[test]
535 fn andr_cycle4() {
536 assert!((neighbor_degree_disparity(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
538 }
539
540 #[test]
541 fn andr_star5() {
542 let r = neighbor_degree_disparity(&star5()).unwrap();
546 assert!((r - 3.25).abs() < 1e-10);
547 }
548
549 #[test]
550 fn andr_single_edge() {
551 assert!((neighbor_degree_disparity(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
553 }
554
555 #[test]
556 fn andr_positive() {
557 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
558 assert!(neighbor_degree_disparity(g).unwrap() > 0.0);
559 }
560 }
561
562 #[test]
565 fn complete_max_connectivity() {
566 assert!((vertex_conn_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
567 assert!((vertex_conn_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
568 assert!((edge_conn_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
569 assert!((edge_conn_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
570 }
571
572 #[test]
573 fn complete_zero_vulnerability() {
574 assert!(diameter_vulnerability(&k3()).unwrap().abs() < 1e-10);
575 assert!(diameter_vulnerability(&k4()).unwrap().abs() < 1e-10);
576 }
577
578 #[test]
579 fn regular_unit_neighbor_ratio() {
580 assert!((neighbor_degree_disparity(&k3()).unwrap() - 1.0).abs() < 1e-10);
581 assert!((neighbor_degree_disparity(&k4()).unwrap() - 1.0).abs() < 1e-10);
582 assert!((neighbor_degree_disparity(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
583 }
584}