weavatrix-graph 0.6.0

Deterministic, evidence-carrying graph primitives for repository intelligence
Documentation
mod stoer_wagner;

pub use stoer_wagner::{StoerWagnerCut, stoer_wagner_min_cut, stoer_wagner_min_cut_filtered};

use crate::IndexUndirectedGraphView;
use crate::Vec;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UndirectedCuts<Node, Edge> {
    bridges: Vec<Edge>,
    articulation_points: Vec<Node>,
}

impl<Node, Edge> UndirectedCuts<Node, Edge> {
    #[must_use]
    pub fn bridges(&self) -> &[Edge] {
        &self.bridges
    }

    #[must_use]
    pub fn articulation_points(&self) -> &[Node] {
        &self.articulation_points
    }
}

pub fn bridges_and_articulation_points<G>(graph: &G) -> UndirectedCuts<G::Node, G::Edge>
where
    G: IndexUndirectedGraphView,
{
    let mut state = CutState::<G>::new(graph);
    let mut nodes = graph.node_indices().collect::<Vec<_>>();
    nodes.sort_unstable_by_key(|node| G::node_slot(*node));
    for node in nodes {
        if state.discovery[G::node_slot(node)].is_none() {
            visit(graph, node, None, &mut state);
        }
    }
    state
        .bridges
        .sort_unstable_by_key(|edge| G::edge_slot(*edge));
    let mut articulation_points = graph
        .node_indices()
        .filter(|node| state.articulation[G::node_slot(*node)])
        .collect::<Vec<_>>();
    articulation_points.sort_unstable_by_key(|node| G::node_slot(*node));
    UndirectedCuts {
        bridges: state.bridges,
        articulation_points,
    }
}

struct CutState<G: IndexUndirectedGraphView> {
    time: usize,
    discovery: Vec<Option<usize>>,
    low: Vec<usize>,
    articulation: Vec<bool>,
    bridges: Vec<G::Edge>,
}

impl<G: IndexUndirectedGraphView> CutState<G> {
    fn new(graph: &G) -> Self {
        Self {
            time: 0,
            discovery: vec![None; graph.node_bound()],
            low: vec![0; graph.node_bound()],
            articulation: vec![false; graph.node_bound()],
            bridges: Vec::new(),
        }
    }
}

fn visit<G>(graph: &G, node: G::Node, parent_edge: Option<G::Edge>, state: &mut CutState<G>)
where
    G: IndexUndirectedGraphView,
{
    let slot = G::node_slot(node);
    state.discovery[slot] = Some(state.time);
    state.low[slot] = state.time;
    state.time += 1;
    let mut children = 0;
    let mut incident = graph.incident_edges(node).collect::<Vec<_>>();
    incident.sort_unstable_by_key(|edge| G::edge_slot(*edge));
    for edge in incident {
        if Some(edge) == parent_edge {
            continue;
        }
        let Some(neighbor) = graph.opposite(edge, node) else {
            continue;
        };
        let neighbor_slot = G::node_slot(neighbor);
        if let Some(discovery) = state.discovery[neighbor_slot] {
            state.low[slot] = state.low[slot].min(discovery);
            continue;
        }
        children += 1;
        visit(graph, neighbor, Some(edge), state);
        state.low[slot] = state.low[slot].min(state.low[neighbor_slot]);
        let node_discovery = state.discovery[slot].expect("visited node has discovery time");
        if state.low[neighbor_slot] > node_discovery {
            state.bridges.push(edge);
        }
        if parent_edge.is_some() && state.low[neighbor_slot] >= node_discovery {
            state.articulation[slot] = true;
        }
    }
    if parent_edge.is_none() && children > 1 {
        state.articulation[slot] = true;
    }
}