rust_igraph/algorithms/properties/
centrality_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 degree_centralization(graph: &Graph) -> IgraphResult<f64> {
40 let n = graph.vcount() as usize;
41 if n < 3 {
42 return Ok(0.0);
43 }
44
45 let mut max_deg = 0_usize;
46 let mut degrees = Vec::with_capacity(n);
47 for v in 0..n {
48 let d = graph.degree(v as u32)?;
49 degrees.push(d);
50 if d > max_deg {
51 max_deg = d;
52 }
53 }
54
55 let sum_diff: usize = degrees.iter().map(|&d| max_deg - d).sum();
56 let denom = (n - 1) * (n - 2);
57
58 if denom == 0 {
59 return Ok(0.0);
60 }
61
62 Ok(sum_diff as f64 / denom as f64)
63}
64
65pub fn betweenness_centralization(graph: &Graph) -> IgraphResult<f64> {
83 let n = graph.vcount() as usize;
84 if n < 3 {
85 return Ok(0.0);
86 }
87
88 let bc = compute_betweenness(graph)?;
89
90 let max_bc = bc.iter().copied().fold(0.0_f64, f64::max);
91 let sum_diff: f64 = bc.iter().map(|&b| max_bc - b).sum();
92
93 let n_f = n as f64;
94 let denom = (n_f - 1.0) * (n_f - 2.0);
95
96 if denom < 1e-30 {
97 return Ok(0.0);
98 }
99
100 Ok(sum_diff / denom)
101}
102
103pub fn closeness_centralization(graph: &Graph) -> IgraphResult<f64> {
119 let n = graph.vcount() as usize;
120 if n < 3 {
121 return Ok(0.0);
122 }
123
124 let dist = all_pairs_bfs(graph)?;
125
126 let mut closeness_vals = Vec::with_capacity(n);
127 for v in 0..n {
128 let mut sum_d = 0_u64;
129 let mut connected = true;
130 for u in 0..n {
131 if u == v {
132 continue;
133 }
134 let d = dist[v * n + u];
135 if d == u32::MAX {
136 connected = false;
137 break;
138 }
139 sum_d += u64::from(d);
140 }
141 if !connected || sum_d == 0 {
142 return Ok(0.0);
143 }
144 closeness_vals.push((n - 1) as f64 / sum_d as f64);
145 }
146
147 let max_cc = closeness_vals.iter().copied().fold(0.0_f64, f64::max);
148 let sum_diff: f64 = closeness_vals.iter().map(|&c| max_cc - c).sum();
149
150 let n_f = n as f64;
151 let denom = (n_f - 2.0) * (n_f - 1.0) / (2.0 * n_f - 3.0);
152
153 if denom < 1e-30 {
154 return Ok(0.0);
155 }
156
157 Ok(sum_diff / denom)
158}
159
160pub fn centrality_correlation(graph: &Graph) -> IgraphResult<f64> {
176 let n = graph.vcount() as usize;
177 if n < 3 {
178 return Ok(0.0);
179 }
180
181 let bc = compute_betweenness(graph)?;
182 let dist = all_pairs_bfs(graph)?;
183
184 let mut closeness_vals = Vec::with_capacity(n);
185 for v in 0..n {
186 let mut sum_d = 0_u64;
187 for u in 0..n {
188 if u == v {
189 continue;
190 }
191 let d = dist[v * n + u];
192 if d == u32::MAX {
193 return Ok(0.0);
194 }
195 sum_d += u64::from(d);
196 }
197 if sum_d == 0 {
198 closeness_vals.push(0.0);
199 } else {
200 closeness_vals.push((n - 1) as f64 / sum_d as f64);
201 }
202 }
203
204 let mean_bc = bc.iter().sum::<f64>() / n as f64;
205 let mean_cc = closeness_vals.iter().sum::<f64>() / n as f64;
206
207 let mut cov = 0.0_f64;
208 let mut var_bc = 0.0_f64;
209 let mut var_cc = 0.0_f64;
210
211 for v in 0..n {
212 let db = bc[v] - mean_bc;
213 let dc = closeness_vals[v] - mean_cc;
214 cov += db * dc;
215 var_bc += db * db;
216 var_cc += dc * dc;
217 }
218
219 if var_bc < 1e-30 || var_cc < 1e-30 {
220 return Ok(0.0);
221 }
222
223 Ok(cov / (var_bc.sqrt() * var_cc.sqrt()))
224}
225
226fn all_pairs_bfs(graph: &Graph) -> IgraphResult<Vec<u32>> {
227 let n = graph.vcount() as usize;
228 let mut dist = vec![u32::MAX; n * n];
229
230 for s in 0..n {
231 dist[s * n + s] = 0;
232 let mut queue = std::collections::VecDeque::new();
233 queue.push_back(s);
234 while let Some(v) = queue.pop_front() {
235 let d = dist[s * n + v];
236 let neighbors = graph.neighbors(v as u32)?;
237 for &u in &neighbors {
238 let ui = u as usize;
239 if dist[s * n + ui] == u32::MAX {
240 dist[s * n + ui] = d + 1;
241 queue.push_back(ui);
242 }
243 }
244 }
245 }
246
247 Ok(dist)
248}
249
250fn compute_betweenness(graph: &Graph) -> IgraphResult<Vec<f64>> {
251 let n = graph.vcount() as usize;
252 let directed = graph.is_directed();
253 let mut bc = vec![0.0_f64; n];
254
255 for s in 0..n {
256 let mut stack = Vec::new();
257 let mut pred: Vec<Vec<usize>> = vec![Vec::new(); n];
258 let mut sigma = vec![0.0_f64; n];
259 sigma[s] = 1.0;
260 let mut dist_bfs = vec![-1_i64; n];
261 dist_bfs[s] = 0;
262
263 let mut queue = std::collections::VecDeque::new();
264 queue.push_back(s);
265
266 while let Some(v) = queue.pop_front() {
267 stack.push(v);
268 let neighbors = graph.neighbors(v as u32)?;
269 for &w in &neighbors {
270 let wi = w as usize;
271 if dist_bfs[wi] < 0 {
272 queue.push_back(wi);
273 dist_bfs[wi] = dist_bfs[v] + 1;
274 }
275 if dist_bfs[wi] == dist_bfs[v] + 1 {
276 sigma[wi] += sigma[v];
277 pred[wi].push(v);
278 }
279 }
280 }
281
282 let mut delta = vec![0.0_f64; n];
283 while let Some(w) = stack.pop() {
284 for &v in &pred[w] {
285 delta[v] += (sigma[v] / sigma[w]) * (1.0 + delta[w]);
286 }
287 if w != s {
288 bc[w] += delta[w];
289 }
290 }
291 }
292
293 if !directed {
294 for b in &mut bc {
295 *b /= 2.0;
296 }
297 }
298
299 Ok(bc)
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305
306 fn single_edge() -> Graph {
307 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
308 }
309
310 fn path3() -> Graph {
311 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
312 }
313
314 fn k3() -> Graph {
315 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
316 }
317
318 fn k4() -> Graph {
319 Graph::from_edges(
320 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
321 false,
322 Some(4),
323 )
324 .unwrap()
325 }
326
327 fn cycle4() -> Graph {
328 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
329 }
330
331 fn star5() -> Graph {
332 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
333 }
334
335 fn paw() -> Graph {
336 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
337 }
338
339 #[test]
342 fn dc_empty() {
343 let g = Graph::with_vertices(0);
344 assert!(degree_centralization(&g).unwrap().abs() < 1e-10);
345 }
346
347 #[test]
348 fn dc_single() {
349 let g = Graph::with_vertices(1);
350 assert!(degree_centralization(&g).unwrap().abs() < 1e-10);
351 }
352
353 #[test]
354 fn dc_two() {
355 assert!(degree_centralization(&single_edge()).unwrap().abs() < 1e-10);
356 }
357
358 #[test]
359 fn dc_k3() {
360 assert!(degree_centralization(&k3()).unwrap().abs() < 1e-10);
362 }
363
364 #[test]
365 fn dc_k4() {
366 assert!(degree_centralization(&k4()).unwrap().abs() < 1e-10);
367 }
368
369 #[test]
370 fn dc_cycle4() {
371 assert!(degree_centralization(&cycle4()).unwrap().abs() < 1e-10);
372 }
373
374 #[test]
375 fn dc_star5() {
376 assert!((degree_centralization(&star5()).unwrap() - 1.0).abs() < 1e-10);
378 }
379
380 #[test]
381 fn dc_path3() {
382 assert!((degree_centralization(&path3()).unwrap() - 1.0).abs() < 1e-10);
384 }
385
386 #[test]
387 fn dc_paw() {
388 assert!((degree_centralization(&paw()).unwrap() - 2.0 / 3.0).abs() < 1e-10);
390 }
391
392 #[test]
393 fn dc_in_01() {
394 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
395 let r = degree_centralization(g).unwrap();
396 assert!(r >= -1e-10);
397 assert!(r <= 1.0 + 1e-10);
398 }
399 }
400
401 #[test]
404 fn bc_empty() {
405 let g = Graph::with_vertices(0);
406 assert!(betweenness_centralization(&g).unwrap().abs() < 1e-10);
407 }
408
409 #[test]
410 fn bc_single() {
411 let g = Graph::with_vertices(1);
412 assert!(betweenness_centralization(&g).unwrap().abs() < 1e-10);
413 }
414
415 #[test]
416 fn bc_two() {
417 assert!(betweenness_centralization(&single_edge()).unwrap().abs() < 1e-10);
418 }
419
420 #[test]
421 fn bc_k3() {
422 assert!(betweenness_centralization(&k3()).unwrap().abs() < 1e-10);
424 }
425
426 #[test]
427 fn bc_k4() {
428 assert!(betweenness_centralization(&k4()).unwrap().abs() < 1e-10);
429 }
430
431 #[test]
432 fn bc_cycle4() {
433 assert!(betweenness_centralization(&cycle4()).unwrap().abs() < 1e-10);
435 }
436
437 #[test]
438 fn bc_path3() {
439 assert!((betweenness_centralization(&path3()).unwrap() - 1.0).abs() < 1e-10);
442 }
443
444 #[test]
445 fn bc_star5() {
446 let r = betweenness_centralization(&star5()).unwrap();
458 assert!(r > 1.0);
459 }
460
461 #[test]
462 fn bc_nonneg() {
463 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
464 assert!(betweenness_centralization(g).unwrap() >= -1e-10);
465 }
466 }
467
468 #[test]
471 fn cc_empty() {
472 let g = Graph::with_vertices(0);
473 assert!(closeness_centralization(&g).unwrap().abs() < 1e-10);
474 }
475
476 #[test]
477 fn cc_single() {
478 let g = Graph::with_vertices(1);
479 assert!(closeness_centralization(&g).unwrap().abs() < 1e-10);
480 }
481
482 #[test]
483 fn cc_two() {
484 assert!(closeness_centralization(&single_edge()).unwrap().abs() < 1e-10);
485 }
486
487 #[test]
488 fn cc_k3() {
489 assert!(closeness_centralization(&k3()).unwrap().abs() < 1e-10);
491 }
492
493 #[test]
494 fn cc_k4() {
495 assert!(closeness_centralization(&k4()).unwrap().abs() < 1e-10);
496 }
497
498 #[test]
499 fn cc_cycle4() {
500 assert!(closeness_centralization(&cycle4()).unwrap().abs() < 1e-10);
502 }
503
504 #[test]
505 fn cc_star5() {
506 assert!((closeness_centralization(&star5()).unwrap() - 1.0).abs() < 1e-10);
512 }
513
514 #[test]
515 fn cc_path3() {
516 assert!((closeness_centralization(&path3()).unwrap() - 1.0).abs() < 1e-10);
521 }
522
523 #[test]
524 fn cc_disconnected() {
525 let g = Graph::from_edges(&[(0, 1), (2, 3)], false, Some(4)).unwrap();
526 assert!(closeness_centralization(&g).unwrap().abs() < 1e-10);
527 }
528
529 #[test]
530 fn cc_nonneg() {
531 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
532 assert!(closeness_centralization(g).unwrap() >= -1e-10);
533 }
534 }
535
536 #[test]
539 fn ccorr_empty() {
540 let g = Graph::with_vertices(0);
541 assert!(centrality_correlation(&g).unwrap().abs() < 1e-10);
542 }
543
544 #[test]
545 fn ccorr_single() {
546 let g = Graph::with_vertices(1);
547 assert!(centrality_correlation(&g).unwrap().abs() < 1e-10);
548 }
549
550 #[test]
551 fn ccorr_two() {
552 assert!(centrality_correlation(&single_edge()).unwrap().abs() < 1e-10);
553 }
554
555 #[test]
556 fn ccorr_k3() {
557 assert!(centrality_correlation(&k3()).unwrap().abs() < 1e-10);
559 }
560
561 #[test]
562 fn ccorr_k4() {
563 assert!(centrality_correlation(&k4()).unwrap().abs() < 1e-10);
564 }
565
566 #[test]
567 fn ccorr_cycle4() {
568 assert!(centrality_correlation(&cycle4()).unwrap().abs() < 1e-10);
570 }
571
572 #[test]
573 fn ccorr_star5() {
574 let r = centrality_correlation(&star5()).unwrap();
576 assert!(r > 0.5);
577 }
578
579 #[test]
580 fn ccorr_path3() {
581 let r = centrality_correlation(&path3()).unwrap();
583 assert!(r > 0.5);
584 }
585
586 #[test]
587 fn ccorr_disconnected() {
588 let g = Graph::from_edges(&[(0, 1), (2, 3)], false, Some(4)).unwrap();
589 assert!(centrality_correlation(&g).unwrap().abs() < 1e-10);
590 }
591
592 #[test]
593 fn ccorr_in_range() {
594 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
595 let r = centrality_correlation(g).unwrap();
596 assert!(r >= -1.0 - 1e-10);
597 assert!(r <= 1.0 + 1e-10);
598 }
599 }
600
601 #[test]
604 fn regular_zero_dc() {
605 assert!(degree_centralization(&k3()).unwrap().abs() < 1e-10);
606 assert!(degree_centralization(&k4()).unwrap().abs() < 1e-10);
607 assert!(degree_centralization(&cycle4()).unwrap().abs() < 1e-10);
608 }
609
610 #[test]
611 fn complete_zero_bc() {
612 assert!(betweenness_centralization(&k3()).unwrap().abs() < 1e-10);
613 assert!(betweenness_centralization(&k4()).unwrap().abs() < 1e-10);
614 }
615
616 #[test]
617 fn complete_zero_cc() {
618 assert!(closeness_centralization(&k3()).unwrap().abs() < 1e-10);
619 assert!(closeness_centralization(&k4()).unwrap().abs() < 1e-10);
620 }
621}