use crate::core::{Graph, IgraphResult};
pub fn transitive_closure(graph: &Graph) -> IgraphResult<Graph> {
let n = graph.vcount();
let directed = graph.is_directed();
let m = super::reachability_matrix::reachability_matrix(graph)?;
let mut closure = Graph::new(n, directed)?;
for (u, row) in m.iter().enumerate() {
let start_j = if directed { 0 } else { u + 1 };
let u_id = u32::try_from(u).map_err(|_| {
crate::core::IgraphError::Internal("vcount overflows u32 in transitive_closure")
})?;
for (v, &reachable) in row.iter().enumerate().skip(start_j) {
if u != v && reachable {
let v_id = u32::try_from(v).map_err(|_| {
crate::core::IgraphError::Internal("vcount overflows u32 in transitive_closure")
})?;
closure.add_edge(u_id, v_id)?;
}
}
}
Ok(closure)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_graph_yields_empty_closure() {
let g = Graph::with_vertices(0);
let tc = transitive_closure(&g).unwrap();
assert_eq!(tc.vcount(), 0);
assert_eq!(tc.ecount(), 0);
}
#[test]
fn isolated_vertices_no_edges_in_closure() {
let g = Graph::with_vertices(5);
let tc = transitive_closure(&g).unwrap();
assert_eq!(tc.vcount(), 5);
assert_eq!(tc.ecount(), 0);
}
#[test]
fn directed_path_3_closure_has_three_edges() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let tc = transitive_closure(&g).unwrap();
assert!(tc.is_directed());
assert_eq!(tc.ecount(), 3);
for &(u, v) in &[(0u32, 1u32), (0, 2), (1, 2)] {
assert!(tc.find_eid(u, v).unwrap().is_some(), "missing {u}->{v}");
}
assert!(tc.find_eid(1, 0).unwrap().is_none());
assert!(tc.find_eid(2, 0).unwrap().is_none());
}
#[test]
fn undirected_path_3_closure_is_complete_graph() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let tc = transitive_closure(&g).unwrap();
assert!(!tc.is_directed());
assert_eq!(tc.ecount(), 3);
for &(u, v) in &[(0u32, 1u32), (0, 2), (1, 2)] {
assert!(tc.find_eid(u, v).unwrap().is_some(), "missing {u}-{v}");
}
}
#[test]
fn directed_cycle_closure_is_complete_directed() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
let tc = transitive_closure(&g).unwrap();
assert_eq!(tc.ecount(), 6); for u in 0..3u32 {
for v in 0..3u32 {
if u != v {
assert!(tc.find_eid(u, v).unwrap().is_some());
}
}
}
}
#[test]
fn disconnected_components_isolate_in_closure() {
let mut g = Graph::with_vertices(5);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(3, 4).unwrap();
let tc = transitive_closure(&g).unwrap();
assert_eq!(tc.ecount(), 4);
for u in 0..3u32 {
for v in 3..5u32 {
assert!(tc.find_eid(u, v).unwrap().is_none());
}
}
}
#[test]
fn closure_of_complete_graph_is_itself() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
let tc = transitive_closure(&g).unwrap();
assert_eq!(tc.vcount(), 3);
assert_eq!(tc.ecount(), 3); }
}