use crate::algorithms::paths::distances::distances;
use crate::core::{Graph, IgraphResult};
pub fn reachability_matrix(graph: &Graph) -> IgraphResult<Vec<Vec<bool>>> {
let n = graph.vcount();
let n_us = n as usize;
let mut out = Vec::with_capacity(n_us);
for v in 0..n {
let d = distances(graph, v)?;
let row: Vec<bool> = d.into_iter().map(|x| x.is_some()).collect();
out.push(row);
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_graph_yields_empty_matrix() {
let g = Graph::with_vertices(0);
let m = reachability_matrix(&g).unwrap();
assert!(m.is_empty());
}
#[test]
fn isolated_vertices_only_self_reachable() {
let g = Graph::with_vertices(3);
let m = reachability_matrix(&g).unwrap();
assert_eq!(m[0], vec![true, false, false]);
assert_eq!(m[1], vec![false, true, false]);
assert_eq!(m[2], vec![false, false, true]);
}
#[test]
fn undirected_path_is_symmetric_and_full() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let m = reachability_matrix(&g).unwrap();
for (i, row) in m.iter().enumerate() {
for (j, &val) in row.iter().enumerate() {
assert!(val, "{i}->{j} should be reachable");
assert_eq!(val, m[j][i], "asymmetry on undirected");
}
}
}
#[test]
fn directed_chain_is_one_way() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let m = reachability_matrix(&g).unwrap();
assert_eq!(m[0], vec![true, true, true]);
assert_eq!(m[1], vec![false, true, true]);
assert_eq!(m[2], vec![false, false, true]);
}
#[test]
fn directed_cycle_makes_all_pairs_reachable() {
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 m = reachability_matrix(&g).unwrap();
for (i, row) in m.iter().enumerate() {
for (j, &val) in row.iter().enumerate() {
assert!(val, "{i}->{j} should be reachable in a 3-cycle");
}
}
}
#[test]
fn disconnected_components_isolate_pairs() {
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 m = reachability_matrix(&g).unwrap();
for row in &m[..3] {
for &v in &row[..3] {
assert!(v);
}
}
for (i, row) in m.iter().enumerate().take(3) {
for (j, &val) in row.iter().enumerate().skip(3).take(2) {
assert!(!val, "{i}->{j} should be unreachable");
assert!(!m[j][i], "{j}->{i} should be unreachable");
}
}
}
#[test]
fn diagonal_is_always_true() {
let g = Graph::with_vertices(7);
let m = reachability_matrix(&g).unwrap();
for (i, row) in m.iter().enumerate() {
assert!(row[i], "vertex {i} not self-reachable");
}
}
}