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 {
60 let comp = self.membership[source as usize] as usize;
61 let word = target as usize / 64;
62 let bit = target as usize % 64;
63 if word >= self.reach[comp].len() {
64 return false;
65 }
66 (self.reach[comp][word] >> bit) & 1 == 1
67 }
68
69 pub fn count_from(&self, v: VertexId) -> u32 {
84 let comp = self.membership[v as usize] as usize;
85 let mut count = 0u32;
86 for &word in &self.reach[comp] {
87 count = count.wrapping_add(word.count_ones());
88 }
89 count
90 }
91}
92
93fn bitvec_words(n: u32) -> usize {
94 (n as usize).div_ceil(64)
95}
96
97fn bitvec_set(bv: &mut [u64], bit: u32) {
98 let w = bit as usize / 64;
99 let b = bit as usize % 64;
100 bv[w] |= 1u64 << b;
101}
102
103fn bitvec_or(dst: &mut [u64], src: &[u64]) {
104 for (d, s) in dst.iter_mut().zip(src.iter()) {
105 *d |= *s;
106 }
107}
108
109pub fn reachability(graph: &Graph, mode: ReachabilityMode) -> IgraphResult<ReachabilityResult> {
134 let n = graph.vcount();
135 let words = bitvec_words(n);
136
137 let effective_mode = if graph.is_directed() {
138 mode
139 } else {
140 ReachabilityMode::All
141 };
142
143 let (membership, num_components) = if effective_mode == ReachabilityMode::All {
145 let cc = connected_components(graph)?;
146 (cc.membership, cc.count)
147 } else {
148 let scc = strongly_connected_components(graph)?;
149 (scc.membership, scc.count)
150 };
151
152 let mut csize = vec![0u32; num_components as usize];
154 for &m in &membership {
155 csize[m as usize] = csize[m as usize]
156 .checked_add(1)
157 .ok_or(IgraphError::Internal("component size overflow"))?;
158 }
159
160 let mut reach = vec![vec![0u64; words]; num_components as usize];
162 for v in 0..n {
163 bitvec_set(&mut reach[membership[v as usize] as usize], v);
164 }
165
166 if effective_mode == ReachabilityMode::All {
168 return Ok(ReachabilityResult {
169 membership,
170 csize,
171 num_components,
172 reach,
173 });
174 }
175
176 let mut dag: Vec<Vec<u32>> = vec![Vec::new(); num_components as usize];
179
180 for v in 0..n {
181 let v_comp = membership[v as usize];
182 let neighbors = match effective_mode {
183 ReachabilityMode::Out => graph.out_neighbors_vec(v)?,
184 ReachabilityMode::In => graph.in_neighbors_vec(v)?,
185 ReachabilityMode::All => unreachable!(),
186 };
187 for w in neighbors {
188 let w_comp = membership[w as usize];
189 if v_comp != w_comp {
190 dag[v_comp as usize].push(w_comp);
191 }
192 }
193 }
194
195 let nc = num_components as usize;
200 for i in 0..nc {
201 let comp = if effective_mode == ReachabilityMode::In {
202 i
203 } else {
204 nc - 1 - i
205 };
206
207 dag[comp].sort_unstable();
209 dag[comp].dedup();
210
211 let neighbor_comps: Vec<u32> = dag[comp].clone();
214 for &nc_id in &neighbor_comps {
215 let src = reach[nc_id as usize].clone();
217 bitvec_or(&mut reach[comp], &src);
218 }
219 }
220
221 Ok(ReachabilityResult {
222 membership,
223 csize,
224 num_components,
225 reach,
226 })
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232
233 #[test]
234 fn empty_graph() {
235 let g = Graph::with_vertices(0);
236 let r = reachability(&g, ReachabilityMode::Out).unwrap();
237 assert_eq!(r.num_components, 0);
238 assert!(r.reach.is_empty());
239 }
240
241 #[test]
242 fn isolated_vertices() {
243 let g = Graph::with_vertices(5);
244 let r = reachability(&g, ReachabilityMode::Out).unwrap();
245 assert_eq!(r.num_components, 5);
246 for v in 0..5u32 {
247 assert_eq!(r.count_from(v), 1);
248 assert!(r.is_reachable(v, v));
249 }
250 assert!(!r.is_reachable(0, 1));
251 }
252
253 #[test]
254 fn undirected_path() {
255 let mut g = Graph::with_vertices(4);
256 for i in 0..3u32 {
257 g.add_edge(i, i + 1).unwrap();
258 }
259 let r = reachability(&g, ReachabilityMode::All).unwrap();
260 assert_eq!(r.num_components, 1);
261 for u in 0..4u32 {
262 for v in 0..4u32 {
263 assert!(r.is_reachable(u, v));
264 }
265 }
266 }
267
268 #[test]
269 fn undirected_two_components() {
270 let mut g = Graph::with_vertices(5);
271 g.add_edge(0, 1).unwrap();
272 g.add_edge(1, 2).unwrap();
273 g.add_edge(3, 4).unwrap();
274
275 let r = reachability(&g, ReachabilityMode::All).unwrap();
276 assert_eq!(r.num_components, 2);
277 assert!(r.is_reachable(0, 2));
278 assert!(r.is_reachable(2, 0));
279 assert!(r.is_reachable(3, 4));
280 assert!(!r.is_reachable(0, 3));
281 assert!(!r.is_reachable(3, 0));
282 }
283
284 #[test]
285 fn directed_chain_out() {
286 let mut g = Graph::new(4, true).unwrap();
288 for i in 0..3u32 {
289 g.add_edge(i, i + 1).unwrap();
290 }
291 let r = reachability(&g, ReachabilityMode::Out).unwrap();
292 assert_eq!(r.count_from(0), 4);
293 assert_eq!(r.count_from(1), 3);
294 assert_eq!(r.count_from(2), 2);
295 assert_eq!(r.count_from(3), 1);
296
297 assert!(r.is_reachable(0, 3));
298 assert!(!r.is_reachable(3, 0));
299 }
300
301 #[test]
302 fn directed_chain_in() {
303 let mut g = Graph::new(4, true).unwrap();
305 for i in 0..3u32 {
306 g.add_edge(i, i + 1).unwrap();
307 }
308 let r = reachability(&g, ReachabilityMode::In).unwrap();
309 assert_eq!(r.count_from(3), 4);
311 assert_eq!(r.count_from(2), 3);
312 assert_eq!(r.count_from(1), 2);
313 assert_eq!(r.count_from(0), 1);
314
315 assert!(r.is_reachable(3, 0));
316 assert!(!r.is_reachable(0, 3));
317 }
318
319 #[test]
320 fn directed_cycle() {
321 let mut g = Graph::new(3, true).unwrap();
323 g.add_edge(0, 1).unwrap();
324 g.add_edge(1, 2).unwrap();
325 g.add_edge(2, 0).unwrap();
326
327 let r = reachability(&g, ReachabilityMode::Out).unwrap();
328 assert_eq!(r.num_components, 1);
329 for u in 0..3u32 {
330 assert_eq!(r.count_from(u), 3);
331 }
332 }
333
334 #[test]
335 fn directed_diamond() {
336 let mut g = Graph::new(4, true).unwrap();
338 g.add_edge(0, 1).unwrap();
339 g.add_edge(0, 2).unwrap();
340 g.add_edge(1, 3).unwrap();
341 g.add_edge(2, 3).unwrap();
342
343 let r = reachability(&g, ReachabilityMode::Out).unwrap();
344 assert_eq!(r.count_from(0), 4);
345 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));
350 assert!(!r.is_reachable(3, 0));
351 }
352
353 #[test]
354 fn directed_two_sccs_connected() {
355 let mut g = Graph::new(4, true).unwrap();
357 g.add_edge(0, 1).unwrap();
358 g.add_edge(1, 0).unwrap();
359 g.add_edge(2, 3).unwrap();
360 g.add_edge(3, 2).unwrap();
361 g.add_edge(1, 2).unwrap(); let r = reachability(&g, ReachabilityMode::Out).unwrap();
364 assert_eq!(r.count_from(0), 4);
366 assert_eq!(r.count_from(1), 4);
367 assert_eq!(r.count_from(2), 2);
369 assert_eq!(r.count_from(3), 2);
370 }
371
372 #[test]
373 fn self_loop_directed() {
374 let mut g = Graph::new(2, true).unwrap();
375 g.add_edge(0, 0).unwrap();
376 g.add_edge(0, 1).unwrap();
377
378 let r = reachability(&g, ReachabilityMode::Out).unwrap();
379 assert_eq!(r.count_from(0), 2);
380 assert_eq!(r.count_from(1), 1);
381 }
382
383 #[test]
384 fn mode_all_on_directed() {
385 let mut g = Graph::new(3, true).unwrap();
387 g.add_edge(0, 1).unwrap();
388 g.add_edge(1, 2).unwrap();
389
390 let r = reachability(&g, ReachabilityMode::All).unwrap();
391 assert_eq!(r.num_components, 1);
392 for v in 0..3u32 {
393 assert_eq!(r.count_from(v), 3);
394 }
395 }
396
397 #[test]
398 fn large_bitvec_more_than_64_vertices() {
399 let mut g = Graph::new(70, true).unwrap();
401 for i in 0..69u32 {
402 g.add_edge(i, i + 1).unwrap();
403 }
404
405 let r = reachability(&g, ReachabilityMode::Out).unwrap();
406 assert_eq!(r.count_from(0), 70);
407 assert_eq!(r.count_from(69), 1);
408 assert!(r.is_reachable(0, 69));
409 assert!(!r.is_reachable(69, 0));
410 }
411
412 #[test]
413 fn csize_correct() {
414 let mut g = Graph::with_vertices(5);
416 g.add_edge(0, 1).unwrap();
417 g.add_edge(1, 2).unwrap();
418 g.add_edge(3, 4).unwrap();
419
420 let r = reachability(&g, ReachabilityMode::All).unwrap();
421 let mut sizes: Vec<u32> = r.csize.clone();
422 sizes.sort_unstable();
423 assert_eq!(sizes, vec![2, 3]);
424 }
425
426 #[test]
427 fn agrees_with_count_reachable() {
428 use crate::algorithms::connectivity::reachability::count_reachable;
429
430 let mut g = Graph::new(6, true).unwrap();
431 g.add_edge(0, 1).unwrap();
432 g.add_edge(1, 2).unwrap();
433 g.add_edge(2, 0).unwrap();
434 g.add_edge(2, 3).unwrap();
435 g.add_edge(3, 4).unwrap();
436 g.add_edge(5, 3).unwrap();
437
438 let r = reachability(&g, ReachabilityMode::Out).unwrap();
439 let cr = count_reachable(&g).unwrap();
440
441 for v in 0..6u32 {
442 assert_eq!(r.count_from(v), cr[v as usize], "mismatch at vertex {v}");
443 }
444 }
445}