rust_igraph/algorithms/connectivity/
articulation.rs1use crate::core::graph::EdgeId;
18use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
19
20pub fn articulation_points(graph: &Graph) -> IgraphResult<Vec<VertexId>> {
44 let n = graph.vcount();
45 if n == 0 {
46 return Ok(Vec::new());
47 }
48 let n_us = n as usize;
49
50 let mut num: Vec<u32> = vec![0; n_us];
54 let mut low: Vec<u32> = vec![0; n_us];
55 let mut nextptr: Vec<usize> = vec![0; n_us];
57 let mut found: Vec<bool> = vec![false; n_us];
60 let mut path: Vec<VertexId> = Vec::new();
62 let mut articulation: Vec<VertexId> = Vec::new();
64 let mut counter: u32 = 1;
65
66 let mut inc: Vec<Vec<EdgeId>> = Vec::with_capacity(n_us);
69 for v in 0..n {
70 inc.push(graph.incident(v)?);
71 }
72
73 for i in 0..n {
74 if low[i as usize] != 0 {
75 continue; }
77 path.push(i);
78 let mut rootdfs: u32 = 0;
79 num[i as usize] = counter;
80 low[i as usize] = counter;
81 counter = counter
82 .checked_add(1)
83 .ok_or(IgraphError::Internal("DFS counter overflow"))?;
84
85 while let Some(&act) = path.last() {
86 let act_us = act as usize;
87 let nbrs = &inc[act_us];
88 let actnext = nextptr[act_us];
89
90 if actnext < nbrs.len() {
91 let edge = nbrs[actnext];
93 let nei = graph.edge_other(edge, act)?;
94 if low[nei as usize] == 0 {
95 if act == i {
96 rootdfs = rootdfs.saturating_add(1);
97 }
98 path.push(nei);
99 num[nei as usize] = counter;
100 low[nei as usize] = counter;
101 counter = counter
102 .checked_add(1)
103 .ok_or(IgraphError::Internal("DFS counter overflow"))?;
104 } else if num[nei as usize] < low[act_us] {
105 low[act_us] = num[nei as usize];
106 }
107 nextptr[act_us] += 1;
108 } else {
109 path.pop();
111 if let Some(&prev) = path.last() {
112 let prev_us = prev as usize;
113 if low[act_us] < low[prev_us] {
114 low[prev_us] = low[act_us];
115 }
116 if low[act_us] >= num[prev_us] && !found[prev_us] && prev != i {
117 articulation.push(prev);
118 found[prev_us] = true;
119 }
120 }
121 }
122 }
123
124 if rootdfs >= 2 {
125 articulation.push(i);
126 }
128 }
129
130 Ok(articulation)
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 fn ap_sorted(g: &Graph) -> Vec<VertexId> {
138 let mut ap = articulation_points(g).unwrap();
139 ap.sort_unstable();
140 ap
141 }
142
143 #[test]
144 fn empty_graph_has_no_articulation_points() {
145 let g = Graph::with_vertices(0);
146 assert!(articulation_points(&g).unwrap().is_empty());
147 }
148
149 #[test]
150 fn isolated_vertices_have_no_articulation_points() {
151 let g = Graph::with_vertices(5);
152 assert!(articulation_points(&g).unwrap().is_empty());
153 }
154
155 #[test]
156 fn cycle_has_no_articulation_points() {
157 let mut g = Graph::with_vertices(4);
159 g.add_edge(0, 1).unwrap();
160 g.add_edge(1, 2).unwrap();
161 g.add_edge(2, 3).unwrap();
162 g.add_edge(3, 0).unwrap();
163 assert!(articulation_points(&g).unwrap().is_empty());
164 }
165
166 #[test]
167 fn path_endpoints_are_not_articulation_points() {
168 let mut g = Graph::with_vertices(5);
170 for i in 0..4 {
171 g.add_edge(i, i + 1).unwrap();
172 }
173 assert_eq!(ap_sorted(&g), vec![1, 2, 3]);
174 }
175
176 #[test]
177 fn star_centre_is_only_articulation_point() {
178 let mut g = Graph::with_vertices(5);
180 for v in 1..5 {
181 g.add_edge(0, v).unwrap();
182 }
183 assert_eq!(ap_sorted(&g), vec![0]);
184 }
185
186 #[test]
187 fn cycle_with_pendant_has_attachment_and_internal_pendant_as_aps() {
188 let mut g = Graph::with_vertices(5);
190 g.add_edge(0, 1).unwrap();
191 g.add_edge(1, 2).unwrap();
192 g.add_edge(2, 0).unwrap();
193 g.add_edge(2, 3).unwrap();
194 g.add_edge(3, 4).unwrap();
195 assert_eq!(ap_sorted(&g), vec![2, 3]);
196 }
197
198 #[test]
199 fn multiple_components_each_contributes_independently() {
200 let mut g = Graph::with_vertices(6);
202 g.add_edge(0, 1).unwrap();
203 g.add_edge(1, 2).unwrap();
204 g.add_edge(3, 4).unwrap();
205 g.add_edge(4, 5).unwrap();
206 assert_eq!(ap_sorted(&g), vec![1, 4]);
207 }
208
209 #[test]
210 fn upstream_biconnected_test_fixture_yields_5_and_2() {
211 let mut g = Graph::with_vertices(10);
215 for &(u, v) in &[
216 (0, 1),
217 (1, 2),
218 (2, 3),
219 (3, 0),
220 (2, 4),
221 (4, 5),
222 (5, 2),
223 (5, 6),
224 (7, 8),
225 ] {
226 g.add_edge(u, v).unwrap();
227 }
228 assert_eq!(ap_sorted(&g), vec![2, 5]);
229 }
230
231 #[test]
232 fn self_loop_does_not_create_articulation() {
233 let mut g = Graph::with_vertices(3);
235 g.add_edge(0, 0).unwrap();
236 g.add_edge(0, 1).unwrap();
237 g.add_edge(1, 2).unwrap();
238 g.add_edge(2, 0).unwrap();
239 assert!(articulation_points(&g).unwrap().is_empty());
240 }
241
242 #[test]
243 fn parallel_edges_collapse_to_single_link_for_articulation() {
244 let mut g = Graph::with_vertices(3);
247 g.add_edge(0, 1).unwrap();
248 g.add_edge(0, 1).unwrap();
249 g.add_edge(0, 1).unwrap();
250 g.add_edge(1, 2).unwrap();
251 assert_eq!(ap_sorted(&g), vec![1]);
252 }
253
254 #[test]
255 fn two_triangles_sharing_a_vertex_make_that_vertex_articulation() {
256 let mut g = Graph::with_vertices(5);
258 g.add_edge(0, 1).unwrap();
259 g.add_edge(1, 2).unwrap();
260 g.add_edge(2, 0).unwrap();
261 g.add_edge(2, 3).unwrap();
262 g.add_edge(3, 4).unwrap();
263 g.add_edge(4, 2).unwrap();
264 assert_eq!(ap_sorted(&g), vec![2]);
265 }
266}