use super::symmetric::{EigenDecomposition, EigenWhich, eigen_matrix_symmetric};
use crate::core::error::{IgraphError, IgraphResult};
use crate::core::graph::Graph;
pub fn eigen_adjacency(
graph: &Graph,
nev: usize,
which: EigenWhich,
) -> IgraphResult<EigenDecomposition> {
let n = graph.vcount() as usize;
if n == 0 {
return Err(IgraphError::InvalidArgument(
"eigen_adjacency: graph has zero vertices".into(),
));
}
let directed = graph.is_directed();
let ecount = graph.ecount();
let ecount_u32 = u32::try_from(ecount).unwrap_or(u32::MAX);
let mut edges: Vec<(usize, usize)> = Vec::with_capacity(ecount);
for eid in 0..ecount_u32 {
if let Ok((u, v)) = graph.edge(eid) {
edges.push((u as usize, v as usize));
}
}
let matvec = |x: &[f64], y: &mut [f64]| {
for val in y.iter_mut() {
*val = 0.0;
}
for &(u, v) in &edges {
if directed {
y[u] += x[v];
y[v] += x[u];
} else {
y[u] += x[v];
y[v] += x[u];
}
}
if directed {
for val in y.iter_mut() {
*val *= 0.5;
}
}
};
eigen_matrix_symmetric(n, matvec, nev, which)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::graph::Graph;
#[test]
fn k3_largest_eigenvalue() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(1, 2).unwrap();
let result = eigen_adjacency(&g, 1, EigenWhich::LargestAlgebraic).unwrap();
assert_eq!(result.eigenvalues.len(), 1);
assert!(
(result.eigenvalues[0] - 2.0).abs() < 1e-4,
"K3 largest eigenvalue should be 2.0, got {}",
result.eigenvalues[0]
);
}
#[test]
fn path_graph_eigenvalues() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let result = eigen_adjacency(&g, 1, EigenWhich::LargestAlgebraic).unwrap();
let expected = std::f64::consts::SQRT_2;
assert!(
(result.eigenvalues[0] - expected).abs() < 1e-4,
"P3 largest eigenvalue should be sqrt(2)={expected}, got {}",
result.eigenvalues[0]
);
}
#[test]
fn isolated_vertices() {
let g = Graph::with_vertices(5);
let result = eigen_adjacency(&g, 3, EigenWhich::LargestAlgebraic).unwrap();
for &ev in &result.eigenvalues {
assert!(
ev.abs() < 1e-6,
"isolated graph eigenvalue should be 0.0, got {ev}"
);
}
}
#[test]
fn empty_graph_error() {
let g = Graph::with_vertices(0);
assert!(eigen_adjacency(&g, 1, EigenWhich::LargestAlgebraic).is_err());
}
#[test]
fn star_graph_eigenvalues() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(0, 3).unwrap();
let result = eigen_adjacency(&g, 1, EigenWhich::LargestAlgebraic).unwrap();
let expected = 3.0_f64.sqrt();
assert!(
(result.eigenvalues[0] - expected).abs() < 1e-4,
"S4 largest eigenvalue should be sqrt(3)={expected}, got {}",
result.eigenvalues[0]
);
}
#[test]
fn cycle_graph_eigenvalues() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
g.add_edge(3, 0).unwrap();
let result = eigen_adjacency(&g, 1, EigenWhich::LargestAlgebraic).unwrap();
assert!(
(result.eigenvalues[0] - 2.0).abs() < 1e-4,
"C4 largest eigenvalue should be 2.0, got {}",
result.eigenvalues[0]
);
}
}