rust_igraph/algorithms/properties/
degree_distance_ratios.rs1#![allow(
12 clippy::cast_possible_truncation,
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 degree_distance_correlation(graph: &Graph) -> IgraphResult<f64> {
39 let n = graph.vcount() as usize;
40 if n < 2 {
41 return Ok(0.0);
42 }
43
44 let dist = all_pairs_bfs(graph)?;
45
46 let mut degrees = Vec::with_capacity(n);
47 let mut eccs = Vec::with_capacity(n);
48
49 for v in 0..n {
50 degrees.push(graph.degree(v as u32)?);
51
52 let mut ecc = 0_u32;
53 for u in 0..n {
54 if u == v {
55 continue;
56 }
57 let d = dist[v * n + u];
58 if d == u32::MAX {
59 return Ok(0.0);
60 }
61 if d > ecc {
62 ecc = d;
63 }
64 }
65 eccs.push(ecc);
66 }
67
68 let mean_deg = degrees.iter().sum::<usize>() as f64 / n as f64;
69 let mean_ecc = f64::from(eccs.iter().sum::<u32>()) / n as f64;
70
71 let mut cov = 0.0_f64;
72 let mut var_deg = 0.0_f64;
73 let mut var_ecc = 0.0_f64;
74
75 for v in 0..n {
76 let dd = degrees[v] as f64 - mean_deg;
77 let de = f64::from(eccs[v]) - mean_ecc;
78 cov += dd * de;
79 var_deg += dd * dd;
80 var_ecc += de * de;
81 }
82
83 if var_deg < 1e-30 || var_ecc < 1e-30 {
84 return Ok(0.0);
85 }
86
87 Ok(cov / (var_deg.sqrt() * var_ecc.sqrt()))
88}
89
90pub fn local_efficiency_ratio(graph: &Graph) -> IgraphResult<f64> {
109 let n = graph.vcount() as usize;
110 if n < 2 {
111 return Ok(0.0);
112 }
113
114 let dist = all_pairs_bfs(graph)?;
115
116 let mut global_sum_inv = 0.0_f64;
117 let mut global_pairs = 0_u64;
118 for v in 0..n {
119 for u in (v + 1)..n {
120 let d = dist[v * n + u];
121 if d != u32::MAX && d > 0 {
122 global_sum_inv += 1.0 / f64::from(d);
123 }
124 global_pairs += 1;
125 }
126 }
127
128 if global_pairs == 0 {
129 return Ok(0.0);
130 }
131 let global_eff = global_sum_inv / global_pairs as f64;
132 if global_eff < 1e-30 {
133 return Ok(0.0);
134 }
135
136 let mut local_eff_sum = 0.0_f64;
137 for v in 0..n {
138 let neighbors = graph.neighbors(v as u32)?;
139 let nn = neighbors.len();
140 if nn < 2 {
141 continue;
142 }
143
144 let mut sub_sum_inv = 0.0_f64;
145 let mut sub_pairs = 0_u64;
146 for i in 0..nn {
147 let ni = neighbors[i] as usize;
148 for j in (i + 1)..nn {
149 let nj = neighbors[j] as usize;
150 let d = dist[ni * n + nj];
151 if d != u32::MAX && d > 0 {
152 sub_sum_inv += 1.0 / f64::from(d);
153 }
154 sub_pairs += 1;
155 }
156 }
157
158 if sub_pairs > 0 {
159 local_eff_sum += sub_sum_inv / sub_pairs as f64;
160 }
161 }
162
163 let mean_local_eff = local_eff_sum / n as f64;
164 Ok(mean_local_eff / global_eff)
165}
166
167pub fn transmission_ratio(graph: &Graph) -> IgraphResult<f64> {
183 let n = graph.vcount() as usize;
184 if n < 2 {
185 return Ok(0.0);
186 }
187
188 let dist = all_pairs_bfs(graph)?;
189
190 let mut transmissions = Vec::with_capacity(n);
191 for v in 0..n {
192 let mut t = 0_u64;
193 for u in 0..n {
194 if u == v {
195 continue;
196 }
197 let d = dist[v * n + u];
198 if d == u32::MAX {
199 return Ok(0.0);
200 }
201 t += u64::from(d);
202 }
203 transmissions.push(t);
204 }
205
206 let max_t = transmissions.iter().copied().max().unwrap_or(0);
207 if max_t == 0 {
208 return Ok(0.0);
209 }
210
211 let mean_t = transmissions.iter().sum::<u64>() as f64 / n as f64;
212 Ok(mean_t / max_t as f64)
213}
214
215pub fn degree_closeness_correlation(graph: &Graph) -> IgraphResult<f64> {
231 let n = graph.vcount() as usize;
232 if n < 2 {
233 return Ok(0.0);
234 }
235
236 let dist = all_pairs_bfs(graph)?;
237
238 let mut degrees = Vec::with_capacity(n);
239 let mut closeness_vals = Vec::with_capacity(n);
240
241 for v in 0..n {
242 degrees.push(graph.degree(v as u32)?);
243
244 let mut sum_d = 0_u64;
245 for u in 0..n {
246 if u == v {
247 continue;
248 }
249 let d = dist[v * n + u];
250 if d == u32::MAX {
251 return Ok(0.0);
252 }
253 sum_d += u64::from(d);
254 }
255 if sum_d == 0 {
256 closeness_vals.push(0.0);
257 } else {
258 closeness_vals.push((n - 1) as f64 / sum_d as f64);
259 }
260 }
261
262 let mean_deg = degrees.iter().sum::<usize>() as f64 / n as f64;
263 let mean_close: f64 = closeness_vals.iter().sum::<f64>() / n as f64;
264
265 let mut cov = 0.0_f64;
266 let mut var_deg = 0.0_f64;
267 let mut var_close = 0.0_f64;
268
269 for v in 0..n {
270 let dd = degrees[v] as f64 - mean_deg;
271 let dc = closeness_vals[v] - mean_close;
272 cov += dd * dc;
273 var_deg += dd * dd;
274 var_close += dc * dc;
275 }
276
277 if var_deg < 1e-30 || var_close < 1e-30 {
278 return Ok(0.0);
279 }
280
281 Ok(cov / (var_deg.sqrt() * var_close.sqrt()))
282}
283
284fn all_pairs_bfs(graph: &Graph) -> IgraphResult<Vec<u32>> {
285 let n = graph.vcount() as usize;
286 let mut dist = vec![u32::MAX; n * n];
287
288 for s in 0..n {
289 dist[s * n + s] = 0;
290 let mut queue = std::collections::VecDeque::new();
291 queue.push_back(s);
292 while let Some(v) = queue.pop_front() {
293 let d = dist[s * n + v];
294 let neighbors = graph.neighbors(v as u32)?;
295 for &u in &neighbors {
296 let ui = u as usize;
297 if dist[s * n + ui] == u32::MAX {
298 dist[s * n + ui] = d + 1;
299 queue.push_back(ui);
300 }
301 }
302 }
303 }
304
305 Ok(dist)
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 fn single_edge() -> Graph {
313 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
314 }
315
316 fn path3() -> Graph {
317 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
318 }
319
320 fn k3() -> Graph {
321 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
322 }
323
324 fn k4() -> Graph {
325 Graph::from_edges(
326 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
327 false,
328 Some(4),
329 )
330 .unwrap()
331 }
332
333 fn cycle4() -> Graph {
334 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
335 }
336
337 fn star5() -> Graph {
338 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
339 }
340
341 fn paw() -> Graph {
342 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
343 }
344
345 #[test]
348 fn ddc_empty() {
349 let g = Graph::with_vertices(0);
350 assert!(degree_distance_correlation(&g).unwrap().abs() < 1e-10);
351 }
352
353 #[test]
354 fn ddc_single() {
355 let g = Graph::with_vertices(1);
356 assert!(degree_distance_correlation(&g).unwrap().abs() < 1e-10);
357 }
358
359 #[test]
360 fn ddc_k3() {
361 assert!(degree_distance_correlation(&k3()).unwrap().abs() < 1e-10);
363 }
364
365 #[test]
366 fn ddc_k4() {
367 assert!(degree_distance_correlation(&k4()).unwrap().abs() < 1e-10);
368 }
369
370 #[test]
371 fn ddc_cycle4() {
372 assert!(degree_distance_correlation(&cycle4()).unwrap().abs() < 1e-10);
374 }
375
376 #[test]
377 fn ddc_star5() {
378 let r = degree_distance_correlation(&star5()).unwrap();
381 assert!(r < -0.5);
382 }
383
384 #[test]
385 fn ddc_path3() {
386 let r = degree_distance_correlation(&path3()).unwrap();
388 assert!(r < -0.5);
389 }
390
391 #[test]
392 fn ddc_disconnected() {
393 let g = Graph::from_edges(&[(0, 1), (2, 3)], false, Some(4)).unwrap();
394 assert!(degree_distance_correlation(&g).unwrap().abs() < 1e-10);
395 }
396
397 #[test]
398 fn ddc_in_range() {
399 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
400 let r = degree_distance_correlation(g).unwrap();
401 assert!(r >= -1.0 - 1e-10);
402 assert!(r <= 1.0 + 1e-10);
403 }
404 }
405
406 #[test]
409 fn ler_empty() {
410 let g = Graph::with_vertices(0);
411 assert!(local_efficiency_ratio(&g).unwrap().abs() < 1e-10);
412 }
413
414 #[test]
415 fn ler_single() {
416 let g = Graph::with_vertices(1);
417 assert!(local_efficiency_ratio(&g).unwrap().abs() < 1e-10);
418 }
419
420 #[test]
421 fn ler_k3() {
422 assert!((local_efficiency_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
424 }
425
426 #[test]
427 fn ler_k4() {
428 assert!((local_efficiency_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
430 }
431
432 #[test]
433 fn ler_star5() {
434 let r = local_efficiency_ratio(&star5()).unwrap();
438 assert!(r > 0.0);
439 assert!(r < 0.5);
440 }
441
442 #[test]
443 fn ler_single_edge() {
444 assert!(local_efficiency_ratio(&single_edge()).unwrap().abs() < 1e-10);
447 }
448
449 #[test]
450 fn ler_nonneg() {
451 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
452 assert!(local_efficiency_ratio(g).unwrap() >= -1e-10);
453 }
454 }
455
456 #[test]
459 fn tr_empty() {
460 let g = Graph::with_vertices(0);
461 assert!(transmission_ratio(&g).unwrap().abs() < 1e-10);
462 }
463
464 #[test]
465 fn tr_single() {
466 let g = Graph::with_vertices(1);
467 assert!(transmission_ratio(&g).unwrap().abs() < 1e-10);
468 }
469
470 #[test]
471 fn tr_k3() {
472 assert!((transmission_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
474 }
475
476 #[test]
477 fn tr_k4() {
478 assert!((transmission_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
480 }
481
482 #[test]
483 fn tr_cycle4() {
484 assert!((transmission_ratio(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
486 }
487
488 #[test]
489 fn tr_path3() {
490 assert!((transmission_ratio(&path3()).unwrap() - 8.0 / 9.0).abs() < 1e-10);
493 }
494
495 #[test]
496 fn tr_star5() {
497 assert!((transmission_ratio(&star5()).unwrap() - 32.0 / 35.0).abs() < 1e-10);
500 }
501
502 #[test]
503 fn tr_disconnected() {
504 let g = Graph::from_edges(&[(0, 1), (2, 3)], false, Some(4)).unwrap();
505 assert!(transmission_ratio(&g).unwrap().abs() < 1e-10);
506 }
507
508 #[test]
509 fn tr_in_01() {
510 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
511 let r = transmission_ratio(g).unwrap();
512 assert!(r >= -1e-10);
513 assert!(r <= 1.0 + 1e-10);
514 }
515 }
516
517 #[test]
520 fn dcc_empty() {
521 let g = Graph::with_vertices(0);
522 assert!(degree_closeness_correlation(&g).unwrap().abs() < 1e-10);
523 }
524
525 #[test]
526 fn dcc_single() {
527 let g = Graph::with_vertices(1);
528 assert!(degree_closeness_correlation(&g).unwrap().abs() < 1e-10);
529 }
530
531 #[test]
532 fn dcc_k3() {
533 assert!(degree_closeness_correlation(&k3()).unwrap().abs() < 1e-10);
535 }
536
537 #[test]
538 fn dcc_k4() {
539 assert!(degree_closeness_correlation(&k4()).unwrap().abs() < 1e-10);
540 }
541
542 #[test]
543 fn dcc_cycle4() {
544 assert!(degree_closeness_correlation(&cycle4()).unwrap().abs() < 1e-10);
545 }
546
547 #[test]
548 fn dcc_star5() {
549 let r = degree_closeness_correlation(&star5()).unwrap();
553 assert!(r > 0.5);
554 }
555
556 #[test]
557 fn dcc_path3() {
558 let r = degree_closeness_correlation(&path3()).unwrap();
561 assert!(r > 0.5);
562 }
563
564 #[test]
565 fn dcc_disconnected() {
566 let g = Graph::from_edges(&[(0, 1), (2, 3)], false, Some(4)).unwrap();
567 assert!(degree_closeness_correlation(&g).unwrap().abs() < 1e-10);
568 }
569
570 #[test]
571 fn dcc_in_range() {
572 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
573 let r = degree_closeness_correlation(g).unwrap();
574 assert!(r >= -1.0 - 1e-10);
575 assert!(r <= 1.0 + 1e-10);
576 }
577 }
578
579 #[test]
582 fn regular_graphs_zero_corr() {
583 assert!(degree_distance_correlation(&k3()).unwrap().abs() < 1e-10);
585 assert!(degree_distance_correlation(&k4()).unwrap().abs() < 1e-10);
586 assert!(degree_distance_correlation(&cycle4()).unwrap().abs() < 1e-10);
587 }
588
589 #[test]
590 fn regular_graphs_unit_transmission() {
591 assert!((transmission_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
593 assert!((transmission_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
594 assert!((transmission_ratio(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
595 }
596}