use crate::core::error::{GraphinaError, Result};
use crate::core::types::{BaseGraph, GraphConstructor, NodeId, NodeMap};
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::default());
}
if graph.edge_count() == 0 {
let mut centrality = NodeMap::default();
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 directed = graph.is_directed();
let mut adj: Vec<(usize, usize, f64)> = Vec::with_capacity(graph.edge_count());
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 directed {
adj.push((vi, ui, weight));
} else {
adj.push((ui, vi, weight));
adj.push((vi, ui, weight));
}
}
let shift = if directed { 0.0 } else { 1.0 };
let mut x = vec![1.0 / (n as f64).sqrt(); n];
let mut converged = false;
for iter in 0..max_iter {
let mut y: Vec<f64> = x.iter().map(|&xi| shift * xi).collect();
for &(row, col, weight) in &adj {
y[row] += weight * x[col];
}
let norm: f64 = y.iter().map(|v| v * v).sum::<f64>().sqrt();
if norm < 1e-10 {
let mut centrality = NodeMap::default();
let uniform_value = 1.0 / n as f64;
for &node in &node_list {
centrality.insert(node, uniform_value);
}
return Ok(centrality);
}
let mut diff_sq = 0.0;
let mut diff_neg_sq = 0.0;
for (xi, yi) in x.iter().zip(&y) {
let normalized = yi / norm;
let d = normalized - xi;
diff_sq += d * d;
let dn = normalized + xi;
diff_neg_sq += dn * dn;
}
for (xi, yi) in x.iter_mut().zip(&y) {
*xi = yi / norm;
}
if diff_sq.sqrt() < tolerance {
converged = true;
break;
}
if directed && iter > 10 && diff_neg_sq.sqrt() < tolerance {
converged = true;
break;
}
}
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 {
for v in x.iter_mut() {
*v = v.abs() * (n as f64) / sum;
}
}
let mut centrality = NodeMap::default();
for (idx, &val) in x.iter().enumerate() {
centrality.insert(node_list[idx], val);
}
Ok(centrality)
}
#[cfg(test)]
mod tests {
#[test]
fn test_eigenvector_with_deleted_nodes() {
use crate::centrality::eigenvector::eigenvector_centrality;
use crate::core::types::Graph;
let mut graph: Graph<i32, f64> = Graph::new();
let n1 = graph.add_node(1);
let n2 = graph.add_node(2);
let n3 = graph.add_node(3);
let n4 = graph.add_node(4);
let n5 = graph.add_node(5);
graph.add_edge(n1, n2, 1.0);
graph.add_edge(n1, n3, 1.0);
graph.add_edge(n1, n4, 1.0);
graph.add_edge(n1, n5, 1.0);
graph.remove_node(n5);
let eig = eigenvector_centrality(&graph, 100, 1e-6).unwrap();
assert!(eig[&n1] > eig[&n2]);
assert!(eig[&n1] > eig[&n3]);
assert!(eig[&n1] > eig[&n4]);
assert!(!eig.contains_key(&n5));
}
#[test]
fn test_eigenvector_issue_21_regression() {
use crate::core::types::Graph;
use crate::centrality::eigenvector::eigenvector_centrality;
let mut g = Graph::<i32, f64>::new();
let n0 = g.add_node(0);
let n1 = g.add_node(1);
let n2 = g.add_node(4);
let n3 = g.add_node(9);
g.add_edge(n0, n3, 1.0);
g.add_edge(n1, n2, 1.0);
g.add_edge(n3, n1, 1.0);
g.remove_node(n1);
let result = eigenvector_centrality(&g, 1000, 1e-6);
assert!(result.is_ok());
let centrality = result.unwrap();
assert!(!centrality.contains_key(&n1));
assert!(centrality.contains_key(&n0));
assert!(centrality.contains_key(&n2));
assert!(centrality.contains_key(&n3));
}
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);
}
#[test]
fn test_eigenvector_bipartite_converges() {
let mut g: Graph<i32, f64> = Graph::new();
let nodes: Vec<_> = (0..4).map(|i| g.add_node(i)).collect();
for i in 0..4 {
g.add_edge(nodes[i], nodes[(i + 1) % 4], 1.0);
}
let c = eigenvector_centrality(&g, 1000, 1e-9).unwrap();
for &node in &nodes {
assert!(c[&node] > 0.0);
assert!((c[&node] - c[&nodes[0]]).abs() < 1e-6);
}
}
}