rust_igraph/algorithms/properties/
robustness_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 min_degree_connectivity_ratio(graph: &Graph) -> IgraphResult<f64> {
44 let n = graph.vcount() as usize;
45 if n < 2 {
46 return Ok(0.0);
47 }
48
49 let mut min_deg = usize::MAX;
50 let mut sum_deg = 0_u64;
51
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 sum_deg += d as u64;
58 }
59
60 if min_deg == 0 {
61 return Ok(0.0);
62 }
63
64 let avg_deg = sum_deg as f64 / n as f64;
65 if avg_deg < 1e-30 {
66 return Ok(0.0);
67 }
68
69 Ok(min_deg as f64 / avg_deg)
70}
71
72pub fn degree_range_ratio(graph: &Graph) -> IgraphResult<f64> {
91 let n = graph.vcount() as usize;
92 if n < 2 {
93 return Ok(0.0);
94 }
95
96 let mut min_deg = usize::MAX;
97 let mut max_deg = 0_usize;
98
99 for v in 0..n {
100 let d = graph.degree(v as u32)?;
101 if d < min_deg {
102 min_deg = d;
103 }
104 if d > max_deg {
105 max_deg = d;
106 }
107 }
108
109 if min_deg == 0 || max_deg == 0 {
110 return Ok(0.0);
111 }
112
113 Ok(min_deg as f64 / max_deg as f64)
114}
115
116pub fn average_path_resilience(graph: &Graph) -> IgraphResult<f64> {
139 let n = graph.vcount() as usize;
140 if n < 3 {
141 return Ok(0.0);
142 }
143
144 let mut max_deg = 0_usize;
146 let mut hub = 0_usize;
147 for v in 0..n {
148 let d = graph.degree(v as u32)?;
149 if d > max_deg {
150 max_deg = d;
151 hub = v;
152 }
153 }
154
155 if max_deg == 0 {
156 return Ok(0.0);
157 }
158
159 let old_diam = compute_diameter(graph, n, None)?;
161 if old_diam == 0 {
162 return Ok(0.0);
163 }
164
165 let new_diam = compute_diameter(graph, n, Some(hub))?;
167 if new_diam == 0 {
168 return Ok(0.0);
169 }
170
171 let diff = if new_diam > old_diam {
172 (new_diam - old_diam) as f64
173 } else {
174 0.0
175 };
176
177 Ok(1.0 - diff / n as f64)
178}
179
180fn compute_diameter(graph: &Graph, n: usize, exclude: Option<usize>) -> IgraphResult<u32> {
182 let mut diam = 0_u32;
183 for source in 0..n {
184 if Some(source) == exclude {
185 continue;
186 }
187 let mut dist = vec![u32::MAX; n];
188 dist[source] = 0;
189 let mut queue = std::collections::VecDeque::new();
190 queue.push_back(source);
191
192 while let Some(v) = queue.pop_front() {
193 let cd = dist[v];
194 let nbrs = graph.neighbors(v as u32)?;
195 for &u in &nbrs {
196 let ui = u as usize;
197 if Some(ui) == exclude {
198 continue;
199 }
200 if dist[ui] == u32::MAX {
201 dist[ui] = cd + 1;
202 queue.push_back(ui);
203 }
204 }
205 }
206
207 for target in (source + 1)..n {
208 if Some(target) == exclude {
209 continue;
210 }
211 if dist[target] == u32::MAX {
212 return Ok(0);
213 }
214 if dist[target] > diam {
215 diam = dist[target];
216 }
217 }
218 }
219 Ok(diam)
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 fn empty() -> Graph {
227 Graph::with_vertices(0)
228 }
229
230 fn single() -> Graph {
231 Graph::with_vertices(1)
232 }
233
234 fn single_edge() -> Graph {
235 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
236 }
237
238 fn path3() -> Graph {
239 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
240 }
241
242 fn k3() -> Graph {
243 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
244 }
245
246 fn k4() -> Graph {
247 Graph::from_edges(
248 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
249 false,
250 Some(4),
251 )
252 .unwrap()
253 }
254
255 fn cycle4() -> Graph {
256 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
257 }
258
259 fn star5() -> Graph {
260 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
261 }
262
263 fn paw() -> Graph {
264 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
265 }
266
267 #[test]
270 fn vcr_empty() {
271 assert!(min_degree_connectivity_ratio(&empty()).unwrap().abs() < 1e-10);
272 }
273
274 #[test]
275 fn vcr_single() {
276 assert!(min_degree_connectivity_ratio(&single()).unwrap().abs() < 1e-10);
277 }
278
279 #[test]
280 fn vcr_k3() {
281 assert!((min_degree_connectivity_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
283 }
284
285 #[test]
286 fn vcr_k4() {
287 assert!((min_degree_connectivity_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
289 }
290
291 #[test]
292 fn vcr_cycle4() {
293 assert!((min_degree_connectivity_ratio(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
295 }
296
297 #[test]
298 fn vcr_star5() {
299 let r = min_degree_connectivity_ratio(&star5()).unwrap();
301 assert!((r - 5.0 / 8.0).abs() < 1e-10);
302 }
303
304 #[test]
305 fn vcr_in_01() {
306 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
307 let r = min_degree_connectivity_ratio(g).unwrap();
308 assert!(r >= -1e-10);
309 assert!(r <= 1.0 + 1e-10);
310 }
311 }
312
313 #[test]
316 fn ecr_empty() {
317 assert!(degree_range_ratio(&empty()).unwrap().abs() < 1e-10);
318 }
319
320 #[test]
321 fn ecr_single() {
322 assert!(degree_range_ratio(&single()).unwrap().abs() < 1e-10);
323 }
324
325 #[test]
326 fn ecr_k3() {
327 assert!((degree_range_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
329 }
330
331 #[test]
332 fn ecr_k4() {
333 assert!((degree_range_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
334 }
335
336 #[test]
337 fn ecr_cycle4() {
338 assert!((degree_range_ratio(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
339 }
340
341 #[test]
342 fn ecr_star5() {
343 assert!((degree_range_ratio(&star5()).unwrap() - 0.25).abs() < 1e-10);
345 }
346
347 #[test]
348 fn ecr_paw() {
349 assert!((degree_range_ratio(&paw()).unwrap() - 1.0 / 3.0).abs() < 1e-10);
351 }
352
353 #[test]
354 fn ecr_in_01() {
355 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
356 let r = degree_range_ratio(g).unwrap();
357 assert!(r >= -1e-10);
358 assert!(r <= 1.0 + 1e-10);
359 }
360 }
361
362 #[test]
365 fn apr_empty() {
366 assert!(average_path_resilience(&empty()).unwrap().abs() < 1e-10);
367 }
368
369 #[test]
370 fn apr_single() {
371 assert!(average_path_resilience(&single()).unwrap().abs() < 1e-10);
372 }
373
374 #[test]
375 fn apr_single_edge() {
376 assert!(average_path_resilience(&single_edge()).unwrap().abs() < 1e-10);
378 }
379
380 #[test]
381 fn apr_k3() {
382 assert!((average_path_resilience(&k3()).unwrap() - 1.0).abs() < 1e-10);
384 }
385
386 #[test]
387 fn apr_k4() {
388 assert!((average_path_resilience(&k4()).unwrap() - 1.0).abs() < 1e-10);
390 }
391
392 #[test]
393 fn apr_cycle4() {
394 assert!((average_path_resilience(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
397 }
398
399 #[test]
400 fn apr_star5() {
401 assert!(average_path_resilience(&star5()).unwrap().abs() < 1e-10);
403 }
404
405 #[test]
406 fn apr_in_01() {
407 for g in &[path3(), k3(), k4(), cycle4(), paw()] {
408 let r = average_path_resilience(g).unwrap();
409 assert!(r >= -1e-10);
410 assert!(r <= 1.0 + 1e-10);
411 }
412 }
413
414 #[test]
417 fn regular_max_connectivity() {
418 assert!((min_degree_connectivity_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
420 assert!((degree_range_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
421 assert!((min_degree_connectivity_ratio(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
422 assert!((degree_range_ratio(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
423 }
424
425 #[test]
426 fn complete_full_resilience() {
427 assert!((average_path_resilience(&k3()).unwrap() - 1.0).abs() < 1e-10);
428 assert!((average_path_resilience(&k4()).unwrap() - 1.0).abs() < 1e-10);
429 }
430}