rust_igraph/algorithms/properties/
robustness.rs1#![allow(
17 clippy::cast_possible_truncation,
18 clippy::cast_precision_loss,
19 clippy::many_single_char_names,
20 clippy::needless_range_loop,
21 clippy::too_many_lines
22)]
23
24use crate::core::{Graph, IgraphError, IgraphResult};
25use std::collections::VecDeque;
26
27pub fn vertex_resilience(graph: &Graph) -> IgraphResult<f64> {
46 let n = graph.vcount() as usize;
47 if n == 0 {
48 return Ok(0.0);
49 }
50 let kappa = crate::algorithms::flow::vertex_connectivity::vertex_connectivity(graph, true)?;
51 Ok(kappa.max(0) as f64 / n as f64)
52}
53
54pub fn edge_resilience(graph: &Graph) -> IgraphResult<f64> {
69 let m = graph.ecount();
70 if m == 0 {
71 return Ok(0.0);
72 }
73 let lambda = crate::algorithms::flow::edge_connectivity::edge_connectivity(graph, true)?;
74 Ok(lambda.max(0) as f64 / m as f64)
75}
76
77fn count_components_after_removal(graph: &Graph, removed: &[bool]) -> (usize, usize) {
79 let n = graph.vcount() as usize;
80 let mut visited = vec![false; n];
81 let mut num_components = 0_usize;
82 let mut largest_component = 0_usize;
83
84 for start in 0..n {
85 if visited[start] || removed[start] {
86 continue;
87 }
88 num_components += 1;
89 let mut size = 0_usize;
90 let mut queue = VecDeque::new();
91 queue.push_back(start as u32);
92 visited[start] = true;
93
94 while let Some(u) = queue.pop_front() {
95 size += 1;
96 if let Ok(nbrs) = graph.neighbors(u) {
97 for &v in &nbrs {
98 let vi = v as usize;
99 if !visited[vi] && !removed[vi] {
100 visited[vi] = true;
101 queue.push_back(v);
102 }
103 }
104 }
105 }
106
107 if size > largest_component {
108 largest_component = size;
109 }
110 }
111
112 (num_components, largest_component)
113}
114
115pub fn graph_toughness(graph: &Graph) -> IgraphResult<f64> {
139 if graph.is_directed() {
140 return Err(IgraphError::InvalidArgument(
141 "graph_toughness is defined for undirected graphs only".into(),
142 ));
143 }
144 let n = graph.vcount() as usize;
145 if n <= 1 {
146 return Ok(0.0);
147 }
148
149 let (initial_comp, _) = count_components_after_removal(graph, &vec![false; n]);
150 if initial_comp > 1 {
151 return Ok(0.0);
152 }
153
154 let mut min_toughness = f64::INFINITY;
155 let mut removed = vec![false; n];
156
157 toughness_search(graph, n, 0, 0, &mut removed, &mut min_toughness);
158
159 Ok(min_toughness)
160}
161
162fn toughness_search(
163 graph: &Graph,
164 n: usize,
165 start: usize,
166 removed_count: usize,
167 removed: &mut Vec<bool>,
168 min_toughness: &mut f64,
169) {
170 if removed_count > 0 {
171 let (comp, _) = count_components_after_removal(graph, removed);
172 if comp > 1 {
173 let t = removed_count as f64 / comp as f64;
174 if t < *min_toughness {
175 *min_toughness = t;
176 }
177 }
178 }
179
180 let active_remaining = (start..n).filter(|&i| !removed[i]).count();
181 if active_remaining <= 1 {
182 return;
183 }
184
185 for i in start..n {
186 if removed[i] {
187 continue;
188 }
189 removed[i] = true;
190 toughness_search(graph, n, i + 1, removed_count + 1, removed, min_toughness);
191 removed[i] = false;
192 }
193}
194
195pub fn graph_integrity(graph: &Graph) -> IgraphResult<f64> {
218 if graph.is_directed() {
219 return Err(IgraphError::InvalidArgument(
220 "graph_integrity is defined for undirected graphs only".into(),
221 ));
222 }
223 let n = graph.vcount() as usize;
224 if n == 0 {
225 return Ok(0.0);
226 }
227
228 let mut min_integrity = n as f64;
229 let mut removed = vec![false; n];
230
231 integrity_search(graph, n, 0, 0, &mut removed, &mut min_integrity);
232
233 Ok(min_integrity)
234}
235
236fn integrity_search(
237 graph: &Graph,
238 n: usize,
239 start: usize,
240 removed_count: usize,
241 removed: &mut Vec<bool>,
242 min_integrity: &mut f64,
243) {
244 let (_, largest) = count_components_after_removal(graph, removed);
245 let val = removed_count as f64 + largest as f64;
246 if val < *min_integrity {
247 *min_integrity = val;
248 }
249
250 if removed_count as f64 >= *min_integrity {
251 return;
252 }
253
254 for i in start..n {
255 if removed[i] {
256 continue;
257 }
258 removed[i] = true;
259 integrity_search(graph, n, i + 1, removed_count + 1, removed, min_integrity);
260 removed[i] = false;
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267
268 fn path3() -> Graph {
269 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
270 }
271
272 fn path4() -> Graph {
273 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
274 }
275
276 fn k3() -> Graph {
277 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
278 }
279
280 fn k4() -> Graph {
281 Graph::from_edges(
282 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
283 false,
284 Some(4),
285 )
286 .unwrap()
287 }
288
289 fn cycle4() -> Graph {
290 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
291 }
292
293 fn star4() -> Graph {
294 Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], false, Some(4)).unwrap()
295 }
296
297 #[test]
300 fn vr_k4() {
301 let g = k4();
302 let r = vertex_resilience(&g).unwrap();
303 assert!((r - 0.75).abs() < 0.01);
304 }
305
306 #[test]
307 fn vr_path() {
308 let g = path4();
309 let r = vertex_resilience(&g).unwrap();
310 assert!((r - 0.25).abs() < 0.01);
311 }
312
313 #[test]
314 fn vr_empty() {
315 let g = Graph::with_vertices(0);
316 let r = vertex_resilience(&g).unwrap();
317 assert!(r.abs() < 1e-10);
318 }
319
320 #[test]
321 fn vr_single() {
322 let g = Graph::with_vertices(1);
323 let r = vertex_resilience(&g).unwrap();
324 assert!(r.abs() < 1e-10);
325 }
326
327 #[test]
330 fn er_path3() {
331 let g = path3();
332 let r = edge_resilience(&g).unwrap();
333 assert!((r - 0.5).abs() < 0.01);
334 }
335
336 #[test]
337 fn er_k3() {
338 let g = k3();
339 let r = edge_resilience(&g).unwrap();
340 assert!((r - 2.0 / 3.0).abs() < 0.01);
342 }
343
344 #[test]
345 fn er_empty() {
346 let g = Graph::with_vertices(1);
347 let r = edge_resilience(&g).unwrap();
348 assert!(r.abs() < 1e-10);
349 }
350
351 #[test]
354 fn gt_path3() {
355 let g = path3();
356 let t = graph_toughness(&g).unwrap();
357 assert!((t - 0.5).abs() < 0.01);
359 }
360
361 #[test]
362 fn gt_k3() {
363 let g = k3();
364 let t = graph_toughness(&g).unwrap();
365 assert!(t.is_infinite());
370 }
371
372 #[test]
373 fn gt_k4() {
374 let g = k4();
375 let t = graph_toughness(&g).unwrap();
376 assert!(t.is_infinite());
377 }
378
379 #[test]
380 fn gt_cycle4() {
381 let g = cycle4();
382 let t = graph_toughness(&g).unwrap();
383 assert!((t - 1.0).abs() < 0.01);
389 }
390
391 #[test]
392 fn gt_star4() {
393 let g = star4();
394 let t = graph_toughness(&g).unwrap();
395 assert!((t - 1.0 / 3.0).abs() < 0.01);
397 }
398
399 #[test]
400 fn gt_disconnected() {
401 let g = Graph::with_vertices(3);
402 let t = graph_toughness(&g).unwrap();
403 assert!(t.abs() < 1e-10);
404 }
405
406 #[test]
407 fn gt_single() {
408 let g = Graph::with_vertices(1);
409 let t = graph_toughness(&g).unwrap();
410 assert!(t.abs() < 1e-10);
411 }
412
413 #[test]
414 fn gt_directed_error() {
415 let g = Graph::from_edges(&[(0, 1)], true, Some(2)).unwrap();
416 assert!(graph_toughness(&g).is_err());
417 }
418
419 #[test]
422 fn gi_k3() {
423 let g = k3();
424 let i = graph_integrity(&g).unwrap();
425 assert!((i - 3.0).abs() < 0.01);
427 }
428
429 #[test]
430 fn gi_path3() {
431 let g = path3();
432 let i = graph_integrity(&g).unwrap();
433 assert!((i - 2.0).abs() < 0.01);
435 }
436
437 #[test]
438 fn gi_path4() {
439 let g = path4();
440 let i = graph_integrity(&g).unwrap();
441 assert!((i - 3.0).abs() < 0.01);
445 }
446
447 #[test]
448 fn gi_star4() {
449 let g = star4();
450 let i = graph_integrity(&g).unwrap();
451 assert!((i - 2.0).abs() < 0.01);
453 }
454
455 #[test]
456 fn gi_empty() {
457 let g = Graph::with_vertices(0);
458 let i = graph_integrity(&g).unwrap();
459 assert!(i.abs() < 1e-10);
460 }
461
462 #[test]
463 fn gi_single() {
464 let g = Graph::with_vertices(1);
465 let i = graph_integrity(&g).unwrap();
466 assert!((i - 1.0).abs() < 0.01);
467 }
468
469 #[test]
470 fn gi_directed_error() {
471 let g = Graph::from_edges(&[(0, 1)], true, Some(2)).unwrap();
472 assert!(graph_integrity(&g).is_err());
473 }
474
475 #[test]
478 fn resilience_bounded() {
479 let g = cycle4();
480 let vr = vertex_resilience(&g).unwrap();
481 let er = edge_resilience(&g).unwrap();
482 assert!((0.0..=1.0).contains(&vr));
483 assert!((0.0..=1.0).contains(&er));
484 }
485
486 #[test]
487 fn complete_graph_is_infinitely_tough() {
488 for n in 3_u32..=5 {
489 let mut edges = Vec::new();
490 for u in 0..n {
491 for v in (u + 1)..n {
492 edges.push((u, v));
493 }
494 }
495 let g = Graph::from_edges(&edges, false, Some(n)).unwrap();
496 let t = graph_toughness(&g).unwrap();
497 assert!(t.is_infinite(), "K_{n} should have infinite toughness");
498 }
499 }
500}