rust_igraph/algorithms/connectivity/
reachability_scc.rs1use crate::algorithms::connectivity::components::connected_components;
16use crate::algorithms::connectivity::strong::strongly_connected_components;
17use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum ReachabilityMode {
22 Out,
24 In,
26 All,
28}
29
30#[derive(Debug, Clone)]
32pub struct ReachabilityResult {
33 pub membership: Vec<u32>,
35 pub csize: Vec<u32>,
37 pub num_components: u32,
39 pub reach: Vec<Vec<u64>>,
42}
43
44impl ReachabilityResult {
45 pub fn is_reachable(&self, source: VertexId, target: VertexId) -> bool {
47 let comp = self.membership[source as usize] as usize;
48 let word = target as usize / 64;
49 let bit = target as usize % 64;
50 if word >= self.reach[comp].len() {
51 return false;
52 }
53 (self.reach[comp][word] >> bit) & 1 == 1
54 }
55
56 pub fn count_from(&self, v: VertexId) -> u32 {
58 let comp = self.membership[v as usize] as usize;
59 let mut count = 0u32;
60 for &word in &self.reach[comp] {
61 count = count.wrapping_add(word.count_ones());
62 }
63 count
64 }
65}
66
67fn bitvec_words(n: u32) -> usize {
68 (n as usize).div_ceil(64)
69}
70
71fn bitvec_set(bv: &mut [u64], bit: u32) {
72 let w = bit as usize / 64;
73 let b = bit as usize % 64;
74 bv[w] |= 1u64 << b;
75}
76
77fn bitvec_or(dst: &mut [u64], src: &[u64]) {
78 for (d, s) in dst.iter_mut().zip(src.iter()) {
79 *d |= *s;
80 }
81}
82
83pub fn reachability(graph: &Graph, mode: ReachabilityMode) -> IgraphResult<ReachabilityResult> {
108 let n = graph.vcount();
109 let words = bitvec_words(n);
110
111 let effective_mode = if graph.is_directed() {
112 mode
113 } else {
114 ReachabilityMode::All
115 };
116
117 let (membership, num_components) = if effective_mode == ReachabilityMode::All {
119 let cc = connected_components(graph)?;
120 (cc.membership, cc.count)
121 } else {
122 let scc = strongly_connected_components(graph)?;
123 (scc.membership, scc.count)
124 };
125
126 let mut csize = vec![0u32; num_components as usize];
128 for &m in &membership {
129 csize[m as usize] = csize[m as usize]
130 .checked_add(1)
131 .ok_or(IgraphError::Internal("component size overflow"))?;
132 }
133
134 let mut reach = vec![vec![0u64; words]; num_components as usize];
136 for v in 0..n {
137 bitvec_set(&mut reach[membership[v as usize] as usize], v);
138 }
139
140 if effective_mode == ReachabilityMode::All {
142 return Ok(ReachabilityResult {
143 membership,
144 csize,
145 num_components,
146 reach,
147 });
148 }
149
150 let mut dag: Vec<Vec<u32>> = vec![Vec::new(); num_components as usize];
153
154 for v in 0..n {
155 let v_comp = membership[v as usize];
156 let neighbors = match effective_mode {
157 ReachabilityMode::Out => graph.out_neighbors_vec(v)?,
158 ReachabilityMode::In => graph.in_neighbors_vec(v)?,
159 ReachabilityMode::All => unreachable!(),
160 };
161 for w in neighbors {
162 let w_comp = membership[w as usize];
163 if v_comp != w_comp {
164 dag[v_comp as usize].push(w_comp);
165 }
166 }
167 }
168
169 let nc = num_components as usize;
174 for i in 0..nc {
175 let comp = if effective_mode == ReachabilityMode::In {
176 i
177 } else {
178 nc - 1 - i
179 };
180
181 dag[comp].sort_unstable();
183 dag[comp].dedup();
184
185 let neighbor_comps: Vec<u32> = dag[comp].clone();
188 for &nc_id in &neighbor_comps {
189 let src = reach[nc_id as usize].clone();
191 bitvec_or(&mut reach[comp], &src);
192 }
193 }
194
195 Ok(ReachabilityResult {
196 membership,
197 csize,
198 num_components,
199 reach,
200 })
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 #[test]
208 fn empty_graph() {
209 let g = Graph::with_vertices(0);
210 let r = reachability(&g, ReachabilityMode::Out).unwrap();
211 assert_eq!(r.num_components, 0);
212 assert!(r.reach.is_empty());
213 }
214
215 #[test]
216 fn isolated_vertices() {
217 let g = Graph::with_vertices(5);
218 let r = reachability(&g, ReachabilityMode::Out).unwrap();
219 assert_eq!(r.num_components, 5);
220 for v in 0..5u32 {
221 assert_eq!(r.count_from(v), 1);
222 assert!(r.is_reachable(v, v));
223 }
224 assert!(!r.is_reachable(0, 1));
225 }
226
227 #[test]
228 fn undirected_path() {
229 let mut g = Graph::with_vertices(4);
230 for i in 0..3u32 {
231 g.add_edge(i, i + 1).unwrap();
232 }
233 let r = reachability(&g, ReachabilityMode::All).unwrap();
234 assert_eq!(r.num_components, 1);
235 for u in 0..4u32 {
236 for v in 0..4u32 {
237 assert!(r.is_reachable(u, v));
238 }
239 }
240 }
241
242 #[test]
243 fn undirected_two_components() {
244 let mut g = Graph::with_vertices(5);
245 g.add_edge(0, 1).unwrap();
246 g.add_edge(1, 2).unwrap();
247 g.add_edge(3, 4).unwrap();
248
249 let r = reachability(&g, ReachabilityMode::All).unwrap();
250 assert_eq!(r.num_components, 2);
251 assert!(r.is_reachable(0, 2));
252 assert!(r.is_reachable(2, 0));
253 assert!(r.is_reachable(3, 4));
254 assert!(!r.is_reachable(0, 3));
255 assert!(!r.is_reachable(3, 0));
256 }
257
258 #[test]
259 fn directed_chain_out() {
260 let mut g = Graph::new(4, true).unwrap();
262 for i in 0..3u32 {
263 g.add_edge(i, i + 1).unwrap();
264 }
265 let r = reachability(&g, ReachabilityMode::Out).unwrap();
266 assert_eq!(r.count_from(0), 4);
267 assert_eq!(r.count_from(1), 3);
268 assert_eq!(r.count_from(2), 2);
269 assert_eq!(r.count_from(3), 1);
270
271 assert!(r.is_reachable(0, 3));
272 assert!(!r.is_reachable(3, 0));
273 }
274
275 #[test]
276 fn directed_chain_in() {
277 let mut g = Graph::new(4, true).unwrap();
279 for i in 0..3u32 {
280 g.add_edge(i, i + 1).unwrap();
281 }
282 let r = reachability(&g, ReachabilityMode::In).unwrap();
283 assert_eq!(r.count_from(3), 4);
285 assert_eq!(r.count_from(2), 3);
286 assert_eq!(r.count_from(1), 2);
287 assert_eq!(r.count_from(0), 1);
288
289 assert!(r.is_reachable(3, 0));
290 assert!(!r.is_reachable(0, 3));
291 }
292
293 #[test]
294 fn directed_cycle() {
295 let mut g = Graph::new(3, true).unwrap();
297 g.add_edge(0, 1).unwrap();
298 g.add_edge(1, 2).unwrap();
299 g.add_edge(2, 0).unwrap();
300
301 let r = reachability(&g, ReachabilityMode::Out).unwrap();
302 assert_eq!(r.num_components, 1);
303 for u in 0..3u32 {
304 assert_eq!(r.count_from(u), 3);
305 }
306 }
307
308 #[test]
309 fn directed_diamond() {
310 let mut g = Graph::new(4, true).unwrap();
312 g.add_edge(0, 1).unwrap();
313 g.add_edge(0, 2).unwrap();
314 g.add_edge(1, 3).unwrap();
315 g.add_edge(2, 3).unwrap();
316
317 let r = reachability(&g, ReachabilityMode::Out).unwrap();
318 assert_eq!(r.count_from(0), 4);
319 assert_eq!(r.count_from(1), 2); assert_eq!(r.count_from(2), 2); assert_eq!(r.count_from(3), 1); assert!(r.is_reachable(0, 3));
324 assert!(!r.is_reachable(3, 0));
325 }
326
327 #[test]
328 fn directed_two_sccs_connected() {
329 let mut g = Graph::new(4, true).unwrap();
331 g.add_edge(0, 1).unwrap();
332 g.add_edge(1, 0).unwrap();
333 g.add_edge(2, 3).unwrap();
334 g.add_edge(3, 2).unwrap();
335 g.add_edge(1, 2).unwrap(); let r = reachability(&g, ReachabilityMode::Out).unwrap();
338 assert_eq!(r.count_from(0), 4);
340 assert_eq!(r.count_from(1), 4);
341 assert_eq!(r.count_from(2), 2);
343 assert_eq!(r.count_from(3), 2);
344 }
345
346 #[test]
347 fn self_loop_directed() {
348 let mut g = Graph::new(2, true).unwrap();
349 g.add_edge(0, 0).unwrap();
350 g.add_edge(0, 1).unwrap();
351
352 let r = reachability(&g, ReachabilityMode::Out).unwrap();
353 assert_eq!(r.count_from(0), 2);
354 assert_eq!(r.count_from(1), 1);
355 }
356
357 #[test]
358 fn mode_all_on_directed() {
359 let mut g = Graph::new(3, true).unwrap();
361 g.add_edge(0, 1).unwrap();
362 g.add_edge(1, 2).unwrap();
363
364 let r = reachability(&g, ReachabilityMode::All).unwrap();
365 assert_eq!(r.num_components, 1);
366 for v in 0..3u32 {
367 assert_eq!(r.count_from(v), 3);
368 }
369 }
370
371 #[test]
372 fn large_bitvec_more_than_64_vertices() {
373 let mut g = Graph::new(70, true).unwrap();
375 for i in 0..69u32 {
376 g.add_edge(i, i + 1).unwrap();
377 }
378
379 let r = reachability(&g, ReachabilityMode::Out).unwrap();
380 assert_eq!(r.count_from(0), 70);
381 assert_eq!(r.count_from(69), 1);
382 assert!(r.is_reachable(0, 69));
383 assert!(!r.is_reachable(69, 0));
384 }
385
386 #[test]
387 fn csize_correct() {
388 let mut g = Graph::with_vertices(5);
390 g.add_edge(0, 1).unwrap();
391 g.add_edge(1, 2).unwrap();
392 g.add_edge(3, 4).unwrap();
393
394 let r = reachability(&g, ReachabilityMode::All).unwrap();
395 let mut sizes: Vec<u32> = r.csize.clone();
396 sizes.sort_unstable();
397 assert_eq!(sizes, vec![2, 3]);
398 }
399
400 #[test]
401 fn agrees_with_count_reachable() {
402 use crate::algorithms::connectivity::reachability::count_reachable;
403
404 let mut g = Graph::new(6, true).unwrap();
405 g.add_edge(0, 1).unwrap();
406 g.add_edge(1, 2).unwrap();
407 g.add_edge(2, 0).unwrap();
408 g.add_edge(2, 3).unwrap();
409 g.add_edge(3, 4).unwrap();
410 g.add_edge(5, 3).unwrap();
411
412 let r = reachability(&g, ReachabilityMode::Out).unwrap();
413 let cr = count_reachable(&g).unwrap();
414
415 for v in 0..6u32 {
416 assert_eq!(r.count_from(v), cr[v as usize], "mismatch at vertex {v}");
417 }
418 }
419}