use crate::core::error::{GraphinaError, Result};
use crate::core::types::{BaseGraph, GraphConstructor, NodeId, NodeMap};
use nalgebra::SymmetricEigen;
use nalgebra::{DMatrix, DVector};
pub fn eigenvector_centrality<A, W, Ty>(
graph: &BaseGraph<A, W, Ty>,
max_iter: usize,
tolerance: f64,
) -> Result<NodeMap<f64>>
where
W: Copy + PartialOrd + Into<f64>,
Ty: GraphConstructor<A, W>,
{
let n = graph.node_count();
if n == 0 {
return Ok(NodeMap::new());
}
if graph.edge_count() == 0 {
let mut centrality = NodeMap::new();
let uniform_value = 1.0 / n as f64;
for (node, _) in graph.nodes() {
centrality.insert(node, uniform_value);
}
return Ok(centrality);
}
let node_list: Vec<NodeId> = graph.nodes().map(|(node, _)| node).collect();
let mut node_to_idx = std::collections::HashMap::new();
for (idx, &node) in node_list.iter().enumerate() {
node_to_idx.insert(node, idx);
}
let mut adj = DMatrix::<f64>::zeros(n, n);
for (u, v, w) in graph.edges() {
let ui = node_to_idx[&u];
let vi = node_to_idx[&v];
let weight: f64 = (*w).into();
if graph.is_directed() {
adj[(vi, ui)] += weight;
} else {
adj[(ui, vi)] += weight;
adj[(vi, ui)] += weight;
}
}
if !graph.is_directed() {
let se = SymmetricEigen::new(adj.clone());
let evals = se.eigenvalues.clone();
let evecs = se.eigenvectors.clone();
let mut max_idx = 0usize;
let mut max_val = f64::NEG_INFINITY;
for (i, &eig) in evals.iter().enumerate() {
if eig > max_val {
max_val = eig;
max_idx = i;
}
}
let mut x = evecs.column(max_idx).into_owned();
for val in x.iter_mut() {
*val = val.abs();
}
let sum: f64 = x.iter().sum();
if sum > 0.0 {
x *= (n as f64) / sum;
}
let mut centrality = NodeMap::new();
for (idx, &val) in x.iter().enumerate() {
centrality.insert(node_list[idx], val);
}
return Ok(centrality);
}
let mut x = DVector::<f64>::from_element(n, 1.0 / (n as f64).sqrt());
let mut converged = false;
for iter in 0..max_iter {
let x_new = &adj * &x;
let norm = x_new.norm();
if norm < 1e-10 {
let mut centrality = NodeMap::new();
let uniform_value = 1.0 / n as f64;
for &node in &node_list {
centrality.insert(node, uniform_value);
}
return Ok(centrality);
}
let x_new_normalized = x_new / norm;
let diff = (&x_new_normalized - &x).norm();
if diff < tolerance {
x = x_new_normalized;
converged = true;
break;
}
if iter > 10 {
let x_neg = -&x;
let diff_neg = (&x_new_normalized - &x_neg).norm();
if diff_neg < tolerance {
x = x_new_normalized;
for val in x.iter_mut() {
*val = val.abs();
}
converged = true;
break;
}
}
x = x_new_normalized;
}
if !converged {
return Err(GraphinaError::convergence_failed(
max_iter,
"Eigenvector centrality failed to converge within maximum iterations",
));
}
let sum: f64 = x.iter().map(|v| v.abs()).sum();
if sum > 0.0 {
x *= n as f64 / sum;
}
let mut centrality = NodeMap::new();
for (idx, &val) in x.iter().enumerate() {
centrality.insert(node_list[idx], val.abs());
}
Ok(centrality)
}
#[cfg(test)]
mod tests {
use super::eigenvector_centrality;
use crate::core::types::{Digraph, Graph};
#[test]
fn eigenvector_directed_vs_undirected_basic() {
let mut dg: Digraph<i32, f64> = Digraph::new();
let n0 = dg.add_node(0);
let n1 = dg.add_node(1);
dg.add_edge(n0, n1, 1.0);
let c_dir = eigenvector_centrality(&dg, 100, 1e-9).unwrap();
assert!(c_dir[&n0] >= 0.0);
assert!(c_dir[&n1] >= 0.0);
let mut ug: Graph<i32, f64> = Graph::new();
let m0 = ug.add_node(0);
let m1 = ug.add_node(1);
ug.add_edge(m0, m1, 1.0);
let c_und = eigenvector_centrality(&ug, 100, 1e-9).unwrap();
let diff = (c_und[&m0] - c_und[&m1]).abs();
assert!(diff < 1e-5);
}
#[test]
fn test_eigenvector_triangle() {
let mut g: Graph<i32, f64> = Graph::new();
let n1 = g.add_node(1);
let n2 = g.add_node(2);
let n3 = g.add_node(3);
g.add_edge(n1, n2, 1.0);
g.add_edge(n2, n3, 1.0);
g.add_edge(n3, n1, 1.0);
let c = eigenvector_centrality(&g, 100, 1e-9).unwrap();
assert!((c[&n1] - c[&n2]).abs() < 1e-5);
assert!((c[&n2] - c[&n3]).abs() < 1e-5);
}
#[test]
fn test_eigenvector_star() {
let mut g: Graph<i32, f64> = Graph::new();
let center = g.add_node(0);
let mut leaves = Vec::new();
for i in 1..=3 {
let leaf = g.add_node(i);
g.add_edge(center, leaf, 1.0);
leaves.push(leaf);
}
g.add_edge(leaves[0], leaves[1], 1.0);
let c = eigenvector_centrality(&g, 10000, 1e-4).unwrap();
for &leaf in &leaves {
assert!(
c[¢er] >= c[&leaf],
"Center: {}, Leaf: {}",
c[¢er],
c[&leaf]
);
}
assert!(c[¢er] > 0.0);
for &leaf in &leaves {
assert!(c[&leaf] > 0.0);
}
}
#[test]
fn test_eigenvector_empty() {
let g: Graph<i32, f64> = Graph::new();
let c = eigenvector_centrality(&g, 100, 1e-9).unwrap();
assert!(c.is_empty());
}
#[test]
fn test_eigenvector_isolated_nodes() {
let mut g: Graph<i32, f64> = Graph::new();
let n1 = g.add_node(1);
let n2 = g.add_node(2);
let c = eigenvector_centrality(&g, 100, 1e-9).unwrap();
assert!((c[&n1] - c[&n2]).abs() < 1e-5);
}
}