rust_igraph/algorithms/properties/
connectivity_ratios.rs1#![allow(
11 clippy::cast_possible_truncation,
12 clippy::cast_precision_loss,
13 clippy::many_single_char_names,
14 clippy::needless_range_loop,
15 clippy::similar_names,
16 clippy::too_many_lines
17)]
18
19use crate::core::{Graph, IgraphResult};
20
21pub fn component_ratio(graph: &Graph) -> IgraphResult<f64> {
37 let n = graph.vcount() as usize;
38 if n == 0 {
39 return Ok(0.0);
40 }
41
42 let comp_sizes = component_sizes(graph)?;
43 Ok(comp_sizes.len() as f64 / n as f64)
44}
45
46pub fn largest_component_fraction(graph: &Graph) -> IgraphResult<f64> {
62 let n = graph.vcount() as usize;
63 if n == 0 {
64 return Ok(0.0);
65 }
66
67 let comp_sizes = component_sizes(graph)?;
68 let max_size = comp_sizes.iter().copied().max().unwrap_or(0);
69 Ok(max_size as f64 / n as f64)
70}
71
72pub fn giant_component_gap(graph: &Graph) -> IgraphResult<f64> {
89 let n = graph.vcount() as usize;
90 if n == 0 {
91 return Ok(0.0);
92 }
93
94 let mut comp_sizes = component_sizes(graph)?;
95 comp_sizes.sort_unstable_by(|a, b| b.cmp(a));
96
97 let largest = comp_sizes[0];
98 let second = if comp_sizes.len() > 1 {
99 comp_sizes[1]
100 } else {
101 0
102 };
103
104 Ok((largest - second) as f64 / n as f64)
105}
106
107pub fn vertex_connectivity_ratio(graph: &Graph) -> IgraphResult<f64> {
132 let n = graph.vcount() as usize;
133 if n < 2 {
134 return Ok(0.0);
135 }
136
137 let comp_sizes = component_sizes(graph)?;
138 if comp_sizes.len() > 1 {
139 return Ok(0.0);
140 }
141
142 let mut min_deg = usize::MAX;
143 for v in 0..n {
144 let d = graph.degree(v as u32)?;
145 if d < min_deg {
146 min_deg = d;
147 }
148 }
149
150 Ok(min_deg as f64 / (n - 1) as f64)
151}
152
153fn component_sizes(graph: &Graph) -> IgraphResult<Vec<usize>> {
154 let n = graph.vcount() as usize;
155 let mut visited = vec![false; n];
156 let mut sizes = Vec::new();
157 let mut queue = std::collections::VecDeque::new();
158
159 for start in 0..n {
160 if visited[start] {
161 continue;
162 }
163 visited[start] = true;
164 let mut size = 1_usize;
165 queue.push_back(start);
166 while let Some(v) = queue.pop_front() {
167 let neighbors = graph.neighbors(v as u32)?;
168 for &u in &neighbors {
169 let ui = u as usize;
170 if !visited[ui] {
171 visited[ui] = true;
172 size += 1;
173 queue.push_back(ui);
174 }
175 }
176 }
177 sizes.push(size);
178 }
179
180 Ok(sizes)
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 fn single_edge() -> Graph {
188 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
189 }
190
191 fn path3() -> Graph {
192 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
193 }
194
195 fn k3() -> Graph {
196 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
197 }
198
199 fn k4() -> Graph {
200 Graph::from_edges(
201 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
202 false,
203 Some(4),
204 )
205 .unwrap()
206 }
207
208 fn cycle4() -> Graph {
209 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
210 }
211
212 fn star5() -> Graph {
213 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
214 }
215
216 fn disconnected_2_2() -> Graph {
217 Graph::from_edges(&[(0, 1), (2, 3)], false, Some(4)).unwrap()
218 }
219
220 fn disconnected_3_1() -> Graph {
221 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(4)).unwrap()
222 }
223
224 #[test]
227 fn cr_empty() {
228 let g = Graph::with_vertices(0);
229 assert!(component_ratio(&g).unwrap().abs() < 1e-10);
230 }
231
232 #[test]
233 fn cr_single() {
234 let g = Graph::with_vertices(1);
235 assert!((component_ratio(&g).unwrap() - 1.0).abs() < 1e-10);
236 }
237
238 #[test]
239 fn cr_isolated() {
240 let g = Graph::with_vertices(5);
241 assert!((component_ratio(&g).unwrap() - 1.0).abs() < 1e-10);
242 }
243
244 #[test]
245 fn cr_k3() {
246 assert!((component_ratio(&k3()).unwrap() - 1.0 / 3.0).abs() < 1e-10);
247 }
248
249 #[test]
250 fn cr_k4() {
251 assert!((component_ratio(&k4()).unwrap() - 0.25).abs() < 1e-10);
252 }
253
254 #[test]
255 fn cr_disconnected_2_2() {
256 assert!((component_ratio(&disconnected_2_2()).unwrap() - 0.5).abs() < 1e-10);
258 }
259
260 #[test]
261 fn cr_disconnected_3_1() {
262 assert!((component_ratio(&disconnected_3_1()).unwrap() - 0.5).abs() < 1e-10);
264 }
265
266 #[test]
267 fn cr_single_edge() {
268 assert!((component_ratio(&single_edge()).unwrap() - 0.5).abs() < 1e-10);
269 }
270
271 #[test]
274 fn lcf_empty() {
275 let g = Graph::with_vertices(0);
276 assert!(largest_component_fraction(&g).unwrap().abs() < 1e-10);
277 }
278
279 #[test]
280 fn lcf_single() {
281 let g = Graph::with_vertices(1);
282 assert!((largest_component_fraction(&g).unwrap() - 1.0).abs() < 1e-10);
283 }
284
285 #[test]
286 fn lcf_isolated() {
287 let g = Graph::with_vertices(5);
289 assert!((largest_component_fraction(&g).unwrap() - 0.2).abs() < 1e-10);
290 }
291
292 #[test]
293 fn lcf_k3() {
294 assert!((largest_component_fraction(&k3()).unwrap() - 1.0).abs() < 1e-10);
295 }
296
297 #[test]
298 fn lcf_disconnected_2_2() {
299 assert!((largest_component_fraction(&disconnected_2_2()).unwrap() - 0.5).abs() < 1e-10);
301 }
302
303 #[test]
304 fn lcf_disconnected_3_1() {
305 assert!((largest_component_fraction(&disconnected_3_1()).unwrap() - 0.75).abs() < 1e-10);
307 }
308
309 #[test]
310 fn lcf_star5() {
311 assert!((largest_component_fraction(&star5()).unwrap() - 1.0).abs() < 1e-10);
312 }
313
314 #[test]
317 fn gcg_empty() {
318 let g = Graph::with_vertices(0);
319 assert!(giant_component_gap(&g).unwrap().abs() < 1e-10);
320 }
321
322 #[test]
323 fn gcg_single() {
324 let g = Graph::with_vertices(1);
325 assert!((giant_component_gap(&g).unwrap() - 1.0).abs() < 1e-10);
326 }
327
328 #[test]
329 fn gcg_k3() {
330 assert!((giant_component_gap(&k3()).unwrap() - 1.0).abs() < 1e-10);
332 }
333
334 #[test]
335 fn gcg_disconnected_2_2() {
336 assert!(giant_component_gap(&disconnected_2_2()).unwrap().abs() < 1e-10);
338 }
339
340 #[test]
341 fn gcg_disconnected_3_1() {
342 assert!((giant_component_gap(&disconnected_3_1()).unwrap() - 0.5).abs() < 1e-10);
344 }
345
346 #[test]
347 fn gcg_isolated() {
348 let g = Graph::with_vertices(5);
350 assert!(giant_component_gap(&g).unwrap().abs() < 1e-10);
351 }
352
353 #[test]
354 fn gcg_star5() {
355 assert!((giant_component_gap(&star5()).unwrap() - 1.0).abs() < 1e-10);
357 }
358
359 #[test]
362 fn vcr_empty() {
363 let g = Graph::with_vertices(0);
364 assert!(vertex_connectivity_ratio(&g).unwrap().abs() < 1e-10);
365 }
366
367 #[test]
368 fn vcr_single() {
369 let g = Graph::with_vertices(1);
370 assert!(vertex_connectivity_ratio(&g).unwrap().abs() < 1e-10);
371 }
372
373 #[test]
374 fn vcr_single_edge() {
375 assert!((vertex_connectivity_ratio(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
377 }
378
379 #[test]
380 fn vcr_k3() {
381 assert!((vertex_connectivity_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
383 }
384
385 #[test]
386 fn vcr_k4() {
387 assert!((vertex_connectivity_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
389 }
390
391 #[test]
392 fn vcr_cycle4() {
393 assert!((vertex_connectivity_ratio(&cycle4()).unwrap() - 2.0 / 3.0).abs() < 1e-10);
395 }
396
397 #[test]
398 fn vcr_star5() {
399 assert!((vertex_connectivity_ratio(&star5()).unwrap() - 0.25).abs() < 1e-10);
401 }
402
403 #[test]
404 fn vcr_path3() {
405 assert!((vertex_connectivity_ratio(&path3()).unwrap() - 0.5).abs() < 1e-10);
407 }
408
409 #[test]
410 fn vcr_disconnected() {
411 assert!(
413 vertex_connectivity_ratio(&disconnected_2_2())
414 .unwrap()
415 .abs()
416 < 1e-10
417 );
418 }
419
420 #[test]
421 fn vcr_isolated() {
422 let g = Graph::with_vertices(5);
424 assert!(vertex_connectivity_ratio(&g).unwrap().abs() < 1e-10);
425 }
426
427 #[test]
430 fn cr_in_01() {
431 for g in &[
432 single_edge(),
433 path3(),
434 k3(),
435 k4(),
436 cycle4(),
437 star5(),
438 disconnected_2_2(),
439 ] {
440 let r = component_ratio(g).unwrap();
441 assert!(r >= -1e-10);
442 assert!(r <= 1.0 + 1e-10);
443 }
444 }
445
446 #[test]
447 fn lcf_in_01() {
448 for g in &[
449 single_edge(),
450 path3(),
451 k3(),
452 k4(),
453 cycle4(),
454 star5(),
455 disconnected_2_2(),
456 ] {
457 let r = largest_component_fraction(g).unwrap();
458 assert!(r >= -1e-10);
459 assert!(r <= 1.0 + 1e-10);
460 }
461 }
462
463 #[test]
464 fn gcg_in_01() {
465 for g in &[
466 single_edge(),
467 path3(),
468 k3(),
469 k4(),
470 cycle4(),
471 star5(),
472 disconnected_2_2(),
473 ] {
474 let r = giant_component_gap(g).unwrap();
475 assert!(r >= -1e-10);
476 assert!(r <= 1.0 + 1e-10);
477 }
478 }
479
480 #[test]
481 fn vcr_in_01() {
482 for g in &[
483 single_edge(),
484 path3(),
485 k3(),
486 k4(),
487 cycle4(),
488 star5(),
489 disconnected_2_2(),
490 ] {
491 let r = vertex_connectivity_ratio(g).unwrap();
492 assert!(r >= -1e-10);
493 assert!(r <= 1.0 + 1e-10);
494 }
495 }
496
497 #[test]
498 fn connected_graphs_lcf_one() {
499 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5()] {
500 assert!((largest_component_fraction(g).unwrap() - 1.0).abs() < 1e-10);
501 }
502 }
503
504 #[test]
505 fn complete_graphs_vcr_one() {
506 assert!((vertex_connectivity_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
507 assert!((vertex_connectivity_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
508 }
509}