rust_igraph/algorithms/connectivity/
reachability_matrix.rs1use crate::algorithms::paths::distances::distances;
18use crate::core::{Graph, IgraphResult};
19
20pub fn reachability_matrix(graph: &Graph) -> IgraphResult<Vec<Vec<bool>>> {
45 let n = graph.vcount();
46 let n_us = n as usize;
47 let mut out = Vec::with_capacity(n_us);
48 for v in 0..n {
49 let d = distances(graph, v)?;
50 let row: Vec<bool> = d.into_iter().map(|x| x.is_some()).collect();
52 out.push(row);
53 }
54 Ok(out)
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 #[test]
62 fn empty_graph_yields_empty_matrix() {
63 let g = Graph::with_vertices(0);
64 let m = reachability_matrix(&g).unwrap();
65 assert!(m.is_empty());
66 }
67
68 #[test]
69 fn isolated_vertices_only_self_reachable() {
70 let g = Graph::with_vertices(3);
71 let m = reachability_matrix(&g).unwrap();
72 assert_eq!(m[0], vec![true, false, false]);
73 assert_eq!(m[1], vec![false, true, false]);
74 assert_eq!(m[2], vec![false, false, true]);
75 }
76
77 #[test]
78 fn undirected_path_is_symmetric_and_full() {
79 let mut g = Graph::with_vertices(3);
80 g.add_edge(0, 1).unwrap();
81 g.add_edge(1, 2).unwrap();
82 let m = reachability_matrix(&g).unwrap();
83 for (i, row) in m.iter().enumerate() {
84 for (j, &val) in row.iter().enumerate() {
85 assert!(val, "{i}->{j} should be reachable");
86 assert_eq!(val, m[j][i], "asymmetry on undirected");
87 }
88 }
89 }
90
91 #[test]
92 fn directed_chain_is_one_way() {
93 let mut g = Graph::new(3, true).unwrap();
94 g.add_edge(0, 1).unwrap();
95 g.add_edge(1, 2).unwrap();
96 let m = reachability_matrix(&g).unwrap();
97 assert_eq!(m[0], vec![true, true, true]);
98 assert_eq!(m[1], vec![false, true, true]);
99 assert_eq!(m[2], vec![false, false, true]);
100 }
101
102 #[test]
103 fn directed_cycle_makes_all_pairs_reachable() {
104 let mut g = Graph::new(3, true).unwrap();
105 g.add_edge(0, 1).unwrap();
106 g.add_edge(1, 2).unwrap();
107 g.add_edge(2, 0).unwrap();
108 let m = reachability_matrix(&g).unwrap();
109 for (i, row) in m.iter().enumerate() {
110 for (j, &val) in row.iter().enumerate() {
111 assert!(val, "{i}->{j} should be reachable in a 3-cycle");
112 }
113 }
114 }
115
116 #[test]
117 fn disconnected_components_isolate_pairs() {
118 let mut g = Graph::with_vertices(5);
120 g.add_edge(0, 1).unwrap();
121 g.add_edge(1, 2).unwrap();
122 g.add_edge(3, 4).unwrap();
123 let m = reachability_matrix(&g).unwrap();
124 for row in &m[..3] {
126 for &v in &row[..3] {
127 assert!(v);
128 }
129 }
130 for (i, row) in m.iter().enumerate().take(3) {
132 for (j, &val) in row.iter().enumerate().skip(3).take(2) {
133 assert!(!val, "{i}->{j} should be unreachable");
134 assert!(!m[j][i], "{j}->{i} should be unreachable");
135 }
136 }
137 }
138
139 #[test]
140 fn diagonal_is_always_true() {
141 let g = Graph::with_vertices(7);
142 let m = reachability_matrix(&g).unwrap();
143 for (i, row) in m.iter().enumerate() {
144 assert!(row[i], "vertex {i} not self-reachable");
145 }
146 }
147}