1use std::collections::VecDeque;
14use std::hash::Hash;
15use std::sync::RwLock;
16
17use hashbrown::HashMap;
18use petgraph::algo::dijkstra;
19use petgraph::visit::{
20 Bfs, EdgeCount, EdgeIndexable, EdgeRef, GraphBase, GraphProp, IntoEdgeReferences, IntoEdges,
21 IntoEdgesDirected, IntoNeighbors, IntoNeighborsDirected, IntoNodeIdentifiers, NodeCount,
22 NodeIndexable, Reversed, ReversedEdgeReference, Visitable,
23};
24use rayon_cond::CondIterator;
25
26pub fn betweenness_centrality<G>(
69 graph: G,
70 include_endpoints: bool,
71 normalized: bool,
72 parallel_threshold: usize,
73) -> Vec<Option<f64>>
74where
75 G: NodeIndexable
76 + IntoNodeIdentifiers
77 + IntoNeighborsDirected
78 + NodeCount
79 + GraphProp
80 + GraphBase
81 + std::marker::Sync,
82 <G as GraphBase>::NodeId: std::cmp::Eq + Hash + Send,
83 {
89 let max_index = graph.node_bound();
99
100 let mut betweenness: Vec<Option<f64>> = vec![None; max_index];
101 for node_s in graph.node_identifiers() {
102 let is: usize = graph.to_index(node_s);
103 betweenness[is] = Some(0.0);
104 }
105 let locked_betweenness = RwLock::new(&mut betweenness);
106 let node_indices: Vec<G::NodeId> = graph.node_identifiers().collect();
107
108 CondIterator::new(node_indices, graph.node_count() >= parallel_threshold)
109 .map(|node_s| (shortest_path_for_centrality(&graph, &node_s), node_s))
110 .for_each(|(mut shortest_path_calc, node_s)| {
111 _accumulate_vertices(
112 &locked_betweenness,
113 max_index,
114 &mut shortest_path_calc,
115 node_s,
116 &graph,
117 include_endpoints,
118 );
119 });
120
121 _rescale(
122 &mut betweenness,
123 graph.node_count(),
124 normalized,
125 graph.is_directed(),
126 include_endpoints,
127 );
128
129 betweenness
130}
131
132pub fn edge_betweenness_centrality<G>(
173 graph: G,
174 normalized: bool,
175 parallel_threshold: usize,
176) -> Vec<Option<f64>>
177where
178 G: NodeIndexable
179 + EdgeIndexable
180 + IntoEdges
181 + IntoNodeIdentifiers
182 + IntoNeighborsDirected
183 + NodeCount
184 + EdgeCount
185 + GraphProp
186 + Sync,
187 G::NodeId: Eq + Hash + Send,
188 G::EdgeId: Eq + Hash + Send,
189{
190 let max_index = graph.node_bound();
191 let mut betweenness = vec![None; graph.edge_bound()];
192 for edge in graph.edge_references() {
193 let is: usize = EdgeIndexable::to_index(&graph, edge.id());
194 betweenness[is] = Some(0.0);
195 }
196 let locked_betweenness = RwLock::new(&mut betweenness);
197 let node_indices: Vec<G::NodeId> = graph.node_identifiers().collect();
198 CondIterator::new(node_indices, graph.node_count() >= parallel_threshold)
199 .map(|node_s| shortest_path_for_edge_centrality(&graph, &node_s))
200 .for_each(|mut shortest_path_calc| {
201 accumulate_edges(
202 &locked_betweenness,
203 max_index,
204 &mut shortest_path_calc,
205 &graph,
206 );
207 });
208
209 _rescale(
210 &mut betweenness,
211 graph.node_count(),
212 normalized,
213 graph.is_directed(),
214 true,
215 );
216 betweenness
217}
218
219fn _rescale(
220 betweenness: &mut [Option<f64>],
221 node_count: usize,
222 normalized: bool,
223 directed: bool,
224 include_endpoints: bool,
225) {
226 let mut do_scale = true;
227 let mut scale = 1.0;
228 if normalized {
229 if include_endpoints {
230 if node_count < 2 {
231 do_scale = false;
232 } else {
233 scale = 1.0 / (node_count * (node_count - 1)) as f64;
234 }
235 } else if node_count <= 2 {
236 do_scale = false;
237 } else {
238 scale = 1.0 / ((node_count - 1) * (node_count - 2)) as f64;
239 }
240 } else if !directed {
241 scale = 0.5;
242 } else {
243 do_scale = false;
244 }
245 if do_scale {
246 for x in betweenness.iter_mut() {
247 *x = x.map(|y| y * scale);
248 }
249 }
250}
251
252fn _accumulate_vertices<G>(
253 locked_betweenness: &RwLock<&mut Vec<Option<f64>>>,
254 max_index: usize,
255 path_calc: &mut ShortestPathData<G>,
256 node_s: <G as GraphBase>::NodeId,
257 graph: G,
258 include_endpoints: bool,
259) where
260 G: NodeIndexable
261 + IntoNodeIdentifiers
262 + IntoNeighborsDirected
263 + NodeCount
264 + GraphProp
265 + GraphBase
266 + std::marker::Sync,
267 <G as GraphBase>::NodeId: std::cmp::Eq + Hash,
268{
269 let mut delta = vec![0.0; max_index];
270 for w in &path_calc.verts_sorted_by_distance {
271 let iw = graph.to_index(*w);
272 let coeff = (1.0 + delta[iw]) / path_calc.sigma[w];
273 let p_w = path_calc.predecessors.get(w).unwrap();
274 for v in p_w {
275 let iv = graph.to_index(*v);
276 delta[iv] += path_calc.sigma[v] * coeff;
277 }
278 }
279 let mut betweenness = locked_betweenness.write().unwrap();
280 if include_endpoints {
281 let i_node_s = graph.to_index(node_s);
282 betweenness[i_node_s] = betweenness[i_node_s]
283 .map(|x| x + ((path_calc.verts_sorted_by_distance.len() - 1) as f64));
284 for w in &path_calc.verts_sorted_by_distance {
285 if *w != node_s {
286 let iw = graph.to_index(*w);
287 betweenness[iw] = betweenness[iw].map(|x| x + delta[iw] + 1.0);
288 }
289 }
290 } else {
291 for w in &path_calc.verts_sorted_by_distance {
292 if *w != node_s {
293 let iw = graph.to_index(*w);
294 betweenness[iw] = betweenness[iw].map(|x| x + delta[iw]);
295 }
296 }
297 }
298}
299
300fn accumulate_edges<G>(
301 locked_betweenness: &RwLock<&mut Vec<Option<f64>>>,
302 max_index: usize,
303 path_calc: &mut ShortestPathDataWithEdges<G>,
304 graph: G,
305) where
306 G: NodeIndexable + EdgeIndexable + Sync,
307 G::NodeId: Eq + Hash,
308 G::EdgeId: Eq + Hash,
309{
310 let mut delta = vec![0.0; max_index];
311 for w in &path_calc.verts_sorted_by_distance {
312 let iw = NodeIndexable::to_index(&graph, *w);
313 let coeff = (1.0 + delta[iw]) / path_calc.sigma[w];
314 let p_w = path_calc.predecessors.get(w).unwrap();
315 let e_w = path_calc.predecessor_edges.get(w).unwrap();
316 let mut betweenness = locked_betweenness.write().unwrap();
317 for i in 0..p_w.len() {
318 let v = p_w[i];
319 let iv = NodeIndexable::to_index(&graph, v);
320 let ie = EdgeIndexable::to_index(&graph, e_w[i]);
321 let c = path_calc.sigma[&v] * coeff;
322 betweenness[ie] = betweenness[ie].map(|x| x + c);
323 delta[iv] += c;
324 }
325 }
326}
327pub fn degree_centrality<G>(graph: G, direction: Option<petgraph::Direction>) -> Vec<f64>
354where
355 G: NodeIndexable
356 + IntoNodeIdentifiers
357 + IntoNeighbors
358 + IntoNeighborsDirected
359 + NodeCount
360 + GraphProp,
361 G::NodeId: Eq + Hash,
362{
363 let node_count = graph.node_count() as f64;
364 let mut centrality = vec![0.0; graph.node_bound()];
365
366 for node in graph.node_identifiers() {
367 let (degree, normalization) = match (graph.is_directed(), direction) {
368 (true, None) => {
369 let out_degree = graph
370 .neighbors_directed(node, petgraph::Direction::Outgoing)
371 .count() as f64;
372 let in_degree = graph
373 .neighbors_directed(node, petgraph::Direction::Incoming)
374 .count() as f64;
375 let total = in_degree + out_degree;
376 let norm = if total == 2.0 * (node_count - 1.0) {
378 2.0 * (node_count - 1.0)
379 } else {
380 node_count - 1.0
381 };
382 (total, norm)
383 }
384 (true, Some(dir)) => (
385 graph.neighbors_directed(node, dir).count() as f64,
386 node_count - 1.0,
387 ),
388 (false, _) => (graph.neighbors(node).count() as f64, node_count - 1.0),
389 };
390 centrality[graph.to_index(node)] = degree / normalization;
391 }
392
393 centrality
394}
395
396struct ShortestPathData<G>
397where
398 G: GraphBase,
399 <G as GraphBase>::NodeId: std::cmp::Eq + Hash,
400{
401 verts_sorted_by_distance: Vec<G::NodeId>,
402 predecessors: HashMap<G::NodeId, Vec<G::NodeId>>,
403 sigma: HashMap<G::NodeId, f64>,
404}
405
406fn shortest_path_for_centrality<G>(graph: G, node_s: &G::NodeId) -> ShortestPathData<G>
407where
408 G: NodeIndexable + IntoNodeIdentifiers + IntoNeighborsDirected + NodeCount + GraphBase,
409 <G as GraphBase>::NodeId: std::cmp::Eq + Hash,
410{
411 let mut verts_sorted_by_distance: Vec<G::NodeId> = Vec::new(); let c = graph.node_count();
413 let mut predecessors = HashMap::<G::NodeId, Vec<G::NodeId>>::with_capacity(c);
414 let mut sigma = HashMap::<G::NodeId, f64>::with_capacity(c);
415 let mut distance = HashMap::<G::NodeId, i64>::with_capacity(c);
416 #[allow(non_snake_case)]
417 let mut Q: VecDeque<G::NodeId> = VecDeque::with_capacity(c);
418
419 for node in graph.node_identifiers() {
420 predecessors.insert(node, Vec::new());
421 sigma.insert(node, 0.0);
422 distance.insert(node, -1);
423 }
424 sigma.insert(*node_s, 1.0);
425 distance.insert(*node_s, 0);
426 Q.push_back(*node_s);
427 while let Some(v) = Q.pop_front() {
428 verts_sorted_by_distance.push(v);
429 let distance_v = distance[&v];
430 for w in graph.neighbors(v) {
431 if distance[&w] < 0 {
432 Q.push_back(w);
433 distance.insert(w, distance_v + 1);
434 }
435 if distance[&w] == distance_v + 1 {
436 sigma.insert(w, sigma[&w] + sigma[&v]);
437 let e_p = predecessors.get_mut(&w).unwrap();
438 e_p.push(v);
439 }
440 }
441 }
442 verts_sorted_by_distance.reverse(); ShortestPathData {
444 verts_sorted_by_distance,
445 predecessors,
446 sigma,
447 }
448}
449
450struct ShortestPathDataWithEdges<G>
451where
452 G: GraphBase,
453 G::NodeId: Eq + Hash,
454 G::EdgeId: Eq + Hash,
455{
456 verts_sorted_by_distance: Vec<G::NodeId>,
457 predecessors: HashMap<G::NodeId, Vec<G::NodeId>>,
458 predecessor_edges: HashMap<G::NodeId, Vec<G::EdgeId>>,
459 sigma: HashMap<G::NodeId, f64>,
460}
461
462fn shortest_path_for_edge_centrality<G>(
463 graph: G,
464 node_s: &G::NodeId,
465) -> ShortestPathDataWithEdges<G>
466where
467 G: NodeIndexable
468 + IntoNodeIdentifiers
469 + IntoNeighborsDirected
470 + NodeCount
471 + GraphBase
472 + IntoEdges,
473 G::NodeId: Eq + Hash,
474 G::EdgeId: Eq + Hash,
475{
476 let mut verts_sorted_by_distance: Vec<G::NodeId> = Vec::new(); let c = graph.node_count();
478 let mut predecessors = HashMap::<G::NodeId, Vec<G::NodeId>>::with_capacity(c);
479 let mut predecessor_edges = HashMap::<G::NodeId, Vec<G::EdgeId>>::with_capacity(c);
480 let mut sigma = HashMap::<G::NodeId, f64>::with_capacity(c);
481 let mut distance = HashMap::<G::NodeId, i64>::with_capacity(c);
482 #[allow(non_snake_case)]
483 let mut Q: VecDeque<G::NodeId> = VecDeque::with_capacity(c);
484
485 for node in graph.node_identifiers() {
486 predecessors.insert(node, Vec::new());
487 predecessor_edges.insert(node, Vec::new());
488 sigma.insert(node, 0.0);
489 distance.insert(node, -1);
490 }
491 sigma.insert(*node_s, 1.0);
492 distance.insert(*node_s, 0);
493 Q.push_back(*node_s);
494 while let Some(v) = Q.pop_front() {
495 verts_sorted_by_distance.push(v);
496 let distance_v = distance[&v];
497 for edge in graph.edges(v) {
498 let w = edge.target();
499 if distance[&w] < 0 {
500 Q.push_back(w);
501 distance.insert(w, distance_v + 1);
502 }
503 if distance[&w] == distance_v + 1 {
504 sigma.insert(w, sigma[&w] + sigma[&v]);
505 let e_p = predecessors.get_mut(&w).unwrap();
506 e_p.push(v);
507 predecessor_edges.get_mut(&w).unwrap().push(edge.id());
508 }
509 }
510 }
511 verts_sorted_by_distance.reverse(); ShortestPathDataWithEdges {
513 verts_sorted_by_distance,
514 predecessors,
515 predecessor_edges,
516 sigma,
517 }
518}
519
520#[cfg(test)]
521mod test_edge_betweenness_centrality {
522 use crate::centrality::edge_betweenness_centrality;
523 use petgraph::graph::edge_index;
524 use petgraph::prelude::StableGraph;
525 use petgraph::Undirected;
526
527 macro_rules! assert_almost_equal {
528 ($x:expr, $y:expr, $d:expr) => {
529 if ($x - $y).abs() >= $d {
530 panic!("{} != {} within delta of {}", $x, $y, $d);
531 }
532 };
533 }
534
535 #[test]
536 fn test_undirected_graph_normalized() {
537 let graph = petgraph::graph::UnGraph::<(), ()>::from_edges([
538 (0, 6),
539 (0, 4),
540 (0, 1),
541 (0, 5),
542 (1, 6),
543 (1, 7),
544 (1, 3),
545 (1, 4),
546 (2, 6),
547 (2, 3),
548 (3, 5),
549 (3, 7),
550 (3, 6),
551 (4, 5),
552 (5, 6),
553 ]);
554 let output = edge_betweenness_centrality(&graph, true, 50);
555 let result = output.iter().map(|x| x.unwrap()).collect::<Vec<f64>>();
556 let expected_values = [
557 0.1023809, 0.0547619, 0.0922619, 0.05654762, 0.09940476, 0.125, 0.09940476, 0.12440476,
558 0.12857143, 0.12142857, 0.13511905, 0.125, 0.06547619, 0.08869048, 0.08154762,
559 ];
560 for i in 0..15 {
561 assert_almost_equal!(result[i], expected_values[i], 1e-4);
562 }
563 }
564
565 #[test]
566 fn test_undirected_graph_unnormalized() {
567 let graph = petgraph::graph::UnGraph::<(), ()>::from_edges([
568 (0, 2),
569 (0, 4),
570 (0, 1),
571 (1, 3),
572 (1, 5),
573 (1, 7),
574 (2, 7),
575 (2, 3),
576 (3, 5),
577 (3, 6),
578 (4, 6),
579 (5, 7),
580 ]);
581 let output = edge_betweenness_centrality(&graph, false, 50);
582 let result = output.iter().map(|x| x.unwrap()).collect::<Vec<f64>>();
583 let expected_values = [
584 3.83333, 5.5, 5.33333, 3.5, 2.5, 3.0, 3.5, 4.0, 3.66667, 6.5, 3.5, 2.16667,
585 ];
586 for i in 0..12 {
587 assert_almost_equal!(result[i], expected_values[i], 1e-4);
588 }
589 }
590
591 #[test]
592 fn test_directed_graph_normalized() {
593 let graph = petgraph::graph::DiGraph::<(), ()>::from_edges([
594 (0, 1),
595 (1, 0),
596 (1, 3),
597 (1, 2),
598 (1, 4),
599 (2, 3),
600 (2, 4),
601 (2, 1),
602 (3, 2),
603 (4, 3),
604 ]);
605 let output = edge_betweenness_centrality(&graph, true, 50);
606 let result = output.iter().map(|x| x.unwrap()).collect::<Vec<f64>>();
607 let expected_values = [0.2, 0.2, 0.1, 0.1, 0.1, 0.05, 0.1, 0.3, 0.35, 0.2];
608 for i in 0..10 {
609 assert_almost_equal!(result[i], expected_values[i], 1e-4);
610 }
611 }
612
613 #[test]
614 fn test_directed_graph_unnormalized() {
615 let graph = petgraph::graph::DiGraph::<(), ()>::from_edges([
616 (0, 4),
617 (1, 0),
618 (1, 3),
619 (2, 3),
620 (2, 4),
621 (2, 0),
622 (3, 4),
623 (3, 2),
624 (3, 1),
625 (4, 1),
626 ]);
627 let output = edge_betweenness_centrality(&graph, false, 50);
628 let result = output.iter().map(|x| x.unwrap()).collect::<Vec<f64>>();
629 let expected_values = [4.5, 3.0, 6.5, 1.5, 1.5, 1.5, 1.5, 4.5, 2.0, 7.5];
630 for i in 0..10 {
631 assert_almost_equal!(result[i], expected_values[i], 1e-4);
632 }
633 }
634
635 #[test]
636 fn test_stable_graph_with_removed_edges() {
637 let mut graph: StableGraph<(), (), Undirected> =
638 StableGraph::from_edges([(0, 1), (1, 2), (2, 3), (3, 0)]);
639 graph.remove_edge(edge_index(1));
640 let result = edge_betweenness_centrality(&graph, false, 50);
641 let expected_values = vec![Some(3.0), None, Some(3.0), Some(4.0)];
642 assert_eq!(result, expected_values);
643 }
644}
645
646pub fn eigenvector_centrality<G, F, E>(
690 graph: G,
691 mut weight_fn: F,
692 max_iter: Option<usize>,
693 tol: Option<f64>,
694) -> Result<Option<Vec<f64>>, E>
695where
696 G: NodeIndexable + IntoNodeIdentifiers + IntoNeighbors + IntoEdges + NodeCount,
697 G::NodeId: Eq + std::hash::Hash,
698 F: FnMut(G::EdgeRef) -> Result<f64, E>,
699{
700 let tol: f64 = tol.unwrap_or(1e-6);
701 let max_iter = max_iter.unwrap_or(100);
702 let mut x: Vec<f64> = vec![1.; graph.node_bound()];
703 let node_count = graph.node_count();
704 for _ in 0..max_iter {
705 let x_last = x.clone();
706 for node_index in graph.node_identifiers() {
707 let node = graph.to_index(node_index);
708 for edge in graph.edges(node_index) {
709 let w = weight_fn(edge)?;
710 let neighbor = edge.target();
711 x[graph.to_index(neighbor)] += x_last[node] * w;
712 }
713 }
714 let norm: f64 = x.iter().map(|val| val.powi(2)).sum::<f64>().sqrt();
715 if norm == 0. {
716 return Ok(None);
717 }
718 for v in x.iter_mut() {
719 *v /= norm;
720 }
721 if (0..x.len())
722 .map(|node| (x[node] - x_last[node]).abs())
723 .sum::<f64>()
724 < node_count as f64 * tol
725 {
726 return Ok(Some(x));
727 }
728 }
729 Ok(None)
730}
731
732pub fn katz_centrality<G, F, E>(
783 graph: G,
784 mut weight_fn: F,
785 alpha: Option<f64>,
786 beta_map: Option<HashMap<usize, f64>>,
787 beta_scalar: Option<f64>,
788 max_iter: Option<usize>,
789 tol: Option<f64>,
790) -> Result<Option<Vec<f64>>, E>
791where
792 G: NodeIndexable + IntoNodeIdentifiers + IntoNeighbors + IntoEdges + NodeCount,
793 G::NodeId: Eq + std::hash::Hash,
794 F: FnMut(G::EdgeRef) -> Result<f64, E>,
795{
796 let alpha: f64 = alpha.unwrap_or(0.1);
797
798 let beta: HashMap<usize, f64> = beta_map.unwrap_or_default();
799 let mut beta_v = vec![beta_scalar.unwrap_or(1.0); graph.node_bound()];
801
802 if !beta.is_empty() {
803 for node_index in graph.node_identifiers() {
805 let node = graph.to_index(node_index);
806 if !beta.contains_key(&node) {
807 return Ok(None); }
809 beta_v[node] = *beta.get(&node).unwrap(); }
811 }
812
813 let tol: f64 = tol.unwrap_or(1e-6);
814 let max_iter = max_iter.unwrap_or(1000);
815
816 let mut x: Vec<f64> = vec![0.; graph.node_bound()];
817 let node_count = graph.node_count();
818 for _ in 0..max_iter {
819 let x_last = x.clone();
820 x = vec![0.; graph.node_bound()];
821 for node_index in graph.node_identifiers() {
822 let node = graph.to_index(node_index);
823 for edge in graph.edges(node_index) {
824 let w = weight_fn(edge)?;
825 let neighbor = edge.target();
826 x[graph.to_index(neighbor)] += x_last[node] * w;
827 }
828 }
829 for node_index in graph.node_identifiers() {
830 let node = graph.to_index(node_index);
831 x[node] = alpha * x[node] + beta_v[node];
832 }
833 if (0..x.len())
834 .map(|node| (x[node] - x_last[node]).abs())
835 .sum::<f64>()
836 < node_count as f64 * tol
837 {
838 let norm: f64 = x.iter().map(|val| val.powi(2)).sum::<f64>().sqrt();
840 if norm == 0. {
841 return Ok(None);
842 }
843 for v in x.iter_mut() {
844 *v /= norm;
845 }
846
847 return Ok(Some(x));
848 }
849 }
850
851 Ok(None)
852}
853
854#[cfg(test)]
855mod test_eigenvector_centrality {
856
857 use crate::centrality::eigenvector_centrality;
858 use crate::petgraph;
859 use crate::Result;
860
861 macro_rules! assert_almost_equal {
862 ($x:expr, $y:expr, $d:expr) => {
863 if ($x - $y).abs() >= $d {
864 panic!("{} != {} within delta of {}", $x, $y, $d);
865 }
866 };
867 }
868 #[test]
869 fn test_no_convergence() {
870 let g = petgraph::graph::UnGraph::<i32, ()>::from_edges([(0, 1), (1, 2)]);
871 let output: Result<Option<Vec<f64>>> =
872 eigenvector_centrality(&g, |_| Ok(1.), Some(0), None);
873 let result = output.unwrap();
874 assert_eq!(None, result);
875 }
876
877 #[test]
878 fn test_undirected_complete_graph() {
879 let g = petgraph::graph::UnGraph::<i32, ()>::from_edges([
880 (0, 1),
881 (0, 2),
882 (0, 3),
883 (0, 4),
884 (1, 2),
885 (1, 3),
886 (1, 4),
887 (2, 3),
888 (2, 4),
889 (3, 4),
890 ]);
891 let output: Result<Option<Vec<f64>>> = eigenvector_centrality(&g, |_| Ok(1.), None, None);
892 let result = output.unwrap().unwrap();
893 let expected_value: f64 = (1_f64 / 5_f64).sqrt();
894 let expected_values: Vec<f64> = vec![expected_value; 5];
895 for i in 0..5 {
896 assert_almost_equal!(expected_values[i], result[i], 1e-4);
897 }
898 }
899
900 #[test]
901 fn test_undirected_path_graph() {
902 let g = petgraph::graph::UnGraph::<i32, ()>::from_edges([(0, 1), (1, 2)]);
903 let output: Result<Option<Vec<f64>>> = eigenvector_centrality(&g, |_| Ok(1.), None, None);
904 let result = output.unwrap().unwrap();
905 let expected_values: Vec<f64> = vec![0.5, std::f64::consts::FRAC_1_SQRT_2, 0.5];
906 for i in 0..3 {
907 assert_almost_equal!(expected_values[i], result[i], 1e-4);
908 }
909 }
910
911 #[test]
912 fn test_directed_graph() {
913 let g = petgraph::graph::DiGraph::<i32, ()>::from_edges([
914 (0, 1),
915 (0, 2),
916 (1, 3),
917 (2, 1),
918 (2, 4),
919 (3, 1),
920 (3, 4),
921 (3, 5),
922 (4, 5),
923 (4, 6),
924 (4, 7),
925 (5, 7),
926 (6, 0),
927 (6, 4),
928 (6, 7),
929 (7, 5),
930 (7, 6),
931 ]);
932 let output: Result<Option<Vec<f64>>> = eigenvector_centrality(&g, |_| Ok(2.), None, None);
933 let result = output.unwrap().unwrap();
934 let expected_values: Vec<f64> = vec![
935 0.2140437, 0.2009269, 0.1036383, 0.0972886, 0.3113323, 0.4891686, 0.4420605, 0.6016448,
936 ];
937 for i in 0..8 {
938 assert_almost_equal!(expected_values[i], result[i], 1e-4);
939 }
940 }
941}
942
943#[cfg(test)]
944mod test_katz_centrality {
945
946 use crate::centrality::katz_centrality;
947 use crate::petgraph;
948 use crate::Result;
949 use hashbrown::HashMap;
950
951 macro_rules! assert_almost_equal {
952 ($x:expr, $y:expr, $d:expr) => {
953 if ($x - $y).abs() >= $d {
954 panic!("{} != {} within delta of {}", $x, $y, $d);
955 }
956 };
957 }
958 #[test]
959 fn test_no_convergence() {
960 let g = petgraph::graph::UnGraph::<i32, ()>::from_edges([(0, 1), (1, 2)]);
961 let output: Result<Option<Vec<f64>>> =
962 katz_centrality(&g, |_| Ok(1.), None, None, None, Some(0), None);
963 let result = output.unwrap();
964 assert_eq!(None, result);
965 }
966
967 #[test]
968 fn test_incomplete_beta() {
969 let g = petgraph::graph::UnGraph::<i32, ()>::from_edges([(0, 1), (1, 2)]);
970 let beta_map: HashMap<usize, f64> = [(0, 1.0)].iter().cloned().collect();
971 let output: Result<Option<Vec<f64>>> =
972 katz_centrality(&g, |_| Ok(1.), None, Some(beta_map), None, None, None);
973 let result = output.unwrap();
974 assert_eq!(None, result);
975 }
976
977 #[test]
978 fn test_complete_beta() {
979 let g = petgraph::graph::UnGraph::<i32, ()>::from_edges([(0, 1), (1, 2)]);
980 let beta_map: HashMap<usize, f64> =
981 [(0, 0.5), (1, 1.0), (2, 0.5)].iter().cloned().collect();
982 let output: Result<Option<Vec<f64>>> =
983 katz_centrality(&g, |_| Ok(1.), None, Some(beta_map), None, None, None);
984 let result = output.unwrap().unwrap();
985 let expected_values: Vec<f64> =
986 vec![0.4318894504492167, 0.791797325823564, 0.4318894504492167];
987 for i in 0..3 {
988 assert_almost_equal!(expected_values[i], result[i], 1e-4);
989 }
990 }
991
992 #[test]
993 fn test_undirected_complete_graph() {
994 let g = petgraph::graph::UnGraph::<i32, ()>::from_edges([
995 (0, 1),
996 (0, 2),
997 (0, 3),
998 (0, 4),
999 (1, 2),
1000 (1, 3),
1001 (1, 4),
1002 (2, 3),
1003 (2, 4),
1004 (3, 4),
1005 ]);
1006 let output: Result<Option<Vec<f64>>> =
1007 katz_centrality(&g, |_| Ok(1.), Some(0.2), None, Some(1.1), None, None);
1008 let result = output.unwrap().unwrap();
1009 let expected_value: f64 = (1_f64 / 5_f64).sqrt();
1010 let expected_values: Vec<f64> = vec![expected_value; 5];
1011 for i in 0..5 {
1012 assert_almost_equal!(expected_values[i], result[i], 1e-4);
1013 }
1014 }
1015
1016 #[test]
1017 fn test_directed_graph() {
1018 let g = petgraph::graph::DiGraph::<i32, ()>::from_edges([
1019 (0, 1),
1020 (0, 2),
1021 (1, 3),
1022 (2, 1),
1023 (2, 4),
1024 (3, 1),
1025 (3, 4),
1026 (3, 5),
1027 (4, 5),
1028 (4, 6),
1029 (4, 7),
1030 (5, 7),
1031 (6, 0),
1032 (6, 4),
1033 (6, 7),
1034 (7, 5),
1035 (7, 6),
1036 ]);
1037 let output: Result<Option<Vec<f64>>> =
1038 katz_centrality(&g, |_| Ok(1.), None, None, None, None, None);
1039 let result = output.unwrap().unwrap();
1040 let expected_values: Vec<f64> = vec![
1041 0.3135463087489011,
1042 0.3719056758615039,
1043 0.3094350787809586,
1044 0.31527101632646026,
1045 0.3760169058294464,
1046 0.38618584417917906,
1047 0.35465874858087904,
1048 0.38976653416801743,
1049 ];
1050
1051 for i in 0..8 {
1052 assert_almost_equal!(expected_values[i], result[i], 1e-4);
1053 }
1054 }
1055}
1056
1057pub fn closeness_centrality<G>(
1114 graph: G,
1115 wf_improved: bool,
1116 parallel_threshold: usize,
1117) -> Vec<Option<f64>>
1118where
1119 G: NodeIndexable
1120 + IntoNodeIdentifiers
1121 + GraphBase
1122 + IntoEdges
1123 + Visitable
1124 + NodeCount
1125 + IntoEdgesDirected
1126 + std::marker::Sync,
1127 G::NodeId: Eq + Hash + Send,
1128 G::EdgeId: Eq + Hash + Send,
1129{
1130 let max_index = graph.node_bound();
1131 let mut node_indices: Vec<Option<G::NodeId>> = vec![None; max_index];
1132 graph.node_identifiers().for_each(|node| {
1133 let index = graph.to_index(node);
1134 node_indices[index] = Some(node);
1135 });
1136
1137 let unweighted_shortest_path = |g: Reversed<&G>, s: G::NodeId| -> HashMap<G::NodeId, usize> {
1138 let mut distances = HashMap::new();
1139 let mut bfs = Bfs::new(g, s);
1140 distances.insert(s, 0);
1141 while let Some(node) = bfs.next(g) {
1142 let distance = distances[&node];
1143 for edge in g.edges(node) {
1144 let target = edge.target();
1145 distances.entry(target).or_insert(distance + 1);
1146 }
1147 }
1148 distances
1149 };
1150
1151 let closeness: Vec<Option<f64>> =
1152 CondIterator::new(node_indices, graph.node_count() >= parallel_threshold)
1153 .map(|node_s| {
1154 let node_s = node_s?;
1155 let map = unweighted_shortest_path(Reversed(&graph), node_s);
1156 let reachable_nodes_count = map.len();
1157 let dists_sum: usize = map.into_values().sum();
1158 if reachable_nodes_count == 1 {
1159 return Some(0.0);
1160 }
1161 let mut centrality_s = Some((reachable_nodes_count - 1) as f64 / dists_sum as f64);
1162 if wf_improved {
1163 let node_count = graph.node_count();
1164 centrality_s = centrality_s
1165 .map(|c| c * (reachable_nodes_count - 1) as f64 / (node_count - 1) as f64);
1166 }
1167 centrality_s
1168 })
1169 .collect();
1170 closeness
1171}
1172
1173pub fn newman_weighted_closeness_centrality<G, F>(
1245 graph: G,
1246 wf_improved: bool,
1247 weight_fn: F,
1248 parallel_threshold: usize,
1249) -> Vec<Option<f64>>
1250where
1251 G: NodeIndexable
1252 + IntoNodeIdentifiers
1253 + GraphBase
1254 + IntoEdges
1255 + Visitable
1256 + NodeCount
1257 + IntoEdgesDirected
1258 + std::marker::Sync,
1259 G::NodeId: Eq + Hash + Send,
1260 G::EdgeId: Eq + Hash + Send,
1261 F: Fn(ReversedEdgeReference<<G as IntoEdgeReferences>::EdgeRef>) -> f64 + Sync,
1262{
1263 let inverted_weight_fn =
1275 |x: ReversedEdgeReference<<G as IntoEdgeReferences>::EdgeRef>| 1.0 / weight_fn(x);
1276
1277 let max_index = graph.node_bound();
1278 let mut node_indices: Vec<Option<G::NodeId>> = vec![None; max_index];
1279 graph.node_identifiers().for_each(|node| {
1280 let index = graph.to_index(node);
1281 node_indices[index] = Some(node);
1282 });
1283
1284 let closeness: Vec<Option<f64>> =
1285 CondIterator::new(node_indices, graph.node_count() >= parallel_threshold)
1286 .map(|node_s| {
1287 let node_s = node_s?;
1288 let map = dijkstra(Reversed(&graph), node_s, None, &inverted_weight_fn);
1289 let reachable_nodes_count = map.len();
1290 let dists_sum: f64 = map.into_values().sum();
1291 if reachable_nodes_count == 1 {
1292 return Some(0.0);
1293 }
1294 let mut centrality_s = Some((reachable_nodes_count - 1) as f64 / dists_sum as f64);
1295 if wf_improved {
1296 let node_count = graph.node_count();
1297 centrality_s = centrality_s
1298 .map(|c| c * (reachable_nodes_count - 1) as f64 / (node_count - 1) as f64);
1299 }
1300 centrality_s
1301 })
1302 .collect();
1303 closeness
1304}
1305
1306#[cfg(test)]
1307mod test_newman_weighted_closeness_centrality {
1308 use crate::centrality::closeness_centrality;
1309
1310 use super::newman_weighted_closeness_centrality;
1311 use petgraph::visit::EdgeRef;
1312
1313 macro_rules! assert_almost_equal {
1314 ($x:expr, $y:expr, $d:expr) => {
1315 if ($x - $y).abs() >= $d {
1316 panic!("{} != {} within delta of {}", $x, $y, $d);
1317 }
1318 };
1319 }
1320
1321 macro_rules! assert_almost_equal_iter {
1322 ($expected:expr, $computed:expr, $tolerance:expr) => {
1323 for (&expected, &computed) in $expected.iter().zip($computed.iter()) {
1324 assert_almost_equal!(expected.unwrap(), computed.unwrap(), $tolerance);
1325 }
1326 };
1327 }
1328
1329 #[test]
1330 fn test_weighted_closeness_graph() {
1331 let test_case = |parallel_threshold: usize| {
1332 let g = petgraph::graph::UnGraph::<u32, f64>::from_edges([
1333 (0, 1, 1.0),
1334 (1, 2, 1.0),
1335 (2, 3, 1.0),
1336 (3, 4, 1.0),
1337 (4, 5, 1.0),
1338 (5, 6, 1.0),
1339 ]);
1340 let classic_closeness = closeness_centrality(&g, false, parallel_threshold);
1341 let weighted_closeness = newman_weighted_closeness_centrality(
1342 &g,
1343 false,
1344 |x| *x.weight(),
1345 parallel_threshold,
1346 );
1347
1348 assert_eq!(classic_closeness, weighted_closeness);
1349 };
1350 test_case(200); test_case(1); }
1353
1354 #[test]
1355 fn test_the_same_as_closeness_centrality_when_weights_are_1_not_improved_digraph() {
1356 let test_case = |parallel_threshold: usize| {
1357 let g = petgraph::graph::DiGraph::<u32, f64>::from_edges([
1358 (0, 1, 1.0),
1359 (1, 2, 1.0),
1360 (2, 3, 1.0),
1361 (3, 4, 1.0),
1362 (4, 5, 1.0),
1363 (5, 6, 1.0),
1364 ]);
1365 let classic_closeness = closeness_centrality(&g, false, parallel_threshold);
1366 let weighted_closeness = newman_weighted_closeness_centrality(
1367 &g,
1368 false,
1369 |x| *x.weight(),
1370 parallel_threshold,
1371 );
1372
1373 assert_eq!(classic_closeness, weighted_closeness);
1374 };
1375 test_case(200); test_case(1); }
1378
1379 #[test]
1380 fn test_the_same_as_closeness_centrality_when_weights_are_1_improved_digraph() {
1381 let test_case = |parallel_threshold: usize| {
1382 let g = petgraph::graph::DiGraph::<u32, f64>::from_edges([
1383 (0, 1, 1.0),
1384 (1, 2, 1.0),
1385 (2, 3, 1.0),
1386 (3, 4, 1.0),
1387 (4, 5, 1.0),
1388 (5, 6, 1.0),
1389 ]);
1390 let classic_closeness = closeness_centrality(&g, true, parallel_threshold);
1391 let weighted_closeness =
1392 newman_weighted_closeness_centrality(&g, true, |x| *x.weight(), parallel_threshold);
1393
1394 assert_eq!(classic_closeness, weighted_closeness);
1395 };
1396 test_case(200); test_case(1); }
1399
1400 #[test]
1401 fn test_weighted_closeness_two_connected_components_not_improved_digraph() {
1402 let test_case = |parallel_threshold: usize| {
1403 let g = petgraph::graph::DiGraph::<u32, f64>::from_edges([
1404 (0, 1, 1.0),
1405 (1, 2, 0.5),
1406 (2, 3, 0.25),
1407 (4, 5, 1.0),
1408 (5, 6, 0.5),
1409 (6, 7, 0.25),
1410 ]);
1411 let c = newman_weighted_closeness_centrality(
1412 &g,
1413 false,
1414 |x| *x.weight(),
1415 parallel_threshold,
1416 );
1417 let result = [
1418 Some(0.0),
1419 Some(1.0),
1420 Some(0.4),
1421 Some(0.176470),
1422 Some(0.0),
1423 Some(1.0),
1424 Some(0.4),
1425 Some(0.176470),
1426 ];
1427
1428 assert_almost_equal_iter!(result, c, 1e-4);
1429 };
1430 test_case(200); test_case(1); }
1433
1434 #[test]
1435 fn test_weighted_closeness_two_connected_components_improved_digraph() {
1436 let test_case = |parallel_threshold: usize| {
1437 let g = petgraph::graph::DiGraph::<u32, f64>::from_edges([
1438 (0, 1, 1.0),
1439 (1, 2, 0.5),
1440 (2, 3, 0.25),
1441 (4, 5, 1.0),
1442 (5, 6, 0.5),
1443 (6, 7, 0.25),
1444 ]);
1445 let c =
1446 newman_weighted_closeness_centrality(&g, true, |x| *x.weight(), parallel_threshold);
1447 let result = [
1448 Some(0.0),
1449 Some(0.14285714),
1450 Some(0.11428571),
1451 Some(0.07563025),
1452 Some(0.0),
1453 Some(0.14285714),
1454 Some(0.11428571),
1455 Some(0.07563025),
1456 ];
1457
1458 assert_almost_equal_iter!(result, c, 1e-4);
1459 };
1460 test_case(200); test_case(1); }
1463
1464 #[test]
1465 fn test_weighted_closeness_two_connected_components_improved_different_cardinality_digraph() {
1466 let test_case = |parallel_threshold: usize| {
1467 let g = petgraph::graph::DiGraph::<u32, f64>::from_edges([
1468 (0, 1, 1.0),
1469 (1, 2, 0.5),
1470 (2, 3, 0.25),
1471 (4, 5, 1.0),
1472 (5, 6, 0.5),
1473 (6, 7, 0.25),
1474 (7, 8, 0.125),
1475 ]);
1476 let c =
1477 newman_weighted_closeness_centrality(&g, true, |x| *x.weight(), parallel_threshold);
1478 let result = [
1479 Some(0.0),
1480 Some(0.125),
1481 Some(0.1),
1482 Some(0.06617647),
1483 Some(0.0),
1484 Some(0.125),
1485 Some(0.1),
1486 Some(0.06617647),
1487 Some(0.04081632),
1488 ];
1489
1490 assert_almost_equal_iter!(result, c, 1e-4);
1491 };
1492 test_case(200); test_case(1); }
1495
1496 #[test]
1497 fn test_weighted_closeness_small_ungraph() {
1498 let test_case = |parallel_threshold: usize| {
1499 let g = petgraph::graph::UnGraph::<u32, f64>::from_edges([
1500 (0, 1, 0.7),
1501 (1, 2, 0.2),
1502 (2, 3, 0.5),
1503 ]);
1504 let c = newman_weighted_closeness_centrality(
1505 &g,
1506 false,
1507 |x| *x.weight(),
1508 parallel_threshold,
1509 );
1510 let result = [
1511 Some(0.1842105),
1512 Some(0.2234042),
1513 Some(0.2234042),
1514 Some(0.1721311),
1515 ];
1516
1517 assert_almost_equal_iter!(result, c, 1e-4);
1518 };
1519 test_case(200); test_case(1); }
1522 #[test]
1523 fn test_weighted_closeness_small_digraph() {
1524 let test_case = |parallel_threshold: usize| {
1525 let g = petgraph::graph::DiGraph::<u32, f64>::from_edges([
1526 (0, 1, 0.7),
1527 (1, 2, 0.2),
1528 (2, 3, 0.5),
1529 ]);
1530 let c = newman_weighted_closeness_centrality(
1531 &g,
1532 false,
1533 |x| *x.weight(),
1534 parallel_threshold,
1535 );
1536 let result = [Some(0.0), Some(0.7), Some(0.175), Some(0.172131)];
1537
1538 assert_almost_equal_iter!(result, c, 1e-4);
1539 };
1540 test_case(200); test_case(1); }
1543
1544 #[test]
1545 fn test_weighted_closeness_many_to_one_connected_digraph() {
1546 let test_case = |parallel_threshold: usize| {
1547 let g = petgraph::graph::DiGraph::<u32, f64>::from_edges([
1548 (1, 0, 0.1),
1549 (2, 0, 0.1),
1550 (3, 0, 0.1),
1551 (4, 0, 0.1),
1552 (5, 0, 0.1),
1553 (6, 0, 0.1),
1554 (7, 0, 0.1),
1555 (0, 8, 1.0),
1556 ]);
1557 let c = newman_weighted_closeness_centrality(
1558 &g,
1559 false,
1560 |x| *x.weight(),
1561 parallel_threshold,
1562 );
1563 let result = [
1564 Some(0.1),
1565 Some(0.0),
1566 Some(0.0),
1567 Some(0.0),
1568 Some(0.0),
1569 Some(0.0),
1570 Some(0.0),
1571 Some(0.0),
1572 Some(0.10256),
1573 ];
1574
1575 assert_almost_equal_iter!(result, c, 1e-4);
1576 };
1577 test_case(200); test_case(1); }
1580
1581 #[test]
1582 fn test_weighted_closeness_many_to_one_connected_ungraph() {
1583 let test_case = |parallel_threshold: usize| {
1584 let g = petgraph::graph::UnGraph::<u32, f64>::from_edges([
1585 (1, 0, 0.1),
1586 (2, 0, 0.1),
1587 (3, 0, 0.1),
1588 (4, 0, 0.1),
1589 (5, 0, 0.1),
1590 (6, 0, 0.1),
1591 (7, 0, 0.1),
1592 (0, 8, 1.0),
1593 ]);
1594 let c = newman_weighted_closeness_centrality(
1595 &g,
1596 false,
1597 |x| *x.weight(),
1598 parallel_threshold,
1599 );
1600 let result = [
1601 Some(0.112676056),
1602 Some(0.056737588),
1603 Some(0.056737588),
1604 Some(0.056737588),
1605 Some(0.056737588),
1606 Some(0.056737588),
1607 Some(0.056737588),
1608 Some(0.056737588),
1609 Some(0.102564102),
1610 ];
1611
1612 assert_almost_equal_iter!(result, c, 1e-4);
1613 };
1614 test_case(200); test_case(1); }
1617
1618 #[test]
1619 fn test_weighted_closeness_many_to_one_not_connected_2_digraph() {
1620 let test_case = |parallel_threshold: usize| {
1621 let g = petgraph::graph::DiGraph::<u32, f64>::from_edges([
1622 (1, 0, 0.1),
1623 (2, 0, 0.1),
1624 (3, 0, 0.1),
1625 (4, 0, 0.1),
1626 (5, 0, 0.1),
1627 (6, 0, 0.1),
1628 (7, 0, 0.1),
1629 (1, 7, 1.0),
1630 ]);
1631 let c = newman_weighted_closeness_centrality(
1632 &g,
1633 false,
1634 |x| *x.weight(),
1635 parallel_threshold,
1636 );
1637 let result = [
1638 Some(0.1),
1639 Some(0.0),
1640 Some(0.0),
1641 Some(0.0),
1642 Some(0.0),
1643 Some(0.0),
1644 Some(0.0),
1645 Some(1.0),
1646 ];
1647
1648 assert_eq!(result, *c);
1649 };
1650 test_case(200); test_case(1); }
1653
1654 #[test]
1655 fn test_weighted_closeness_many_to_one_not_connected_1_digraph() {
1656 let test_case = |parallel_threshold: usize| {
1657 let g = petgraph::graph::DiGraph::<u32, f64>::from_edges([
1658 (1, 0, 0.1),
1659 (2, 0, 0.1),
1660 (3, 0, 0.1),
1661 (4, 0, 0.1),
1662 (5, 0, 0.1),
1663 (6, 0, 0.1),
1664 (7, 0, 0.1),
1665 (8, 7, 1.0),
1666 ]);
1667 let c = newman_weighted_closeness_centrality(
1668 &g,
1669 false,
1670 |x| *x.weight(),
1671 parallel_threshold,
1672 );
1673 let result = [
1674 Some(0.098765),
1675 Some(0.0),
1676 Some(0.0),
1677 Some(0.0),
1678 Some(0.0),
1679 Some(0.0),
1680 Some(0.0),
1681 Some(1.0),
1682 Some(0.0),
1683 ];
1684
1685 assert_almost_equal_iter!(result, c, 1e-4);
1686 };
1687 test_case(200); test_case(1); }
1690}