1use std::collections::{HashMap, HashSet};
8use std::hash::Hash;
9
10use scirs2_core::ndarray::{Array1, Array2};
11
12use crate::algorithms::{dijkstra_path, dijkstra_path_digraph};
13use crate::base::{DiGraph, EdgeWeight, Graph, Node};
14use crate::error::{GraphError, Result};
15use petgraph::graph::IndexType;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum CentralityType {
20 Degree,
22 Betweenness,
24 Closeness,
26 Eigenvector,
28 Katz,
30 PageRank,
32 WeightedDegree,
34 WeightedBetweenness,
36 WeightedCloseness,
38}
39
40#[allow(dead_code)]
60pub fn centrality<N, E, Ix>(
61 graph: &Graph<N, E, Ix>,
62 centrality_type: CentralityType,
63) -> Result<HashMap<N, f64>>
64where
65 N: Node + std::fmt::Debug,
66 E: EdgeWeight
67 + scirs2_core::numeric::Zero
68 + scirs2_core::numeric::One
69 + std::ops::Add<Output = E>
70 + PartialOrd
71 + Into<f64>
72 + std::marker::Copy
73 + std::fmt::Debug
74 + std::default::Default,
75 Ix: petgraph::graph::IndexType,
76{
77 let nodes = graph.nodes();
78 let n = nodes.len();
79
80 if n == 0 {
81 return Err(GraphError::InvalidGraph("Empty graph".to_string()));
82 }
83
84 match centrality_type {
85 CentralityType::Degree => degree_centrality(graph),
86 CentralityType::Betweenness => betweenness_centrality(graph),
87 CentralityType::Closeness => closeness_centrality(graph),
88 CentralityType::Eigenvector => eigenvector_centrality(graph),
89 CentralityType::Katz => katz_centrality(graph, 0.1, 1.0), CentralityType::PageRank => pagerank_centrality(graph, 0.85, 1e-6), CentralityType::WeightedDegree => weighted_degree_centrality(graph),
92 CentralityType::WeightedBetweenness => weighted_betweenness_centrality(graph),
93 CentralityType::WeightedCloseness => weighted_closeness_centrality(graph),
94 }
95}
96
97#[allow(dead_code)]
106pub fn centrality_digraph<N, E, Ix>(
107 graph: &DiGraph<N, E, Ix>,
108 centrality_type: CentralityType,
109) -> Result<HashMap<N, f64>>
110where
111 N: Node + std::fmt::Debug,
112 E: EdgeWeight
113 + scirs2_core::numeric::Zero
114 + scirs2_core::numeric::One
115 + std::ops::Add<Output = E>
116 + PartialOrd
117 + Into<f64>
118 + std::marker::Copy
119 + std::fmt::Debug
120 + std::default::Default,
121 Ix: petgraph::graph::IndexType,
122{
123 let nodes = graph.nodes();
124 let n = nodes.len();
125
126 if n == 0 {
127 return Err(GraphError::InvalidGraph("Empty graph".to_string()));
128 }
129
130 match centrality_type {
131 CentralityType::Degree => degree_centrality_digraph(graph),
132 CentralityType::Betweenness => betweenness_centrality_digraph(graph),
133 CentralityType::Closeness => closeness_centrality_digraph(graph),
134 CentralityType::Eigenvector => eigenvector_centrality_digraph(graph),
135 CentralityType::Katz => katz_centrality_digraph(graph, 0.1, 1.0), CentralityType::PageRank => pagerank_centrality_digraph(graph, 0.85, 1e-6), CentralityType::WeightedDegree => weighted_degree_centrality_digraph(graph),
138 CentralityType::WeightedBetweenness => weighted_betweenness_centrality_digraph(graph),
139 CentralityType::WeightedCloseness => weighted_closeness_centrality_digraph(graph),
140 }
141}
142
143#[allow(dead_code)]
151fn degree_centrality<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<HashMap<N, f64>>
152where
153 N: Node + std::fmt::Debug,
154 E: EdgeWeight,
155 Ix: petgraph::graph::IndexType,
156{
157 let nodes = graph.nodes();
158 let n = nodes.len() as f64;
159 let degrees = graph.degree_vector();
160
161 let mut centrality = HashMap::new();
162 let normalization = if n <= 1.0 { 1.0 } else { n - 1.0 };
163
164 for (i, node) in nodes.iter().enumerate() {
165 let degree = degrees[i] as f64;
166 centrality.insert((*node).clone(), degree / normalization);
167 }
168
169 Ok(centrality)
170}
171
172#[allow(dead_code)]
174fn degree_centrality_digraph<N, E, Ix>(graph: &DiGraph<N, E, Ix>) -> Result<HashMap<N, f64>>
175where
176 N: Node + std::fmt::Debug,
177 E: EdgeWeight,
178 Ix: petgraph::graph::IndexType,
179{
180 let nodes = graph.nodes();
181 let n = nodes.len() as f64;
182 let in_degrees = graph.in_degree_vector();
183 let out_degrees = graph.out_degree_vector();
184
185 let mut centrality = HashMap::new();
186 let normalization = if n <= 1.0 { 1.0 } else { n - 1.0 };
187
188 for (i, node) in nodes.iter().enumerate() {
189 let degree = (in_degrees[i] + out_degrees[i]) as f64;
191 centrality.insert((*node).clone(), degree / normalization);
192 }
193
194 Ok(centrality)
195}
196
197#[allow(dead_code)]
206fn betweenness_centrality<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<HashMap<N, f64>>
207where
208 N: Node + std::fmt::Debug,
209 E: EdgeWeight
210 + scirs2_core::numeric::Zero
211 + scirs2_core::numeric::One
212 + std::ops::Add<Output = E>
213 + PartialOrd
214 + Into<f64>
215 + std::marker::Copy
216 + std::fmt::Debug
217 + std::default::Default,
218 Ix: petgraph::graph::IndexType,
219{
220 let nodes = graph.nodes();
221 let n = nodes.len();
222
223 if n <= 1 {
224 let mut result = HashMap::new();
225 for node in nodes {
226 result.insert(node.clone(), 0.0);
227 }
228 return Ok(result);
229 }
230
231 let mut betweenness = HashMap::new();
232 for node in nodes.iter() {
233 betweenness.insert((*node).clone(), 0.0);
234 }
235
236 for (i, &s) in nodes.iter().enumerate() {
238 for (j, &t) in nodes.iter().enumerate() {
239 if i == j {
240 continue;
241 }
242
243 if let Ok(Some(path)) = dijkstra_path(graph, s, t) {
245 for node in &path.nodes[1..path.nodes.len() - 1] {
247 *betweenness.entry(node.clone()).or_insert(0.0) += 1.0;
248 }
249 }
250 }
251 }
252
253 let scale = 1.0 / ((n - 1) * (n - 2)) as f64;
255 for val in betweenness.values_mut() {
256 *val *= scale;
257 }
258
259 Ok(betweenness)
260}
261
262#[allow(dead_code)]
264fn betweenness_centrality_digraph<N, E, Ix>(graph: &DiGraph<N, E, Ix>) -> Result<HashMap<N, f64>>
265where
266 N: Node + std::fmt::Debug,
267 E: EdgeWeight
268 + scirs2_core::numeric::Zero
269 + scirs2_core::numeric::One
270 + std::ops::Add<Output = E>
271 + PartialOrd
272 + Into<f64>
273 + std::marker::Copy
274 + std::fmt::Debug
275 + std::default::Default,
276 Ix: petgraph::graph::IndexType,
277{
278 let nodes = graph.nodes();
279 let n = nodes.len();
280
281 if n <= 1 {
282 let mut result = HashMap::new();
283 for node in nodes {
284 result.insert(node.clone(), 0.0);
285 }
286 return Ok(result);
287 }
288
289 let mut betweenness = HashMap::new();
290 for node in nodes.iter() {
291 betweenness.insert((*node).clone(), 0.0);
292 }
293
294 for (i, &s) in nodes.iter().enumerate() {
296 for (j, &t) in nodes.iter().enumerate() {
297 if i == j {
298 continue;
299 }
300
301 if let Ok(Some(path)) = dijkstra_path_digraph(graph, s, t) {
303 for node in &path.nodes[1..path.nodes.len() - 1] {
305 *betweenness.entry(node.clone()).or_insert(0.0) += 1.0;
306 }
307 }
308 }
309 }
310
311 let scale = 1.0 / ((n - 1) * (n - 2)) as f64;
313 for val in betweenness.values_mut() {
314 *val *= scale;
315 }
316
317 Ok(betweenness)
318}
319
320#[allow(dead_code)]
322fn closeness_centrality<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<HashMap<N, f64>>
323where
324 N: Node + std::fmt::Debug,
325 E: EdgeWeight
326 + scirs2_core::numeric::Zero
327 + scirs2_core::numeric::One
328 + std::ops::Add<Output = E>
329 + PartialOrd
330 + Into<f64>
331 + std::marker::Copy
332 + std::fmt::Debug
333 + std::default::Default,
334 Ix: petgraph::graph::IndexType,
335{
336 let nodes = graph.nodes();
337 let n = nodes.len();
338
339 if n <= 1 {
340 let mut result = HashMap::new();
341 for node in nodes {
342 result.insert(node.clone(), 1.0);
343 }
344 return Ok(result);
345 }
346
347 let mut closeness = HashMap::new();
348
349 for &node in nodes.iter() {
351 let mut sum_distances = 0.0;
352 let mut reachable_nodes = 0;
353
354 for &other in nodes.iter() {
355 if node == other {
356 continue;
357 }
358
359 if let Ok(Some(path)) = dijkstra_path(graph, node, other) {
361 sum_distances += path.total_weight.into();
362 reachable_nodes += 1;
363 }
364 }
365
366 if reachable_nodes > 0 {
368 let closeness_value = reachable_nodes as f64 / sum_distances;
370 let normalized = closeness_value * (reachable_nodes as f64 / (n - 1) as f64);
372 closeness.insert(node.clone(), normalized);
373 } else {
374 closeness.insert(node.clone(), 0.0);
375 }
376 }
377
378 Ok(closeness)
379}
380
381#[allow(dead_code)]
383fn closeness_centrality_digraph<N, E, Ix>(graph: &DiGraph<N, E, Ix>) -> Result<HashMap<N, f64>>
384where
385 N: Node + std::fmt::Debug,
386 E: EdgeWeight
387 + scirs2_core::numeric::Zero
388 + scirs2_core::numeric::One
389 + std::ops::Add<Output = E>
390 + PartialOrd
391 + Into<f64>
392 + std::marker::Copy
393 + std::fmt::Debug
394 + std::default::Default,
395 Ix: petgraph::graph::IndexType,
396{
397 let nodes = graph.nodes();
398 let n = nodes.len();
399
400 if n <= 1 {
401 let mut result = HashMap::new();
402 for node in nodes {
403 result.insert(node.clone(), 1.0);
404 }
405 return Ok(result);
406 }
407
408 let mut closeness = HashMap::new();
409
410 for &node in nodes.iter() {
412 let mut sum_distances = 0.0;
413 let mut reachable_nodes = 0;
414
415 for &other in nodes.iter() {
416 if node == other {
417 continue;
418 }
419
420 if let Ok(Some(path)) = dijkstra_path_digraph(graph, node, other) {
422 sum_distances += path.total_weight.into();
423 reachable_nodes += 1;
424 }
425 }
426
427 if reachable_nodes > 0 {
429 let closeness_value = reachable_nodes as f64 / sum_distances;
431 let normalized = closeness_value * (reachable_nodes as f64 / (n - 1) as f64);
433 closeness.insert(node.clone(), normalized);
434 } else {
435 closeness.insert(node.clone(), 0.0);
436 }
437 }
438
439 Ok(closeness)
440}
441
442#[allow(dead_code)]
446fn eigenvector_centrality<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<HashMap<N, f64>>
447where
448 N: Node + std::fmt::Debug,
449 E: EdgeWeight
450 + scirs2_core::numeric::Zero
451 + scirs2_core::numeric::One
452 + PartialOrd
453 + Into<f64>
454 + std::marker::Copy,
455 Ix: petgraph::graph::IndexType,
456{
457 let nodes = graph.nodes();
458 let n = nodes.len();
459
460 if n == 0 {
461 return Err(GraphError::InvalidGraph("Empty _graph".to_string()));
462 }
463
464 let adj_mat = graph.adjacency_matrix();
466 let mut adj_f64 = Array2::<f64>::zeros((n, n));
467 for i in 0..n {
468 for j in 0..n {
469 adj_f64[[i, j]] = adj_mat[[i, j]].into();
470 }
471 }
472
473 let mut x = Array1::<f64>::ones(n);
475 x.mapv_inplace(|v| v / (n as f64).sqrt()); let max_iter = 100;
479 let tol = 1e-6;
480
481 for _ in 0..max_iter {
482 let y = adj_f64.dot(&x);
484
485 let norm = (y.iter().map(|v| v * v).sum::<f64>()).sqrt();
487 if norm < 1e-10 {
488 return Err(GraphError::AlgorithmError(
490 "Eigenvector centrality computation failed: zero eigenvalue".to_string(),
491 ));
492 }
493
494 let y_norm = y / norm;
496
497 let diff = (&y_norm - &x).iter().map(|v| v.abs()).sum::<f64>();
499 if diff < tol {
500 let mut result = HashMap::new();
502 for (i, &node) in nodes.iter().enumerate() {
503 result.insert(node.clone(), y_norm[i]);
504 }
505 return Ok(result);
506 }
507
508 x = y_norm;
510 }
511
512 Err(GraphError::AlgorithmError(
514 "Eigenvector centrality computation did not converge".to_string(),
515 ))
516}
517
518#[allow(dead_code)]
522fn eigenvector_centrality_digraph<N, E, Ix>(graph: &DiGraph<N, E, Ix>) -> Result<HashMap<N, f64>>
523where
524 N: Node + std::fmt::Debug,
525 E: EdgeWeight
526 + scirs2_core::numeric::Zero
527 + scirs2_core::numeric::One
528 + PartialOrd
529 + Into<f64>
530 + std::marker::Copy,
531 Ix: petgraph::graph::IndexType,
532{
533 let nodes = graph.nodes();
534 let n = nodes.len();
535
536 if n == 0 {
537 return Err(GraphError::InvalidGraph("Empty _graph".to_string()));
538 }
539
540 let adj_mat = graph.adjacency_matrix();
542 let mut adj_f64 = Array2::<f64>::zeros((n, n));
543 for i in 0..n {
544 for j in 0..n {
545 adj_f64[[i, j]] = adj_mat[[i, j]];
546 }
547 }
548
549 let mut x = Array1::<f64>::ones(n);
551 x.mapv_inplace(|v| v / (n as f64).sqrt()); let max_iter = 100;
555 let tol = 1e-6;
556
557 for _ in 0..max_iter {
558 let y = adj_f64.dot(&x);
560
561 let norm = (y.iter().map(|v| v * v).sum::<f64>()).sqrt();
563 if norm < 1e-10 {
564 return Err(GraphError::AlgorithmError(
566 "Eigenvector centrality computation failed: zero eigenvalue".to_string(),
567 ));
568 }
569
570 let y_norm = y / norm;
572
573 let diff = (&y_norm - &x).iter().map(|v| v.abs()).sum::<f64>();
575 if diff < tol {
576 let mut result = HashMap::new();
578 for (i, &node) in nodes.iter().enumerate() {
579 result.insert(node.clone(), y_norm[i]);
580 }
581 return Ok(result);
582 }
583
584 x = y_norm;
586 }
587
588 Err(GraphError::AlgorithmError(
590 "Eigenvector centrality computation did not converge".to_string(),
591 ))
592}
593
594#[allow(dead_code)]
604pub fn clustering_coefficient<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<HashMap<N, f64>>
605where
606 N: Node + std::fmt::Debug,
607 E: EdgeWeight,
608 Ix: petgraph::graph::IndexType,
609{
610 let mut coefficients = HashMap::new();
611
612 for (idx, &node) in graph.nodes().iter().enumerate() {
613 let node_idx = petgraph::graph::NodeIndex::new(idx);
615 let neighbors: HashSet<_> = graph.inner().neighbors(node_idx).collect();
616
617 let k = neighbors.len();
618
619 if k < 2 {
621 coefficients.insert(node.clone(), 0.0);
622 continue;
623 }
624
625 let mut edge_count = 0;
627
628 for &n1 in &neighbors {
629 for &n2 in &neighbors {
630 if n1 != n2 && graph.inner().contains_edge(n1, n2) {
631 edge_count += 1;
632 }
633 }
634 }
635
636 edge_count /= 2;
638
639 let possible_edges = k * (k - 1) / 2;
641 let coefficient = edge_count as f64 / possible_edges as f64;
642
643 coefficients.insert(node.clone(), coefficient);
644 }
645
646 Ok(coefficients)
647}
648
649#[allow(dead_code)]
659pub fn graph_density<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<f64>
660where
661 N: Node + std::fmt::Debug,
662 E: EdgeWeight,
663 Ix: petgraph::graph::IndexType,
664{
665 let n = graph.node_count();
666
667 if n <= 1 {
668 return Err(GraphError::InvalidGraph(
669 "Graph density undefined for graphs with 0 or 1 nodes".to_string(),
670 ));
671 }
672
673 let m = graph.edge_count();
674 let possible_edges = n * (n - 1) / 2;
675
676 Ok(m as f64 / possible_edges as f64)
677}
678
679#[allow(dead_code)]
687pub fn graph_density_digraph<N, E, Ix>(graph: &DiGraph<N, E, Ix>) -> Result<f64>
688where
689 N: Node + std::fmt::Debug,
690 E: EdgeWeight,
691 Ix: petgraph::graph::IndexType,
692{
693 let n = graph.node_count();
694
695 if n <= 1 {
696 return Err(GraphError::InvalidGraph(
697 "Graph density undefined for graphs with 0 or 1 nodes".to_string(),
698 ));
699 }
700
701 let m = graph.edge_count();
702 let possible_edges = n * (n - 1); Ok(m as f64 / possible_edges as f64)
705}
706
707#[allow(dead_code)]
720pub fn katz_centrality<N, E, Ix>(
721 graph: &Graph<N, E, Ix>,
722 alpha: f64,
723 beta: f64,
724) -> Result<HashMap<N, f64>>
725where
726 N: Node + std::fmt::Debug,
727 E: EdgeWeight + scirs2_core::numeric::Zero + scirs2_core::numeric::One + Into<f64> + Copy,
728 Ix: petgraph::graph::IndexType,
729{
730 let nodes = graph.nodes();
731 let n = nodes.len();
732
733 if n == 0 {
734 return Err(GraphError::InvalidGraph("Empty graph".to_string()));
735 }
736
737 let adj_mat = graph.adjacency_matrix();
739 let mut adj_f64 = Array2::<f64>::zeros((n, n));
740 for i in 0..n {
741 for j in 0..n {
742 adj_f64[[i, j]] = adj_mat[[i, j]].into();
743 }
744 }
745
746 let mut identity = Array2::<f64>::zeros((n, n));
748 for i in 0..n {
749 identity[[i, i]] = 1.0;
750 }
751
752 let _factor_matrix = &identity - &(alpha * &adj_f64);
754
755 let beta_vec = Array1::<f64>::from_elem(n, beta);
757
758 let mut centrality_vec = Array1::<f64>::ones(n);
761 let max_iter = 100;
762 let tol = 1e-6;
763
764 for _ in 0..max_iter {
765 let new_centrality = alpha * adj_f64.dot(¢rality_vec) + &beta_vec;
767
768 let diff = (&new_centrality - ¢rality_vec)
770 .iter()
771 .map(|v| v.abs())
772 .sum::<f64>();
773 if diff < tol {
774 centrality_vec = new_centrality;
775 break;
776 }
777
778 centrality_vec = new_centrality;
779 }
780
781 let mut result = HashMap::new();
783 for (i, &node) in nodes.iter().enumerate() {
784 result.insert(node.clone(), centrality_vec[i]);
785 }
786
787 Ok(result)
788}
789
790#[allow(dead_code)]
800pub fn katz_centrality_digraph<N, E, Ix>(
801 graph: &DiGraph<N, E, Ix>,
802 alpha: f64,
803 beta: f64,
804) -> Result<HashMap<N, f64>>
805where
806 N: Node + std::fmt::Debug,
807 E: EdgeWeight + scirs2_core::numeric::Zero + scirs2_core::numeric::One + Into<f64> + Copy,
808 Ix: petgraph::graph::IndexType,
809{
810 let nodes = graph.nodes();
811 let n = nodes.len();
812
813 if n == 0 {
814 return Err(GraphError::InvalidGraph("Empty graph".to_string()));
815 }
816
817 let adj_mat = graph.adjacency_matrix();
819 let mut adj_f64 = Array2::<f64>::zeros((n, n));
820 for i in 0..n {
821 for j in 0..n {
822 adj_f64[[i, j]] = adj_mat[[i, j]];
823 }
824 }
825
826 let beta_vec = Array1::<f64>::from_elem(n, beta);
828
829 let mut centrality_vec = Array1::<f64>::ones(n);
831 let max_iter = 100;
832 let tol = 1e-6;
833
834 for _ in 0..max_iter {
835 let new_centrality = alpha * adj_f64.t().dot(¢rality_vec) + &beta_vec;
837
838 let diff = (&new_centrality - ¢rality_vec)
840 .iter()
841 .map(|v| v.abs())
842 .sum::<f64>();
843 if diff < tol {
844 centrality_vec = new_centrality;
845 break;
846 }
847
848 centrality_vec = new_centrality;
849 }
850
851 let mut result = HashMap::new();
853 for (i, &node) in nodes.iter().enumerate() {
854 result.insert(node.clone(), centrality_vec[i]);
855 }
856
857 Ok(result)
858}
859
860#[allow(dead_code)]
873pub fn pagerank_centrality<N, E, Ix>(
874 graph: &Graph<N, E, Ix>,
875 damping: f64,
876 tolerance: f64,
877) -> Result<HashMap<N, f64>>
878where
879 N: Node + std::fmt::Debug,
880 E: EdgeWeight,
881 Ix: petgraph::graph::IndexType,
882{
883 let nodes = graph.nodes();
884 let n = nodes.len();
885
886 if n == 0 {
887 return Err(GraphError::InvalidGraph("Empty graph".to_string()));
888 }
889
890 let mut pagerank = Array1::<f64>::from_elem(n, 1.0 / n as f64);
892 let max_iter = 100;
893
894 let degrees = graph.degree_vector();
896
897 for _ in 0..max_iter {
898 let mut dangling_sum = 0.0;
900 for (i, _) in graph.inner().node_indices().enumerate() {
901 if degrees[i] == 0 {
902 dangling_sum += pagerank[i];
903 }
904 }
905 let dangling_contrib = damping * dangling_sum / n as f64;
907
908 let mut new_pagerank =
909 Array1::<f64>::from_elem(n, (1.0 - damping) / n as f64 + dangling_contrib);
910
911 for (i, node_idx) in graph.inner().node_indices().enumerate() {
913 let node_degree = degrees[i] as f64;
914 if node_degree > 0.0 {
915 let contribution = damping * pagerank[i] / node_degree;
916
917 for neighbor_idx in graph.inner().neighbors(node_idx) {
919 let neighbor_i = neighbor_idx.index();
920 new_pagerank[neighbor_i] += contribution;
921 }
922 }
923 }
924
925 let diff = (&new_pagerank - &pagerank)
927 .iter()
928 .map(|v| v.abs())
929 .sum::<f64>();
930 if diff < tolerance {
931 pagerank = new_pagerank;
932 break;
933 }
934
935 pagerank = new_pagerank;
936 }
937
938 let mut result = HashMap::new();
940 for (i, &node) in nodes.iter().enumerate() {
941 result.insert(node.clone(), pagerank[i]);
942 }
943
944 Ok(result)
945}
946
947#[allow(dead_code)]
974pub fn parallel_pagerank_centrality<N, E, Ix>(
975 graph: &Graph<N, E, Ix>,
976 damping: f64,
977 tolerance: f64,
978 max_iterations: Option<usize>,
979) -> Result<HashMap<N, f64>>
980where
981 N: Node + Send + Sync + std::fmt::Debug,
982 E: EdgeWeight + Send + Sync,
983 Ix: petgraph::graph::IndexType + Send + Sync,
984{
985 use scirs2_core::parallel_ops::*;
986 use std::sync::{Arc, Mutex};
987
988 let nodes = graph.nodes();
989 let n = nodes.len();
990
991 if n == 0 {
992 return Err(GraphError::InvalidGraph("Empty graph".to_string()));
993 }
994
995 if n < 1000 {
997 return pagerank_centrality(graph, damping, tolerance);
998 }
999
1000 let max_iter = max_iterations.unwrap_or(100);
1001
1002 let mut pagerank = Array1::<f64>::from_elem(n, 1.0 / n as f64);
1004
1005 let degrees = graph.degree_vector();
1007
1008 let neighbor_lists: Vec<Vec<usize>> = (0..n)
1010 .map(|i| {
1011 let node_idx = petgraph::graph::NodeIndex::new(i);
1012 graph
1013 .inner()
1014 .neighbors(node_idx)
1015 .map(|neighbor_idx| neighbor_idx.index())
1016 .collect()
1017 })
1018 .collect();
1019
1020 for _iteration in 0..max_iter {
1021 let new_pagerank = Arc::new(Mutex::new(Array1::<f64>::from_elem(
1023 n,
1024 (1.0 - damping) / n as f64,
1025 )));
1026
1027 (0..n).into_par_iter().for_each(|i| {
1029 let node_degree = degrees[i] as f64;
1030 if node_degree > 0.0 {
1031 let contribution = damping * pagerank[i] / node_degree;
1032
1033 let mut local_updates = Vec::new();
1035 for &neighbor_i in &neighbor_lists[i] {
1036 local_updates.push((neighbor_i, contribution));
1037 }
1038
1039 if !local_updates.is_empty() {
1041 let mut new_pr = new_pagerank.lock().expect("Failed to acquire lock");
1042 for (neighbor_i, contrib) in local_updates {
1043 new_pr[neighbor_i] += contrib;
1044 }
1045 }
1046 }
1047 });
1048
1049 let new_pagerank = Arc::try_unwrap(new_pagerank)
1050 .expect("Failed to unwrap Arc")
1051 .into_inner()
1052 .expect("Failed to get inner value");
1053
1054 let diff = pagerank
1056 .iter()
1057 .zip(new_pagerank.iter())
1058 .map(|(old, new)| (new - old).abs())
1059 .sum::<f64>();
1060
1061 if diff < tolerance {
1062 pagerank = new_pagerank;
1063 break;
1064 }
1065
1066 pagerank = new_pagerank;
1067 }
1068
1069 let result: HashMap<N, f64> = nodes
1071 .iter()
1072 .enumerate()
1073 .map(|(i, node)| ((*node).clone(), pagerank[i]))
1074 .collect();
1075
1076 Ok(result)
1077}
1078
1079#[derive(Debug, Clone)]
1081pub struct HitsScores<N: Node> {
1082 pub authorities: HashMap<N, f64>,
1084 pub hubs: HashMap<N, f64>,
1086}
1087
1088#[allow(dead_code)]
1102pub fn hits_algorithm<N, E, Ix>(
1103 graph: &DiGraph<N, E, Ix>,
1104 max_iter: usize,
1105 tolerance: f64,
1106) -> Result<HitsScores<N>>
1107where
1108 N: Node + Clone + Hash + Eq + std::fmt::Debug,
1109 E: EdgeWeight,
1110 Ix: IndexType,
1111{
1112 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
1113 let n = nodes.len();
1114
1115 if n == 0 {
1116 return Ok(HitsScores {
1117 authorities: HashMap::new(),
1118 hubs: HashMap::new(),
1119 });
1120 }
1121
1122 let mut authorities = vec![1.0 / (n as f64).sqrt(); n];
1124 let mut hubs = vec![1.0 / (n as f64).sqrt(); n];
1125 let mut new_authorities = vec![0.0; n];
1126 let mut new_hubs = vec![0.0; n];
1127
1128 let node_to_idx: HashMap<N, usize> = nodes
1130 .iter()
1131 .enumerate()
1132 .map(|(i, n)| (n.clone(), i))
1133 .collect();
1134
1135 for _ in 0..max_iter {
1137 new_authorities.fill(0.0);
1139 for (i, node) in nodes.iter().enumerate() {
1140 if let Ok(predecessors) = graph.predecessors(node) {
1142 for pred in predecessors {
1143 if let Some(&pred_idx) = node_to_idx.get(&pred) {
1144 new_authorities[i] += hubs[pred_idx];
1145 }
1146 }
1147 }
1148 }
1149
1150 new_hubs.fill(0.0);
1152 for (i, node) in nodes.iter().enumerate() {
1153 if let Ok(successors) = graph.successors(node) {
1155 for succ in successors {
1156 if let Some(&succ_idx) = node_to_idx.get(&succ) {
1157 new_hubs[i] += authorities[succ_idx];
1158 }
1159 }
1160 }
1161 }
1162
1163 let auth_norm: f64 = new_authorities.iter().map(|x| x * x).sum::<f64>().sqrt();
1165 let hub_norm: f64 = new_hubs.iter().map(|x| x * x).sum::<f64>().sqrt();
1166
1167 if auth_norm > 0.0 {
1168 for score in &mut new_authorities {
1169 *score /= auth_norm;
1170 }
1171 }
1172
1173 if hub_norm > 0.0 {
1174 for score in &mut new_hubs {
1175 *score /= hub_norm;
1176 }
1177 }
1178
1179 let auth_diff: f64 = authorities
1181 .iter()
1182 .zip(&new_authorities)
1183 .map(|(old, new)| (old - new).abs())
1184 .sum();
1185 let hub_diff: f64 = hubs
1186 .iter()
1187 .zip(&new_hubs)
1188 .map(|(old, new)| (old - new).abs())
1189 .sum();
1190
1191 if auth_diff < tolerance && hub_diff < tolerance {
1192 break;
1193 }
1194
1195 authorities.copy_from_slice(&new_authorities);
1197 hubs.copy_from_slice(&new_hubs);
1198 }
1199
1200 let authority_map = nodes
1202 .iter()
1203 .enumerate()
1204 .map(|(i, n)| (n.clone(), authorities[i]))
1205 .collect();
1206 let hub_map = nodes
1207 .iter()
1208 .enumerate()
1209 .map(|(i, n)| (n.clone(), hubs[i]))
1210 .collect();
1211
1212 Ok(HitsScores {
1213 authorities: authority_map,
1214 hubs: hub_map,
1215 })
1216}
1217
1218#[allow(dead_code)]
1228pub fn pagerank_centrality_digraph<N, E, Ix>(
1229 graph: &DiGraph<N, E, Ix>,
1230 damping: f64,
1231 tolerance: f64,
1232) -> Result<HashMap<N, f64>>
1233where
1234 N: Node + std::fmt::Debug,
1235 E: EdgeWeight,
1236 Ix: petgraph::graph::IndexType,
1237{
1238 let nodes = graph.nodes();
1239 let n = nodes.len();
1240
1241 if n == 0 {
1242 return Err(GraphError::InvalidGraph("Empty graph".to_string()));
1243 }
1244
1245 let mut pagerank = Array1::<f64>::from_elem(n, 1.0 / n as f64);
1247 let max_iter = 100;
1248
1249 let out_degrees = graph.out_degree_vector();
1251
1252 for _ in 0..max_iter {
1253 let mut new_pagerank = Array1::<f64>::from_elem(n, (1.0 - damping) / n as f64);
1254
1255 for (i, node_idx) in graph.inner().node_indices().enumerate() {
1257 let node_out_degree = out_degrees[i] as f64;
1258 if node_out_degree > 0.0 {
1259 let contribution = damping * pagerank[i] / node_out_degree;
1260
1261 for neighbor_idx in graph
1263 .inner()
1264 .neighbors_directed(node_idx, petgraph::Direction::Outgoing)
1265 {
1266 let neighbor_i = neighbor_idx.index();
1267 new_pagerank[neighbor_i] += contribution;
1268 }
1269 } else {
1270 let contribution = damping * pagerank[i] / n as f64;
1272 for j in 0..n {
1273 new_pagerank[j] += contribution;
1274 }
1275 }
1276 }
1277
1278 let diff = (&new_pagerank - &pagerank)
1280 .iter()
1281 .map(|v| v.abs())
1282 .sum::<f64>();
1283 if diff < tolerance {
1284 pagerank = new_pagerank;
1285 break;
1286 }
1287
1288 pagerank = new_pagerank;
1289 }
1290
1291 let mut result = HashMap::new();
1293 for (i, &node) in nodes.iter().enumerate() {
1294 result.insert(node.clone(), pagerank[i]);
1295 }
1296
1297 Ok(result)
1298}
1299
1300#[allow(dead_code)]
1304fn weighted_degree_centrality<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<HashMap<N, f64>>
1305where
1306 N: Node + std::fmt::Debug,
1307 E: EdgeWeight + Into<f64> + Clone,
1308 Ix: IndexType,
1309{
1310 let mut centrality = HashMap::new();
1311
1312 for node in graph.nodes() {
1313 let mut weight_sum = 0.0;
1314
1315 if let Ok(neighbors) = graph.neighbors(node) {
1316 for neighbor in neighbors {
1317 if let Ok(weight) = graph.edge_weight(node, &neighbor) {
1318 weight_sum += weight.into();
1319 }
1320 }
1321 }
1322
1323 centrality.insert(node.clone(), weight_sum);
1324 }
1325
1326 Ok(centrality)
1327}
1328
1329#[allow(dead_code)]
1331fn weighted_degree_centrality_digraph<N, E, Ix>(
1332 graph: &DiGraph<N, E, Ix>,
1333) -> Result<HashMap<N, f64>>
1334where
1335 N: Node + std::fmt::Debug,
1336 E: EdgeWeight + Into<f64> + Clone,
1337 Ix: IndexType,
1338{
1339 let mut centrality = HashMap::new();
1340
1341 for node in graph.nodes() {
1342 let mut in_weight = 0.0;
1343 let mut out_weight = 0.0;
1344
1345 if let Ok(predecessors) = graph.predecessors(node) {
1347 for pred in predecessors {
1348 if let Ok(weight) = graph.edge_weight(&pred, node) {
1349 in_weight += weight.into();
1350 }
1351 }
1352 }
1353
1354 if let Ok(successors) = graph.successors(node) {
1356 for succ in successors {
1357 if let Ok(weight) = graph.edge_weight(node, &succ) {
1358 out_weight += weight.into();
1359 }
1360 }
1361 }
1362
1363 centrality.insert(node.clone(), in_weight + out_weight);
1364 }
1365
1366 Ok(centrality)
1367}
1368
1369#[allow(dead_code)]
1373fn weighted_betweenness_centrality<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<HashMap<N, f64>>
1374where
1375 N: Node + std::fmt::Debug,
1376 E: EdgeWeight
1377 + Into<f64>
1378 + scirs2_core::numeric::Zero
1379 + scirs2_core::numeric::One
1380 + std::ops::Add<Output = E>
1381 + PartialOrd
1382 + Copy
1383 + std::fmt::Debug
1384 + Default,
1385 Ix: IndexType,
1386{
1387 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
1388 let n = nodes.len();
1389 let mut centrality: HashMap<N, f64> = nodes.iter().map(|n| (n.clone(), 0.0)).collect();
1390
1391 for source in &nodes {
1393 for target in &nodes {
1394 if source != target {
1395 if let Ok(Some(path)) = dijkstra_path(graph, source, target) {
1397 for intermediate in &path.nodes[1..path.nodes.len() - 1] {
1399 *centrality
1400 .get_mut(intermediate)
1401 .expect("Failed to get mutable reference") += 1.0;
1402 }
1403 }
1404 }
1405 }
1406 }
1407
1408 if n > 2 {
1410 let normalization = ((n - 1) * (n - 2)) as f64;
1411 for value in centrality.values_mut() {
1412 *value /= normalization;
1413 }
1414 }
1415
1416 Ok(centrality)
1417}
1418
1419#[allow(dead_code)]
1421fn weighted_betweenness_centrality_digraph<N, E, Ix>(
1422 graph: &DiGraph<N, E, Ix>,
1423) -> Result<HashMap<N, f64>>
1424where
1425 N: Node + std::fmt::Debug,
1426 E: EdgeWeight
1427 + Into<f64>
1428 + scirs2_core::numeric::Zero
1429 + scirs2_core::numeric::One
1430 + std::ops::Add<Output = E>
1431 + PartialOrd
1432 + Copy
1433 + std::fmt::Debug
1434 + Default,
1435 Ix: IndexType,
1436{
1437 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
1438 let n = nodes.len();
1439 let mut centrality: HashMap<N, f64> = nodes.iter().map(|n| (n.clone(), 0.0)).collect();
1440
1441 for source in &nodes {
1443 for target in &nodes {
1444 if source != target {
1445 if let Ok(Some(path)) = dijkstra_path_digraph(graph, source, target) {
1447 for intermediate in &path.nodes[1..path.nodes.len() - 1] {
1449 *centrality
1450 .get_mut(intermediate)
1451 .expect("Failed to get mutable reference") += 1.0;
1452 }
1453 }
1454 }
1455 }
1456 }
1457
1458 if n > 2 {
1460 let normalization = ((n - 1) * (n - 2)) as f64;
1461 for value in centrality.values_mut() {
1462 *value /= normalization;
1463 }
1464 }
1465
1466 Ok(centrality)
1467}
1468
1469#[allow(dead_code)]
1473fn weighted_closeness_centrality<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<HashMap<N, f64>>
1474where
1475 N: Node + std::fmt::Debug,
1476 E: EdgeWeight
1477 + Into<f64>
1478 + scirs2_core::numeric::Zero
1479 + scirs2_core::numeric::One
1480 + std::ops::Add<Output = E>
1481 + PartialOrd
1482 + Copy
1483 + std::fmt::Debug
1484 + Default,
1485 Ix: IndexType,
1486{
1487 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
1488 let n = nodes.len();
1489 let mut centrality = HashMap::new();
1490
1491 for node in &nodes {
1492 let mut total_distance = 0.0;
1493 let mut reachable_count = 0;
1494
1495 for other in &nodes {
1497 if node != other {
1498 if let Ok(Some(path)) = dijkstra_path(graph, node, other) {
1499 let distance: f64 = path.total_weight.into();
1500 total_distance += distance;
1501 reachable_count += 1;
1502 }
1503 }
1504 }
1505
1506 if reachable_count > 0 && total_distance > 0.0 {
1507 let closeness = reachable_count as f64 / total_distance;
1508 let normalized_closeness = if n > 1 {
1510 closeness * (reachable_count as f64 / (n - 1) as f64)
1511 } else {
1512 closeness
1513 };
1514 centrality.insert(node.clone(), normalized_closeness);
1515 } else {
1516 centrality.insert(node.clone(), 0.0);
1517 }
1518 }
1519
1520 Ok(centrality)
1521}
1522
1523#[allow(dead_code)]
1525fn weighted_closeness_centrality_digraph<N, E, Ix>(
1526 graph: &DiGraph<N, E, Ix>,
1527) -> Result<HashMap<N, f64>>
1528where
1529 N: Node + std::fmt::Debug,
1530 E: EdgeWeight
1531 + Into<f64>
1532 + scirs2_core::numeric::Zero
1533 + scirs2_core::numeric::One
1534 + std::ops::Add<Output = E>
1535 + PartialOrd
1536 + Copy
1537 + std::fmt::Debug
1538 + Default,
1539 Ix: IndexType,
1540{
1541 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
1542 let n = nodes.len();
1543 let mut centrality = HashMap::new();
1544
1545 for node in &nodes {
1546 let mut total_distance = 0.0;
1547 let mut reachable_count = 0;
1548
1549 for other in &nodes {
1551 if node != other {
1552 if let Ok(Some(path)) = dijkstra_path_digraph(graph, node, other) {
1553 let distance: f64 = path.total_weight.into();
1554 total_distance += distance;
1555 reachable_count += 1;
1556 }
1557 }
1558 }
1559
1560 if reachable_count > 0 && total_distance > 0.0 {
1561 let closeness = reachable_count as f64 / total_distance;
1562 let normalized_closeness = if n > 1 {
1564 closeness * (reachable_count as f64 / (n - 1) as f64)
1565 } else {
1566 closeness
1567 };
1568 centrality.insert(node.clone(), normalized_closeness);
1569 } else {
1570 centrality.insert(node.clone(), 0.0);
1571 }
1572 }
1573
1574 Ok(centrality)
1575}
1576
1577#[cfg(test)]
1578mod tests {
1579 use super::*;
1580
1581 #[test]
1582 fn test_degree_centrality() {
1583 let mut graph: Graph<char, f64> = Graph::new();
1584
1585 graph.add_edge('A', 'B', 1.0).expect("Test failed");
1592 graph.add_edge('A', 'C', 1.0).expect("Test failed");
1593 graph.add_edge('C', 'D', 1.0).expect("Test failed");
1594
1595 let centrality = centrality(&graph, CentralityType::Degree).expect("Test failed");
1596
1597 assert_eq!(centrality[&'A'], 2.0 / 3.0);
1603 assert_eq!(centrality[&'B'], 1.0 / 3.0);
1604 assert_eq!(centrality[&'C'], 2.0 / 3.0);
1605 assert_eq!(centrality[&'D'], 1.0 / 3.0);
1606 }
1607
1608 #[test]
1609 fn test_clustering_coefficient() {
1610 let mut graph: Graph<i32, f64> = Graph::new();
1611
1612 graph.add_edge(1, 2, 1.0).expect("Test failed");
1620 graph.add_edge(2, 3, 1.0).expect("Test failed");
1621 graph.add_edge(3, 4, 1.0).expect("Test failed");
1622 graph.add_edge(4, 1, 1.0).expect("Test failed");
1623 graph.add_edge(4, 5, 1.0).expect("Test failed");
1624
1625 let coefficients = clustering_coefficient(&graph).expect("Test failed");
1626
1627 assert_eq!(coefficients[&1], 0.0);
1629
1630 assert_eq!(coefficients[&2], 0.0);
1632
1633 assert_eq!(coefficients[&3], 0.0);
1635
1636 assert_eq!(coefficients[&4], 0.0);
1640
1641 assert_eq!(coefficients[&5], 0.0);
1643
1644 graph.add_edge(1, 3, 1.0).expect("Test failed");
1646
1647 let coefficients = clustering_coefficient(&graph).expect("Test failed");
1648
1649 assert!((coefficients[&4] - 1.0 / 3.0).abs() < 1e-10);
1654 }
1655
1656 #[test]
1657 fn test_graph_density() {
1658 let mut graph: Graph<i32, f64> = Graph::new();
1659
1660 assert!(graph_density(&graph).is_err());
1662
1663 graph.add_node(1);
1665
1666 assert!(graph_density(&graph).is_err());
1668
1669 graph.add_node(2);
1671 graph.add_node(3);
1672 graph.add_node(4);
1673
1674 graph.add_edge(1, 2, 1.0).expect("Test failed");
1675 graph.add_edge(2, 3, 1.0).expect("Test failed");
1676 graph.add_edge(3, 4, 1.0).expect("Test failed");
1677
1678 let density = graph_density(&graph).expect("Test failed");
1680 assert_eq!(density, 0.5);
1681
1682 graph.add_edge(1, 3, 1.0).expect("Test failed");
1684 graph.add_edge(1, 4, 1.0).expect("Test failed");
1685 graph.add_edge(2, 4, 1.0).expect("Test failed");
1686
1687 let density = graph_density(&graph).expect("Test failed");
1689 assert_eq!(density, 1.0);
1690 }
1691
1692 #[test]
1693 fn test_katz_centrality() {
1694 let mut graph: Graph<char, f64> = Graph::new();
1695
1696 graph.add_edge('A', 'B', 1.0).expect("Test failed");
1698 graph.add_edge('A', 'C', 1.0).expect("Test failed");
1699 graph.add_edge('A', 'D', 1.0).expect("Test failed");
1700
1701 let centrality = katz_centrality(&graph, 0.1, 1.0).expect("Test failed");
1702
1703 assert!(centrality[&'A'] > centrality[&'B']);
1705 assert!(centrality[&'A'] > centrality[&'C']);
1706 assert!(centrality[&'A'] > centrality[&'D']);
1707
1708 assert!((centrality[&'B'] - centrality[&'C']).abs() < 0.1);
1710 assert!((centrality[&'B'] - centrality[&'D']).abs() < 0.1);
1711 }
1712
1713 #[test]
1714 fn test_pagerank_centrality() {
1715 let mut graph: Graph<char, f64> = Graph::new();
1716
1717 graph.add_edge('A', 'B', 1.0).expect("Test failed");
1719 graph.add_edge('B', 'C', 1.0).expect("Test failed");
1720 graph.add_edge('C', 'A', 1.0).expect("Test failed");
1721
1722 let centrality = pagerank_centrality(&graph, 0.85, 1e-6).expect("Test failed");
1723
1724 let values: Vec<f64> = centrality.values().cloned().collect();
1726 let expected = 1.0 / 3.0; for &value in &values {
1729 assert!((value - expected).abs() < 0.1);
1730 }
1731
1732 let sum: f64 = values.iter().sum();
1734 assert!((sum - 1.0).abs() < 0.01);
1735 }
1736
1737 #[test]
1738 fn test_pagerank_centrality_digraph() {
1739 let mut graph: DiGraph<char, f64> = DiGraph::new();
1740
1741 graph.add_edge('A', 'B', 1.0).expect("Test failed");
1743 graph.add_edge('B', 'C', 1.0).expect("Test failed");
1744
1745 let centrality = pagerank_centrality_digraph(&graph, 0.85, 1e-6).expect("Test failed");
1746
1747 assert!(centrality[&'C'] > centrality[&'B']);
1750 assert!(centrality[&'B'] > centrality[&'A']);
1751 }
1752
1753 #[test]
1754 fn test_centrality_enum_katz_pagerank() {
1755 let mut graph: Graph<char, f64> = Graph::new();
1756
1757 graph.add_edge('A', 'B', 1.0).expect("Test failed");
1758 graph.add_edge('A', 'C', 1.0).expect("Test failed");
1759
1760 let katz_result = centrality(&graph, CentralityType::Katz).expect("Test failed");
1762 let pagerank_result = centrality(&graph, CentralityType::PageRank).expect("Test failed");
1763
1764 assert_eq!(katz_result.len(), 3);
1766 assert_eq!(pagerank_result.len(), 3);
1767
1768 for value in katz_result.values() {
1770 assert!(*value > 0.0);
1771 }
1772 for value in pagerank_result.values() {
1773 assert!(*value > 0.0);
1774 }
1775 }
1776
1777 #[test]
1778 fn test_hits_algorithm() {
1779 let mut graph: DiGraph<char, f64> = DiGraph::new();
1780
1781 graph.add_edge('A', 'C', 1.0).expect("Test failed");
1785 graph.add_edge('A', 'D', 1.0).expect("Test failed");
1786 graph.add_edge('B', 'C', 1.0).expect("Test failed");
1787 graph.add_edge('B', 'D', 1.0).expect("Test failed");
1788 graph.add_edge('E', 'C', 1.0).expect("Test failed");
1790 graph.add_edge('B', 'E', 1.0).expect("Test failed");
1791
1792 let hits = hits_algorithm(&graph, 100, 1e-6).expect("Test failed");
1793
1794 assert_eq!(hits.authorities.len(), 5);
1796 assert_eq!(hits.hubs.len(), 5);
1797
1798 assert!(hits.authorities[&'C'] > hits.authorities[&'A']);
1800 assert!(hits.authorities[&'D'] > hits.authorities[&'A']);
1801
1802 assert!(hits.hubs[&'A'] > hits.hubs[&'C']);
1804 assert!(hits.hubs[&'B'] > hits.hubs[&'C']);
1805
1806 let auth_norm: f64 = hits.authorities.values().map(|&x| x * x).sum::<f64>();
1808 let hub_norm: f64 = hits.hubs.values().map(|&x| x * x).sum::<f64>();
1809 assert!((auth_norm - 1.0).abs() < 0.01);
1810 assert!((hub_norm - 1.0).abs() < 0.01);
1811 }
1812
1813 #[test]
1814 fn test_weighted_degree_centrality() {
1815 let mut graph = crate::generators::create_graph::<&str, f64>();
1816
1817 graph.add_edge("A", "B", 2.0).expect("Test failed");
1819 graph.add_edge("A", "C", 3.0).expect("Test failed");
1820 graph.add_edge("B", "C", 1.0).expect("Test failed");
1821
1822 let centrality = centrality(&graph, CentralityType::WeightedDegree).expect("Test failed");
1823
1824 assert_eq!(centrality[&"A"], 5.0);
1826
1827 assert_eq!(centrality[&"B"], 3.0);
1829
1830 assert_eq!(centrality[&"C"], 4.0);
1832 }
1833
1834 #[test]
1835 fn test_weighted_closeness_centrality() {
1836 let mut graph = crate::generators::create_graph::<&str, f64>();
1837
1838 graph.add_edge("A", "B", 1.0).expect("Test failed");
1840 graph.add_edge("B", "C", 2.0).expect("Test failed");
1841
1842 let centrality =
1843 centrality(&graph, CentralityType::WeightedCloseness).expect("Test failed");
1844
1845 for value in centrality.values() {
1847 assert!(*value > 0.0);
1848 }
1849
1850 assert!(centrality[&"B"] > centrality[&"A"]);
1852 assert!(centrality[&"B"] > centrality[&"C"]);
1853 }
1854
1855 #[test]
1856 fn test_weighted_betweenness_centrality() {
1857 let mut graph = crate::generators::create_graph::<&str, f64>();
1858
1859 graph.add_edge("A", "B", 1.0).expect("Test failed");
1861 graph.add_edge("B", "C", 1.0).expect("Test failed");
1862
1863 let centrality =
1864 centrality(&graph, CentralityType::WeightedBetweenness).expect("Test failed");
1865
1866 assert!(centrality[&"B"] > 0.0);
1868
1869 assert_eq!(centrality[&"A"], 0.0);
1871 assert_eq!(centrality[&"C"], 0.0);
1872 }
1873
1874 #[test]
1875 fn test_weighted_centrality_digraph() {
1876 let mut graph = crate::generators::create_digraph::<&str, f64>();
1877
1878 graph.add_edge("A", "B", 2.0).expect("Test failed");
1880 graph.add_edge("B", "C", 3.0).expect("Test failed");
1881 graph.add_edge("A", "C", 1.0).expect("Test failed");
1882
1883 let degree_centrality =
1884 centrality_digraph(&graph, CentralityType::WeightedDegree).expect("Test failed");
1885 let closeness_centrality =
1886 centrality_digraph(&graph, CentralityType::WeightedCloseness).expect("Test failed");
1887
1888 for value in degree_centrality.values() {
1890 assert!(*value >= 0.0);
1891 }
1892 for value in closeness_centrality.values() {
1893 assert!(*value >= 0.0);
1894 }
1895
1896 assert!(degree_centrality[&"B"] > degree_centrality[&"A"]);
1901 assert!(degree_centrality[&"B"] > degree_centrality[&"C"]);
1902 }
1903}