use alloc::vec::Vec;
use num_traits::{AsPrimitive, Zero};
use crate::{
impls::WeightedForest,
traits::{Finite, SparseValuedMatrix2D, TotalOrd},
};
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum MinimumSpanningTreeError {
#[error("The adjacency matrix is not square: {rows} rows by {columns} columns.")]
NonSquareMatrix {
rows: usize,
columns: usize,
},
#[error("Edge ({source_id}, {destination_id}) has a non-finite weight.")]
NonFiniteWeight {
source_id: usize,
destination_id: usize,
},
}
pub(super) struct CollectedGraph<F> {
pub(super) node_count: usize,
pub(super) edges: Vec<(usize, usize, F)>,
}
impl<F: Copy + TotalOrd + Finite> CollectedGraph<F> {
pub(super) fn from_matrix<M>(matrix: &M) -> Result<Self, MinimumSpanningTreeError>
where
M: SparseValuedMatrix2D<Value = F>,
M::RowIndex: AsPrimitive<usize>,
M::ColumnIndex: AsPrimitive<usize>,
{
let node_count = matrix.number_of_rows().as_();
let columns = matrix.number_of_columns().as_();
if node_count != columns {
return Err(MinimumSpanningTreeError::NonSquareMatrix { rows: node_count, columns });
}
let mut edges = Vec::new();
for row_id in matrix.row_indices() {
let source = row_id.as_();
for (column_id, weight) in
matrix.sparse_row(row_id).zip(matrix.sparse_row_values(row_id))
{
let destination = column_id.as_();
if source == destination {
continue;
}
if !weight.is_finite() {
return Err(MinimumSpanningTreeError::NonFiniteWeight {
source_id: source,
destination_id: destination,
});
}
let (low, high) = if source < destination {
(source, destination)
} else {
(destination, source)
};
edges.push((low, high, weight));
}
}
edges.sort_by(|left, right| {
(left.0, left.1).cmp(&(right.0, right.1)).then_with(|| left.2.total_cmp(&right.2))
});
edges.dedup_by(|left, right| left.0 == right.0 && left.1 == right.1);
Ok(Self { node_count, edges })
}
}
pub(super) fn root_forest<F: Copy + Zero>(
node_count: usize,
tree_edges: &[(usize, usize, F)],
) -> WeightedForest<usize, F> {
let mut adjacency: Vec<Vec<(usize, F)>> = alloc::vec![Vec::new(); node_count];
for &(source, destination, weight) in tree_edges {
adjacency[source].push((destination, weight));
adjacency[destination].push((source, weight));
}
let mut parent: Vec<(usize, F)> = (0..node_count).map(|node| (node, F::zero())).collect();
let mut visited = alloc::vec![false; node_count];
let mut stack: Vec<usize> = Vec::new();
for root in 0..node_count {
if visited[root] {
continue;
}
visited[root] = true;
stack.push(root);
while let Some(node) = stack.pop() {
for &(neighbor, weight) in &adjacency[node] {
if !visited[neighbor] {
visited[neighbor] = true;
parent[neighbor] = (node, weight);
stack.push(neighbor);
}
}
}
}
WeightedForest::from_parent_array(parent)
}