use crate::core::graph::EdgeId;
use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
pub fn articulation_points(graph: &Graph) -> IgraphResult<Vec<VertexId>> {
let n = graph.vcount();
if n == 0 {
return Ok(Vec::new());
}
let n_us = n as usize;
let mut num: Vec<u32> = vec![0; n_us];
let mut low: Vec<u32> = vec![0; n_us];
let mut nextptr: Vec<usize> = vec![0; n_us];
let mut found: Vec<bool> = vec![false; n_us];
let mut path: Vec<VertexId> = Vec::new();
let mut articulation: Vec<VertexId> = Vec::new();
let mut counter: u32 = 1;
let mut inc: Vec<Vec<EdgeId>> = Vec::with_capacity(n_us);
for v in 0..n {
inc.push(graph.incident(v)?);
}
for i in 0..n {
if low[i as usize] != 0 {
continue; }
path.push(i);
let mut rootdfs: u32 = 0;
num[i as usize] = counter;
low[i as usize] = counter;
counter = counter
.checked_add(1)
.ok_or(IgraphError::Internal("DFS counter overflow"))?;
while let Some(&act) = path.last() {
let act_us = act as usize;
let nbrs = &inc[act_us];
let actnext = nextptr[act_us];
if actnext < nbrs.len() {
let edge = nbrs[actnext];
let nei = graph.edge_other(edge, act)?;
if low[nei as usize] == 0 {
if act == i {
rootdfs = rootdfs.saturating_add(1);
}
path.push(nei);
num[nei as usize] = counter;
low[nei as usize] = counter;
counter = counter
.checked_add(1)
.ok_or(IgraphError::Internal("DFS counter overflow"))?;
} else if num[nei as usize] < low[act_us] {
low[act_us] = num[nei as usize];
}
nextptr[act_us] += 1;
} else {
path.pop();
if let Some(&prev) = path.last() {
let prev_us = prev as usize;
if low[act_us] < low[prev_us] {
low[prev_us] = low[act_us];
}
if low[act_us] >= num[prev_us] && !found[prev_us] && prev != i {
articulation.push(prev);
found[prev_us] = true;
}
}
}
}
if rootdfs >= 2 {
articulation.push(i);
}
}
Ok(articulation)
}
#[cfg(test)]
mod tests {
use super::*;
fn ap_sorted(g: &Graph) -> Vec<VertexId> {
let mut ap = articulation_points(g).unwrap();
ap.sort_unstable();
ap
}
#[test]
fn empty_graph_has_no_articulation_points() {
let g = Graph::with_vertices(0);
assert!(articulation_points(&g).unwrap().is_empty());
}
#[test]
fn isolated_vertices_have_no_articulation_points() {
let g = Graph::with_vertices(5);
assert!(articulation_points(&g).unwrap().is_empty());
}
#[test]
fn cycle_has_no_articulation_points() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
g.add_edge(3, 0).unwrap();
assert!(articulation_points(&g).unwrap().is_empty());
}
#[test]
fn path_endpoints_are_not_articulation_points() {
let mut g = Graph::with_vertices(5);
for i in 0..4 {
g.add_edge(i, i + 1).unwrap();
}
assert_eq!(ap_sorted(&g), vec![1, 2, 3]);
}
#[test]
fn star_centre_is_only_articulation_point() {
let mut g = Graph::with_vertices(5);
for v in 1..5 {
g.add_edge(0, v).unwrap();
}
assert_eq!(ap_sorted(&g), vec![0]);
}
#[test]
fn cycle_with_pendant_has_attachment_and_internal_pendant_as_aps() {
let mut g = Graph::with_vertices(5);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
g.add_edge(2, 3).unwrap();
g.add_edge(3, 4).unwrap();
assert_eq!(ap_sorted(&g), vec![2, 3]);
}
#[test]
fn multiple_components_each_contributes_independently() {
let mut g = Graph::with_vertices(6);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(3, 4).unwrap();
g.add_edge(4, 5).unwrap();
assert_eq!(ap_sorted(&g), vec![1, 4]);
}
#[test]
fn upstream_biconnected_test_fixture_yields_5_and_2() {
let mut g = Graph::with_vertices(10);
for &(u, v) in &[
(0, 1),
(1, 2),
(2, 3),
(3, 0),
(2, 4),
(4, 5),
(5, 2),
(5, 6),
(7, 8),
] {
g.add_edge(u, v).unwrap();
}
assert_eq!(ap_sorted(&g), vec![2, 5]);
}
#[test]
fn self_loop_does_not_create_articulation() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 0).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
assert!(articulation_points(&g).unwrap().is_empty());
}
#[test]
fn parallel_edges_collapse_to_single_link_for_articulation() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
assert_eq!(ap_sorted(&g), vec![1]);
}
#[test]
fn two_triangles_sharing_a_vertex_make_that_vertex_articulation() {
let mut g = Graph::with_vertices(5);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
g.add_edge(2, 3).unwrap();
g.add_edge(3, 4).unwrap();
g.add_edge(4, 2).unwrap();
assert_eq!(ap_sorted(&g), vec![2]);
}
}