Skip to main content

rust_igraph/algorithms/connectivity/
reachability.rs

1//! Reachability counts (ALGO-CC-020).
2//!
3//! Counterpart of `igraph_count_reachable()` from
4//! `references/igraph/src/connectivity/reachability.c:179`. For each
5//! vertex `v`, returns how many vertices (including `v` itself) are
6//! reachable from `v`.
7//!
8//! Phase-1 minimal slice: BFS-from-each-vertex via the existing
9//! [`crate::algorithms::paths::distances::distances`] primitive. The
10//! O(|V|*(|V|+|E|)) cost is simpler to maintain than upstream's
11//! SCC-condensation approach (O(|C||V|/w + |V| + |E|), where |C| is
12//! the number of strongly connected components and w is the word size).
13//! A perf pass with the SCC trick can land later.
14//!
15//! Edge directions follow the same rule as `distances`: out-edges only
16//! for directed graphs (matching upstream's `IGRAPH_OUT` mode default).
17
18use crate::algorithms::paths::distances::distances;
19use crate::core::{Graph, IgraphResult};
20
21/// Per-vertex count of reachable vertices (including the vertex itself).
22///
23/// Counterpart of `igraph_count_reachable(_, _, IGRAPH_OUT)` for
24/// directed graphs and `IGRAPH_ALL` for undirected.
25///
26/// # Examples
27///
28/// ```
29/// use rust_igraph::{Graph, count_reachable};
30///
31/// // Path 0-1-2-3 undirected: every vertex reaches every vertex.
32/// let mut g = Graph::with_vertices(4);
33/// for i in 0..3 { g.add_edge(i, i + 1).unwrap(); }
34/// assert_eq!(count_reachable(&g).unwrap(), vec![4, 4, 4, 4]);
35///
36/// // Directed chain 0 -> 1 -> 2: 0 reaches all 3, 1 reaches 2, 2 reaches just itself.
37/// let mut g = Graph::new(3, true).unwrap();
38/// g.add_edge(0, 1).unwrap();
39/// g.add_edge(1, 2).unwrap();
40/// assert_eq!(count_reachable(&g).unwrap(), vec![3, 2, 1]);
41/// ```
42pub fn count_reachable(graph: &Graph) -> IgraphResult<Vec<u32>> {
43    let n = graph.vcount();
44    let mut out = Vec::with_capacity(n as usize);
45    for v in 0..n {
46        let d = distances(graph, v)?;
47        let count = u32::try_from(d.iter().filter(|x| x.is_some()).count())
48            .map_err(|_| crate::core::IgraphError::Internal("reachable count exceeds u32"))?;
49        out.push(count);
50    }
51    Ok(out)
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn empty_graph_yields_empty_counts() {
60        let g = Graph::with_vertices(0);
61        assert!(count_reachable(&g).unwrap().is_empty());
62    }
63
64    #[test]
65    fn isolated_vertices_each_reach_only_themselves() {
66        let g = Graph::with_vertices(5);
67        assert_eq!(count_reachable(&g).unwrap(), vec![1; 5]);
68    }
69
70    #[test]
71    fn undirected_path_each_reaches_all() {
72        let mut g = Graph::with_vertices(4);
73        for i in 0..3 {
74            g.add_edge(i, i + 1).unwrap();
75        }
76        assert_eq!(count_reachable(&g).unwrap(), vec![4, 4, 4, 4]);
77    }
78
79    #[test]
80    fn undirected_two_components() {
81        // {0,1,2} and {3,4}: each vertex reaches its component.
82        let mut g = Graph::with_vertices(5);
83        g.add_edge(0, 1).unwrap();
84        g.add_edge(1, 2).unwrap();
85        g.add_edge(3, 4).unwrap();
86        assert_eq!(count_reachable(&g).unwrap(), vec![3, 3, 3, 2, 2]);
87    }
88
89    #[test]
90    fn directed_chain_each_only_reaches_downstream() {
91        // 0 -> 1 -> 2 -> 3.
92        let mut g = Graph::new(4, true).unwrap();
93        g.add_edge(0, 1).unwrap();
94        g.add_edge(1, 2).unwrap();
95        g.add_edge(2, 3).unwrap();
96        assert_eq!(count_reachable(&g).unwrap(), vec![4, 3, 2, 1]);
97    }
98
99    #[test]
100    fn directed_cycle_all_reach_all() {
101        // 0 -> 1 -> 2 -> 0: each vertex reaches all 3.
102        let mut g = Graph::new(3, true).unwrap();
103        g.add_edge(0, 1).unwrap();
104        g.add_edge(1, 2).unwrap();
105        g.add_edge(2, 0).unwrap();
106        assert_eq!(count_reachable(&g).unwrap(), vec![3, 3, 3]);
107    }
108
109    #[test]
110    fn self_loop_does_not_inflate_count() {
111        let mut g = Graph::with_vertices(2);
112        g.add_edge(0, 0).unwrap();
113        g.add_edge(0, 1).unwrap();
114        assert_eq!(count_reachable(&g).unwrap(), vec![2, 2]);
115    }
116}