rust_igraph/algorithms/properties/
graph_connectivity_ratios.rs1#![allow(
11 clippy::cast_possible_truncation,
12 clippy::cast_possible_wrap,
13 clippy::cast_precision_loss,
14 clippy::many_single_char_names,
15 clippy::needless_range_loop,
16 clippy::similar_names,
17 clippy::too_many_lines
18)]
19
20use crate::core::{Graph, IgraphResult};
21
22pub fn circuit_rank_ratio(graph: &Graph) -> IgraphResult<f64> {
42 let m = graph.ecount() as i64;
43 if m == 0 {
44 return Ok(0.0);
45 }
46
47 let n = i64::from(graph.vcount());
48 let c = count_components(graph)? as i64;
49 let circuit_rank = (m - n + c).max(0);
50
51 Ok(circuit_rank as f64 / m as f64)
52}
53
54pub fn meshedness_coefficient(graph: &Graph) -> IgraphResult<f64> {
74 let n = i64::from(graph.vcount());
75 let m = graph.ecount() as i64;
76
77 if n < 3 || m == 0 {
78 return Ok(0.0);
79 }
80
81 let c = count_components(graph)? as i64;
82 if c > 1 {
83 return Ok(0.0);
84 }
85
86 let denom = 2 * n - 5;
87 if denom <= 0 {
88 return Ok(0.0);
89 }
90
91 let circuit_rank = (m - n + 1).max(0);
92 Ok(circuit_rank as f64 / denom as f64)
93}
94
95pub fn edge_surplus_ratio(graph: &Graph) -> IgraphResult<f64> {
115 let n = i64::from(graph.vcount());
116 let m = graph.ecount() as i64;
117
118 if n < 2 || m == 0 {
119 return Ok(0.0);
120 }
121
122 let c = count_components(graph)? as i64;
123 let circuit_rank = (m - n + c).max(0);
124
125 let max_m = n * (n - 1) / 2;
126 let max_circuit_rank = (max_m - n + c).max(0);
127
128 if max_circuit_rank == 0 {
129 return Ok(0.0);
130 }
131
132 Ok(circuit_rank as f64 / max_circuit_rank as f64)
133}
134
135pub fn connectivity_index(graph: &Graph) -> IgraphResult<f64> {
154 let n = u64::from(graph.vcount());
155 if n < 2 {
156 return Ok(0.0);
157 }
158
159 let m = graph.ecount() as f64;
160 let avg_deg = 2.0 * m / n as f64;
161 let max_avg_deg = (n - 1) as f64;
162
163 Ok(avg_deg / max_avg_deg)
164}
165
166fn count_components(graph: &Graph) -> IgraphResult<usize> {
167 let n = graph.vcount() as usize;
168 if n == 0 {
169 return Ok(0);
170 }
171
172 let mut visited = vec![false; n];
173 let mut count = 0_usize;
174
175 for start in 0..n {
176 if visited[start] {
177 continue;
178 }
179 count += 1;
180 let mut stack = vec![start];
181 visited[start] = true;
182 while let Some(v) = stack.pop() {
183 let neighbors = graph.neighbors(v as u32)?;
184 for &u in &neighbors {
185 let ui = u as usize;
186 if !visited[ui] {
187 visited[ui] = true;
188 stack.push(ui);
189 }
190 }
191 }
192 }
193
194 Ok(count)
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 fn single_edge() -> Graph {
202 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
203 }
204
205 fn path3() -> Graph {
206 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
207 }
208
209 fn k3() -> Graph {
210 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
211 }
212
213 fn k4() -> Graph {
214 Graph::from_edges(
215 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
216 false,
217 Some(4),
218 )
219 .unwrap()
220 }
221
222 fn cycle4() -> Graph {
223 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
224 }
225
226 fn star5() -> Graph {
227 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
228 }
229
230 fn paw() -> Graph {
231 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
232 }
233
234 fn disconnected() -> Graph {
235 Graph::from_edges(&[(0, 1), (2, 3)], false, Some(4)).unwrap()
237 }
238
239 #[test]
242 fn crr_empty() {
243 let g = Graph::with_vertices(0);
244 assert!(circuit_rank_ratio(&g).unwrap().abs() < 1e-10);
245 }
246
247 #[test]
248 fn crr_isolated() {
249 let g = Graph::with_vertices(5);
250 assert!(circuit_rank_ratio(&g).unwrap().abs() < 1e-10);
251 }
252
253 #[test]
254 fn crr_single_edge() {
255 assert!(circuit_rank_ratio(&single_edge()).unwrap().abs() < 1e-10);
257 }
258
259 #[test]
260 fn crr_path3() {
261 assert!(circuit_rank_ratio(&path3()).unwrap().abs() < 1e-10);
263 }
264
265 #[test]
266 fn crr_k3() {
267 assert!((circuit_rank_ratio(&k3()).unwrap() - 1.0 / 3.0).abs() < 1e-10);
269 }
270
271 #[test]
272 fn crr_k4() {
273 assert!((circuit_rank_ratio(&k4()).unwrap() - 0.5).abs() < 1e-10);
275 }
276
277 #[test]
278 fn crr_cycle4() {
279 assert!((circuit_rank_ratio(&cycle4()).unwrap() - 0.25).abs() < 1e-10);
281 }
282
283 #[test]
284 fn crr_star5() {
285 assert!(circuit_rank_ratio(&star5()).unwrap().abs() < 1e-10);
287 }
288
289 #[test]
290 fn crr_paw() {
291 assert!((circuit_rank_ratio(&paw()).unwrap() - 0.25).abs() < 1e-10);
293 }
294
295 #[test]
296 fn crr_disconnected() {
297 assert!(circuit_rank_ratio(&disconnected()).unwrap().abs() < 1e-10);
299 }
300
301 #[test]
304 fn mc_empty() {
305 let g = Graph::with_vertices(0);
306 assert!(meshedness_coefficient(&g).unwrap().abs() < 1e-10);
307 }
308
309 #[test]
310 fn mc_too_small() {
311 let g = Graph::with_vertices(2);
312 assert!(meshedness_coefficient(&g).unwrap().abs() < 1e-10);
313 }
314
315 #[test]
316 fn mc_path3() {
317 assert!(meshedness_coefficient(&path3()).unwrap().abs() < 1e-10);
319 }
320
321 #[test]
322 fn mc_k3() {
323 assert!((meshedness_coefficient(&k3()).unwrap() - 1.0).abs() < 1e-10);
325 }
326
327 #[test]
328 fn mc_k4() {
329 assert!((meshedness_coefficient(&k4()).unwrap() - 1.0).abs() < 1e-10);
331 }
332
333 #[test]
334 fn mc_cycle4() {
335 assert!((meshedness_coefficient(&cycle4()).unwrap() - 1.0 / 3.0).abs() < 1e-10);
337 }
338
339 #[test]
340 fn mc_star5() {
341 assert!(meshedness_coefficient(&star5()).unwrap().abs() < 1e-10);
343 }
344
345 #[test]
346 fn mc_paw() {
347 assert!((meshedness_coefficient(&paw()).unwrap() - 1.0 / 3.0).abs() < 1e-10);
349 }
350
351 #[test]
352 fn mc_disconnected() {
353 assert!(meshedness_coefficient(&disconnected()).unwrap().abs() < 1e-10);
355 }
356
357 #[test]
360 fn esr_empty() {
361 let g = Graph::with_vertices(0);
362 assert!(edge_surplus_ratio(&g).unwrap().abs() < 1e-10);
363 }
364
365 #[test]
366 fn esr_single_vertex() {
367 let g = Graph::with_vertices(1);
368 assert!(edge_surplus_ratio(&g).unwrap().abs() < 1e-10);
369 }
370
371 #[test]
372 fn esr_single_edge() {
373 assert!(edge_surplus_ratio(&single_edge()).unwrap().abs() < 1e-10);
375 }
376
377 #[test]
378 fn esr_path3() {
379 assert!(edge_surplus_ratio(&path3()).unwrap().abs() < 1e-10);
381 }
382
383 #[test]
384 fn esr_k3() {
385 assert!((edge_surplus_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
387 }
388
389 #[test]
390 fn esr_k4() {
391 assert!((edge_surplus_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
393 }
394
395 #[test]
396 fn esr_cycle4() {
397 assert!((edge_surplus_ratio(&cycle4()).unwrap() - 1.0 / 3.0).abs() < 1e-10);
399 }
400
401 #[test]
402 fn esr_star5() {
403 assert!(edge_surplus_ratio(&star5()).unwrap().abs() < 1e-10);
405 }
406
407 #[test]
408 fn esr_paw() {
409 assert!((edge_surplus_ratio(&paw()).unwrap() - 1.0 / 3.0).abs() < 1e-10);
411 }
412
413 #[test]
416 fn ci_empty() {
417 let g = Graph::with_vertices(0);
418 assert!(connectivity_index(&g).unwrap().abs() < 1e-10);
419 }
420
421 #[test]
422 fn ci_single() {
423 let g = Graph::with_vertices(1);
424 assert!(connectivity_index(&g).unwrap().abs() < 1e-10);
425 }
426
427 #[test]
428 fn ci_single_edge() {
429 assert!((connectivity_index(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
431 }
432
433 #[test]
434 fn ci_k3() {
435 assert!((connectivity_index(&k3()).unwrap() - 1.0).abs() < 1e-10);
437 }
438
439 #[test]
440 fn ci_k4() {
441 assert!((connectivity_index(&k4()).unwrap() - 1.0).abs() < 1e-10);
443 }
444
445 #[test]
446 fn ci_cycle4() {
447 assert!((connectivity_index(&cycle4()).unwrap() - 2.0 / 3.0).abs() < 1e-10);
449 }
450
451 #[test]
452 fn ci_star5() {
453 assert!((connectivity_index(&star5()).unwrap() - 0.4).abs() < 1e-10);
455 }
456
457 #[test]
458 fn ci_path3() {
459 assert!((connectivity_index(&path3()).unwrap() - 2.0 / 3.0).abs() < 1e-10);
461 }
462
463 #[test]
464 fn ci_paw() {
465 assert!((connectivity_index(&paw()).unwrap() - 2.0 / 3.0).abs() < 1e-10);
467 }
468
469 #[test]
470 fn ci_isolated() {
471 let g = Graph::with_vertices(5);
472 assert!(connectivity_index(&g).unwrap().abs() < 1e-10);
473 }
474
475 #[test]
478 fn crr_in_01() {
479 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
480 let r = circuit_rank_ratio(g).unwrap();
481 assert!(r >= -1e-10);
482 assert!(r <= 1.0 + 1e-10);
483 }
484 }
485
486 #[test]
487 fn esr_in_01() {
488 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
489 let r = edge_surplus_ratio(g).unwrap();
490 assert!(r >= -1e-10);
491 assert!(r <= 1.0 + 1e-10);
492 }
493 }
494
495 #[test]
496 fn ci_in_01() {
497 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
498 let r = connectivity_index(g).unwrap();
499 assert!(r >= -1e-10);
500 assert!(r <= 1.0 + 1e-10);
501 }
502 }
503
504 #[test]
505 fn complete_graphs_all_one() {
506 assert!((edge_surplus_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
508 assert!((edge_surplus_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
509 assert!((connectivity_index(&k3()).unwrap() - 1.0).abs() < 1e-10);
510 assert!((connectivity_index(&k4()).unwrap() - 1.0).abs() < 1e-10);
511 }
512
513 #[test]
514 fn trees_zero_surplus() {
515 assert!(circuit_rank_ratio(&path3()).unwrap().abs() < 1e-10);
517 assert!(circuit_rank_ratio(&star5()).unwrap().abs() < 1e-10);
518 assert!(edge_surplus_ratio(&path3()).unwrap().abs() < 1e-10);
519 assert!(edge_surplus_ratio(&star5()).unwrap().abs() < 1e-10);
520 }
521
522 #[test]
523 fn mc_nonneg() {
524 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
525 assert!(meshedness_coefficient(g).unwrap() >= -1e-10);
526 }
527 }
528}