use alloc::vec::Vec;
use num_traits::AsPrimitive;
use crate::traits::{
MonopartiteGraph, PositiveInteger, SparseMatrix2D, UndirectedMonopartiteEdges,
UndirectedMonopartiteMonoplexGraph,
};
pub type BiconnectedEdge<NodeId> = [NodeId; 2];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BiconnectedComponentsResult<NodeId: PositiveInteger> {
edge_biconnected_components: Vec<Vec<BiconnectedEdge<NodeId>>>,
vertex_biconnected_components: Vec<Vec<NodeId>>,
articulation_points: Vec<NodeId>,
bridges: Vec<BiconnectedEdge<NodeId>>,
vertices_without_biconnected_component: Vec<NodeId>,
cyclic_biconnected_component_ids: Vec<usize>,
number_of_connected_components: usize,
is_biconnected: bool,
}
impl<NodeId: PositiveInteger> BiconnectedComponentsResult<NodeId> {
#[inline]
#[must_use]
pub fn number_of_biconnected_components(&self) -> usize {
self.edge_biconnected_components.len()
}
#[inline]
#[must_use]
pub fn number_of_connected_components(&self) -> usize {
self.number_of_connected_components
}
#[inline]
#[must_use]
pub fn is_biconnected(&self) -> bool {
self.is_biconnected
}
#[inline]
pub fn edge_biconnected_components(
&self,
) -> core::slice::Iter<'_, Vec<BiconnectedEdge<NodeId>>> {
self.edge_biconnected_components.iter()
}
#[inline]
pub fn vertex_biconnected_components(&self) -> core::slice::Iter<'_, Vec<NodeId>> {
self.vertex_biconnected_components.iter()
}
#[inline]
#[must_use]
pub fn edge_biconnected_component(&self, component_id: usize) -> &[BiconnectedEdge<NodeId>] {
&self.edge_biconnected_components[component_id]
}
#[inline]
#[must_use]
pub fn vertex_biconnected_component(&self, component_id: usize) -> &[NodeId] {
&self.vertex_biconnected_components[component_id]
}
#[inline]
pub fn articulation_points(&self) -> core::iter::Copied<core::slice::Iter<'_, NodeId>> {
self.articulation_points.iter().copied()
}
#[inline]
pub fn bridges(&self) -> core::iter::Copied<core::slice::Iter<'_, BiconnectedEdge<NodeId>>> {
self.bridges.iter().copied()
}
#[inline]
pub fn vertices_without_biconnected_component(
&self,
) -> core::iter::Copied<core::slice::Iter<'_, NodeId>> {
self.vertices_without_biconnected_component.iter().copied()
}
#[inline]
pub fn cyclic_biconnected_component_ids(
&self,
) -> core::iter::Copied<core::slice::Iter<'_, usize>> {
self.cyclic_biconnected_component_ids.iter().copied()
}
}
type NeighborIterator<'graph, G> = <<<G as UndirectedMonopartiteMonoplexGraph>::UndirectedMonopartiteEdges as UndirectedMonopartiteEdges>::SymmetricSquaredMatrix as SparseMatrix2D>::SparseRow<'graph>;
struct DfsFrame<'graph, G: UndirectedMonopartiteMonoplexGraph + ?Sized>
where
<<G as UndirectedMonopartiteMonoplexGraph>::UndirectedMonopartiteEdges as UndirectedMonopartiteEdges>::SymmetricSquaredMatrix:
'graph,
{
node: G::NodeId,
parent: Option<G::NodeId>,
neighbors: NeighborIterator<'graph, G>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
struct NormalizedComponent<NodeId: PositiveInteger> {
edges: Vec<BiconnectedEdge<NodeId>>,
vertices: Vec<NodeId>,
}
#[inline]
fn normalize_edge<NodeId: PositiveInteger>(left: NodeId, right: NodeId) -> BiconnectedEdge<NodeId> {
if left <= right { [left, right] } else { [right, left] }
}
#[inline]
fn pop_component_until<NodeId: PositiveInteger>(
edge_stack: &mut Vec<BiconnectedEdge<NodeId>>,
stop_edge: Option<BiconnectedEdge<NodeId>>,
) -> Option<Vec<BiconnectedEdge<NodeId>>> {
let mut component = Vec::new();
while let Some(edge) = edge_stack.pop() {
component.push(edge);
if Some(edge) == stop_edge {
break;
}
}
if component.is_empty() {
None
} else {
component.sort_unstable();
component.dedup();
Some(component)
}
}
#[inline]
fn normalize_component<NodeId: PositiveInteger>(
mut edges: Vec<BiconnectedEdge<NodeId>>,
) -> NormalizedComponent<NodeId> {
edges.sort_unstable();
edges.dedup();
let mut vertices = edges.iter().flat_map(|edge| [edge[0], edge[1]]).collect::<Vec<_>>();
vertices.sort_unstable();
vertices.dedup();
NormalizedComponent { edges, vertices }
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum BiconnectedComponentsError {
#[error(
"The biconnected-components algorithm currently supports only simple undirected graphs and does not accept self-loops."
)]
SelfLoopsUnsupported,
#[error(
"The biconnected-components algorithm currently supports only simple undirected graphs and does not accept parallel edges."
)]
ParallelEdgesUnsupported,
}
impl From<BiconnectedComponentsError>
for crate::errors::monopartite_graph_error::algorithms::MonopartiteAlgorithmError
{
#[inline]
fn from(error: BiconnectedComponentsError) -> Self {
Self::BiconnectedComponentsError(error)
}
}
impl<G: MonopartiteGraph> From<BiconnectedComponentsError> for crate::errors::MonopartiteError<G> {
#[inline]
fn from(error: BiconnectedComponentsError) -> Self {
Self::AlgorithmError(error.into())
}
}
pub trait BiconnectedComponents: UndirectedMonopartiteMonoplexGraph {
#[inline]
#[allow(clippy::too_many_lines)]
fn biconnected_components(
&self,
) -> Result<BiconnectedComponentsResult<Self::NodeId>, crate::errors::MonopartiteError<Self>>
where
Self: Sized,
{
if self.has_self_loops() {
return Err(BiconnectedComponentsError::SelfLoopsUnsupported.into());
}
let number_of_nodes = self.number_of_nodes().as_();
let mut discovery_time: Vec<Option<usize>> = vec![None; number_of_nodes];
let mut low: Vec<usize> = vec![0; number_of_nodes];
let mut connected_component_count = 0usize;
let mut dfs_time = 0usize;
let mut edge_stack: Vec<BiconnectedEdge<Self::NodeId>> = Vec::new();
let mut raw_components: Vec<Vec<BiconnectedEdge<Self::NodeId>>> = Vec::new();
let mut dfs_stack: Vec<DfsFrame<'_, Self>> = Vec::new();
for root in self.node_ids() {
if discovery_time[root.as_()].is_some() {
continue;
}
connected_component_count += 1;
discovery_time[root.as_()] = Some(dfs_time);
low[root.as_()] = dfs_time;
dfs_time += 1;
dfs_stack.push(DfsFrame { node: root, parent: None, neighbors: self.neighbors(root) });
while !dfs_stack.is_empty() {
let maybe_neighbor = {
let frame = dfs_stack.last_mut().unwrap();
frame.neighbors.next()
};
if let Some(neighbor) = maybe_neighbor {
let node = dfs_stack.last().unwrap().node;
let parent = dfs_stack.last().unwrap().parent;
if discovery_time[neighbor.as_()].is_none() {
discovery_time[neighbor.as_()] = Some(dfs_time);
low[neighbor.as_()] = dfs_time;
dfs_time += 1;
edge_stack.push(normalize_edge(node, neighbor));
dfs_stack.push(DfsFrame {
node: neighbor,
parent: Some(node),
neighbors: self.neighbors(neighbor),
});
continue;
}
if Some(neighbor) != parent
&& discovery_time[neighbor.as_()].unwrap()
<= discovery_time[node.as_()].unwrap()
{
low[node.as_()] =
low[node.as_()].min(discovery_time[neighbor.as_()].unwrap());
edge_stack.push(normalize_edge(node, neighbor));
}
continue;
}
let frame = dfs_stack.pop().unwrap();
let node = frame.node;
if let Some(parent) = frame.parent {
let parent_index = parent.as_();
let node_index = node.as_();
low[parent_index] = low[parent_index].min(low[node_index]);
if low[node_index] >= discovery_time[parent_index].unwrap()
&& let Some(component) =
pop_component_until(&mut edge_stack, Some(normalize_edge(parent, node)))
{
raw_components.push(component);
}
}
}
if let Some(component) = pop_component_until(&mut edge_stack, None) {
raw_components.push(component);
}
}
let mut components: Vec<NormalizedComponent<Self::NodeId>> =
raw_components.into_iter().map(normalize_component).collect();
components.sort_unstable();
let mut edge_biconnected_components = Vec::with_capacity(components.len());
let mut vertex_biconnected_components = Vec::with_capacity(components.len());
let mut covered_vertices = vec![false; number_of_nodes];
let mut articulation_memberships = vec![0usize; number_of_nodes];
let mut bridges = Vec::new();
let mut cyclic_biconnected_component_ids = Vec::new();
for (component_id, component) in components.into_iter().enumerate() {
if component.edges.len() == 1 {
bridges.push(component.edges[0]);
}
if !component.edges.is_empty() && component.edges.len() >= component.vertices.len() {
cyclic_biconnected_component_ids.push(component_id);
}
for &vertex in &component.vertices {
covered_vertices[vertex.as_()] = true;
articulation_memberships[vertex.as_()] += 1;
}
edge_biconnected_components.push(component.edges);
vertex_biconnected_components.push(component.vertices);
}
let articulation_points: Vec<Self::NodeId> =
self.node_ids().filter(|&vertex| articulation_memberships[vertex.as_()] > 1).collect();
let vertices_without_biconnected_component: Vec<Self::NodeId> =
self.node_ids().filter(|&vertex| !covered_vertices[vertex.as_()]).collect();
let is_biconnected = number_of_nodes >= 2
&& connected_component_count == 1
&& articulation_points.is_empty();
Ok(BiconnectedComponentsResult {
edge_biconnected_components,
vertex_biconnected_components,
articulation_points,
bridges,
vertices_without_biconnected_component,
cyclic_biconnected_component_ids,
number_of_connected_components: connected_component_count,
is_biconnected,
})
}
#[inline]
fn is_biconnected(&self) -> Result<bool, crate::errors::MonopartiteError<Self>>
where
Self: Sized,
{
Ok(self.biconnected_components()?.is_biconnected())
}
}
impl<G: ?Sized + UndirectedMonopartiteMonoplexGraph> BiconnectedComponents for G {}