use std::collections::VecDeque;
use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
pub fn decompose(graph: &Graph) -> IgraphResult<Vec<Graph>> {
let n = graph.vcount();
if n == 0 {
return Ok(Vec::new());
}
let mut vid_in_comp = vec![u32::MAX; n as usize];
let mut comp_of = vec![u32::MAX; n as usize];
let mut comp_vertices: Vec<Vec<VertexId>> = Vec::new();
let mut queue: VecDeque<VertexId> = VecDeque::new();
for actstart in 0..n {
if vid_in_comp[actstart as usize] != u32::MAX {
continue;
}
let comp_id = u32::try_from(comp_vertices.len())
.map_err(|_| IgraphError::Internal("too many components in decompose"))?;
let mut verts: Vec<VertexId> = Vec::new();
vid_in_comp[actstart as usize] = 0;
comp_of[actstart as usize] = comp_id;
verts.push(actstart);
queue.clear();
queue.push_back(actstart);
while let Some(v) = queue.pop_front() {
for w in graph.neighbors_iter(v)? {
if vid_in_comp[w as usize] == u32::MAX {
let new_local = u32::try_from(verts.len())
.map_err(|_| IgraphError::Internal("component too large in decompose"))?;
vid_in_comp[w as usize] = new_local;
comp_of[w as usize] = comp_id;
verts.push(w);
queue.push_back(w);
}
}
}
comp_vertices.push(verts);
}
let directed = graph.is_directed();
let mut parts: Vec<Graph> = comp_vertices
.iter()
.map(|verts| {
let k = u32::try_from(verts.len())
.map_err(|_| IgraphError::Internal("component vertex count exceeds u32"))?;
Graph::new(k, directed)
})
.collect::<IgraphResult<Vec<_>>>()?;
let m = graph.ecount();
for e in 0..m {
let eid = u32::try_from(e)
.map_err(|_| IgraphError::Internal("edge id exceeds u32 in decompose"))?;
let s = graph.edge_source(eid)?;
let t = graph.edge_target(eid)?;
let comp_s = comp_of[s as usize];
debug_assert_eq!(comp_s, comp_of[t as usize]);
let local_s = vid_in_comp[s as usize];
let local_t = vid_in_comp[t as usize];
parts[comp_s as usize].add_edge(local_s, local_t)?;
}
Ok(parts)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_graph_yields_no_components() {
let g = Graph::with_vertices(0);
let parts = decompose(&g).unwrap();
assert!(parts.is_empty());
}
#[test]
fn null_graph_with_isolated_vertices() {
let g = Graph::with_vertices(3);
let parts = decompose(&g).unwrap();
assert_eq!(parts.len(), 3);
for p in &parts {
assert_eq!(p.vcount(), 1);
assert_eq!(p.ecount(), 0);
}
}
#[test]
fn single_component_preserves_size() {
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();
let parts = decompose(&g).unwrap();
assert_eq!(parts.len(), 1);
assert_eq!(parts[0].vcount(), 4);
assert_eq!(parts[0].ecount(), 3);
}
#[test]
fn two_disjoint_triangles() {
let mut g = Graph::with_vertices(6);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
g.add_edge(3, 4).unwrap();
g.add_edge(4, 5).unwrap();
g.add_edge(5, 3).unwrap();
let parts = decompose(&g).unwrap();
assert_eq!(parts.len(), 2);
for p in &parts {
assert_eq!(p.vcount(), 3);
assert_eq!(p.ecount(), 3);
}
}
#[test]
fn vertex_remapping_starts_at_zero() {
let mut g = Graph::with_vertices(6);
g.add_edge(3, 4).unwrap();
g.add_edge(4, 5).unwrap();
g.add_edge(5, 3).unwrap();
let parts = decompose(&g).unwrap();
assert_eq!(parts.len(), 4);
for p in parts.iter().take(3) {
assert_eq!(p.vcount(), 1);
assert_eq!(p.ecount(), 0);
}
let tri = &parts[3];
assert_eq!(tri.vcount(), 3);
assert_eq!(tri.ecount(), 3);
assert_eq!(tri.edge_source(0).unwrap(), 0);
assert_eq!(tri.edge_target(0).unwrap(), 1);
}
#[test]
fn directed_graph_preserves_edge_orientation() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let parts = decompose(&g).unwrap();
assert_eq!(parts.len(), 2);
let chain = &parts[0];
assert!(chain.is_directed());
assert_eq!(chain.vcount(), 3);
assert_eq!(chain.ecount(), 2);
assert_eq!(chain.edge_source(0).unwrap(), 0);
assert_eq!(chain.edge_target(0).unwrap(), 1);
assert_eq!(chain.edge_source(1).unwrap(), 1);
assert_eq!(chain.edge_target(1).unwrap(), 2);
}
#[test]
fn loops_and_parallel_edges_preserved() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 0).unwrap(); g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap(); let parts = decompose(&g).unwrap();
assert_eq!(parts.len(), 2);
let main = &parts[0];
assert_eq!(main.vcount(), 2);
assert_eq!(main.ecount(), 3);
}
#[test]
fn component_count_matches_connected_components() {
let mut g = Graph::with_vertices(7);
g.add_edge(0, 1).unwrap();
g.add_edge(2, 3).unwrap();
g.add_edge(3, 4).unwrap();
let parts = decompose(&g).unwrap();
let cc = crate::algorithms::connectivity::components::connected_components(&g).unwrap();
assert_eq!(u32::try_from(parts.len()).unwrap(), cc.count);
}
#[test]
fn round_trip_edge_count_total_matches() {
let mut g = Graph::with_vertices(8);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(3, 4).unwrap();
g.add_edge(4, 5).unwrap();
g.add_edge(6, 7).unwrap();
let parts = decompose(&g).unwrap();
let total_e: usize = parts.iter().map(Graph::ecount).sum();
assert_eq!(total_e, g.ecount());
let total_v: u32 = parts.iter().map(Graph::vcount).sum();
assert_eq!(total_v, g.vcount());
}
}