1use std::collections::{HashMap, HashSet};
8use std::hash::Hash;
9
10use 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 + num_traits::Zero
68 + num_traits::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 + num_traits::Zero
114 + num_traits::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 + num_traits::Zero
211 + num_traits::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 + num_traits::Zero
269 + num_traits::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 + num_traits::Zero
327 + num_traits::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 + num_traits::Zero
388 + num_traits::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 + num_traits::Zero + num_traits::One + PartialOrd + Into<f64> + std::marker::Copy,
450 Ix: petgraph::graph::IndexType,
451{
452 let nodes = graph.nodes();
453 let n = nodes.len();
454
455 if n == 0 {
456 return Err(GraphError::InvalidGraph("Empty _graph".to_string()));
457 }
458
459 let adj_mat = graph.adjacency_matrix();
461 let mut adj_f64 = Array2::<f64>::zeros((n, n));
462 for i in 0..n {
463 for j in 0..n {
464 adj_f64[[i, j]] = adj_mat[[i, j]].into();
465 }
466 }
467
468 let mut x = Array1::<f64>::ones(n);
470 x.mapv_inplace(|v| v / (n as f64).sqrt()); let max_iter = 100;
474 let tol = 1e-6;
475
476 for _ in 0..max_iter {
477 let y = adj_f64.dot(&x);
479
480 let norm = (y.iter().map(|v| v * v).sum::<f64>()).sqrt();
482 if norm < 1e-10 {
483 return Err(GraphError::AlgorithmError(
485 "Eigenvector centrality computation failed: zero eigenvalue".to_string(),
486 ));
487 }
488
489 let y_norm = y / norm;
491
492 let diff = (&y_norm - &x).iter().map(|v| v.abs()).sum::<f64>();
494 if diff < tol {
495 let mut result = HashMap::new();
497 for (i, &node) in nodes.iter().enumerate() {
498 result.insert(node.clone(), y_norm[i]);
499 }
500 return Ok(result);
501 }
502
503 x = y_norm;
505 }
506
507 Err(GraphError::AlgorithmError(
509 "Eigenvector centrality computation did not converge".to_string(),
510 ))
511}
512
513#[allow(dead_code)]
517fn eigenvector_centrality_digraph<N, E, Ix>(graph: &DiGraph<N, E, Ix>) -> Result<HashMap<N, f64>>
518where
519 N: Node + std::fmt::Debug,
520 E: EdgeWeight + num_traits::Zero + num_traits::One + PartialOrd + Into<f64> + std::marker::Copy,
521 Ix: petgraph::graph::IndexType,
522{
523 let nodes = graph.nodes();
524 let n = nodes.len();
525
526 if n == 0 {
527 return Err(GraphError::InvalidGraph("Empty _graph".to_string()));
528 }
529
530 let adj_mat = graph.adjacency_matrix();
532 let mut adj_f64 = Array2::<f64>::zeros((n, n));
533 for i in 0..n {
534 for j in 0..n {
535 adj_f64[[i, j]] = adj_mat[[i, j]].into();
536 }
537 }
538
539 let mut x = Array1::<f64>::ones(n);
541 x.mapv_inplace(|v| v / (n as f64).sqrt()); let max_iter = 100;
545 let tol = 1e-6;
546
547 for _ in 0..max_iter {
548 let y = adj_f64.dot(&x);
550
551 let norm = (y.iter().map(|v| v * v).sum::<f64>()).sqrt();
553 if norm < 1e-10 {
554 return Err(GraphError::AlgorithmError(
556 "Eigenvector centrality computation failed: zero eigenvalue".to_string(),
557 ));
558 }
559
560 let y_norm = y / norm;
562
563 let diff = (&y_norm - &x).iter().map(|v| v.abs()).sum::<f64>();
565 if diff < tol {
566 let mut result = HashMap::new();
568 for (i, &node) in nodes.iter().enumerate() {
569 result.insert(node.clone(), y_norm[i]);
570 }
571 return Ok(result);
572 }
573
574 x = y_norm;
576 }
577
578 Err(GraphError::AlgorithmError(
580 "Eigenvector centrality computation did not converge".to_string(),
581 ))
582}
583
584#[allow(dead_code)]
594pub fn clustering_coefficient<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<HashMap<N, f64>>
595where
596 N: Node + std::fmt::Debug,
597 E: EdgeWeight,
598 Ix: petgraph::graph::IndexType,
599{
600 let mut coefficients = HashMap::new();
601
602 for (idx, &node) in graph.nodes().iter().enumerate() {
603 let node_idx = petgraph::graph::NodeIndex::new(idx);
605 let neighbors: HashSet<_> = graph.inner().neighbors(node_idx).collect();
606
607 let k = neighbors.len();
608
609 if k < 2 {
611 coefficients.insert(node.clone(), 0.0);
612 continue;
613 }
614
615 let mut edge_count = 0;
617
618 for &n1 in &neighbors {
619 for &n2 in &neighbors {
620 if n1 != n2 && graph.inner().contains_edge(n1, n2) {
621 edge_count += 1;
622 }
623 }
624 }
625
626 edge_count /= 2;
628
629 let possible_edges = k * (k - 1) / 2;
631 let coefficient = edge_count as f64 / possible_edges as f64;
632
633 coefficients.insert(node.clone(), coefficient);
634 }
635
636 Ok(coefficients)
637}
638
639#[allow(dead_code)]
649pub fn graph_density<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<f64>
650where
651 N: Node + std::fmt::Debug,
652 E: EdgeWeight,
653 Ix: petgraph::graph::IndexType,
654{
655 let n = graph.node_count();
656
657 if n <= 1 {
658 return Err(GraphError::InvalidGraph(
659 "Graph density undefined for graphs with 0 or 1 nodes".to_string(),
660 ));
661 }
662
663 let m = graph.edge_count();
664 let possible_edges = n * (n - 1) / 2;
665
666 Ok(m as f64 / possible_edges as f64)
667}
668
669#[allow(dead_code)]
677pub fn graph_density_digraph<N, E, Ix>(graph: &DiGraph<N, E, Ix>) -> Result<f64>
678where
679 N: Node + std::fmt::Debug,
680 E: EdgeWeight,
681 Ix: petgraph::graph::IndexType,
682{
683 let n = graph.node_count();
684
685 if n <= 1 {
686 return Err(GraphError::InvalidGraph(
687 "Graph density undefined for graphs with 0 or 1 nodes".to_string(),
688 ));
689 }
690
691 let m = graph.edge_count();
692 let possible_edges = n * (n - 1); Ok(m as f64 / possible_edges as f64)
695}
696
697#[allow(dead_code)]
710pub fn katz_centrality<N, E, Ix>(
711 graph: &Graph<N, E, Ix>,
712 alpha: f64,
713 beta: f64,
714) -> Result<HashMap<N, f64>>
715where
716 N: Node + std::fmt::Debug,
717 E: EdgeWeight + num_traits::Zero + num_traits::One + Into<f64> + Copy,
718 Ix: petgraph::graph::IndexType,
719{
720 let nodes = graph.nodes();
721 let n = nodes.len();
722
723 if n == 0 {
724 return Err(GraphError::InvalidGraph("Empty graph".to_string()));
725 }
726
727 let adj_mat = graph.adjacency_matrix();
729 let mut adj_f64 = Array2::<f64>::zeros((n, n));
730 for i in 0..n {
731 for j in 0..n {
732 adj_f64[[i, j]] = adj_mat[[i, j]].into();
733 }
734 }
735
736 let mut identity = Array2::<f64>::zeros((n, n));
738 for i in 0..n {
739 identity[[i, i]] = 1.0;
740 }
741
742 let _factor_matrix = &identity - &(alpha * &adj_f64);
744
745 let beta_vec = Array1::<f64>::from_elem(n, beta);
747
748 let mut centrality_vec = Array1::<f64>::ones(n);
751 let max_iter = 100;
752 let tol = 1e-6;
753
754 for _ in 0..max_iter {
755 let new_centrality = alpha * adj_f64.dot(¢rality_vec) + &beta_vec;
757
758 let diff = (&new_centrality - ¢rality_vec)
760 .iter()
761 .map(|v| v.abs())
762 .sum::<f64>();
763 if diff < tol {
764 centrality_vec = new_centrality;
765 break;
766 }
767
768 centrality_vec = new_centrality;
769 }
770
771 let mut result = HashMap::new();
773 for (i, &node) in nodes.iter().enumerate() {
774 result.insert(node.clone(), centrality_vec[i]);
775 }
776
777 Ok(result)
778}
779
780#[allow(dead_code)]
790pub fn katz_centrality_digraph<N, E, Ix>(
791 graph: &DiGraph<N, E, Ix>,
792 alpha: f64,
793 beta: f64,
794) -> Result<HashMap<N, f64>>
795where
796 N: Node + std::fmt::Debug,
797 E: EdgeWeight + num_traits::Zero + num_traits::One + Into<f64> + Copy,
798 Ix: petgraph::graph::IndexType,
799{
800 let nodes = graph.nodes();
801 let n = nodes.len();
802
803 if n == 0 {
804 return Err(GraphError::InvalidGraph("Empty graph".to_string()));
805 }
806
807 let adj_mat = graph.adjacency_matrix();
809 let mut adj_f64 = Array2::<f64>::zeros((n, n));
810 for i in 0..n {
811 for j in 0..n {
812 adj_f64[[i, j]] = adj_mat[[i, j]].into();
813 }
814 }
815
816 let beta_vec = Array1::<f64>::from_elem(n, beta);
818
819 let mut centrality_vec = Array1::<f64>::ones(n);
821 let max_iter = 100;
822 let tol = 1e-6;
823
824 for _ in 0..max_iter {
825 let new_centrality = alpha * adj_f64.t().dot(¢rality_vec) + &beta_vec;
827
828 let diff = (&new_centrality - ¢rality_vec)
830 .iter()
831 .map(|v| v.abs())
832 .sum::<f64>();
833 if diff < tol {
834 centrality_vec = new_centrality;
835 break;
836 }
837
838 centrality_vec = new_centrality;
839 }
840
841 let mut result = HashMap::new();
843 for (i, &node) in nodes.iter().enumerate() {
844 result.insert(node.clone(), centrality_vec[i]);
845 }
846
847 Ok(result)
848}
849
850#[allow(dead_code)]
863pub fn pagerank_centrality<N, E, Ix>(
864 graph: &Graph<N, E, Ix>,
865 damping: f64,
866 tolerance: f64,
867) -> Result<HashMap<N, f64>>
868where
869 N: Node + std::fmt::Debug,
870 E: EdgeWeight,
871 Ix: petgraph::graph::IndexType,
872{
873 let nodes = graph.nodes();
874 let n = nodes.len();
875
876 if n == 0 {
877 return Err(GraphError::InvalidGraph("Empty graph".to_string()));
878 }
879
880 let mut pagerank = Array1::<f64>::from_elem(n, 1.0 / n as f64);
882 let max_iter = 100;
883
884 let degrees = graph.degree_vector();
886
887 for _ in 0..max_iter {
888 let mut new_pagerank = Array1::<f64>::from_elem(n, (1.0 - damping) / n as f64);
889
890 for (i, node_idx) in graph.inner().node_indices().enumerate() {
892 let node_degree = degrees[i] as f64;
893 if node_degree > 0.0 {
894 let contribution = damping * pagerank[i] / node_degree;
895
896 for neighbor_idx in graph.inner().neighbors(node_idx) {
898 let neighbor_i = neighbor_idx.index();
899 new_pagerank[neighbor_i] += contribution;
900 }
901 }
902 }
903
904 let diff = (&new_pagerank - &pagerank)
906 .iter()
907 .map(|v| v.abs())
908 .sum::<f64>();
909 if diff < tolerance {
910 pagerank = new_pagerank;
911 break;
912 }
913
914 pagerank = new_pagerank;
915 }
916
917 let mut result = HashMap::new();
919 for (i, &node) in nodes.iter().enumerate() {
920 result.insert(node.clone(), pagerank[i]);
921 }
922
923 Ok(result)
924}
925
926#[allow(dead_code)]
953pub fn parallel_pagerank_centrality<N, E, Ix>(
954 graph: &Graph<N, E, Ix>,
955 damping: f64,
956 tolerance: f64,
957 max_iterations: Option<usize>,
958) -> Result<HashMap<N, f64>>
959where
960 N: Node + Send + Sync + std::fmt::Debug,
961 E: EdgeWeight + Send + Sync,
962 Ix: petgraph::graph::IndexType + Send + Sync,
963{
964 use scirs2_core::parallel_ops::*;
965 use std::sync::{Arc, Mutex};
966
967 let nodes = graph.nodes();
968 let n = nodes.len();
969
970 if n == 0 {
971 return Err(GraphError::InvalidGraph("Empty graph".to_string()));
972 }
973
974 if n < 1000 {
976 return pagerank_centrality(graph, damping, tolerance);
977 }
978
979 let max_iter = max_iterations.unwrap_or(100);
980
981 let mut pagerank = Array1::<f64>::from_elem(n, 1.0 / n as f64);
983
984 let degrees = graph.degree_vector();
986
987 let neighbor_lists: Vec<Vec<usize>> = (0..n)
989 .map(|i| {
990 let node_idx = petgraph::graph::NodeIndex::new(i);
991 graph
992 .inner()
993 .neighbors(node_idx)
994 .map(|neighbor_idx| neighbor_idx.index())
995 .collect()
996 })
997 .collect();
998
999 for _iteration in 0..max_iter {
1000 let new_pagerank = Arc::new(Mutex::new(Array1::<f64>::from_elem(
1002 n,
1003 (1.0 - damping) / n as f64,
1004 )));
1005
1006 (0..n).into_par_iter().for_each(|i| {
1008 let node_degree = degrees[i] as f64;
1009 if node_degree > 0.0 {
1010 let contribution = damping * pagerank[i] / node_degree;
1011
1012 let mut local_updates = Vec::new();
1014 for &neighbor_i in &neighbor_lists[i] {
1015 local_updates.push((neighbor_i, contribution));
1016 }
1017
1018 if !local_updates.is_empty() {
1020 let mut new_pr = new_pagerank.lock().unwrap();
1021 for (neighbor_i, contrib) in local_updates {
1022 new_pr[neighbor_i] += contrib;
1023 }
1024 }
1025 }
1026 });
1027
1028 let new_pagerank = Arc::try_unwrap(new_pagerank).unwrap().into_inner().unwrap();
1029
1030 let diff = pagerank
1032 .iter()
1033 .zip(new_pagerank.iter())
1034 .map(|(old, new)| (new - old).abs())
1035 .sum::<f64>();
1036
1037 if diff < tolerance {
1038 pagerank = new_pagerank;
1039 break;
1040 }
1041
1042 pagerank = new_pagerank;
1043 }
1044
1045 let result: HashMap<N, f64> = nodes
1047 .iter()
1048 .enumerate()
1049 .map(|(i, node)| ((*node).clone(), pagerank[i]))
1050 .collect();
1051
1052 Ok(result)
1053}
1054
1055#[derive(Debug, Clone)]
1057pub struct HitsScores<N: Node> {
1058 pub authorities: HashMap<N, f64>,
1060 pub hubs: HashMap<N, f64>,
1062}
1063
1064#[allow(dead_code)]
1078pub fn hits_algorithm<N, E, Ix>(
1079 graph: &DiGraph<N, E, Ix>,
1080 max_iter: usize,
1081 tolerance: f64,
1082) -> Result<HitsScores<N>>
1083where
1084 N: Node + Clone + Hash + Eq + std::fmt::Debug,
1085 E: EdgeWeight,
1086 Ix: IndexType,
1087{
1088 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
1089 let n = nodes.len();
1090
1091 if n == 0 {
1092 return Ok(HitsScores {
1093 authorities: HashMap::new(),
1094 hubs: HashMap::new(),
1095 });
1096 }
1097
1098 let mut authorities = vec![1.0 / (n as f64).sqrt(); n];
1100 let mut hubs = vec![1.0 / (n as f64).sqrt(); n];
1101 let mut new_authorities = vec![0.0; n];
1102 let mut new_hubs = vec![0.0; n];
1103
1104 let node_to_idx: HashMap<N, usize> = nodes
1106 .iter()
1107 .enumerate()
1108 .map(|(i, n)| (n.clone(), i))
1109 .collect();
1110
1111 for _ in 0..max_iter {
1113 new_authorities.fill(0.0);
1115 for (i, node) in nodes.iter().enumerate() {
1116 if let Ok(predecessors) = graph.predecessors(node) {
1118 for pred in predecessors {
1119 if let Some(&pred_idx) = node_to_idx.get(&pred) {
1120 new_authorities[i] += hubs[pred_idx];
1121 }
1122 }
1123 }
1124 }
1125
1126 new_hubs.fill(0.0);
1128 for (i, node) in nodes.iter().enumerate() {
1129 if let Ok(successors) = graph.successors(node) {
1131 for succ in successors {
1132 if let Some(&succ_idx) = node_to_idx.get(&succ) {
1133 new_hubs[i] += authorities[succ_idx];
1134 }
1135 }
1136 }
1137 }
1138
1139 let auth_norm: f64 = new_authorities.iter().map(|x| x * x).sum::<f64>().sqrt();
1141 let hub_norm: f64 = new_hubs.iter().map(|x| x * x).sum::<f64>().sqrt();
1142
1143 if auth_norm > 0.0 {
1144 for score in &mut new_authorities {
1145 *score /= auth_norm;
1146 }
1147 }
1148
1149 if hub_norm > 0.0 {
1150 for score in &mut new_hubs {
1151 *score /= hub_norm;
1152 }
1153 }
1154
1155 let auth_diff: f64 = authorities
1157 .iter()
1158 .zip(&new_authorities)
1159 .map(|(old, new)| (old - new).abs())
1160 .sum();
1161 let hub_diff: f64 = hubs
1162 .iter()
1163 .zip(&new_hubs)
1164 .map(|(old, new)| (old - new).abs())
1165 .sum();
1166
1167 if auth_diff < tolerance && hub_diff < tolerance {
1168 break;
1169 }
1170
1171 authorities.copy_from_slice(&new_authorities);
1173 hubs.copy_from_slice(&new_hubs);
1174 }
1175
1176 let authority_map = nodes
1178 .iter()
1179 .enumerate()
1180 .map(|(i, n)| (n.clone(), authorities[i]))
1181 .collect();
1182 let hub_map = nodes
1183 .iter()
1184 .enumerate()
1185 .map(|(i, n)| (n.clone(), hubs[i]))
1186 .collect();
1187
1188 Ok(HitsScores {
1189 authorities: authority_map,
1190 hubs: hub_map,
1191 })
1192}
1193
1194#[allow(dead_code)]
1204pub fn pagerank_centrality_digraph<N, E, Ix>(
1205 graph: &DiGraph<N, E, Ix>,
1206 damping: f64,
1207 tolerance: f64,
1208) -> Result<HashMap<N, f64>>
1209where
1210 N: Node + std::fmt::Debug,
1211 E: EdgeWeight,
1212 Ix: petgraph::graph::IndexType,
1213{
1214 let nodes = graph.nodes();
1215 let n = nodes.len();
1216
1217 if n == 0 {
1218 return Err(GraphError::InvalidGraph("Empty graph".to_string()));
1219 }
1220
1221 let mut pagerank = Array1::<f64>::from_elem(n, 1.0 / n as f64);
1223 let max_iter = 100;
1224
1225 let out_degrees = graph.out_degree_vector();
1227
1228 for _ in 0..max_iter {
1229 let mut new_pagerank = Array1::<f64>::from_elem(n, (1.0 - damping) / n as f64);
1230
1231 for (i, node_idx) in graph.inner().node_indices().enumerate() {
1233 let node_out_degree = out_degrees[i] as f64;
1234 if node_out_degree > 0.0 {
1235 let contribution = damping * pagerank[i] / node_out_degree;
1236
1237 for neighbor_idx in graph
1239 .inner()
1240 .neighbors_directed(node_idx, petgraph::Direction::Outgoing)
1241 {
1242 let neighbor_i = neighbor_idx.index();
1243 new_pagerank[neighbor_i] += contribution;
1244 }
1245 } else {
1246 let contribution = damping * pagerank[i] / n as f64;
1248 for j in 0..n {
1249 new_pagerank[j] += contribution;
1250 }
1251 }
1252 }
1253
1254 let diff = (&new_pagerank - &pagerank)
1256 .iter()
1257 .map(|v| v.abs())
1258 .sum::<f64>();
1259 if diff < tolerance {
1260 pagerank = new_pagerank;
1261 break;
1262 }
1263
1264 pagerank = new_pagerank;
1265 }
1266
1267 let mut result = HashMap::new();
1269 for (i, &node) in nodes.iter().enumerate() {
1270 result.insert(node.clone(), pagerank[i]);
1271 }
1272
1273 Ok(result)
1274}
1275
1276#[allow(dead_code)]
1280fn weighted_degree_centrality<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<HashMap<N, f64>>
1281where
1282 N: Node + std::fmt::Debug,
1283 E: EdgeWeight + Into<f64> + Clone,
1284 Ix: IndexType,
1285{
1286 let mut centrality = HashMap::new();
1287
1288 for node in graph.nodes() {
1289 let mut weight_sum = 0.0;
1290
1291 if let Ok(neighbors) = graph.neighbors(node) {
1292 for neighbor in neighbors {
1293 if let Ok(weight) = graph.edge_weight(node, &neighbor) {
1294 weight_sum += weight.into();
1295 }
1296 }
1297 }
1298
1299 centrality.insert(node.clone(), weight_sum);
1300 }
1301
1302 Ok(centrality)
1303}
1304
1305#[allow(dead_code)]
1307fn weighted_degree_centrality_digraph<N, E, Ix>(
1308 graph: &DiGraph<N, E, Ix>,
1309) -> Result<HashMap<N, f64>>
1310where
1311 N: Node + std::fmt::Debug,
1312 E: EdgeWeight + Into<f64> + Clone,
1313 Ix: IndexType,
1314{
1315 let mut centrality = HashMap::new();
1316
1317 for node in graph.nodes() {
1318 let mut in_weight = 0.0;
1319 let mut out_weight = 0.0;
1320
1321 if let Ok(predecessors) = graph.predecessors(node) {
1323 for pred in predecessors {
1324 if let Ok(weight) = graph.edge_weight(&pred, node) {
1325 in_weight += weight.into();
1326 }
1327 }
1328 }
1329
1330 if let Ok(successors) = graph.successors(node) {
1332 for succ in successors {
1333 if let Ok(weight) = graph.edge_weight(node, &succ) {
1334 out_weight += weight.into();
1335 }
1336 }
1337 }
1338
1339 centrality.insert(node.clone(), in_weight + out_weight);
1340 }
1341
1342 Ok(centrality)
1343}
1344
1345#[allow(dead_code)]
1349fn weighted_betweenness_centrality<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<HashMap<N, f64>>
1350where
1351 N: Node + std::fmt::Debug,
1352 E: EdgeWeight
1353 + Into<f64>
1354 + num_traits::Zero
1355 + num_traits::One
1356 + std::ops::Add<Output = E>
1357 + PartialOrd
1358 + Copy
1359 + std::fmt::Debug
1360 + Default,
1361 Ix: IndexType,
1362{
1363 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
1364 let n = nodes.len();
1365 let mut centrality: HashMap<N, f64> = nodes.iter().map(|n| (n.clone(), 0.0)).collect();
1366
1367 for source in &nodes {
1369 for target in &nodes {
1370 if source != target {
1371 if let Ok(Some(path)) = dijkstra_path(graph, source, target) {
1373 for intermediate in &path.nodes[1..path.nodes.len() - 1] {
1375 *centrality.get_mut(intermediate).unwrap() += 1.0;
1376 }
1377 }
1378 }
1379 }
1380 }
1381
1382 if n > 2 {
1384 let normalization = ((n - 1) * (n - 2)) as f64;
1385 for value in centrality.values_mut() {
1386 *value /= normalization;
1387 }
1388 }
1389
1390 Ok(centrality)
1391}
1392
1393#[allow(dead_code)]
1395fn weighted_betweenness_centrality_digraph<N, E, Ix>(
1396 graph: &DiGraph<N, E, Ix>,
1397) -> Result<HashMap<N, f64>>
1398where
1399 N: Node + std::fmt::Debug,
1400 E: EdgeWeight
1401 + Into<f64>
1402 + num_traits::Zero
1403 + num_traits::One
1404 + std::ops::Add<Output = E>
1405 + PartialOrd
1406 + Copy
1407 + std::fmt::Debug
1408 + Default,
1409 Ix: IndexType,
1410{
1411 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
1412 let n = nodes.len();
1413 let mut centrality: HashMap<N, f64> = nodes.iter().map(|n| (n.clone(), 0.0)).collect();
1414
1415 for source in &nodes {
1417 for target in &nodes {
1418 if source != target {
1419 if let Ok(Some(path)) = dijkstra_path_digraph(graph, source, target) {
1421 for intermediate in &path.nodes[1..path.nodes.len() - 1] {
1423 *centrality.get_mut(intermediate).unwrap() += 1.0;
1424 }
1425 }
1426 }
1427 }
1428 }
1429
1430 if n > 2 {
1432 let normalization = ((n - 1) * (n - 2)) as f64;
1433 for value in centrality.values_mut() {
1434 *value /= normalization;
1435 }
1436 }
1437
1438 Ok(centrality)
1439}
1440
1441#[allow(dead_code)]
1445fn weighted_closeness_centrality<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<HashMap<N, f64>>
1446where
1447 N: Node + std::fmt::Debug,
1448 E: EdgeWeight
1449 + Into<f64>
1450 + num_traits::Zero
1451 + num_traits::One
1452 + std::ops::Add<Output = E>
1453 + PartialOrd
1454 + Copy
1455 + std::fmt::Debug
1456 + Default,
1457 Ix: IndexType,
1458{
1459 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
1460 let n = nodes.len();
1461 let mut centrality = HashMap::new();
1462
1463 for node in &nodes {
1464 let mut total_distance = 0.0;
1465 let mut reachable_count = 0;
1466
1467 for other in &nodes {
1469 if node != other {
1470 if let Ok(Some(path)) = dijkstra_path(graph, node, other) {
1471 let distance: f64 = path.total_weight.into();
1472 total_distance += distance;
1473 reachable_count += 1;
1474 }
1475 }
1476 }
1477
1478 if reachable_count > 0 && total_distance > 0.0 {
1479 let closeness = reachable_count as f64 / total_distance;
1480 let normalized_closeness = if n > 1 {
1482 closeness * (reachable_count as f64 / (n - 1) as f64)
1483 } else {
1484 closeness
1485 };
1486 centrality.insert(node.clone(), normalized_closeness);
1487 } else {
1488 centrality.insert(node.clone(), 0.0);
1489 }
1490 }
1491
1492 Ok(centrality)
1493}
1494
1495#[allow(dead_code)]
1497fn weighted_closeness_centrality_digraph<N, E, Ix>(
1498 graph: &DiGraph<N, E, Ix>,
1499) -> Result<HashMap<N, f64>>
1500where
1501 N: Node + std::fmt::Debug,
1502 E: EdgeWeight
1503 + Into<f64>
1504 + num_traits::Zero
1505 + num_traits::One
1506 + std::ops::Add<Output = E>
1507 + PartialOrd
1508 + Copy
1509 + std::fmt::Debug
1510 + Default,
1511 Ix: IndexType,
1512{
1513 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
1514 let n = nodes.len();
1515 let mut centrality = HashMap::new();
1516
1517 for node in &nodes {
1518 let mut total_distance = 0.0;
1519 let mut reachable_count = 0;
1520
1521 for other in &nodes {
1523 if node != other {
1524 if let Ok(Some(path)) = dijkstra_path_digraph(graph, node, other) {
1525 let distance: f64 = path.total_weight.into();
1526 total_distance += distance;
1527 reachable_count += 1;
1528 }
1529 }
1530 }
1531
1532 if reachable_count > 0 && total_distance > 0.0 {
1533 let closeness = reachable_count as f64 / total_distance;
1534 let normalized_closeness = if n > 1 {
1536 closeness * (reachable_count as f64 / (n - 1) as f64)
1537 } else {
1538 closeness
1539 };
1540 centrality.insert(node.clone(), normalized_closeness);
1541 } else {
1542 centrality.insert(node.clone(), 0.0);
1543 }
1544 }
1545
1546 Ok(centrality)
1547}
1548
1549#[cfg(test)]
1550mod tests {
1551 use super::*;
1552
1553 #[test]
1554 fn test_degree_centrality() {
1555 let mut graph: Graph<char, f64> = Graph::new();
1556
1557 graph.add_edge('A', 'B', 1.0).unwrap();
1564 graph.add_edge('A', 'C', 1.0).unwrap();
1565 graph.add_edge('C', 'D', 1.0).unwrap();
1566
1567 let centrality = centrality(&graph, CentralityType::Degree).unwrap();
1568
1569 assert_eq!(centrality[&'A'], 2.0 / 3.0);
1575 assert_eq!(centrality[&'B'], 1.0 / 3.0);
1576 assert_eq!(centrality[&'C'], 2.0 / 3.0);
1577 assert_eq!(centrality[&'D'], 1.0 / 3.0);
1578 }
1579
1580 #[test]
1581 fn test_clustering_coefficient() {
1582 let mut graph: Graph<i32, f64> = Graph::new();
1583
1584 graph.add_edge(1, 2, 1.0).unwrap();
1592 graph.add_edge(2, 3, 1.0).unwrap();
1593 graph.add_edge(3, 4, 1.0).unwrap();
1594 graph.add_edge(4, 1, 1.0).unwrap();
1595 graph.add_edge(4, 5, 1.0).unwrap();
1596
1597 let coefficients = clustering_coefficient(&graph).unwrap();
1598
1599 assert_eq!(coefficients[&1], 0.0);
1601
1602 assert_eq!(coefficients[&2], 0.0);
1604
1605 assert_eq!(coefficients[&3], 0.0);
1607
1608 assert_eq!(coefficients[&4], 0.0);
1612
1613 assert_eq!(coefficients[&5], 0.0);
1615
1616 graph.add_edge(1, 3, 1.0).unwrap();
1618
1619 let coefficients = clustering_coefficient(&graph).unwrap();
1620
1621 assert!((coefficients[&4] - 1.0 / 3.0).abs() < 1e-10);
1626 }
1627
1628 #[test]
1629 fn test_graph_density() {
1630 let mut graph: Graph<i32, f64> = Graph::new();
1631
1632 assert!(graph_density(&graph).is_err());
1634
1635 graph.add_node(1);
1637
1638 assert!(graph_density(&graph).is_err());
1640
1641 graph.add_node(2);
1643 graph.add_node(3);
1644 graph.add_node(4);
1645
1646 graph.add_edge(1, 2, 1.0).unwrap();
1647 graph.add_edge(2, 3, 1.0).unwrap();
1648 graph.add_edge(3, 4, 1.0).unwrap();
1649
1650 let density = graph_density(&graph).unwrap();
1652 assert_eq!(density, 0.5);
1653
1654 graph.add_edge(1, 3, 1.0).unwrap();
1656 graph.add_edge(1, 4, 1.0).unwrap();
1657 graph.add_edge(2, 4, 1.0).unwrap();
1658
1659 let density = graph_density(&graph).unwrap();
1661 assert_eq!(density, 1.0);
1662 }
1663
1664 #[test]
1665 fn test_katz_centrality() {
1666 let mut graph: Graph<char, f64> = Graph::new();
1667
1668 graph.add_edge('A', 'B', 1.0).unwrap();
1670 graph.add_edge('A', 'C', 1.0).unwrap();
1671 graph.add_edge('A', 'D', 1.0).unwrap();
1672
1673 let centrality = katz_centrality(&graph, 0.1, 1.0).unwrap();
1674
1675 assert!(centrality[&'A'] > centrality[&'B']);
1677 assert!(centrality[&'A'] > centrality[&'C']);
1678 assert!(centrality[&'A'] > centrality[&'D']);
1679
1680 assert!((centrality[&'B'] - centrality[&'C']).abs() < 0.1);
1682 assert!((centrality[&'B'] - centrality[&'D']).abs() < 0.1);
1683 }
1684
1685 #[test]
1686 fn test_pagerank_centrality() {
1687 let mut graph: Graph<char, f64> = Graph::new();
1688
1689 graph.add_edge('A', 'B', 1.0).unwrap();
1691 graph.add_edge('B', 'C', 1.0).unwrap();
1692 graph.add_edge('C', 'A', 1.0).unwrap();
1693
1694 let centrality = pagerank_centrality(&graph, 0.85, 1e-6).unwrap();
1695
1696 let values: Vec<f64> = centrality.values().cloned().collect();
1698 let expected = 1.0 / 3.0; for &value in &values {
1701 assert!((value - expected).abs() < 0.1);
1702 }
1703
1704 let sum: f64 = values.iter().sum();
1706 assert!((sum - 1.0).abs() < 0.01);
1707 }
1708
1709 #[test]
1710 fn test_pagerank_centrality_digraph() {
1711 let mut graph: DiGraph<char, f64> = DiGraph::new();
1712
1713 graph.add_edge('A', 'B', 1.0).unwrap();
1715 graph.add_edge('B', 'C', 1.0).unwrap();
1716
1717 let centrality = pagerank_centrality_digraph(&graph, 0.85, 1e-6).unwrap();
1718
1719 assert!(centrality[&'C'] > centrality[&'B']);
1722 assert!(centrality[&'B'] > centrality[&'A']);
1723 }
1724
1725 #[test]
1726 fn test_centrality_enum_katz_pagerank() {
1727 let mut graph: Graph<char, f64> = Graph::new();
1728
1729 graph.add_edge('A', 'B', 1.0).unwrap();
1730 graph.add_edge('A', 'C', 1.0).unwrap();
1731
1732 let katz_result = centrality(&graph, CentralityType::Katz).unwrap();
1734 let pagerank_result = centrality(&graph, CentralityType::PageRank).unwrap();
1735
1736 assert_eq!(katz_result.len(), 3);
1738 assert_eq!(pagerank_result.len(), 3);
1739
1740 for value in katz_result.values() {
1742 assert!(*value > 0.0);
1743 }
1744 for value in pagerank_result.values() {
1745 assert!(*value > 0.0);
1746 }
1747 }
1748
1749 #[test]
1750 fn test_hits_algorithm() {
1751 let mut graph: DiGraph<char, f64> = DiGraph::new();
1752
1753 graph.add_edge('A', 'C', 1.0).unwrap();
1757 graph.add_edge('A', 'D', 1.0).unwrap();
1758 graph.add_edge('B', 'C', 1.0).unwrap();
1759 graph.add_edge('B', 'D', 1.0).unwrap();
1760 graph.add_edge('E', 'C', 1.0).unwrap();
1762 graph.add_edge('B', 'E', 1.0).unwrap();
1763
1764 let hits = hits_algorithm(&graph, 100, 1e-6).unwrap();
1765
1766 assert_eq!(hits.authorities.len(), 5);
1768 assert_eq!(hits.hubs.len(), 5);
1769
1770 assert!(hits.authorities[&'C'] > hits.authorities[&'A']);
1772 assert!(hits.authorities[&'D'] > hits.authorities[&'A']);
1773
1774 assert!(hits.hubs[&'A'] > hits.hubs[&'C']);
1776 assert!(hits.hubs[&'B'] > hits.hubs[&'C']);
1777
1778 let auth_norm: f64 = hits.authorities.values().map(|&x| x * x).sum::<f64>();
1780 let hub_norm: f64 = hits.hubs.values().map(|&x| x * x).sum::<f64>();
1781 assert!((auth_norm - 1.0).abs() < 0.01);
1782 assert!((hub_norm - 1.0).abs() < 0.01);
1783 }
1784
1785 #[test]
1786 fn test_weighted_degree_centrality() {
1787 let mut graph = crate::generators::create_graph::<&str, f64>();
1788
1789 graph.add_edge("A", "B", 2.0).unwrap();
1791 graph.add_edge("A", "C", 3.0).unwrap();
1792 graph.add_edge("B", "C", 1.0).unwrap();
1793
1794 let centrality = centrality(&graph, CentralityType::WeightedDegree).unwrap();
1795
1796 assert_eq!(centrality[&"A"], 5.0);
1798
1799 assert_eq!(centrality[&"B"], 3.0);
1801
1802 assert_eq!(centrality[&"C"], 4.0);
1804 }
1805
1806 #[test]
1807 fn test_weighted_closeness_centrality() {
1808 let mut graph = crate::generators::create_graph::<&str, f64>();
1809
1810 graph.add_edge("A", "B", 1.0).unwrap();
1812 graph.add_edge("B", "C", 2.0).unwrap();
1813
1814 let centrality = centrality(&graph, CentralityType::WeightedCloseness).unwrap();
1815
1816 for value in centrality.values() {
1818 assert!(*value > 0.0);
1819 }
1820
1821 assert!(centrality[&"B"] > centrality[&"A"]);
1823 assert!(centrality[&"B"] > centrality[&"C"]);
1824 }
1825
1826 #[test]
1827 fn test_weighted_betweenness_centrality() {
1828 let mut graph = crate::generators::create_graph::<&str, f64>();
1829
1830 graph.add_edge("A", "B", 1.0).unwrap();
1832 graph.add_edge("B", "C", 1.0).unwrap();
1833
1834 let centrality = centrality(&graph, CentralityType::WeightedBetweenness).unwrap();
1835
1836 assert!(centrality[&"B"] > 0.0);
1838
1839 assert_eq!(centrality[&"A"], 0.0);
1841 assert_eq!(centrality[&"C"], 0.0);
1842 }
1843
1844 #[test]
1845 fn test_weighted_centrality_digraph() {
1846 let mut graph = crate::generators::create_digraph::<&str, f64>();
1847
1848 graph.add_edge("A", "B", 2.0).unwrap();
1850 graph.add_edge("B", "C", 3.0).unwrap();
1851 graph.add_edge("A", "C", 1.0).unwrap();
1852
1853 let degree_centrality = centrality_digraph(&graph, CentralityType::WeightedDegree).unwrap();
1854 let closeness_centrality =
1855 centrality_digraph(&graph, CentralityType::WeightedCloseness).unwrap();
1856
1857 for value in degree_centrality.values() {
1859 assert!(*value >= 0.0);
1860 }
1861 for value in closeness_centrality.values() {
1862 assert!(*value >= 0.0);
1863 }
1864
1865 assert!(degree_centrality[&"B"] > degree_centrality[&"A"]);
1870 assert!(degree_centrality[&"B"] > degree_centrality[&"C"]);
1871 }
1872}