use crate::csc_array::CscArray;
use crate::csr::CsrMatrix;
use crate::csr_array::CsrArray;
use crate::error::{SparseError, SparseResult};
use crate::sparray::SparseArray;
use scirs2_core::numeric::{SparseElement, Zero};
use std::fmt::Debug;
use std::ops::Div;
#[derive(Debug, Clone)]
pub struct AdjacencyGraph {
adj: Vec<Vec<usize>>,
}
impl AdjacencyGraph {
pub fn from_adjacency_list(mut adj: Vec<Vec<usize>>) -> Self {
let n = adj.len();
for (u, nbrs) in adj.iter_mut().enumerate() {
nbrs.retain(|&v| v != u && v < n);
nbrs.sort_unstable();
nbrs.dedup();
}
Self { adj }
}
pub fn from_csr_matrix<T>(mat: &CsrMatrix<T>) -> SparseResult<Self>
where
T: Clone + Copy + SparseElement + Zero + PartialEq + Debug,
{
let (n, nc) = mat.shape();
if n != nc {
return Err(SparseError::ValueError(
"adjacency graph requires a square matrix".to_string(),
));
}
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
for row in 0..n {
for j in mat.indptr[row]..mat.indptr[row + 1] {
let col = mat.indices[j];
if col != row {
adj[row].push(col);
adj[col].push(row);
}
}
}
for nbrs in adj.iter_mut() {
nbrs.sort_unstable();
nbrs.dedup();
}
Ok(Self { adj })
}
pub fn from_csr_array<T>(arr: &CsrArray<T>) -> SparseResult<Self>
where
T: SparseElement + Div<Output = T> + Zero + PartialOrd + 'static,
{
let (n, nc) = arr.shape();
if n != nc {
return Err(SparseError::ValueError(
"adjacency graph requires a square matrix".to_string(),
));
}
let dense = <CsrArray<T> as SparseArray<T>>::to_array(arr);
let zero = <T as Zero>::zero();
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
for row in 0..n {
for col in 0..n {
if row != col && dense[[row, col]] != zero {
adj[row].push(col);
}
}
}
for nbrs in adj.iter_mut() {
nbrs.sort_unstable();
nbrs.dedup();
}
Ok(Self { adj })
}
pub fn from_csc_array<T>(arr: &CscArray<T>) -> SparseResult<Self>
where
T: SparseElement
+ Div<Output = T>
+ Zero
+ PartialOrd
+ scirs2_core::numeric::Float
+ 'static,
{
let (n, nc) = <CscArray<T> as SparseArray<T>>::shape(arr);
if n != nc {
return Err(SparseError::ValueError(
"adjacency graph requires a square matrix".to_string(),
));
}
let dense = <CscArray<T> as SparseArray<T>>::to_array(arr);
let zero = <T as Zero>::zero();
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
for row in 0..n {
for col in 0..n {
if row != col && dense[[row, col]] != zero {
adj[row].push(col);
}
}
}
for nbrs in adj.iter_mut() {
nbrs.sort_unstable();
nbrs.dedup();
}
Ok(Self { adj })
}
#[inline]
pub fn num_nodes(&self) -> usize {
self.adj.len()
}
#[inline]
pub fn degree(&self, u: usize) -> usize {
self.adj.get(u).map_or(0, |v| v.len())
}
#[inline]
pub fn neighbors(&self, u: usize) -> &[usize] {
self.adj.get(u).map_or(&[], |v| v.as_slice())
}
pub fn num_edges(&self) -> usize {
let total: usize = self.adj.iter().map(|v| v.len()).sum();
total / 2
}
pub fn has_edge(&self, u: usize, v: usize) -> bool {
if u >= self.adj.len() || v >= self.adj.len() {
return false;
}
self.adj[u].binary_search(&v).is_ok()
}
pub fn subgraph(&self, nodes: &[usize]) -> (AdjacencyGraph, Vec<usize>) {
let n = nodes.len();
let mut rev = vec![usize::MAX; self.adj.len()];
for (new_i, &old_i) in nodes.iter().enumerate() {
if old_i < rev.len() {
rev[old_i] = new_i;
}
}
let mut adj = vec![Vec::new(); n];
for (new_u, &old_u) in nodes.iter().enumerate() {
if old_u >= self.adj.len() {
continue;
}
for &old_v in &self.adj[old_u] {
if old_v < rev.len() && rev[old_v] != usize::MAX {
adj[new_u].push(rev[old_v]);
}
}
adj[new_u].sort_unstable();
adj[new_u].dedup();
}
(AdjacencyGraph { adj }, nodes.to_vec())
}
pub(crate) fn raw_adj(&self) -> &[Vec<usize>] {
&self.adj
}
}
pub fn apply_permutation<T>(mat: &CsrMatrix<T>, perm: &[usize]) -> SparseResult<CsrMatrix<T>>
where
T: Clone + Copy + SparseElement + Zero + PartialEq + Debug,
{
let (n, nc) = mat.shape();
if n != nc {
return Err(SparseError::ValueError(
"apply_permutation requires a square matrix".to_string(),
));
}
if perm.len() != n {
return Err(SparseError::DimensionMismatch {
expected: n,
found: perm.len(),
});
}
let mut inv_perm = vec![0usize; n];
for (new_i, &old_i) in perm.iter().enumerate() {
if old_i >= n {
return Err(SparseError::ValueError(format!(
"permutation index {} out of range (n={})",
old_i, n
)));
}
inv_perm[old_i] = new_i;
}
let mut rows = Vec::with_capacity(mat.nnz());
let mut cols = Vec::with_capacity(mat.nnz());
let mut data = Vec::with_capacity(mat.nnz());
for new_row in 0..n {
let old_row = perm[new_row];
for j in mat.indptr[old_row]..mat.indptr[old_row + 1] {
let old_col = mat.indices[j];
let new_col = inv_perm[old_col];
rows.push(new_row);
cols.push(new_col);
data.push(mat.data[j]);
}
}
CsrMatrix::new(data, rows, cols, (n, n))
}
pub fn apply_permutation_csr_array<T>(
arr: &CsrArray<T>,
perm: &[usize],
) -> SparseResult<CsrArray<T>>
where
T: SparseElement + Div<Output = T> + Zero + PartialOrd + 'static,
{
let (n, nc) = arr.shape();
if n != nc {
return Err(SparseError::ValueError(
"apply_permutation requires a square matrix".to_string(),
));
}
if perm.len() != n {
return Err(SparseError::DimensionMismatch {
expected: n,
found: perm.len(),
});
}
let mut inv_perm = vec![0usize; n];
for (new_i, &old_i) in perm.iter().enumerate() {
if old_i >= n {
return Err(SparseError::ValueError(format!(
"permutation index {} out of range (n={})",
old_i, n
)));
}
inv_perm[old_i] = new_i;
}
let dense = <CsrArray<T> as SparseArray<T>>::to_array(arr);
let mut rows = Vec::new();
let mut cols = Vec::new();
let mut data = Vec::new();
let zero = <T as Zero>::zero();
for new_row in 0..n {
let old_row = perm[new_row];
for old_col in 0..n {
let val = dense[[old_row, old_col]];
if val != zero {
let new_col = inv_perm[old_col];
rows.push(new_row);
cols.push(new_col);
data.push(val);
}
}
}
CsrArray::from_triplets(&rows, &cols, &data, (n, n), false)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_adjacency_from_list() {
let adj = vec![vec![1, 2], vec![0, 2], vec![0, 1]];
let graph = AdjacencyGraph::from_adjacency_list(adj);
assert_eq!(graph.num_nodes(), 3);
assert_eq!(graph.degree(0), 2);
assert_eq!(graph.num_edges(), 3);
assert!(graph.has_edge(0, 1));
assert!(!graph.has_edge(0, 0)); }
#[test]
fn test_adjacency_from_csr_matrix() {
let rows = vec![0, 0, 1, 1, 2, 2];
let cols = vec![1, 2, 0, 2, 0, 1];
let data = vec![1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
let mat = CsrMatrix::new(data, rows, cols, (3, 3)).expect("csr");
let graph = AdjacencyGraph::from_csr_matrix(&mat).expect("adj");
assert_eq!(graph.num_nodes(), 3);
assert_eq!(graph.degree(0), 2);
}
#[test]
fn test_subgraph() {
let adj = vec![vec![1, 2, 3], vec![0, 2], vec![0, 1], vec![0]];
let graph = AdjacencyGraph::from_adjacency_list(adj);
let (sub, mapping) = graph.subgraph(&[0, 1, 2]);
assert_eq!(sub.num_nodes(), 3);
assert_eq!(mapping, vec![0, 1, 2]);
assert_eq!(sub.degree(0), 2);
}
#[test]
fn test_apply_permutation() {
let rows = vec![0, 0, 1, 1, 1, 2, 2];
let cols = vec![0, 1, 0, 1, 2, 1, 2];
let data = vec![2.0, -1.0, -1.0, 2.0, -1.0, -1.0, 2.0];
let mat = CsrMatrix::new(data, rows, cols, (3, 3)).expect("csr");
let perm = vec![2, 1, 0];
let permuted = apply_permutation(&mat, &perm).expect("apply");
assert_eq!(permuted.shape(), (3, 3));
assert_eq!(permuted.nnz(), 7);
}
}