use rustc_hash::FxHashMap;
use crate::graph::graph_structures::UnionFind;
use crate::prelude::*;
pub fn connected_components<T>(
graph: &CompressedSparseData2<T>,
) -> Result<(usize, Vec<usize>), MethodsErrors>
where
T: Clone,
{
let n = validate_square_csr(graph)?;
let mut uf = UnionFind::new(n);
for i in 0..n {
for idx in graph.indptr[i] as usize..graph.indptr[i + 1] as usize {
uf.union(i as u32, graph.indices[idx]);
}
}
Ok(label_by_root(
&mut uf,
(0..n as u32).collect::<Vec<_>>().as_slice(),
))
}
pub fn induced_components<T>(
graph: &CompressedSparseData2<T>,
members: &[usize],
) -> Result<(usize, Vec<usize>), MethodsErrors>
where
T: Clone,
{
let n = validate_square_csr(graph)?;
let mut position: FxHashMap<usize, u32> = FxHashMap::default();
for (p, &node) in members.iter().enumerate() {
if node >= n {
return Err(CoreErrors::SliceIndexOutOfBounds {
index: node,
len: n,
}
.into());
}
position.entry(node).or_insert(p as u32);
}
let mut uf = UnionFind::new(members.len());
for (p, &node) in members.iter().enumerate() {
if let Some(&first) = position.get(&node)
&& first != p as u32
{
uf.union(first, p as u32);
}
for idx in graph.indptr[node] as usize..graph.indptr[node + 1] as usize {
let neighbour = graph.indices[idx] as usize;
if let Some(&q) = position.get(&neighbour) {
uf.union(p as u32, q);
}
}
}
Ok(label_by_root(
&mut uf,
(0..members.len() as u32).collect::<Vec<_>>().as_slice(),
))
}
fn label_by_root(uf: &mut UnionFind, elements: &[u32]) -> (usize, Vec<usize>) {
let mut root_to_label: FxHashMap<u32, usize> = FxHashMap::default();
let mut labels = Vec::with_capacity(elements.len());
for &e in elements {
let root = uf.find(e);
let next = root_to_label.len();
let label = *root_to_label.entry(root).or_insert(next);
labels.push(label);
}
(root_to_label.len(), labels)
}
fn validate_square_csr<T>(graph: &CompressedSparseData2<T>) -> Result<usize, MethodsErrors>
where
T: Clone,
{
if !graph.cs_type.is_csr() {
return Err(CoreErrors::SparseMatrixMustBeCsr.into());
}
let (rows, cols) = graph.shape;
if rows != cols {
return Err(CoreErrors::ShapeMismatch {
expected: (rows, rows),
got: (rows, cols),
}
.into());
}
Ok(rows)
}
#[cfg(test)]
mod tests {
use super::*;
fn directed_csr(n: usize, edges: &[(usize, usize)]) -> CompressedSparseData2<f64> {
let mut rows: Vec<Vec<u32>> = vec![Vec::new(); n];
for &(a, b) in edges {
rows[a].push(b as u32);
}
let mut indptr: Vec<u32> = vec![0];
let mut indices: Vec<u32> = Vec::new();
let mut data: Vec<f64> = Vec::new();
for row in rows.iter_mut() {
row.sort_unstable();
for &j in row.iter() {
indices.push(j);
data.push(1.0);
}
indptr.push(indices.len() as u32);
}
CompressedSparseData2::new_csr(&data, &indices, &indptr, None, (n, n))
}
#[test]
fn test_components_of_two_cliques() {
let edges = [(0, 1), (1, 2), (2, 0), (3, 4), (4, 3)];
let graph = directed_csr(5, &edges);
let (n_comps, labels) = connected_components(&graph).unwrap();
assert_eq!(n_comps, 2);
assert_eq!(labels, vec![0, 0, 0, 1, 1]);
}
#[test]
fn test_components_are_weak_not_strong() {
let graph = directed_csr(3, &[(0, 1), (1, 2)]);
let (n_comps, labels) = connected_components(&graph).unwrap();
assert_eq!(n_comps, 1);
assert_eq!(labels, vec![0, 0, 0]);
}
#[test]
fn test_isolated_nodes_get_own_components() {
let graph = directed_csr(4, &[(0, 1)]);
let (n_comps, labels) = connected_components(&graph).unwrap();
assert_eq!(n_comps, 3);
assert_eq!(labels, vec![0, 0, 1, 2]);
}
#[test]
fn test_induced_components_split_a_connected_graph() {
let edges = [
(0, 1),
(1, 0),
(1, 2),
(2, 1),
(2, 3),
(3, 2),
(3, 4),
(4, 3),
];
let graph = directed_csr(5, &edges);
let (n_comps, labels) = induced_components(&graph, &[0, 1, 3, 4]).unwrap();
assert_eq!(n_comps, 2);
assert_eq!(labels, vec![0, 0, 1, 1]);
}
#[test]
fn test_induced_components_ignore_edges_leaving_the_subset() {
let edges = [(0, 1), (1, 0), (1, 2), (2, 1)];
let graph = directed_csr(3, &edges);
let (n_comps, labels) = induced_components(&graph, &[0, 2]).unwrap();
assert_eq!(n_comps, 2);
assert_eq!(labels, vec![0, 1]);
}
#[test]
fn test_induced_components_put_duplicate_members_in_one_component() {
let edges = [(0, 1), (1, 0), (1, 2), (2, 1)];
let graph = directed_csr(3, &edges);
let (n_comps, labels) = induced_components(&graph, &[0, 2, 0, 2, 0]).unwrap();
assert_eq!(n_comps, 2);
assert_eq!(labels, vec![0, 1, 0, 1, 0]);
}
#[test]
fn test_induced_components_merge_through_a_duplicated_member() {
let edges = [(0, 1), (1, 0), (1, 2), (2, 1), (2, 3), (3, 2)];
let graph = directed_csr(4, &edges);
let (n_comps, labels) = induced_components(&graph, &[0, 1, 2, 1]).unwrap();
assert_eq!(n_comps, 1);
assert_eq!(labels, vec![0, 0, 0, 0]);
}
#[test]
fn test_induced_components_rejects_out_of_range_member() {
let graph = directed_csr(3, &[(0, 1)]);
assert!(induced_components(&graph, &[0, 9]).is_err());
}
#[test]
fn test_components_reject_csc_input() {
let graph = directed_csr(3, &[(0, 1)]).transform();
assert!(connected_components(&graph).is_err());
}
}