1#[cfg(feature = "parallel")]
23use rayon::prelude::*;
24use std::collections::{HashMap, VecDeque};
25
26pub type NodeId = usize;
28
29#[derive(Debug, Clone, PartialEq)]
31pub struct Edge {
32 pub source: NodeId,
33 pub target: NodeId,
34 pub weight: Option<f64>,
35}
36
37#[derive(Debug)]
49pub struct Graph {
50 row_ptr: Vec<usize>, col_indices: Vec<NodeId>, edge_weights: Vec<f64>, #[allow(dead_code)]
57 node_labels: Vec<Option<String>>, #[allow(dead_code)]
59 label_to_id: HashMap<String, NodeId>, is_directed: bool,
62 n_nodes: usize,
63 n_edges: usize,
64}
65
66impl Graph {
67 pub fn new(is_directed: bool) -> Self {
80 Self {
81 row_ptr: vec![0],
82 col_indices: Vec::new(),
83 edge_weights: Vec::new(),
84 node_labels: Vec::new(),
85 label_to_id: HashMap::new(),
86 is_directed,
87 n_nodes: 0,
88 n_edges: 0,
89 }
90 }
91
92 pub fn num_nodes(&self) -> usize {
94 self.n_nodes
95 }
96
97 pub fn num_edges(&self) -> usize {
99 self.n_edges
100 }
101
102 pub fn is_directed(&self) -> bool {
104 self.is_directed
105 }
106
107 pub fn neighbors(&self, v: NodeId) -> &[NodeId] {
123 if v >= self.n_nodes {
124 return &[];
125 }
126 let start = self.row_ptr[v];
127 let end = self.row_ptr[v + 1];
128 &self.col_indices[start..end]
129 }
130
131 pub fn from_edges(edges: &[(NodeId, NodeId)], is_directed: bool) -> Self {
149 if edges.is_empty() {
150 return Self::new(is_directed);
151 }
152
153 let max_node = edges.iter().flat_map(|&(s, t)| [s, t]).max().unwrap_or(0);
155 let n_nodes = max_node + 1;
156
157 let mut adj_list: Vec<Vec<NodeId>> = vec![Vec::new(); n_nodes];
159 for &(source, target) in edges {
160 adj_list[source].push(target);
161 if !is_directed && source != target {
162 adj_list[target].push(source);
164 }
165 }
166
167 for neighbors in &mut adj_list {
169 neighbors.sort_unstable();
170 neighbors.dedup();
171 }
172
173 let mut row_ptr = Vec::with_capacity(n_nodes + 1);
175 let mut col_indices = Vec::new();
176
177 row_ptr.push(0);
178 for neighbors in &adj_list {
179 col_indices.extend_from_slice(neighbors);
180 row_ptr.push(col_indices.len());
181 }
182
183 let n_edges = if is_directed {
184 edges.len()
185 } else {
186 edges.len()
188 };
189
190 Self {
191 row_ptr,
192 col_indices,
193 edge_weights: Vec::new(),
194 node_labels: vec![None; n_nodes],
195 label_to_id: HashMap::new(),
196 is_directed,
197 n_nodes,
198 n_edges,
199 }
200 }
201
202 pub fn from_weighted_edges(edges: &[(NodeId, NodeId, f64)], is_directed: bool) -> Self {
217 if edges.is_empty() {
218 return Self::new(is_directed);
219 }
220
221 let max_node = edges
223 .iter()
224 .flat_map(|&(s, t, _)| [s, t])
225 .max()
226 .unwrap_or(0);
227 let n_nodes = max_node + 1;
228
229 let mut adj_list: Vec<Vec<(NodeId, f64)>> = vec![Vec::new(); n_nodes];
231 for &(source, target, weight) in edges {
232 adj_list[source].push((target, weight));
233 if !is_directed && source != target {
234 adj_list[target].push((source, weight));
235 }
236 }
237
238 for neighbors in &mut adj_list {
240 neighbors.sort_unstable_by_key(|&(id, _)| id);
241 neighbors.dedup_by_key(|&mut (id, _)| id);
242 }
243
244 let mut row_ptr = Vec::with_capacity(n_nodes + 1);
246 let mut col_indices = Vec::new();
247 let mut edge_weights = Vec::new();
248
249 row_ptr.push(0);
250 for neighbors in &adj_list {
251 for &(neighbor, weight) in neighbors {
252 col_indices.push(neighbor);
253 edge_weights.push(weight);
254 }
255 row_ptr.push(col_indices.len());
256 }
257
258 let n_edges = edges.len();
259
260 Self {
261 row_ptr,
262 col_indices,
263 edge_weights,
264 node_labels: vec![None; n_nodes],
265 label_to_id: HashMap::new(),
266 is_directed,
267 n_nodes,
268 n_edges,
269 }
270 }
271
272 #[allow(dead_code)]
278 fn edge_weight(&self, source: NodeId, target: NodeId) -> Option<f64> {
279 if source >= self.n_nodes {
280 return None;
281 }
282
283 let start = self.row_ptr[source];
284 let end = self.row_ptr[source + 1];
285 let neighbors = &self.col_indices[start..end];
286
287 let pos = neighbors.binary_search(&target).ok()?;
289
290 if self.edge_weights.is_empty() {
291 Some(1.0) } else {
293 Some(self.edge_weights[start + pos])
294 }
295 }
296
297 pub fn degree_centrality(&self) -> HashMap<NodeId, f64> {
319 let mut centrality = HashMap::with_capacity(self.n_nodes);
320
321 if self.n_nodes <= 1 {
322 for v in 0..self.n_nodes {
324 centrality.insert(v, 0.0);
325 }
326 return centrality;
327 }
328
329 let norm = (self.n_nodes - 1) as f64;
330
331 #[allow(clippy::needless_range_loop)]
332 for v in 0..self.n_nodes {
333 let degree = self.neighbors(v).len() as f64;
334 centrality.insert(v, degree / norm);
335 }
336
337 centrality
338 }
339
340 pub fn pagerank(&self, damping: f64, max_iter: usize, tol: f64) -> Result<Vec<f64>, String> {
366 if self.n_nodes == 0 {
367 return Ok(Vec::new());
368 }
369
370 let n = self.n_nodes;
371 let mut ranks = vec![1.0 / n as f64; n];
372 let mut new_ranks = vec![0.0; n];
373
374 for _ in 0..max_iter {
375 let mut dangling_sum = 0.0;
378 #[allow(clippy::needless_range_loop)]
379 for v in 0..n {
380 if self.neighbors(v).is_empty() {
381 dangling_sum += ranks[v];
382 }
383 }
384 let dangling_contribution = damping * dangling_sum / n as f64;
385
386 #[allow(clippy::needless_range_loop)]
388 for v in 0..n {
389 let incoming_neighbors = self.incoming_neighbors(v);
390
391 let mut sum = 0.0;
392 let mut c = 0.0; for u in &incoming_neighbors {
395 let out_degree = self.neighbors(*u).len() as f64;
396 if out_degree > 0.0 {
397 let y = (ranks[*u] / out_degree) - c;
398 let t = sum + y;
399 c = (t - sum) - y; sum = t;
401 }
402 }
403
404 new_ranks[v] = (1.0 - damping) / n as f64 + damping * sum + dangling_contribution;
405 }
406
407 let diff = kahan_diff(&ranks, &new_ranks);
409 if diff < tol {
410 return Ok(new_ranks);
411 }
412
413 std::mem::swap(&mut ranks, &mut new_ranks);
414 }
415
416 Ok(ranks)
417 }
418
419 fn incoming_neighbors(&self, v: NodeId) -> Vec<NodeId> {
424 if !self.is_directed {
425 return self.neighbors(v).to_vec();
427 }
428
429 let mut incoming = Vec::new();
431 for u in 0..self.n_nodes {
432 if self.neighbors(u).contains(&v) {
433 incoming.push(u);
434 }
435 }
436 incoming
437 }
438
439 pub fn betweenness_centrality(&self) -> Vec<f64> {
464 if self.n_nodes == 0 {
465 return Vec::new();
466 }
467
468 #[cfg(feature = "parallel")]
470 let partial_scores: Vec<Vec<f64>> = (0..self.n_nodes)
471 .into_par_iter()
472 .map(|source| self.brandes_bfs_from_source(source))
473 .collect();
474
475 #[cfg(not(feature = "parallel"))]
476 let partial_scores: Vec<Vec<f64>> = (0..self.n_nodes)
477 .map(|source| self.brandes_bfs_from_source(source))
478 .collect();
479
480 let mut centrality = vec![0.0; self.n_nodes];
482 for partial in partial_scores {
483 for (i, &score) in partial.iter().enumerate() {
484 centrality[i] += score;
485 }
486 }
487
488 if !self.is_directed {
490 for score in &mut centrality {
491 *score /= 2.0;
492 }
493 }
494
495 centrality
496 }
497
498 fn brandes_bfs_from_source(&self, source: NodeId) -> Vec<f64> {
503 let n = self.n_nodes;
504 let mut stack = Vec::new(); let mut paths = vec![0u64; n]; let mut distance = vec![i32::MAX; n]; let mut predecessors: Vec<Vec<NodeId>> = vec![Vec::new(); n]; let mut dependency = vec![0.0; n]; paths[source] = 1;
512 distance[source] = 0;
513 let mut queue = VecDeque::new();
514 queue.push_back(source);
515
516 while let Some(v) = queue.pop_front() {
518 stack.push(v);
519 for &w in self.neighbors(v) {
520 if distance[w] == i32::MAX {
522 distance[w] = distance[v] + 1;
523 queue.push_back(w);
524 }
525 if distance[w] == distance[v] + 1 {
527 paths[w] = paths[w].saturating_add(paths[v]);
528 predecessors[w].push(v);
529 }
530 }
531 }
532
533 while let Some(w) = stack.pop() {
535 for &v in &predecessors[w] {
536 let coeff = (paths[v] as f64 / paths[w] as f64) * (1.0 + dependency[w]);
537 dependency[v] += coeff;
538 }
539 }
540
541 dependency
542 }
543
544 pub fn modularity(&self, communities: &[Vec<NodeId>]) -> f64 {
562 if self.n_nodes == 0 || communities.is_empty() {
563 return 0.0;
564 }
565
566 let mut community_map = vec![None; self.n_nodes];
568 for (comm_id, community) in communities.iter().enumerate() {
569 for &node in community {
570 community_map[node] = Some(comm_id);
571 }
572 }
573
574 let m = self.n_edges as f64;
576
577 if m == 0.0 {
578 return 0.0;
579 }
580
581 let two_m = 2.0 * m;
582
583 let degrees: Vec<f64> = (0..self.n_nodes)
585 .map(|i| self.neighbors(i).len() as f64)
586 .collect();
587
588 let mut q = 0.0;
590
591 for i in 0..self.n_nodes {
592 for j in 0..self.n_nodes {
593 match (community_map[i], community_map[j]) {
595 (Some(ci), Some(cj)) if ci == cj => {
596 let a_ij = if self.neighbors(i).contains(&j) {
598 1.0
599 } else {
600 0.0
601 };
602
603 let expected = (degrees[i] * degrees[j]) / two_m;
605
606 q += a_ij - expected;
607 }
608 _ => {}
609 }
610 }
611 }
612
613 q / two_m
614 }
615
616 pub fn louvain(&self) -> Vec<Vec<NodeId>> {
646 if self.n_nodes == 0 {
647 return Vec::new();
648 }
649
650 let mut node_to_comm: Vec<usize> = (0..self.n_nodes).collect();
652 let mut improved = true;
653 let mut iteration = 0;
654 let max_iterations = 100;
655
656 while improved && iteration < max_iterations {
658 improved = false;
659 iteration += 1;
660
661 for node in 0..self.n_nodes {
662 let current_comm = node_to_comm[node];
663 let neighbors = self.neighbors(node);
664
665 if neighbors.is_empty() {
666 continue;
667 }
668
669 let mut best_comm = current_comm;
671 let mut best_gain = 0.0;
672
673 let mut tried_comms = std::collections::HashSet::new();
675 for &neighbor in neighbors {
676 let neighbor_comm = node_to_comm[neighbor];
677
678 if tried_comms.contains(&neighbor_comm) {
679 continue;
680 }
681 tried_comms.insert(neighbor_comm);
682
683 let gain =
685 self.modularity_gain(node, current_comm, neighbor_comm, &node_to_comm);
686
687 if gain > best_gain {
688 best_gain = gain;
689 best_comm = neighbor_comm;
690 }
691 }
692
693 if best_comm != current_comm && best_gain > 1e-10 {
695 node_to_comm[node] = best_comm;
696 improved = true;
697 }
698 }
699 }
700
701 let mut communities: HashMap<usize, Vec<NodeId>> = HashMap::new();
703
704 for (node, &comm) in node_to_comm.iter().enumerate() {
705 communities.entry(comm).or_default().push(node);
706 }
707
708 communities.into_values().collect()
709 }
710
711 fn modularity_gain(
713 &self,
714 node: NodeId,
715 from_comm: usize,
716 to_comm: usize,
717 node_to_comm: &[usize],
718 ) -> f64 {
719 if from_comm == to_comm {
720 return 0.0;
721 }
722
723 let m = self.n_edges as f64;
724 if m == 0.0 {
725 return 0.0;
726 }
727
728 let two_m = 2.0 * m;
729 let k_i = self.neighbors(node).len() as f64;
730
731 let mut k_i_to = 0.0;
733 for &neighbor in self.neighbors(node) {
734 if node_to_comm[neighbor] == to_comm {
735 k_i_to += 1.0;
736 }
737 }
738
739 let mut k_i_from = 0.0;
741 for &neighbor in self.neighbors(node) {
742 if node_to_comm[neighbor] == from_comm && neighbor != node {
743 k_i_from += 1.0;
744 }
745 }
746
747 let sigma_tot_to: f64 = node_to_comm
749 .iter()
750 .enumerate()
751 .filter(|&(n, &comm)| comm == to_comm && n != node)
752 .map(|(n, _)| self.neighbors(n).len() as f64)
753 .sum();
754
755 let sigma_tot_from: f64 = node_to_comm
757 .iter()
758 .enumerate()
759 .filter(|&(n, &comm)| comm == from_comm && n != node)
760 .map(|(n, _)| self.neighbors(n).len() as f64)
761 .sum();
762
763 (k_i_to - k_i_from) / m - k_i * (sigma_tot_to - sigma_tot_from) / (two_m * m)
765 }
766
767 pub fn closeness_centrality(&self) -> Vec<f64> {
789 if self.n_nodes == 0 {
790 return Vec::new();
791 }
792
793 let mut centrality = vec![0.0; self.n_nodes];
794
795 #[allow(clippy::needless_range_loop)]
796 for v in 0..self.n_nodes {
797 let distances = self.bfs_distances(v);
798
799 let sum: usize = distances.iter().filter(|&&d| d != usize::MAX).sum();
801 let reachable = distances
802 .iter()
803 .filter(|&&d| d != usize::MAX && d > 0)
804 .count();
805
806 if reachable > 0 && sum > 0 {
807 centrality[v] = reachable as f64 / sum as f64;
809 }
810 }
811
812 centrality
813 }
814
815 fn bfs_distances(&self, source: NodeId) -> Vec<usize> {
817 let mut distances = vec![usize::MAX; self.n_nodes];
818 distances[source] = 0;
819
820 let mut queue = VecDeque::new();
821 queue.push_back(source);
822
823 while let Some(v) = queue.pop_front() {
824 for &w in self.neighbors(v) {
825 if distances[w] == usize::MAX {
826 distances[w] = distances[v] + 1;
827 queue.push_back(w);
828 }
829 }
830 }
831
832 distances
833 }
834
835 pub fn eigenvector_centrality(&self, max_iter: usize, tol: f64) -> Result<Vec<f64>, String> {
860 if self.n_nodes == 0 {
861 return Ok(Vec::new());
862 }
863
864 let n = self.n_nodes;
865 let mut x = vec![1.0 / (n as f64).sqrt(); n]; let mut x_new = vec![0.0; n];
867
868 for _ in 0..max_iter {
869 #[allow(clippy::needless_range_loop)]
871 for v in 0..n {
872 x_new[v] = self.neighbors(v).iter().map(|&u| x[u]).sum();
873 }
874
875 let norm: f64 = x_new.iter().map(|&val| val * val).sum::<f64>().sqrt();
877
878 if norm < 1e-10 {
879 return Ok(vec![0.0; n]);
881 }
882
883 for val in &mut x_new {
884 *val /= norm;
885 }
886
887 let diff: f64 = x.iter().zip(&x_new).map(|(a, b)| (a - b).abs()).sum();
889
890 if diff < tol {
891 return Ok(x_new);
892 }
893
894 std::mem::swap(&mut x, &mut x_new);
895 }
896
897 Ok(x)
898 }
899
900 pub fn katz_centrality(
925 &self,
926 alpha: f64,
927 max_iter: usize,
928 tol: f64,
929 ) -> Result<Vec<f64>, String> {
930 if self.n_nodes == 0 {
931 return Ok(Vec::new());
932 }
933
934 if alpha <= 0.0 || alpha >= 1.0 {
935 return Err("Alpha must be in (0, 1)".to_string());
936 }
937
938 let n = self.n_nodes;
939 let mut x = vec![1.0; n]; let mut x_new = vec![0.0; n];
941
942 for _ in 0..max_iter {
943 #[allow(clippy::needless_range_loop)]
946 for v in 0..n {
947 let incoming = self.incoming_neighbors(v);
948 let neighbors_sum: f64 = incoming.iter().map(|&u| x[u]).sum();
949 x_new[v] = 1.0 + alpha * neighbors_sum;
950 }
951
952 let diff: f64 = x.iter().zip(&x_new).map(|(a, b)| (a - b).abs()).sum();
954
955 if diff < tol {
956 return Ok(x_new);
957 }
958
959 std::mem::swap(&mut x, &mut x_new);
960 }
961
962 Ok(x)
963 }
964
965 pub fn harmonic_centrality(&self) -> Vec<f64> {
988 if self.n_nodes == 0 {
989 return Vec::new();
990 }
991
992 let mut centrality = vec![0.0; self.n_nodes];
993
994 #[allow(clippy::needless_range_loop)]
995 for v in 0..self.n_nodes {
996 let distances = self.bfs_distances(v);
997
998 for &dist in &distances {
1000 if dist > 0 && dist != usize::MAX {
1001 centrality[v] += 1.0 / dist as f64;
1002 }
1003 }
1004 }
1005
1006 centrality
1007 }
1008
1009 pub fn density(&self) -> f64 {
1027 if self.n_nodes <= 1 {
1028 return 0.0;
1029 }
1030
1031 let n = self.n_nodes as f64;
1032 let m = self.n_edges as f64;
1033 let possible = n * (n - 1.0);
1034
1035 if self.is_directed {
1036 m / possible
1037 } else {
1038 (2.0 * m) / possible
1039 }
1040 }
1041
1042 pub fn diameter(&self) -> Option<usize> {
1061 if self.n_nodes == 0 {
1062 return None;
1063 }
1064
1065 let mut max_dist = 0;
1066
1067 for v in 0..self.n_nodes {
1068 let distances = self.bfs_distances(v);
1069
1070 for &dist in &distances {
1071 if dist == usize::MAX {
1072 return None;
1074 }
1075 if dist > max_dist {
1076 max_dist = dist;
1077 }
1078 }
1079 }
1080
1081 Some(max_dist)
1082 }
1083
1084 #[allow(clippy::cast_lossless)]
1104 pub fn clustering_coefficient(&self) -> f64 {
1105 if self.n_nodes == 0 {
1106 return 0.0;
1107 }
1108
1109 let mut triangles = 0;
1110 let mut triads = 0;
1111
1112 for v in 0..self.n_nodes {
1113 let neighbors = self.neighbors(v);
1114 let deg = neighbors.len();
1115
1116 if deg < 2 {
1117 continue;
1118 }
1119
1120 triads += deg * (deg - 1) / 2;
1122
1123 for i in 0..neighbors.len() {
1125 for j in (i + 1)..neighbors.len() {
1126 let u = neighbors[i];
1127 let w = neighbors[j];
1128
1129 if self.neighbors(u).contains(&w) {
1131 triangles += 1;
1132 }
1133 }
1134 }
1135 }
1136
1137 if triads == 0 {
1138 return 0.0;
1139 }
1140
1141 (triangles as f64) / (triads as f64)
1144 }
1145
1146 pub fn assortativity(&self) -> f64 {
1167 if self.n_edges == 0 {
1168 return 0.0;
1169 }
1170
1171 let degrees: Vec<f64> = (0..self.n_nodes)
1173 .map(|i| self.neighbors(i).len() as f64)
1174 .collect();
1175
1176 let m = self.n_edges as f64;
1177 let mut sum_jk = 0.0;
1178 let mut sum_j = 0.0;
1179 let mut sum_k = 0.0;
1180 let mut sum_j_sq = 0.0;
1181 let mut sum_k_sq = 0.0;
1182
1183 for v in 0..self.n_nodes {
1185 let j = degrees[v];
1186 for &u in self.neighbors(v) {
1187 let k = degrees[u];
1188 sum_jk += j * k;
1189 sum_j += j;
1190 sum_k += k;
1191 sum_j_sq += j * j;
1192 sum_k_sq += k * k;
1193 }
1194 }
1195
1196 let normalization = if self.is_directed { m } else { 2.0 * m };
1198
1199 sum_jk /= normalization;
1200 sum_j /= normalization;
1201 sum_k /= normalization;
1202 sum_j_sq /= normalization;
1203 sum_k_sq /= normalization;
1204
1205 let numerator = sum_jk - sum_j * sum_k;
1206 let denominator = ((sum_j_sq - sum_j * sum_j) * (sum_k_sq - sum_k * sum_k)).sqrt();
1207
1208 if denominator < 1e-10 {
1209 return 0.0;
1210 }
1211
1212 numerator / denominator
1213 }
1214
1215 pub fn shortest_path(&self, source: NodeId, target: NodeId) -> Option<Vec<NodeId>> {
1253 if source >= self.n_nodes || target >= self.n_nodes {
1255 return None;
1256 }
1257
1258 if source == target {
1260 return Some(vec![source]);
1261 }
1262
1263 let mut visited = vec![false; self.n_nodes];
1265 let mut predecessor = vec![None; self.n_nodes];
1266 let mut queue = VecDeque::new();
1267
1268 visited[source] = true;
1269 queue.push_back(source);
1270
1271 while let Some(v) = queue.pop_front() {
1272 if v == target {
1274 break;
1275 }
1276
1277 for &w in self.neighbors(v) {
1278 if !visited[w] {
1279 visited[w] = true;
1280 predecessor[w] = Some(v);
1281 queue.push_back(w);
1282 }
1283 }
1284 }
1285
1286 if !visited[target] {
1288 return None; }
1290
1291 let mut path = Vec::new();
1292 let mut current = Some(target);
1293
1294 while let Some(node) = current {
1295 path.push(node);
1296 current = predecessor[node];
1297 }
1298
1299 path.reverse();
1300 Some(path)
1301 }
1302
1303 pub fn dijkstra(&self, source: NodeId, target: NodeId) -> Option<(Vec<NodeId>, f64)> {
1335 use std::cmp::Ordering;
1336 use std::collections::BinaryHeap;
1337
1338 if source >= self.n_nodes || target >= self.n_nodes {
1340 return None;
1341 }
1342
1343 if source == target {
1345 return Some((vec![source], 0.0));
1346 }
1347
1348 #[derive(Copy, Clone, PartialEq)]
1351 struct State {
1352 cost: f64,
1353 node: NodeId,
1354 }
1355
1356 impl Eq for State {}
1357
1358 impl Ord for State {
1359 fn cmp(&self, other: &Self) -> Ordering {
1360 other
1362 .cost
1363 .partial_cmp(&self.cost)
1364 .unwrap_or(Ordering::Equal)
1365 }
1366 }
1367
1368 impl PartialOrd for State {
1369 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1370 Some(self.cmp(other))
1371 }
1372 }
1373
1374 let mut distances = vec![f64::INFINITY; self.n_nodes];
1375 let mut predecessor = vec![None; self.n_nodes];
1376 let mut heap = BinaryHeap::new();
1377
1378 distances[source] = 0.0;
1379 heap.push(State {
1380 cost: 0.0,
1381 node: source,
1382 });
1383
1384 while let Some(State { cost, node }) = heap.pop() {
1385 if node == target {
1387 break;
1388 }
1389
1390 if cost > distances[node] {
1392 continue;
1393 }
1394
1395 let start = self.row_ptr[node];
1397 let end = self.row_ptr[node + 1];
1398
1399 for i in start..end {
1400 let neighbor = self.col_indices[i];
1401 let edge_weight = if self.edge_weights.is_empty() {
1402 1.0
1403 } else {
1404 self.edge_weights[i]
1405 };
1406
1407 assert!(
1409 edge_weight >= 0.0,
1410 "Dijkstra's algorithm requires non-negative edge weights. \
1411 Found negative weight {edge_weight} on edge ({node}, {neighbor})"
1412 );
1413
1414 let next_cost = cost + edge_weight;
1415
1416 if next_cost < distances[neighbor] {
1418 distances[neighbor] = next_cost;
1419 predecessor[neighbor] = Some(node);
1420 heap.push(State {
1421 cost: next_cost,
1422 node: neighbor,
1423 });
1424 }
1425 }
1426 }
1427
1428 if distances[target].is_infinite() {
1430 return None;
1431 }
1432
1433 let mut path = Vec::new();
1435 let mut current = Some(target);
1436
1437 while let Some(node) = current {
1438 path.push(node);
1439 current = predecessor[node];
1440 }
1441
1442 path.reverse();
1443 Some((path, distances[target]))
1444 }
1445
1446 pub fn all_pairs_shortest_paths(&self) -> Vec<Vec<Option<usize>>> {
1475 let n = self.n_nodes;
1476 let mut distances = vec![vec![None; n]; n];
1477
1478 for (source, row) in distances.iter_mut().enumerate().take(n) {
1480 let dist = self.bfs_distances(source);
1481
1482 for (target, cell) in row.iter_mut().enumerate().take(n) {
1483 if dist[target] != usize::MAX {
1484 *cell = Some(dist[target]);
1485 }
1486 }
1487 }
1488
1489 distances
1490 }
1491
1492 pub fn a_star<F>(&self, source: NodeId, target: NodeId, heuristic: F) -> Option<Vec<NodeId>>
1529 where
1530 F: Fn(NodeId) -> f64,
1531 {
1532 use std::cmp::Ordering;
1533 use std::collections::BinaryHeap;
1534
1535 if source >= self.n_nodes || target >= self.n_nodes {
1537 return None;
1538 }
1539
1540 if source == target {
1542 return Some(vec![source]);
1543 }
1544
1545 #[derive(Copy, Clone, PartialEq)]
1547 struct State {
1548 f_score: f64, g_score: f64, node: NodeId,
1551 }
1552
1553 impl Eq for State {}
1554
1555 impl Ord for State {
1556 fn cmp(&self, other: &Self) -> Ordering {
1557 other
1559 .f_score
1560 .partial_cmp(&self.f_score)
1561 .unwrap_or(Ordering::Equal)
1562 }
1563 }
1564
1565 impl PartialOrd for State {
1566 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1567 Some(self.cmp(other))
1568 }
1569 }
1570
1571 let mut g_scores = vec![f64::INFINITY; self.n_nodes];
1572 let mut predecessor = vec![None; self.n_nodes];
1573 let mut heap = BinaryHeap::new();
1574
1575 g_scores[source] = 0.0;
1576 heap.push(State {
1577 f_score: heuristic(source),
1578 g_score: 0.0,
1579 node: source,
1580 });
1581
1582 while let Some(State {
1583 f_score: _,
1584 g_score,
1585 node,
1586 }) = heap.pop()
1587 {
1588 if node == target {
1590 break;
1591 }
1592
1593 if g_score > g_scores[node] {
1595 continue;
1596 }
1597
1598 let start = self.row_ptr[node];
1600 let end = self.row_ptr[node + 1];
1601
1602 for i in start..end {
1603 let neighbor = self.col_indices[i];
1604 let edge_weight = if self.edge_weights.is_empty() {
1605 1.0
1606 } else {
1607 self.edge_weights[i]
1608 };
1609
1610 let tentative_g = g_score + edge_weight;
1611
1612 if tentative_g < g_scores[neighbor] {
1614 g_scores[neighbor] = tentative_g;
1615 predecessor[neighbor] = Some(node);
1616
1617 let f = tentative_g + heuristic(neighbor);
1618 heap.push(State {
1619 f_score: f,
1620 g_score: tentative_g,
1621 node: neighbor,
1622 });
1623 }
1624 }
1625 }
1626
1627 if g_scores[target].is_infinite() {
1629 return None;
1630 }
1631
1632 let mut path = Vec::new();
1634 let mut current = Some(target);
1635
1636 while let Some(node) = current {
1637 path.push(node);
1638 current = predecessor[node];
1639 }
1640
1641 path.reverse();
1642 Some(path)
1643 }
1644
1645 pub fn dfs(&self, source: NodeId) -> Option<Vec<NodeId>> {
1669 if source >= self.n_nodes {
1671 return None;
1672 }
1673
1674 let mut visited = vec![false; self.n_nodes];
1675 let mut stack = Vec::new();
1676 let mut order = Vec::new();
1677
1678 stack.push(source);
1680
1681 while let Some(node) = stack.pop() {
1682 if visited[node] {
1683 continue;
1684 }
1685
1686 visited[node] = true;
1687 order.push(node);
1688
1689 let neighbors = self.neighbors(node);
1691 for &neighbor in neighbors.iter().rev() {
1692 if !visited[neighbor] {
1693 stack.push(neighbor);
1694 }
1695 }
1696 }
1697
1698 Some(order)
1699 }
1700
1701 pub fn connected_components(&self) -> Vec<usize> {
1726 let n = self.n_nodes;
1727 if n == 0 {
1728 return Vec::new();
1729 }
1730
1731 let mut parent: Vec<usize> = (0..n).collect();
1733 let mut rank = vec![0; n];
1734
1735 fn find(parent: &mut [usize], mut x: usize) -> usize {
1737 while parent[x] != x {
1738 let next = parent[x];
1739 parent[x] = parent[next]; x = next;
1741 }
1742 x
1743 }
1744
1745 fn union(parent: &mut [usize], rank: &mut [usize], x: usize, y: usize) {
1747 let root_x = find(parent, x);
1748 let root_y = find(parent, y);
1749
1750 if root_x == root_y {
1751 return;
1752 }
1753
1754 use std::cmp::Ordering;
1756 match rank[root_x].cmp(&rank[root_y]) {
1757 Ordering::Less => parent[root_x] = root_y,
1758 Ordering::Greater => parent[root_y] = root_x,
1759 Ordering::Equal => {
1760 parent[root_y] = root_x;
1761 rank[root_x] += 1;
1762 }
1763 }
1764 }
1765
1766 for node in 0..n {
1768 for &neighbor in self.neighbors(node) {
1769 union(&mut parent, &mut rank, node, neighbor);
1770 }
1771 }
1772
1773 let mut component_map = HashMap::new();
1775 let mut next_component_id = 0;
1776 let mut result = vec![0; n];
1777
1778 for (node, component) in result.iter_mut().enumerate().take(n) {
1779 let root = find(&mut parent, node);
1780 let component_id = *component_map.entry(root).or_insert_with(|| {
1781 let id = next_component_id;
1782 next_component_id += 1;
1783 id
1784 });
1785 *component = component_id;
1786 }
1787
1788 result
1789 }
1790
1791 pub fn strongly_connected_components(&self) -> Vec<usize> {
1817 let n = self.n_nodes;
1818 if n == 0 {
1819 return Vec::new();
1820 }
1821
1822 struct TarjanState {
1824 disc: Vec<Option<usize>>,
1825 low: Vec<usize>,
1826 on_stack: Vec<bool>,
1827 stack: Vec<usize>,
1828 time: usize,
1829 scc_id: Vec<usize>,
1830 scc_counter: usize,
1831 }
1832
1833 impl TarjanState {
1834 fn new(n: usize) -> Self {
1835 Self {
1836 disc: vec![None; n],
1837 low: vec![0; n],
1838 on_stack: vec![false; n],
1839 stack: Vec::new(),
1840 time: 0,
1841 scc_id: vec![0; n],
1842 scc_counter: 0,
1843 }
1844 }
1845
1846 fn dfs(&mut self, v: usize, graph: &Graph) {
1847 self.disc[v] = Some(self.time);
1849 self.low[v] = self.time;
1850 self.time += 1;
1851 self.stack.push(v);
1852 self.on_stack[v] = true;
1853
1854 for &w in graph.neighbors(v) {
1856 if self.disc[w].is_none() {
1857 self.dfs(w, graph);
1859 self.low[v] = self.low[v].min(self.low[w]);
1860 } else if self.on_stack[w] {
1861 self.low[v] =
1863 self.low[v].min(self.disc[w].expect("disc[w] should be Some"));
1864 }
1865 }
1866
1867 if let Some(disc_v) = self.disc[v] {
1869 if self.low[v] == disc_v {
1870 loop {
1872 let w = self.stack.pop().expect("stack should not be empty");
1873 self.on_stack[w] = false;
1874 self.scc_id[w] = self.scc_counter;
1875 if w == v {
1876 break;
1877 }
1878 }
1879 self.scc_counter += 1;
1880 }
1881 }
1882 }
1883 }
1884
1885 let mut state = TarjanState::new(n);
1886
1887 for v in 0..n {
1889 if state.disc[v].is_none() {
1890 state.dfs(v, self);
1891 }
1892 }
1893
1894 state.scc_id
1895 }
1896
1897 pub fn topological_sort(&self) -> Option<Vec<NodeId>> {
1923 let n = self.n_nodes;
1924 if n == 0 {
1925 return Some(Vec::new());
1926 }
1927
1928 let mut visited = vec![false; n];
1930 let mut in_stack = vec![false; n]; let mut order = Vec::new();
1932
1933 fn dfs(
1934 v: usize,
1935 graph: &Graph,
1936 visited: &mut [bool],
1937 in_stack: &mut [bool],
1938 order: &mut Vec<usize>,
1939 ) -> bool {
1940 if in_stack[v] {
1941 return false;
1943 }
1944 if visited[v] {
1945 return true;
1947 }
1948
1949 visited[v] = true;
1950 in_stack[v] = true;
1951
1952 for &neighbor in graph.neighbors(v) {
1954 if !dfs(neighbor, graph, visited, in_stack, order) {
1955 return false; }
1957 }
1958
1959 in_stack[v] = false;
1960 order.push(v); true
1963 }
1964
1965 for v in 0..n {
1967 if !visited[v] && !dfs(v, self, &mut visited, &mut in_stack, &mut order) {
1968 return None; }
1970 }
1971
1972 order.reverse();
1974 Some(order)
1975 }
1976
1977 pub fn common_neighbors(&self, u: NodeId, v: NodeId) -> Option<usize> {
2004 if u >= self.n_nodes || v >= self.n_nodes {
2006 return None;
2007 }
2008
2009 let neighbors_u = self.neighbors(u);
2010 let neighbors_v = self.neighbors(v);
2011
2012 let mut count = 0;
2014 let mut i = 0;
2015 let mut j = 0;
2016
2017 while i < neighbors_u.len() && j < neighbors_v.len() {
2018 use std::cmp::Ordering;
2019 match neighbors_u[i].cmp(&neighbors_v[j]) {
2020 Ordering::Equal => {
2021 count += 1;
2022 i += 1;
2023 j += 1;
2024 }
2025 Ordering::Less => i += 1,
2026 Ordering::Greater => j += 1,
2027 }
2028 }
2029
2030 Some(count)
2031 }
2032
2033 pub fn adamic_adar_index(&self, u: NodeId, v: NodeId) -> Option<f64> {
2063 if u >= self.n_nodes || v >= self.n_nodes {
2065 return None;
2066 }
2067
2068 let neighbors_u = self.neighbors(u);
2069 let neighbors_v = self.neighbors(v);
2070
2071 let mut score = 0.0;
2073 let mut i = 0;
2074 let mut j = 0;
2075
2076 while i < neighbors_u.len() && j < neighbors_v.len() {
2077 use std::cmp::Ordering;
2078 match neighbors_u[i].cmp(&neighbors_v[j]) {
2079 Ordering::Equal => {
2080 let common_neighbor = neighbors_u[i];
2081 let degree = self.neighbors(common_neighbor).len();
2082
2083 if degree > 1 {
2085 score += 1.0 / (degree as f64).ln();
2086 }
2087
2088 i += 1;
2089 j += 1;
2090 }
2091 Ordering::Less => i += 1,
2092 Ordering::Greater => j += 1,
2093 }
2094 }
2095
2096 Some(score)
2097 }
2098
2099 pub fn label_propagation(&self, max_iter: usize, seed: Option<u64>) -> Vec<usize> {
2129 let n = self.n_nodes;
2130 if n == 0 {
2131 return Vec::new();
2132 }
2133
2134 let mut labels: Vec<usize> = (0..n).collect();
2136
2137 let mut node_order: Vec<usize> = (0..n).collect();
2139 if let Some(s) = seed {
2140 for i in 0..n {
2142 let j = ((s.wrapping_mul(i as u64 + 1)) % (n as u64)) as usize;
2143 node_order.swap(i, j);
2144 }
2145 }
2146
2147 for _ in 0..max_iter {
2148 let mut changed = false;
2149
2150 for &node in &node_order {
2152 let neighbors = self.neighbors(node);
2153 if neighbors.is_empty() {
2154 continue;
2155 }
2156
2157 let mut label_counts = HashMap::new();
2159 for &neighbor in neighbors {
2160 *label_counts.entry(labels[neighbor]).or_insert(0) += 1;
2161 }
2162
2163 let most_common_label = label_counts
2165 .iter()
2166 .max_by_key(|(label, count)| (*count, std::cmp::Reverse(*label)))
2167 .map(|(label, _)| *label)
2168 .expect("label_counts should not be empty");
2169
2170 if labels[node] != most_common_label {
2171 labels[node] = most_common_label;
2172 changed = true;
2173 }
2174 }
2175
2176 if !changed {
2178 break;
2179 }
2180 }
2181
2182 labels
2183 }
2184}
2185
2186fn kahan_diff(a: &[f64], b: &[f64]) -> f64 {
2191 let mut sum = 0.0;
2192 let mut c = 0.0;
2193 for i in 0..a.len() {
2194 let y = (a[i] - b[i]).abs() - c;
2195 let t = sum + y;
2196 c = (t - sum) - y;
2197 sum = t;
2198 }
2199 sum
2200}
2201
2202#[cfg(test)]
2203mod tests {
2204 use super::*;
2205
2206 #[test]
2207 fn test_empty_graph() {
2208 let g = Graph::new(false);
2209 assert_eq!(g.num_nodes(), 0);
2210 assert_eq!(g.num_edges(), 0);
2211 assert!(!g.is_directed());
2212 }
2213
2214 #[test]
2215 fn test_directed_graph() {
2216 let g = Graph::new(true);
2217 assert!(g.is_directed());
2218 }
2219
2220 #[test]
2221 fn test_from_edges_empty() {
2222 let g = Graph::from_edges(&[], false);
2223 assert_eq!(g.num_nodes(), 0);
2224 assert_eq!(g.num_edges(), 0);
2225 }
2226
2227 #[test]
2228 fn test_from_edges_undirected() {
2229 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], false);
2230 assert_eq!(g.num_nodes(), 3);
2231 assert_eq!(g.num_edges(), 3);
2232
2233 assert_eq!(g.neighbors(0), &[1, 2]);
2235 assert_eq!(g.neighbors(1), &[0, 2]);
2236 assert_eq!(g.neighbors(2), &[0, 1]);
2237 }
2238
2239 #[test]
2240 fn test_from_edges_directed() {
2241 let g = Graph::from_edges(&[(0, 1), (1, 2)], true);
2242 assert_eq!(g.num_nodes(), 3);
2243 assert_eq!(g.num_edges(), 2);
2244
2245 assert_eq!(g.neighbors(0), &[1]);
2247 assert_eq!(g.neighbors(1), &[2]);
2248 assert!(g.neighbors(2).is_empty()); }
2250
2251 #[test]
2252 fn test_from_edges_with_gaps() {
2253 let g = Graph::from_edges(&[(0, 5), (5, 10)], false);
2255 assert_eq!(g.num_nodes(), 11); assert_eq!(g.num_edges(), 2);
2257
2258 assert_eq!(g.neighbors(0), &[5]);
2259 assert_eq!(g.neighbors(5), &[0, 10]);
2260 assert!(g.neighbors(1).is_empty()); }
2262
2263 #[test]
2264 fn test_from_edges_duplicate_edges() {
2265 let g = Graph::from_edges(&[(0, 1), (0, 1), (1, 0)], false);
2267 assert_eq!(g.num_nodes(), 2);
2268
2269 assert_eq!(g.neighbors(0), &[1]);
2271 assert_eq!(g.neighbors(1), &[0]);
2272 }
2273
2274 #[test]
2275 fn test_from_edges_self_loop() {
2276 let g = Graph::from_edges(&[(0, 0), (0, 1)], false);
2277 assert_eq!(g.num_nodes(), 2);
2278
2279 assert_eq!(g.neighbors(0), &[0, 1]);
2281 }
2282
2283 #[test]
2284 fn test_neighbors_invalid_node() {
2285 let g = Graph::from_edges(&[(0, 1)], false);
2286 assert!(g.neighbors(999).is_empty()); }
2288
2289 #[test]
2290 fn test_degree_centrality_empty() {
2291 let g = Graph::new(false);
2292 let dc = g.degree_centrality();
2293 assert_eq!(dc.len(), 0);
2294 }
2295
2296 #[test]
2297 fn test_degree_centrality_single_node() {
2298 let g = Graph::from_edges(&[(0, 0)], false);
2299 let dc = g.degree_centrality();
2300 assert_eq!(dc[&0], 0.0); }
2302
2303 #[test]
2304 fn test_degree_centrality_star_graph() {
2305 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], false);
2307 let dc = g.degree_centrality();
2308
2309 assert_eq!(dc[&0], 1.0); assert!((dc[&1] - 1.0 / 3.0).abs() < 1e-6); assert!((dc[&2] - 1.0 / 3.0).abs() < 1e-6);
2312 assert!((dc[&3] - 1.0 / 3.0).abs() < 1e-6);
2313 }
2314
2315 #[test]
2316 fn test_degree_centrality_complete_graph() {
2317 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], false);
2319 let dc = g.degree_centrality();
2320
2321 for v in 0..4 {
2323 assert_eq!(dc[&v], 1.0);
2324 }
2325 }
2326
2327 #[test]
2328 fn test_degree_centrality_path_graph() {
2329 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false);
2331 let dc = g.degree_centrality();
2332
2333 assert!((dc[&0] - 1.0 / 3.0).abs() < 1e-6);
2335 assert!((dc[&1] - 2.0 / 3.0).abs() < 1e-6);
2336 assert!((dc[&2] - 2.0 / 3.0).abs() < 1e-6);
2337 assert!((dc[&3] - 1.0 / 3.0).abs() < 1e-6);
2338 }
2339
2340 #[test]
2341 fn test_degree_centrality_directed() {
2342 let g = Graph::from_edges(&[(0, 1), (0, 2), (1, 2)], true);
2344 let dc = g.degree_centrality();
2345
2346 assert!((dc[&0] - 2.0 / 2.0).abs() < 1e-6); assert!((dc[&1] - 1.0 / 2.0).abs() < 1e-6); assert_eq!(dc[&2], 0.0); }
2350
2351 #[test]
2354 fn test_pagerank_empty() {
2355 let g = Graph::new(true);
2356 let pr = g
2357 .pagerank(0.85, 100, 1e-6)
2358 .expect("pagerank should succeed for empty graph");
2359 assert!(pr.is_empty());
2360 }
2361
2362 #[test]
2363 fn test_pagerank_single_node() {
2364 let g = Graph::from_edges(&[(0, 0)], true);
2365 let pr = g
2366 .pagerank(0.85, 100, 1e-6)
2367 .expect("pagerank should succeed for single node graph");
2368 assert_eq!(pr.len(), 1);
2369 assert!((pr[0] - 1.0).abs() < 1e-6); }
2371
2372 #[test]
2373 fn test_pagerank_sum_equals_one() {
2374 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], true);
2376 let pr = g
2377 .pagerank(0.85, 100, 1e-6)
2378 .expect("pagerank should converge for cycle graph");
2379 let sum: f64 = pr.iter().sum();
2380 assert!((sum - 1.0).abs() < 1e-10); }
2382
2383 #[test]
2384 fn test_pagerank_cycle_graph() {
2385 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], true);
2388 let pr = g
2389 .pagerank(0.85, 100, 1e-6)
2390 .expect("pagerank should converge for symmetric cycle");
2391
2392 assert_eq!(pr.len(), 3);
2393 assert!((pr[0] - 1.0 / 3.0).abs() < 1e-6);
2395 assert!((pr[1] - 1.0 / 3.0).abs() < 1e-6);
2396 assert!((pr[2] - 1.0 / 3.0).abs() < 1e-6);
2397 }
2398
2399 #[test]
2400 fn test_pagerank_star_graph_directed() {
2401 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], true);
2404 let pr = g
2405 .pagerank(0.85, 100, 1e-6)
2406 .expect("pagerank should converge for directed star graph");
2407
2408 assert_eq!(pr.len(), 4);
2409 assert!(pr[0] < pr[1]); assert!((pr[1] - pr[2]).abs() < 1e-6); assert!((pr[2] - pr[3]).abs() < 1e-6);
2414 }
2415
2416 #[test]
2417 fn test_pagerank_convergence() {
2418 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (1, 0)], true);
2420 let pr = g
2421 .pagerank(0.85, 100, 1e-6)
2422 .expect("pagerank should converge within max iterations");
2423
2424 assert_eq!(pr.len(), 3);
2426 assert!((pr.iter().sum::<f64>() - 1.0).abs() < 1e-10);
2427 }
2428
2429 #[test]
2430 fn test_pagerank_no_outgoing_edges() {
2431 let g = Graph::from_edges(&[(0, 1), (1, 2)], true);
2433 let pr = g
2434 .pagerank(0.85, 100, 1e-6)
2435 .expect("pagerank should handle dangling nodes correctly");
2436
2437 assert_eq!(pr.len(), 3);
2439 assert!(pr[2] > 0.0);
2440 assert!((pr.iter().sum::<f64>() - 1.0).abs() < 1e-10);
2441 }
2442
2443 #[test]
2444 fn test_pagerank_undirected() {
2445 let g = Graph::from_edges(&[(0, 1), (1, 2)], false);
2447 let pr = g
2448 .pagerank(0.85, 100, 1e-6)
2449 .expect("pagerank should converge for undirected path graph");
2450
2451 assert_eq!(pr.len(), 3);
2452 assert!(pr[1] > pr[0]);
2454 assert!(pr[1] > pr[2]);
2455 assert!((pr[0] - pr[2]).abs() < 1e-6); }
2457
2458 #[test]
2459 fn test_kahan_diff() {
2460 let a = vec![1.0, 2.0, 3.0];
2461 let b = vec![1.1, 2.1, 2.9];
2462 let diff = kahan_diff(&a, &b);
2463 assert!((diff - 0.3).abs() < 1e-10);
2464 }
2465
2466 #[test]
2469 fn test_betweenness_centrality_empty() {
2470 let g = Graph::new(false);
2471 let bc = g.betweenness_centrality();
2472 assert!(bc.is_empty());
2473 }
2474
2475 #[test]
2476 fn test_betweenness_centrality_single_node() {
2477 let g = Graph::from_edges(&[(0, 0)], false);
2478 let bc = g.betweenness_centrality();
2479 assert_eq!(bc.len(), 1);
2480 assert_eq!(bc[0], 0.0); }
2482
2483 #[test]
2484 fn test_betweenness_centrality_path_graph() {
2485 let g = Graph::from_edges(&[(0, 1), (1, 2)], false);
2488 let bc = g.betweenness_centrality();
2489
2490 assert_eq!(bc.len(), 3);
2491 assert!(bc[1] > bc[0]);
2493 assert!(bc[1] > bc[2]);
2494 assert!((bc[0] - bc[2]).abs() < 1e-6);
2496 }
2497
2498 #[test]
2499 fn test_betweenness_centrality_star_graph() {
2500 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], false);
2503 let bc = g.betweenness_centrality();
2504
2505 assert_eq!(bc.len(), 4);
2506 assert!(bc[0] > bc[1]);
2508 assert!(bc[0] > bc[2]);
2509 assert!(bc[0] > bc[3]);
2510 assert!((bc[1] - bc[2]).abs() < 1e-6);
2512 assert!((bc[2] - bc[3]).abs() < 1e-6);
2513 }
2514
2515 #[test]
2516 fn test_betweenness_centrality_cycle_graph() {
2517 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false);
2520 let bc = g.betweenness_centrality();
2521
2522 assert_eq!(bc.len(), 4);
2523 for i in 0..4 {
2525 for j in i + 1..4 {
2526 assert!((bc[i] - bc[j]).abs() < 1e-6);
2527 }
2528 }
2529 }
2530
2531 #[test]
2532 fn test_betweenness_centrality_complete_graph() {
2533 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], false);
2536 let bc = g.betweenness_centrality();
2537
2538 assert_eq!(bc.len(), 4);
2539 for i in 0..4 {
2541 for j in i + 1..4 {
2542 assert!((bc[i] - bc[j]).abs() < 1e-6);
2543 }
2544 }
2545 }
2546
2547 #[test]
2548 fn test_betweenness_centrality_bridge_graph() {
2549 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4)], false);
2552 let bc = g.betweenness_centrality();
2553
2554 assert_eq!(bc.len(), 5);
2555 assert!(bc[2] > bc[0]);
2557 assert!(bc[2] > bc[1]);
2558 assert!(bc[2] > bc[3]);
2559 assert!(bc[2] > bc[4]);
2560 assert!(bc[1] > bc[0]);
2562 assert!(bc[3] > bc[4]);
2563 }
2564
2565 #[test]
2566 fn test_betweenness_centrality_directed() {
2567 let g = Graph::from_edges(&[(0, 1), (1, 2)], true);
2569 let bc = g.betweenness_centrality();
2570
2571 assert_eq!(bc.len(), 3);
2572 assert!(bc.iter().any(|&x| x > 0.0));
2576 }
2577
2578 #[test]
2579 fn test_betweenness_centrality_disconnected() {
2580 let g = Graph::from_edges(&[(0, 1), (2, 3)], false);
2582 let bc = g.betweenness_centrality();
2583
2584 assert_eq!(bc.len(), 4);
2585 assert!((bc[0] - bc[1]).abs() < 1e-6);
2587 assert!((bc[2] - bc[3]).abs() < 1e-6);
2588 }
2589
2590 #[test]
2593 fn test_modularity_empty_graph() {
2594 let g = Graph::new(false);
2595 let communities = vec![];
2596 let modularity = g.modularity(&communities);
2597 assert_eq!(modularity, 0.0);
2598 }
2599
2600 #[test]
2601 fn test_modularity_single_community() {
2602 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], false);
2604 let communities = vec![vec![0, 1, 2]];
2605 let modularity = g.modularity(&communities);
2606 assert!((modularity - 0.0).abs() < 1e-6);
2608 }
2609
2610 #[test]
2611 fn test_modularity_two_communities() {
2612 let g = Graph::from_edges(
2614 &[
2615 (0, 1),
2616 (1, 2),
2617 (2, 0), (3, 4),
2619 (4, 5),
2620 (5, 3), (2, 3), ],
2623 false,
2624 );
2625
2626 let communities = vec![vec![0, 1, 2], vec![3, 4, 5]];
2627 let modularity = g.modularity(&communities);
2628
2629 assert!(modularity > 0.0);
2631 assert!(modularity < 1.0); }
2633
2634 #[test]
2635 fn test_modularity_perfect_split() {
2636 let g = Graph::from_edges(
2638 &[
2639 (0, 1),
2640 (1, 2),
2641 (2, 0), (3, 4),
2643 (4, 5),
2644 (5, 3), ],
2646 false,
2647 );
2648
2649 let communities = vec![vec![0, 1, 2], vec![3, 4, 5]];
2650 let modularity = g.modularity(&communities);
2651
2652 assert!(modularity > 0.5);
2654 }
2655
2656 #[test]
2657 fn test_louvain_empty_graph() {
2659 let g = Graph::new(false);
2660 let communities = g.louvain();
2661 assert_eq!(communities.len(), 0);
2662 }
2663
2664 #[test]
2665 fn test_louvain_single_node() {
2667 let g = Graph::from_edges(&[(0, 0)], false);
2669 let communities = g.louvain();
2670 assert_eq!(communities.len(), 1);
2671 assert_eq!(communities[0].len(), 1);
2672 }
2673
2674 #[test]
2675 fn test_louvain_two_nodes() {
2677 let g = Graph::from_edges(&[(0, 1)], false);
2678 let communities = g.louvain();
2679
2680 assert_eq!(communities.len(), 1);
2682 assert_eq!(communities[0].len(), 2);
2683 }
2684
2685 #[test]
2686 fn test_louvain_triangle() {
2688 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], false);
2690 let communities = g.louvain();
2691
2692 assert_eq!(communities.len(), 1);
2693 assert_eq!(communities[0].len(), 3);
2694 }
2695
2696 #[test]
2697 fn test_louvain_two_triangles_connected() {
2699 let g = Graph::from_edges(
2701 &[
2702 (0, 1),
2703 (1, 2),
2704 (2, 0), (3, 4),
2706 (4, 5),
2707 (5, 3), (2, 3), ],
2710 false,
2711 );
2712
2713 let communities = g.louvain();
2714
2715 assert_eq!(communities.len(), 2);
2717
2718 let all_nodes: Vec<_> = communities.iter().flat_map(|c| c.iter()).copied().collect();
2720 assert_eq!(all_nodes.len(), 6);
2721 }
2722
2723 #[test]
2724 fn test_louvain_disconnected_components() {
2726 let g = Graph::from_edges(
2728 &[
2729 (0, 1),
2730 (1, 2),
2731 (2, 0), (3, 4),
2733 (4, 5),
2734 (5, 3), ],
2736 false,
2737 );
2738
2739 let communities = g.louvain();
2740
2741 assert!(communities.len() >= 2);
2743
2744 let comm1_nodes: Vec<_> = communities
2746 .iter()
2747 .find(|c| c.contains(&0))
2748 .expect("node 0 should be assigned to a community")
2749 .clone();
2750 let comm2_nodes: Vec<_> = communities
2751 .iter()
2752 .find(|c| c.contains(&3))
2753 .expect("node 3 should be assigned to a community")
2754 .clone();
2755
2756 assert!(comm1_nodes.contains(&0));
2757 assert!(comm1_nodes.contains(&1));
2758 assert!(comm1_nodes.contains(&2));
2759
2760 assert!(comm2_nodes.contains(&3));
2761 assert!(comm2_nodes.contains(&4));
2762 assert!(comm2_nodes.contains(&5));
2763
2764 assert!(!comm1_nodes.contains(&3));
2766 assert!(!comm2_nodes.contains(&0));
2767 }
2768
2769 #[test]
2770 fn test_louvain_karate_club() {
2772 let g = Graph::from_edges(
2775 &[
2776 (0, 1),
2777 (0, 2),
2778 (1, 2), (2, 3), (3, 4),
2781 (3, 5),
2782 (4, 5), ],
2784 false,
2785 );
2786
2787 let communities = g.louvain();
2788
2789 assert!(communities.len() >= 2);
2791
2792 let all_nodes: Vec<_> = communities.iter().flat_map(|c| c.iter()).copied().collect();
2795 assert_eq!(all_nodes.len(), 6);
2796 }
2797
2798 #[test]
2799 fn test_louvain_star_graph() {
2801 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false);
2803
2804 let communities = g.louvain();
2805
2806 assert!(!communities.is_empty());
2809 let all_nodes: Vec<_> = communities.iter().flat_map(|c| c.iter()).copied().collect();
2810 assert_eq!(all_nodes.len(), 5);
2811 }
2812
2813 #[test]
2814 fn test_louvain_complete_graph() {
2816 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], false);
2818
2819 let communities = g.louvain();
2820
2821 assert_eq!(communities.len(), 1);
2823 assert_eq!(communities[0].len(), 4);
2824 }
2825
2826 #[test]
2827 fn test_louvain_modularity_improves() {
2829 let g = Graph::from_edges(
2831 &[
2832 (0, 1),
2833 (1, 2),
2834 (2, 0), (3, 4),
2836 (4, 5),
2837 (5, 3), ],
2839 false,
2840 );
2841
2842 let communities = g.louvain();
2843 let modularity = g.modularity(&communities);
2844
2845 assert!(modularity > 0.3);
2847 }
2848
2849 #[test]
2850 fn test_louvain_all_nodes_assigned() {
2852 let g = Graph::from_edges(
2854 &[
2855 (0, 1),
2856 (1, 2),
2857 (2, 3),
2858 (3, 4),
2859 (4, 0), ],
2861 false,
2862 );
2863
2864 let communities = g.louvain();
2865
2866 let mut assigned_nodes: Vec<NodeId> = Vec::new();
2867 for community in &communities {
2868 assigned_nodes.extend(community);
2869 }
2870
2871 assigned_nodes.sort_unstable();
2873 assert_eq!(assigned_nodes, vec![0, 1, 2, 3, 4]);
2874
2875 let unique_count = assigned_nodes.len();
2877 assigned_nodes.dedup();
2878 assert_eq!(assigned_nodes.len(), unique_count);
2879 }
2880
2881 #[test]
2882 fn test_modularity_bad_partition() {
2883 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], false);
2885
2886 let communities = vec![vec![0], vec![1], vec![2]];
2887 let modularity = g.modularity(&communities);
2888
2889 assert!(modularity < 0.1);
2891 }
2892
2893 #[test]
2896 fn test_closeness_centrality_empty() {
2897 let g = Graph::new(false);
2898 let cc = g.closeness_centrality();
2899 assert!(cc.is_empty());
2900 }
2901
2902 #[test]
2903 fn test_closeness_centrality_star_graph() {
2904 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], false);
2906 let cc = g.closeness_centrality();
2907
2908 assert_eq!(cc.len(), 4);
2909 assert!(cc[0] > cc[1]);
2911 assert!(cc[0] > cc[2]);
2912 assert!(cc[0] > cc[3]);
2913 assert!((cc[1] - cc[2]).abs() < 1e-6);
2915 assert!((cc[2] - cc[3]).abs() < 1e-6);
2916 }
2917
2918 #[test]
2919 fn test_closeness_centrality_path_graph() {
2920 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false);
2923 let cc = g.closeness_centrality();
2924
2925 assert_eq!(cc.len(), 4);
2926 assert!(cc[1] > cc[0]);
2928 assert!(cc[2] > cc[0]);
2929 assert!(cc[1] > cc[3]);
2930 assert!(cc[2] > cc[3]);
2931 }
2932
2933 #[test]
2936 fn test_eigenvector_centrality_empty() {
2937 let g = Graph::new(false);
2938 let ec = g
2939 .eigenvector_centrality(100, 1e-6)
2940 .expect("eigenvector centrality should succeed on empty graph");
2941 assert!(ec.is_empty());
2942 }
2943
2944 #[test]
2945 fn test_eigenvector_centrality_star_graph() {
2946 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], false);
2948 let ec = g
2949 .eigenvector_centrality(100, 1e-6)
2950 .expect("eigenvector centrality should succeed on star graph");
2951
2952 assert_eq!(ec.len(), 4);
2953 assert!(ec[0] > ec[1]);
2955 assert!(ec[0] > ec[2]);
2956 assert!(ec[0] > ec[3]);
2957 }
2958
2959 #[test]
2960 fn test_eigenvector_centrality_path_graph() {
2961 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false);
2963 let ec = g
2964 .eigenvector_centrality(100, 1e-6)
2965 .expect("eigenvector centrality should succeed on path graph");
2966
2967 assert_eq!(ec.len(), 4);
2968 assert!(ec[1] > ec[0]);
2970 assert!(ec[2] > ec[3]);
2971 }
2972
2973 #[test]
2974 fn test_eigenvector_centrality_disconnected() {
2975 let g = Graph::from_edges(&[], false);
2977 let ec = g
2978 .eigenvector_centrality(100, 1e-6)
2979 .expect("eigenvector centrality should succeed on graph with no edges");
2980 assert!(ec.is_empty());
2981 }
2982
2983 #[test]
2986 fn test_katz_centrality_empty() {
2987 let g = Graph::new(true);
2988 let kc = g
2989 .katz_centrality(0.1, 100, 1e-6)
2990 .expect("katz centrality should succeed on empty graph");
2991 assert!(kc.is_empty());
2992 }
2993
2994 #[test]
2995 fn test_katz_centrality_invalid_alpha() {
2996 let g = Graph::from_edges(&[(0, 1), (1, 2)], true);
2997
2998 assert!(g.katz_centrality(0.0, 100, 1e-6).is_err());
3000
3001 assert!(g.katz_centrality(1.0, 100, 1e-6).is_err());
3003
3004 assert!(g.katz_centrality(1.5, 100, 1e-6).is_err());
3006 }
3007
3008 #[test]
3009 fn test_katz_centrality_cycle() {
3010 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], true);
3012 let kc = g
3013 .katz_centrality(0.1, 100, 1e-6)
3014 .expect("katz centrality should succeed on cycle graph");
3015
3016 assert_eq!(kc.len(), 3);
3017 assert!((kc[0] - kc[1]).abs() < 1e-3);
3019 assert!((kc[1] - kc[2]).abs() < 1e-3);
3020 }
3021
3022 #[test]
3023 fn test_katz_centrality_star_directed() {
3024 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], true);
3026 let kc = g
3027 .katz_centrality(0.1, 100, 1e-6)
3028 .expect("katz centrality should succeed on directed star graph");
3029
3030 assert_eq!(kc.len(), 4);
3031 assert!(kc[1] > kc[0]);
3033 assert!(kc[2] > kc[0]);
3034 assert!(kc[3] > kc[0]);
3035 }
3036
3037 #[test]
3040 fn test_harmonic_centrality_empty() {
3041 let g = Graph::new(false);
3042 let hc = g.harmonic_centrality();
3043 assert!(hc.is_empty());
3044 }
3045
3046 #[test]
3047 fn test_harmonic_centrality_star_graph() {
3048 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], false);
3050 let hc = g.harmonic_centrality();
3051
3052 assert_eq!(hc.len(), 4);
3053 assert!(hc[0] > hc[1]);
3055 assert!(hc[0] > hc[2]);
3056 assert!(hc[0] > hc[3]);
3057 }
3058
3059 #[test]
3060 fn test_harmonic_centrality_disconnected() {
3061 let g = Graph::from_edges(&[(0, 1), (2, 3)], false);
3063 let hc = g.harmonic_centrality();
3064
3065 assert_eq!(hc.len(), 4);
3066 assert!((hc[0] - hc[1]).abs() < 1e-6);
3068 assert!((hc[2] - hc[3]).abs() < 1e-6);
3069 }
3070
3071 #[test]
3074 fn test_density_empty() {
3075 let g = Graph::new(false);
3076 assert_eq!(g.density(), 0.0);
3077 }
3078
3079 #[test]
3080 fn test_density_single_node() {
3081 let g = Graph::from_edges(&[(0, 0)], false);
3082 assert_eq!(g.density(), 0.0);
3083 }
3084
3085 #[test]
3086 fn test_density_complete_graph() {
3087 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], false);
3089 assert!((g.density() - 1.0).abs() < 1e-6);
3090 }
3091
3092 #[test]
3093 fn test_density_path_graph() {
3094 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false);
3096
3097 assert!((g.density() - 0.5).abs() < 1e-6);
3099 }
3100
3101 #[test]
3102 fn test_density_directed() {
3103 let g = Graph::from_edges(&[(0, 1), (1, 2)], true);
3105
3106 assert!((g.density() - 1.0 / 3.0).abs() < 1e-6);
3108 }
3109
3110 #[test]
3113 fn test_diameter_empty() {
3114 let g = Graph::new(false);
3115 assert_eq!(g.diameter(), None);
3116 }
3117
3118 #[test]
3119 fn test_diameter_single_node() {
3120 let g = Graph::from_edges(&[(0, 0)], false);
3121 assert_eq!(g.diameter(), Some(0));
3122 }
3123
3124 #[test]
3125 fn test_diameter_path_graph() {
3126 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false);
3128 assert_eq!(g.diameter(), Some(3));
3129 }
3130
3131 #[test]
3132 fn test_diameter_star_graph() {
3133 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], false);
3135 assert_eq!(g.diameter(), Some(2));
3136 }
3137
3138 #[test]
3139 fn test_diameter_disconnected() {
3140 let g = Graph::from_edges(&[(0, 1), (2, 3)], false);
3142 assert_eq!(g.diameter(), None); }
3144
3145 #[test]
3146 fn test_diameter_complete_graph() {
3147 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], false);
3149 assert_eq!(g.diameter(), Some(1));
3150 }
3151
3152 #[test]
3155 fn test_clustering_coefficient_empty() {
3156 let g = Graph::new(false);
3157 assert_eq!(g.clustering_coefficient(), 0.0);
3158 }
3159
3160 #[test]
3161 fn test_clustering_coefficient_triangle() {
3162 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], false);
3164 assert!((g.clustering_coefficient() - 1.0).abs() < 1e-6);
3165 }
3166
3167 #[test]
3168 fn test_clustering_coefficient_star_graph() {
3169 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], false);
3171 assert_eq!(g.clustering_coefficient(), 0.0);
3172 }
3173
3174 #[test]
3175 fn test_clustering_coefficient_partial() {
3176 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (0, 3)], false);
3178
3179 let cc = g.clustering_coefficient();
3184 assert!(cc > 0.0);
3185 assert!(cc < 1.0);
3186 }
3187
3188 #[test]
3191 fn test_assortativity_empty() {
3192 let g = Graph::new(false);
3193 assert_eq!(g.assortativity(), 0.0);
3194 }
3195
3196 #[test]
3197 fn test_assortativity_star_graph() {
3198 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], false);
3201 assert!(g.assortativity() < 0.0);
3202 }
3203
3204 #[test]
3205 fn test_assortativity_complete_graph() {
3206 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], false);
3209 let assort = g.assortativity();
3210
3211 assert_eq!(assort, 0.0);
3214 }
3215
3216 #[test]
3217 fn test_assortativity_path_graph() {
3218 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false);
3222 let assort = g.assortativity();
3223
3224 assert!(assort < 0.0);
3226 }
3227
3228 #[test]
3233 fn test_shortest_path_direct_edge() {
3234 let g = Graph::from_edges(&[(0, 1)], false);
3236 let path = g.shortest_path(0, 1).expect("path should exist");
3237 assert_eq!(path, vec![0, 1]);
3238 assert_eq!(path.len(), 2);
3239 }
3240
3241 #[test]
3242 fn test_shortest_path_same_node() {
3243 let g = Graph::from_edges(&[(0, 1), (1, 2)], false);
3245 let path = g.shortest_path(1, 1).expect("path should exist");
3246 assert_eq!(path, vec![1]);
3247 }
3248
3249 #[test]
3250 fn test_shortest_path_disconnected() {
3251 let g = Graph::from_edges(&[(0, 1), (2, 3)], false);
3253 assert!(g.shortest_path(0, 3).is_none());
3254 assert!(g.shortest_path(1, 2).is_none());
3255 }
3256
3257 #[test]
3258 fn test_shortest_path_invalid_nodes() {
3259 let g = Graph::from_edges(&[(0, 1)], false);
3261 assert!(g.shortest_path(0, 10).is_none());
3262 assert!(g.shortest_path(10, 0).is_none());
3263 }
3264
3265 #[test]
3266 fn test_shortest_path_multiple_paths() {
3267 let g = Graph::from_edges(&[(0, 1), (1, 3), (0, 2), (2, 3)], false);
3272 let path = g.shortest_path(0, 3).expect("path should exist");
3273
3274 assert_eq!(path.len(), 3);
3276 assert_eq!(path[0], 0);
3277 assert_eq!(path[2], 3);
3278 assert!(path[1] == 1 || path[1] == 2); }
3280
3281 #[test]
3282 fn test_shortest_path_linear_chain() {
3283 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4)], false);
3285
3286 let path = g.shortest_path(0, 4).expect("path should exist");
3288 assert_eq!(path, vec![0, 1, 2, 3, 4]);
3289
3290 let path = g.shortest_path(0, 2).expect("path should exist");
3291 assert_eq!(path, vec![0, 1, 2]);
3292
3293 let path = g.shortest_path(1, 3).expect("path should exist");
3294 assert_eq!(path, vec![1, 2, 3]);
3295 }
3296
3297 #[test]
3298 fn test_shortest_path_triangle() {
3299 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], false);
3301
3302 let path = g.shortest_path(0, 1).expect("path should exist");
3304 assert_eq!(path.len(), 2);
3305
3306 let path = g.shortest_path(0, 2).expect("path should exist");
3307 assert_eq!(path.len(), 2);
3308
3309 let path = g.shortest_path(1, 2).expect("path should exist");
3310 assert_eq!(path.len(), 2);
3311 }
3312
3313 #[test]
3314 fn test_shortest_path_directed() {
3315 let g = Graph::from_edges(&[(0, 1), (1, 2)], true);
3317
3318 let path = g.shortest_path(0, 2).expect("forward path should exist");
3320 assert_eq!(path, vec![0, 1, 2]);
3321
3322 assert!(g.shortest_path(2, 0).is_none());
3324 }
3325
3326 #[test]
3327 fn test_shortest_path_cycle() {
3328 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], true);
3330
3331 let path = g.shortest_path(0, 3).expect("path should exist");
3333
3334 assert_eq!(path.len(), 4);
3336 assert_eq!(path, vec![0, 1, 2, 3]);
3337 }
3338
3339 #[test]
3340 fn test_shortest_path_star_graph() {
3341 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false);
3343
3344 let path = g.shortest_path(0, 1).expect("path should exist");
3346 assert_eq!(path.len(), 2);
3347
3348 let path = g.shortest_path(1, 2).expect("path should exist");
3350 assert_eq!(path.len(), 3);
3351 assert_eq!(path[0], 1);
3352 assert_eq!(path[1], 0); assert_eq!(path[2], 2);
3354 }
3355
3356 #[test]
3357 fn test_shortest_path_empty_graph() {
3358 let g = Graph::new(false);
3360 assert!(g.shortest_path(0, 0).is_none());
3361 }
3362
3363 #[test]
3364 fn test_shortest_path_single_node_graph() {
3365 let g = Graph::from_edges(&[(0, 0)], false);
3367 let path = g.shortest_path(0, 0).expect("path should exist");
3368 assert_eq!(path, vec![0]);
3369 }
3370
3371 #[test]
3372 fn test_shortest_path_complete_graph() {
3373 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], false);
3375
3376 for i in 0..4 {
3378 for j in 0..4 {
3379 if i != j {
3380 let path = g.shortest_path(i, j).expect("path should exist");
3381 assert_eq!(path.len(), 2, "Path from {i} to {j} should be direct");
3382 assert_eq!(path[0], i);
3383 assert_eq!(path[1], j);
3384 }
3385 }
3386 }
3387 }
3388
3389 #[test]
3390 fn test_shortest_path_bidirectional() {
3391 let g = Graph::from_edges(&[(0, 1), (1, 2)], false);
3393
3394 let path_forward = g.shortest_path(0, 2).expect("forward path should exist");
3395 let path_backward = g.shortest_path(2, 0).expect("backward path should exist");
3396
3397 assert_eq!(path_forward.len(), path_backward.len());
3398 assert_eq!(path_forward.len(), 3);
3399
3400 let reversed: Vec<_> = path_backward.iter().rev().copied().collect();
3402 assert_eq!(path_forward, reversed);
3403 }
3404
3405 #[test]
3410 fn test_dijkstra_simple_weighted() {
3411 let g = Graph::from_weighted_edges(&[(0, 1, 1.0), (1, 2, 2.0), (0, 2, 5.0)], false);
3413
3414 let (path, dist) = g.dijkstra(0, 2).expect("path should exist");
3415 assert_eq!(dist, 3.0); assert_eq!(path, vec![0, 1, 2]);
3417 }
3418
3419 #[test]
3420 fn test_dijkstra_same_node() {
3421 let g = Graph::from_weighted_edges(&[(0, 1, 1.0)], false);
3422 let (path, dist) = g.dijkstra(0, 0).expect("path should exist");
3423 assert_eq!(path, vec![0]);
3424 assert_eq!(dist, 0.0);
3425 }
3426
3427 #[test]
3428 fn test_dijkstra_disconnected() {
3429 let g = Graph::from_weighted_edges(&[(0, 1, 1.0), (2, 3, 1.0)], false);
3430 assert!(g.dijkstra(0, 3).is_none());
3431 }
3432
3433 #[test]
3434 fn test_dijkstra_unweighted() {
3435 let g = Graph::from_edges(&[(0, 1), (1, 2)], false);
3437 let (path, dist) = g.dijkstra(0, 2).expect("path should exist");
3438 assert_eq!(dist, 2.0);
3439 assert_eq!(path, vec![0, 1, 2]);
3440 }
3441
3442 #[test]
3443 fn test_dijkstra_triangle_weighted() {
3444 let g = Graph::from_weighted_edges(&[(0, 1, 1.0), (1, 2, 1.0), (0, 2, 5.0)], false);
3446
3447 let (path, dist) = g.dijkstra(0, 2).expect("path should exist");
3448 assert_eq!(dist, 2.0); assert_eq!(path, vec![0, 1, 2]);
3450 }
3451
3452 #[test]
3453 fn test_dijkstra_multiple_paths() {
3454 let g = Graph::from_weighted_edges(
3463 &[
3464 (0, 1, 1.0),
3465 (1, 2, 2.0),
3466 (0, 3, 1.0),
3467 (3, 4, 1.0),
3468 (4, 2, 1.0),
3469 ],
3470 false,
3471 );
3472
3473 let (_path, dist) = g.dijkstra(0, 2).expect("path should exist");
3474 assert_eq!(dist, 3.0); }
3476
3477 #[test]
3478 fn test_dijkstra_linear_chain() {
3479 let g = Graph::from_weighted_edges(&[(0, 1, 1.0), (1, 2, 2.0), (2, 3, 3.0)], false);
3481
3482 let (path, dist) = g.dijkstra(0, 3).expect("path should exist");
3483 assert_eq!(dist, 6.0);
3484 assert_eq!(path, vec![0, 1, 2, 3]);
3485 }
3486
3487 #[test]
3488 fn test_dijkstra_directed_graph() {
3489 let g = Graph::from_weighted_edges(&[(0, 1, 1.0), (1, 2, 2.0)], true);
3491
3492 let (path, dist) = g.dijkstra(0, 2).expect("forward path should exist");
3493 assert_eq!(dist, 3.0);
3494 assert_eq!(path, vec![0, 1, 2]);
3495
3496 assert!(g.dijkstra(2, 0).is_none());
3498 }
3499
3500 #[test]
3501 fn test_dijkstra_invalid_nodes() {
3502 let g = Graph::from_weighted_edges(&[(0, 1, 1.0)], false);
3503 assert!(g.dijkstra(0, 10).is_none());
3504 assert!(g.dijkstra(10, 0).is_none());
3505 }
3506
3507 #[test]
3508 #[should_panic(expected = "negative edge weights")]
3509 fn test_dijkstra_negative_weights() {
3510 let g = Graph::from_weighted_edges(&[(0, 1, -1.0)], false);
3512 let _ = g.dijkstra(0, 1);
3513 }
3514
3515 #[test]
3516 fn test_dijkstra_zero_weight_edges() {
3517 let g = Graph::from_weighted_edges(&[(0, 1, 0.0), (1, 2, 1.0)], false);
3519 let (path, dist) = g.dijkstra(0, 2).expect("path should exist");
3520 assert_eq!(dist, 1.0);
3521 assert_eq!(path, vec![0, 1, 2]);
3522 }
3523
3524 #[test]
3525 fn test_dijkstra_complete_graph_weighted() {
3526 let g = Graph::from_weighted_edges(&[(0, 1, 1.0), (1, 2, 1.0), (0, 2, 3.0)], false);
3528
3529 let (path, dist) = g.dijkstra(0, 2).expect("path should exist");
3531 assert_eq!(dist, 2.0);
3532 assert_eq!(path, vec![0, 1, 2]);
3533 }
3534
3535 #[test]
3536 fn test_dijkstra_star_graph_weighted() {
3537 let g = Graph::from_weighted_edges(&[(0, 1, 1.0), (0, 2, 2.0), (0, 3, 3.0)], false);
3539
3540 let (path, dist) = g.dijkstra(1, 3).expect("path should exist");
3542 assert_eq!(dist, 4.0); assert_eq!(path, vec![1, 0, 3]);
3544 }
3545
3546 #[test]
3547 fn test_dijkstra_vs_shortest_path() {
3548 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false);
3550
3551 let sp_path = g
3552 .shortest_path(0, 3)
3553 .expect("shortest_path should find path");
3554 let (dij_path, dij_dist) = g.dijkstra(0, 3).expect("dijkstra should find path");
3555
3556 assert_eq!(sp_path.len(), dij_path.len());
3557 assert_eq!(dij_dist, (dij_path.len() - 1) as f64);
3558 }
3559
3560 #[test]
3561 fn test_dijkstra_floating_point_precision() {
3562 let g = Graph::from_weighted_edges(&[(0, 1, 0.1), (1, 2, 0.2), (0, 2, 0.31)], false);
3564
3565 let (path, dist) = g.dijkstra(0, 2).expect("path should exist");
3566 assert!((dist - 0.3).abs() < 1e-10); assert_eq!(path, vec![0, 1, 2]);
3568 }
3569
3570 #[test]
3575 fn test_apsp_linear_chain() {
3576 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false);
3578 let dist = g.all_pairs_shortest_paths();
3579
3580 for (i, row) in dist.iter().enumerate().take(4) {
3582 assert_eq!(row[i], Some(0));
3583 }
3584
3585 assert_eq!(dist[0][1], Some(1));
3587 assert_eq!(dist[0][2], Some(2));
3588 assert_eq!(dist[0][3], Some(3));
3589 assert_eq!(dist[1][2], Some(1));
3590 assert_eq!(dist[1][3], Some(2));
3591 assert_eq!(dist[2][3], Some(1));
3592
3593 assert_eq!(dist[0][3], dist[3][0]);
3595 assert_eq!(dist[1][2], dist[2][1]);
3596 }
3597
3598 #[test]
3599 fn test_apsp_complete_graph() {
3600 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], false);
3602 let dist = g.all_pairs_shortest_paths();
3603
3604 for (i, row) in dist.iter().enumerate().take(4) {
3606 for (j, &cell) in row.iter().enumerate().take(4) {
3607 if i == j {
3608 assert_eq!(cell, Some(0));
3609 } else {
3610 assert_eq!(cell, Some(1));
3611 }
3612 }
3613 }
3614 }
3615
3616 #[test]
3617 fn test_apsp_disconnected() {
3618 let g = Graph::from_edges(&[(0, 1), (2, 3)], false);
3620 let dist = g.all_pairs_shortest_paths();
3621
3622 assert_eq!(dist[0][1], Some(1));
3624 assert_eq!(dist[1][0], Some(1));
3625 assert_eq!(dist[2][3], Some(1));
3626 assert_eq!(dist[3][2], Some(1));
3627
3628 assert_eq!(dist[0][2], None);
3630 assert_eq!(dist[0][3], None);
3631 assert_eq!(dist[1][2], None);
3632 assert_eq!(dist[1][3], None);
3633 }
3634
3635 #[test]
3636 fn test_apsp_directed() {
3637 let g = Graph::from_edges(&[(0, 1), (1, 2)], true);
3639 let dist = g.all_pairs_shortest_paths();
3640
3641 assert_eq!(dist[0][1], Some(1));
3643 assert_eq!(dist[0][2], Some(2));
3644 assert_eq!(dist[1][2], Some(1));
3645
3646 assert_eq!(dist[1][0], None);
3648 assert_eq!(dist[2][0], None);
3649 assert_eq!(dist[2][1], None);
3650 }
3651
3652 #[test]
3653 fn test_apsp_triangle() {
3654 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], false);
3656 let dist = g.all_pairs_shortest_paths();
3657
3658 for (i, row) in dist.iter().enumerate().take(3) {
3660 for (j, &cell) in row.iter().enumerate().take(3) {
3661 if i == j {
3662 assert_eq!(cell, Some(0));
3663 } else {
3664 assert_eq!(cell, Some(1));
3665 }
3666 }
3667 }
3668 }
3669
3670 #[test]
3671 fn test_apsp_star_graph() {
3672 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], false);
3674 let dist = g.all_pairs_shortest_paths();
3675
3676 assert_eq!(dist[0][1], Some(1));
3678 assert_eq!(dist[0][2], Some(1));
3679 assert_eq!(dist[0][3], Some(1));
3680
3681 assert_eq!(dist[1][2], Some(2));
3683 assert_eq!(dist[1][3], Some(2));
3684 assert_eq!(dist[2][3], Some(2));
3685 }
3686
3687 #[test]
3688 fn test_apsp_empty_graph() {
3689 let g = Graph::new(false);
3690 let dist = g.all_pairs_shortest_paths();
3691 assert_eq!(dist.len(), 0);
3692 }
3693
3694 #[test]
3695 fn test_apsp_single_node() {
3696 let g = Graph::from_edges(&[(0, 0)], false);
3697 let dist = g.all_pairs_shortest_paths();
3698
3699 assert_eq!(dist.len(), 1);
3700 assert_eq!(dist[0][0], Some(0));
3701 }
3702
3703 #[test]
3704 fn test_apsp_cycle() {
3705 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], true);
3707 let dist = g.all_pairs_shortest_paths();
3708
3709 assert_eq!(dist[0][1], Some(1));
3711 assert_eq!(dist[0][2], Some(2));
3712 assert_eq!(dist[0][3], Some(3));
3713 assert_eq!(dist[1][3], Some(2));
3714
3715 for row in dist.iter().take(4) {
3717 for &cell in row.iter().take(4) {
3718 assert!(cell.is_some());
3719 }
3720 }
3721 }
3722
3723 #[test]
3724 fn test_apsp_matrix_size() {
3725 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false);
3726 let dist = g.all_pairs_shortest_paths();
3727
3728 assert_eq!(dist.len(), 4);
3730 for row in &dist {
3731 assert_eq!(row.len(), 4);
3732 }
3733 }
3734
3735 #[test]
3740 fn test_astar_linear_chain() {
3741 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false);
3743
3744 let heuristic = |node: usize| (3 - node) as f64;
3746
3747 let path = g.a_star(0, 3, heuristic).expect("path should exist");
3748 assert_eq!(path, vec![0, 1, 2, 3]);
3749 }
3750
3751 #[test]
3752 fn test_astar_same_node() {
3753 let g = Graph::from_edges(&[(0, 1)], false);
3754 let heuristic = |_: usize| 0.0;
3755
3756 let path = g.a_star(0, 0, heuristic).expect("path should exist");
3757 assert_eq!(path, vec![0]);
3758 }
3759
3760 #[test]
3761 fn test_astar_disconnected() {
3762 let g = Graph::from_edges(&[(0, 1), (2, 3)], false);
3764 let heuristic = |_: usize| 0.0;
3765
3766 assert!(g.a_star(0, 3, heuristic).is_none());
3767 }
3768
3769 #[test]
3770 fn test_astar_zero_heuristic() {
3771 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false);
3773 let heuristic = |_: usize| 0.0;
3774
3775 let path = g.a_star(0, 3, heuristic).expect("path should exist");
3776 assert_eq!(path, vec![0, 1, 2, 3]);
3777 }
3778
3779 #[test]
3780 fn test_astar_admissible_heuristic() {
3781 let g = Graph::from_weighted_edges(
3786 &[(0, 1, 1.0), (1, 2, 1.0), (0, 3, 0.5), (3, 2, 0.5)],
3787 false,
3788 );
3789
3790 let heuristic = |node: usize| match node {
3792 0 | 1 => 1.0, 3 => 0.5,
3794 _ => 0.0, };
3796
3797 let path = g.a_star(0, 2, heuristic).expect("path should exist");
3798 assert!(path.contains(&3)); }
3801
3802 #[test]
3803 fn test_astar_directed() {
3804 let g = Graph::from_edges(&[(0, 1), (1, 2)], true);
3806 let heuristic = |node: usize| (2 - node) as f64;
3807
3808 let path = g
3809 .a_star(0, 2, heuristic)
3810 .expect("forward path should exist");
3811 assert_eq!(path, vec![0, 1, 2]);
3812
3813 assert!(g.a_star(2, 0, |_| 0.0).is_none());
3815 }
3816
3817 #[test]
3818 fn test_astar_triangle() {
3819 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], false);
3821 let heuristic = |_: usize| 0.0;
3822
3823 let path = g.a_star(0, 2, heuristic).expect("path should exist");
3824 assert_eq!(path.len(), 2); assert_eq!(path[0], 0);
3826 assert_eq!(path[1], 2);
3827 }
3828
3829 #[test]
3830 fn test_astar_weighted_graph() {
3831 let g = Graph::from_weighted_edges(&[(0, 1, 1.0), (1, 2, 2.0), (0, 2, 5.0)], false);
3833
3834 let heuristic = |node: usize| match node {
3836 0 => 3.0,
3837 1 => 2.0,
3838 _ => 0.0, };
3840
3841 let path = g.a_star(0, 2, heuristic).expect("path should exist");
3842 assert_eq!(path, vec![0, 1, 2]);
3844 }
3845
3846 #[test]
3847 fn test_astar_complete_graph() {
3848 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], false);
3850 let heuristic = |_: usize| 0.0;
3851
3852 let path = g.a_star(0, 3, heuristic).expect("path should exist");
3854 assert_eq!(path.len(), 2); assert_eq!(path[0], 0);
3856 assert_eq!(path[1], 3);
3857 }
3858
3859 #[test]
3860 fn test_astar_invalid_nodes() {
3861 let g = Graph::from_edges(&[(0, 1)], false);
3862 let heuristic = |_: usize| 0.0;
3863
3864 assert!(g.a_star(0, 10, heuristic).is_none());
3865 assert!(g.a_star(10, 0, heuristic).is_none());
3866 }
3867
3868 #[test]
3869 fn test_astar_vs_shortest_path() {
3870 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false);
3872
3873 let sp_path = g
3874 .shortest_path(0, 3)
3875 .expect("shortest_path should find path");
3876 let astar_path = g.a_star(0, 3, |_| 0.0).expect("astar should find path");
3877
3878 assert_eq!(sp_path.len(), astar_path.len());
3879 assert_eq!(sp_path, astar_path);
3880 }
3881
3882 #[test]
3883 fn test_astar_star_graph() {
3884 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], false);
3886
3887 let heuristic = |node: usize| if node == 3 { 0.0 } else { 1.0 };
3889
3890 let path = g.a_star(1, 3, heuristic).expect("path should exist");
3891 assert_eq!(path.len(), 3); assert_eq!(path[0], 1);
3893 assert_eq!(path[1], 0);
3894 assert_eq!(path[2], 3);
3895 }
3896
3897 #[test]
3898 fn test_astar_perfect_heuristic() {
3899 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false);
3901
3902 let heuristic = |node: usize| (3 - node) as f64;
3904
3905 let path = g.a_star(0, 3, heuristic).expect("path should exist");
3906 assert_eq!(path, vec![0, 1, 2, 3]);
3907 }
3908
3909 #[test]
3910 fn test_astar_complex_graph() {
3911 let g = Graph::from_weighted_edges(
3918 &[
3919 (0, 1, 1.0),
3920 (0, 2, 1.0),
3921 (1, 3, 1.0),
3922 (2, 3, 1.0),
3923 (3, 4, 1.0),
3924 ],
3925 false,
3926 );
3927
3928 let heuristic = |node: usize| (4 - node) as f64;
3930
3931 let path = g.a_star(0, 4, heuristic).expect("path should exist");
3932 assert_eq!(path.len(), 4); assert_eq!(path[0], 0);
3934 assert_eq!(path[3], 4);
3935 }
3936
3937 #[test]
3940 fn test_dfs_linear_chain() {
3941 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false);
3943 let visited = g.dfs(0).expect("valid source");
3944
3945 assert_eq!(visited.len(), 4);
3946 assert_eq!(visited[0], 0); assert!(visited.contains(&1));
3948 assert!(visited.contains(&2));
3949 assert!(visited.contains(&3));
3950 }
3951
3952 #[test]
3953 fn test_dfs_tree() {
3954 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], false);
3956 let visited = g.dfs(0).expect("valid source");
3957
3958 assert_eq!(visited.len(), 4);
3959 assert_eq!(visited[0], 0); assert!(visited.contains(&1));
3962 assert!(visited.contains(&2));
3963 assert!(visited.contains(&3));
3964 }
3965
3966 #[test]
3967 fn test_dfs_cycle() {
3968 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], false);
3970 let visited = g.dfs(0).expect("valid source");
3971
3972 assert_eq!(visited.len(), 3);
3973 assert_eq!(visited[0], 0);
3974 assert!(visited.contains(&1));
3975 assert!(visited.contains(&2));
3976 }
3977
3978 #[test]
3979 fn test_dfs_disconnected() {
3980 let g = Graph::from_edges(&[(0, 1), (2, 3)], false);
3982
3983 let visited = g.dfs(0).expect("valid source");
3985 assert_eq!(visited.len(), 2);
3986 assert!(visited.contains(&0));
3987 assert!(visited.contains(&1));
3988 assert!(!visited.contains(&2));
3989 assert!(!visited.contains(&3));
3990
3991 let visited2 = g.dfs(2).expect("valid source");
3993 assert_eq!(visited2.len(), 2);
3994 assert!(visited2.contains(&2));
3995 assert!(visited2.contains(&3));
3996 }
3997
3998 #[test]
3999 fn test_dfs_directed() {
4000 let g = Graph::from_edges(&[(0, 1), (1, 2)], true);
4002
4003 let visited = g.dfs(0).expect("valid source");
4005 assert_eq!(visited.len(), 3);
4006 assert_eq!(visited[0], 0);
4007
4008 let visited2 = g.dfs(2).expect("valid source");
4010 assert_eq!(visited2.len(), 1);
4011 assert_eq!(visited2[0], 2);
4012 }
4013
4014 #[test]
4015 fn test_dfs_single_node() {
4016 let g = Graph::from_edges(&[(0, 0)], false);
4018
4019 let visited = g.dfs(0).expect("valid source");
4020 assert_eq!(visited.len(), 1);
4021 assert_eq!(visited[0], 0);
4022 }
4023
4024 #[test]
4025 fn test_dfs_invalid_source() {
4026 let g = Graph::from_edges(&[(0, 1), (1, 2)], false);
4027
4028 assert!(g.dfs(10).is_none());
4030 assert!(g.dfs(100).is_none());
4031 }
4032
4033 #[test]
4034 fn test_dfs_complete_graph() {
4035 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], false);
4037 let visited = g.dfs(0).expect("valid source");
4038
4039 assert_eq!(visited.len(), 4);
4040 assert_eq!(visited[0], 0);
4041 assert!(visited.contains(&1));
4043 assert!(visited.contains(&2));
4044 assert!(visited.contains(&3));
4045 }
4046
4047 #[test]
4048 fn test_dfs_dag() {
4049 let g = Graph::from_edges(&[(0, 1), (0, 2), (1, 3), (2, 3)], true);
4051 let visited = g.dfs(0).expect("valid source");
4052
4053 assert_eq!(visited.len(), 4);
4054 assert_eq!(visited[0], 0);
4055 assert!(visited.contains(&1));
4057 assert!(visited.contains(&2));
4058 assert!(visited.contains(&3));
4059
4060 let visited3 = g.dfs(3).expect("valid source");
4062 assert_eq!(visited3.len(), 1);
4063 assert_eq!(visited3[0], 3);
4064 }
4065
4066 #[test]
4067 fn test_dfs_empty_graph() {
4068 let g = Graph::new(false);
4069 assert!(g.dfs(0).is_none());
4071 }
4072
4073 #[test]
4076 fn test_connected_components_single() {
4077 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false);
4079 let components = g.connected_components();
4080
4081 assert_eq!(components.len(), 4);
4082 assert_eq!(components[0], components[1]);
4084 assert_eq!(components[1], components[2]);
4085 assert_eq!(components[2], components[3]);
4086 }
4087
4088 #[test]
4089 fn test_connected_components_two() {
4090 let g = Graph::from_edges(&[(0, 1), (2, 3)], false);
4092 let components = g.connected_components();
4093
4094 assert_eq!(components.len(), 4);
4095 assert_eq!(components[0], components[1]);
4097 assert_eq!(components[2], components[3]);
4099 assert_ne!(components[0], components[2]);
4101 }
4102
4103 #[test]
4104 fn test_connected_components_three() {
4105 let g = Graph::from_edges(&[(0, 1), (2, 3), (4, 4)], false);
4107 let components = g.connected_components();
4108
4109 assert_eq!(components.len(), 5);
4110 assert_eq!(components[0], components[1]);
4112 assert_eq!(components[2], components[3]);
4113 assert_ne!(components[0], components[2]);
4114 assert_ne!(components[0], components[4]);
4115 assert_ne!(components[2], components[4]);
4116 }
4117
4118 #[test]
4119 fn test_connected_components_complete() {
4120 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], false);
4122 let components = g.connected_components();
4123
4124 assert_eq!(components.len(), 4);
4125 let first = components[0];
4126 assert!(components.iter().all(|&c| c == first));
4127 }
4128
4129 #[test]
4130 fn test_connected_components_star() {
4131 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], false);
4133 let components = g.connected_components();
4134
4135 assert_eq!(components.len(), 4);
4136 assert_eq!(components[0], components[1]);
4138 assert_eq!(components[0], components[2]);
4139 assert_eq!(components[0], components[3]);
4140 }
4141
4142 #[test]
4143 fn test_connected_components_directed_weak() {
4144 let g = Graph::from_edges(&[(0, 1), (1, 2)], true);
4146 let components = g.connected_components();
4147
4148 assert_eq!(components.len(), 3);
4149 assert_eq!(components[0], components[1]);
4151 assert_eq!(components[1], components[2]);
4152 }
4153
4154 #[test]
4155 fn test_connected_components_cycle() {
4156 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], false);
4158 let components = g.connected_components();
4159
4160 assert_eq!(components.len(), 3);
4161 assert_eq!(components[0], components[1]);
4163 assert_eq!(components[1], components[2]);
4164 }
4165
4166 #[test]
4167 fn test_connected_components_empty() {
4168 let g = Graph::new(false);
4169 let components = g.connected_components();
4170 assert!(components.is_empty());
4171 }
4172
4173 #[test]
4174 fn test_connected_components_isolated_nodes() {
4175 let g = Graph::from_edges(&[(0, 1), (3, 4)], false);
4177 let components = g.connected_components();
4180
4181 assert_eq!(components.len(), 5);
4182 assert_eq!(components[0], components[1]);
4184 assert_eq!(components[3], components[4]);
4185 assert_ne!(components[0], components[3]);
4186 assert_ne!(components[2], components[0]);
4188 assert_ne!(components[2], components[3]);
4189 }
4190
4191 #[test]
4192 fn test_connected_components_count() {
4193 fn count_components(components: &[usize]) -> usize {
4195 use std::collections::HashSet;
4196 components.iter().copied().collect::<HashSet<_>>().len()
4197 }
4198
4199 let g1 = Graph::from_edges(&[(0, 1), (1, 2)], false);
4201 assert_eq!(count_components(&g1.connected_components()), 1);
4202
4203 let g2 = Graph::from_edges(&[(0, 1), (2, 3)], false);
4205 assert_eq!(count_components(&g2.connected_components()), 2);
4206
4207 let g3 = Graph::from_edges(&[(0, 1), (2, 3), (4, 5)], false);
4209 assert_eq!(count_components(&g3.connected_components()), 3);
4210 }
4211
4212 #[test]
4215 fn test_scc_single_cycle() {
4216 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], true);
4218 let sccs = g.strongly_connected_components();
4219
4220 assert_eq!(sccs.len(), 3);
4221 assert_eq!(sccs[0], sccs[1]);
4223 assert_eq!(sccs[1], sccs[2]);
4224 }
4225
4226 #[test]
4227 fn test_scc_dag() {
4228 let g = Graph::from_edges(&[(0, 1), (1, 2)], true);
4230 let sccs = g.strongly_connected_components();
4231
4232 assert_eq!(sccs.len(), 3);
4233 assert_ne!(sccs[0], sccs[1]);
4235 assert_ne!(sccs[1], sccs[2]);
4236 assert_ne!(sccs[0], sccs[2]);
4237 }
4238
4239 #[test]
4240 fn test_scc_two_components() {
4241 let g = Graph::from_edges(&[(0, 1), (1, 0), (2, 3), (3, 2)], true);
4243 let sccs = g.strongly_connected_components();
4244
4245 assert_eq!(sccs.len(), 4);
4246 assert_eq!(sccs[0], sccs[1]);
4248 assert_eq!(sccs[2], sccs[3]);
4250 assert_ne!(sccs[0], sccs[2]);
4252 }
4253
4254 #[test]
4255 fn test_scc_complex() {
4256 let g = Graph::from_edges(&[(0, 1), (1, 0), (1, 2), (2, 3), (3, 4), (4, 2)], true);
4261 let sccs = g.strongly_connected_components();
4262
4263 assert_eq!(sccs.len(), 5);
4264 assert_eq!(sccs[0], sccs[1]);
4266 assert_eq!(sccs[2], sccs[3]);
4268 assert_eq!(sccs[3], sccs[4]);
4269 assert_ne!(sccs[0], sccs[2]);
4271 }
4272
4273 #[test]
4274 fn test_scc_self_loop() {
4275 let g = Graph::from_edges(&[(0, 0)], true);
4277 let sccs = g.strongly_connected_components();
4278
4279 assert_eq!(sccs.len(), 1);
4280 assert_eq!(sccs[0], 0);
4281 }
4282
4283 #[test]
4284 fn test_scc_disconnected() {
4285 let g = Graph::from_edges(&[(0, 1), (1, 0), (2, 3), (3, 2)], true);
4287 let sccs = g.strongly_connected_components();
4288
4289 assert_eq!(sccs.len(), 4);
4290 assert_eq!(sccs[0], sccs[1]);
4292 assert_eq!(sccs[2], sccs[3]);
4293 assert_ne!(sccs[0], sccs[2]);
4294 }
4295
4296 #[test]
4297 fn test_scc_empty() {
4298 let g = Graph::new(true);
4299 let sccs = g.strongly_connected_components();
4300 assert!(sccs.is_empty());
4301 }
4302
4303 #[test]
4304 fn test_scc_linear_dag() {
4305 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], true);
4307 let sccs = g.strongly_connected_components();
4308
4309 assert_eq!(sccs.len(), 4);
4310 use std::collections::HashSet;
4312 let unique_sccs: HashSet<_> = sccs.iter().copied().collect();
4313 assert_eq!(unique_sccs.len(), 4);
4314 }
4315
4316 #[test]
4317 fn test_scc_complete_graph() {
4318 let g = Graph::from_edges(&[(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)], true);
4320 let sccs = g.strongly_connected_components();
4321
4322 assert_eq!(sccs.len(), 3);
4323 assert_eq!(sccs[0], sccs[1]);
4325 assert_eq!(sccs[1], sccs[2]);
4326 }
4327
4328 #[test]
4329 fn test_scc_count() {
4330 fn count_sccs(sccs: &[usize]) -> usize {
4332 use std::collections::HashSet;
4333 sccs.iter().copied().collect::<HashSet<_>>().len()
4334 }
4335
4336 let g1 = Graph::from_edges(&[(0, 1), (1, 0)], true);
4338 assert_eq!(count_sccs(&g1.strongly_connected_components()), 1);
4339
4340 let g2 = Graph::from_edges(&[(0, 1)], true);
4342 assert_eq!(count_sccs(&g2.strongly_connected_components()), 2);
4343
4344 let g3 = Graph::from_edges(&[(0, 1), (2, 3), (3, 2)], true);
4346 assert_eq!(count_sccs(&g3.strongly_connected_components()), 3);
4347 }
4348
4349 #[test]
4352 fn test_topo_linear_dag() {
4353 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], true);
4355 let order = g
4356 .topological_sort()
4357 .expect("DAG should have topological order");
4358
4359 assert_eq!(order.len(), 4);
4360 assert!(order.iter().position(|&x| x == 0) < order.iter().position(|&x| x == 1));
4362 assert!(order.iter().position(|&x| x == 1) < order.iter().position(|&x| x == 2));
4363 assert!(order.iter().position(|&x| x == 2) < order.iter().position(|&x| x == 3));
4364 }
4365
4366 #[test]
4367 fn test_topo_cycle() {
4368 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], true);
4370 assert!(g.topological_sort().is_none()); }
4372
4373 #[test]
4374 fn test_topo_diamond() {
4375 let g = Graph::from_edges(&[(0, 1), (0, 2), (1, 3), (2, 3)], true);
4377 let order = g
4378 .topological_sort()
4379 .expect("DAG should have topological order");
4380
4381 assert_eq!(order.len(), 4);
4382 let pos_0 = order
4384 .iter()
4385 .position(|&x| x == 0)
4386 .expect("0 should be in order");
4387 assert!(
4388 pos_0
4389 < order
4390 .iter()
4391 .position(|&x| x == 1)
4392 .expect("1 should be in order")
4393 );
4394 assert!(
4395 pos_0
4396 < order
4397 .iter()
4398 .position(|&x| x == 2)
4399 .expect("2 should be in order")
4400 );
4401 assert!(
4402 pos_0
4403 < order
4404 .iter()
4405 .position(|&x| x == 3)
4406 .expect("3 should be in order")
4407 );
4408
4409 let pos_3 = order
4411 .iter()
4412 .position(|&x| x == 3)
4413 .expect("3 should be in order");
4414 assert!(
4415 order
4416 .iter()
4417 .position(|&x| x == 1)
4418 .expect("1 should be in order")
4419 < pos_3
4420 );
4421 assert!(
4422 order
4423 .iter()
4424 .position(|&x| x == 2)
4425 .expect("2 should be in order")
4426 < pos_3
4427 );
4428 }
4429
4430 #[test]
4431 fn test_topo_empty() {
4432 let g = Graph::new(true);
4433 let order = g
4434 .topological_sort()
4435 .expect("Empty graph has topological order");
4436 assert!(order.is_empty());
4437 }
4438
4439 #[test]
4440 fn test_topo_single_node() {
4441 let g = Graph::from_edges(&[(0, 0)], true);
4443 assert!(g.topological_sort().is_none()); }
4445
4446 #[test]
4447 fn test_topo_disconnected_dag() {
4448 let g = Graph::from_edges(&[(0, 1), (2, 3)], true);
4450 let order = g
4451 .topological_sort()
4452 .expect("Disconnected DAG has topological order");
4453
4454 assert_eq!(order.len(), 4);
4455 assert!(order.iter().position(|&x| x == 0) < order.iter().position(|&x| x == 1));
4457 assert!(order.iter().position(|&x| x == 2) < order.iter().position(|&x| x == 3));
4458 }
4459
4460 #[test]
4461 fn test_topo_tree() {
4462 let g = Graph::from_edges(&[(0, 1), (0, 2), (1, 3), (1, 4)], true);
4464 let order = g.topological_sort().expect("Tree is a DAG");
4465
4466 assert_eq!(order.len(), 5);
4467 assert_eq!(order.iter().position(|&x| x == 0), Some(0));
4469 let pos_1 = order
4471 .iter()
4472 .position(|&x| x == 1)
4473 .expect("1 should be in order");
4474 assert!(
4475 pos_1
4476 < order
4477 .iter()
4478 .position(|&x| x == 3)
4479 .expect("3 should be in order")
4480 );
4481 assert!(
4482 pos_1
4483 < order
4484 .iter()
4485 .position(|&x| x == 4)
4486 .expect("4 should be in order")
4487 );
4488 }
4489
4490 #[test]
4491 fn test_topo_self_loop() {
4492 let g = Graph::from_edges(&[(0, 0)], true);
4494 assert!(g.topological_sort().is_none());
4495 }
4496
4497 #[test]
4498 fn test_topo_complete_dag() {
4499 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], true);
4501 let order = g
4502 .topological_sort()
4503 .expect("Complete DAG has topological order");
4504
4505 assert_eq!(order.len(), 4);
4506 let positions: Vec<_> = (0..4)
4508 .map(|i| {
4509 order
4510 .iter()
4511 .position(|&x| x == i)
4512 .expect("node should be in order")
4513 })
4514 .collect();
4515
4516 assert!(positions[0] < positions[1]);
4517 assert!(positions[0] < positions[2]);
4518 assert!(positions[0] < positions[3]);
4519 assert!(positions[1] < positions[2]);
4520 assert!(positions[1] < positions[3]);
4521 assert!(positions[2] < positions[3]);
4522 }
4523
4524 #[test]
4525 fn test_topo_undirected() {
4526 let g = Graph::from_edges(&[(0, 1), (1, 2)], false);
4528 assert!(g.topological_sort().is_none());
4530 }
4531
4532 #[test]
4535 fn test_common_neighbors_triangle() {
4536 let g = Graph::from_edges(&[(0, 1), (0, 2), (1, 2)], false);
4538
4539 assert_eq!(g.common_neighbors(1, 2), Some(1));
4541 assert_eq!(g.common_neighbors(0, 1), Some(1));
4543 assert_eq!(g.common_neighbors(0, 2), Some(1));
4545 }
4546
4547 #[test]
4548 fn test_common_neighbors_no_overlap() {
4549 let g = Graph::from_edges(&[(0, 1), (0, 2), (3, 4), (3, 5)], false);
4551
4552 assert_eq!(g.common_neighbors(1, 2), Some(1));
4554 assert_eq!(g.common_neighbors(1, 4), Some(0));
4556 assert_eq!(g.common_neighbors(0, 3), Some(0));
4557 }
4558
4559 #[test]
4560 fn test_common_neighbors_complete() {
4561 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], false);
4563
4564 assert_eq!(g.common_neighbors(0, 1), Some(2)); assert_eq!(g.common_neighbors(0, 2), Some(2)); assert_eq!(g.common_neighbors(1, 2), Some(2)); }
4569
4570 #[test]
4571 fn test_common_neighbors_directed() {
4572 let g = Graph::from_edges(&[(0, 1), (0, 2), (1, 2)], true);
4574
4575 assert_eq!(g.common_neighbors(0, 1), Some(1)); }
4579
4580 #[test]
4581 fn test_common_neighbors_invalid() {
4582 let g = Graph::from_edges(&[(0, 1), (1, 2)], false);
4583
4584 assert!(g.common_neighbors(0, 10).is_none());
4586 assert!(g.common_neighbors(10, 0).is_none());
4587 assert!(g.common_neighbors(10, 20).is_none());
4588 }
4589
4590 #[test]
4591 fn test_common_neighbors_self() {
4592 let g = Graph::from_edges(&[(0, 1), (0, 2), (1, 2)], false);
4593
4594 assert_eq!(g.common_neighbors(0, 0), Some(2)); }
4597
4598 #[test]
4599 fn test_common_neighbors_empty() {
4600 let g = Graph::new(false);
4601 assert!(g.common_neighbors(0, 1).is_none());
4602 }
4603
4604 #[test]
4605 fn test_common_neighbors_star() {
4606 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false);
4608
4609 assert_eq!(g.common_neighbors(1, 2), Some(1)); assert_eq!(g.common_neighbors(1, 3), Some(1)); assert_eq!(g.common_neighbors(2, 4), Some(1)); }
4614
4615 #[test]
4618 fn test_adamic_adar_triangle() {
4619 let g = Graph::from_edges(&[(0, 1), (0, 2), (1, 2)], false);
4621
4622 let aa = g.adamic_adar_index(1, 2).expect("valid nodes");
4624 assert!((aa - 1.0 / 2.0_f64.ln()).abs() < 1e-10);
4626 }
4627
4628 #[test]
4629 fn test_adamic_adar_no_common() {
4630 let g = Graph::from_edges(&[(0, 1), (2, 3)], false);
4632
4633 assert_eq!(g.adamic_adar_index(0, 2).expect("valid nodes"), 0.0);
4635 assert_eq!(g.adamic_adar_index(1, 3).expect("valid nodes"), 0.0);
4636 }
4637
4638 #[test]
4639 fn test_adamic_adar_star() {
4640 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false);
4642
4643 let aa = g.adamic_adar_index(1, 2).expect("valid nodes");
4645 assert!((aa - 1.0 / 4.0_f64.ln()).abs() < 1e-10);
4647 }
4648
4649 #[test]
4650 fn test_adamic_adar_multiple_common() {
4651 let g = Graph::from_edges(&[(0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4)], false);
4654
4655 let aa = g.adamic_adar_index(0, 1).expect("valid nodes");
4656 let expected = 3.0 / 2.0_f64.ln();
4659 assert!((aa - expected).abs() < 1e-10);
4660 }
4661
4662 #[test]
4663 fn test_adamic_adar_degree_one() {
4664 let g = Graph::from_edges(&[(0, 1), (1, 2)], false);
4666
4667 let aa = g.adamic_adar_index(0, 2).expect("valid nodes");
4669 assert!((aa - 1.0 / 2.0_f64.ln()).abs() < 1e-10);
4670 }
4671
4672 #[test]
4673 fn test_adamic_adar_invalid() {
4674 let g = Graph::from_edges(&[(0, 1), (1, 2)], false);
4675
4676 assert!(g.adamic_adar_index(0, 10).is_none());
4677 assert!(g.adamic_adar_index(10, 0).is_none());
4678 }
4679
4680 #[test]
4681 fn test_adamic_adar_directed() {
4682 let g = Graph::from_edges(&[(0, 2), (1, 2), (2, 3)], true);
4684
4685 let aa = g.adamic_adar_index(0, 1).expect("valid nodes");
4687 assert!(aa >= 0.0);
4691 }
4692
4693 #[test]
4694 fn test_adamic_adar_empty() {
4695 let g = Graph::new(false);
4696 assert!(g.adamic_adar_index(0, 1).is_none());
4697 }
4698
4699 #[test]
4702 fn test_label_propagation_two_triangles() {
4703 let g = Graph::from_edges(
4706 &[(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3), (2, 3)],
4707 false,
4708 );
4709
4710 let communities = g.label_propagation(100, Some(42));
4711
4712 assert_eq!(communities.len(), 6);
4715
4716 use std::collections::HashSet;
4718 let unique: HashSet<_> = communities.iter().copied().collect();
4719 assert!(!unique.is_empty() && unique.len() <= 6);
4721 }
4722
4723 #[test]
4724 fn test_label_propagation_complete_graph() {
4725 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], false);
4727
4728 let communities = g.label_propagation(100, Some(42));
4729
4730 assert_eq!(communities.len(), 4);
4731 let first_label = communities[0];
4733 assert!(communities.iter().all(|&c| c == first_label));
4734 }
4735
4736 #[test]
4737 fn test_label_propagation_disconnected() {
4738 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3)], false);
4740
4741 let communities = g.label_propagation(100, Some(42));
4742
4743 assert_eq!(communities.len(), 6);
4744
4745 assert_eq!(communities[0], communities[1]);
4748 assert_eq!(communities[1], communities[2]);
4749
4750 assert_eq!(communities[3], communities[4]);
4752 assert_eq!(communities[4], communities[5]);
4753
4754 assert_ne!(communities[0], communities[3]);
4756 }
4757
4758 #[test]
4759 fn test_label_propagation_star() {
4760 let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false);
4762
4763 let communities = g.label_propagation(100, Some(42));
4764
4765 assert_eq!(communities.len(), 5);
4766 use std::collections::HashSet;
4769 let unique: HashSet<_> = communities.iter().copied().collect();
4770 assert_eq!(unique.len(), 1);
4771 }
4772
4773 #[test]
4774 fn test_label_propagation_linear() {
4775 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4)], false);
4777
4778 let communities = g.label_propagation(100, Some(42));
4779
4780 assert_eq!(communities.len(), 5);
4781 use std::collections::HashSet;
4783 let unique: HashSet<_> = communities.iter().copied().collect();
4784 assert!(!unique.is_empty() && unique.len() <= 5);
4785 }
4786
4787 #[test]
4788 fn test_label_propagation_empty() {
4789 let g = Graph::new(false);
4790 let communities = g.label_propagation(100, Some(42));
4791 assert!(communities.is_empty());
4792 }
4793
4794 #[test]
4795 fn test_label_propagation_single_node() {
4796 let g = Graph::from_edges(&[(0, 0)], false);
4798 let communities = g.label_propagation(100, Some(42));
4799
4800 assert_eq!(communities.len(), 1);
4801 assert_eq!(communities[0], 0); }
4803
4804 #[test]
4805 fn test_label_propagation_convergence() {
4806 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], false);
4808
4809 let communities = g.label_propagation(100, Some(42));
4810
4811 assert_eq!(communities.len(), 3);
4812 assert_eq!(communities[0], communities[1]);
4814 assert_eq!(communities[1], communities[2]);
4815 }
4816
4817 #[test]
4818 fn test_label_propagation_directed() {
4819 let g = Graph::from_edges(&[(0, 1), (1, 0), (1, 2), (2, 1), (0, 2), (2, 0)], true);
4822
4823 let communities = g.label_propagation(100, Some(42));
4824
4825 assert_eq!(communities.len(), 3);
4826 assert_eq!(communities[0], communities[1]);
4828 assert_eq!(communities[1], communities[2]);
4829 }
4830
4831 #[test]
4832 fn test_label_propagation_barbell() {
4833 let g = Graph::from_edges(
4836 &[(0, 1), (0, 2), (1, 2), (3, 4), (3, 5), (4, 5), (2, 3)],
4837 false,
4838 );
4839
4840 let communities = g.label_propagation(100, Some(42));
4841
4842 assert_eq!(communities.len(), 6);
4843
4844 use std::collections::HashSet;
4846 let unique: HashSet<_> = communities.iter().copied().collect();
4847 assert!(!unique.is_empty() && unique.len() <= 3);
4848 }
4849}