use crate::core::{Graph, IgraphError, IgraphResult};
const DEFAULT_EPS: f64 = 1e-14;
const DEFAULT_MAX_ITER: usize = 5000;
pub fn eigenvector_centrality(graph: &Graph) -> IgraphResult<Vec<f64>> {
if graph.is_directed() {
return Err(IgraphError::Unsupported(
"directed eigenvector_centrality is PR-012b; not yet ported",
));
}
let n = graph.vcount();
let n_us = n as usize;
if n == 0 {
return Ok(Vec::new());
}
if n == 1 {
return Ok(vec![1.0]);
}
let mut adj: Vec<Vec<u32>> = Vec::with_capacity(n_us);
for v in 0..n {
adj.push(graph.neighbors(v)?);
}
let mut x = vec![1.0_f64; n_us];
let mut x_new = vec![0.0_f64; n_us];
for _ in 0..DEFAULT_MAX_ITER {
for v in 0..n_us {
let mut sum = x[v];
for &u in &adj[v] {
sum += x[u as usize];
}
x_new[v] = sum;
}
let max = x_new.iter().fold(0.0_f64, |acc, &v| acc.max(v.abs()));
if max > 0.0 {
for slot in &mut x_new {
*slot /= max;
}
}
let mut diff = 0.0_f64;
for v in 0..n_us {
diff += (x_new[v] - x[v]).abs();
}
std::mem::swap(&mut x, &mut x_new);
if diff < DEFAULT_EPS {
break;
}
}
Ok(x)
}
#[cfg(test)]
mod tests {
use super::*;
fn close(actual: &[f64], expected: &[f64], tol: f64) {
assert_eq!(actual.len(), expected.len(), "length mismatch");
for (i, (a, e)) in actual.iter().zip(expected.iter()).enumerate() {
assert!((a - e).abs() < tol, "vertex {i}: actual={a} expected={e}");
}
}
#[test]
fn empty_graph_yields_empty() {
let g = Graph::with_vertices(0);
assert!(eigenvector_centrality(&g).unwrap().is_empty());
}
#[test]
fn singleton_yields_one() {
let g = Graph::with_vertices(1);
assert_eq!(eigenvector_centrality(&g).unwrap(), vec![1.0]);
}
#[test]
fn isolated_vertices_yield_uniform_one() {
let g = Graph::with_vertices(3);
let ec = eigenvector_centrality(&g).unwrap();
close(&ec, &[1.0, 1.0, 1.0], 1e-9);
}
#[test]
fn triangle_uniform_one() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
let ec = eigenvector_centrality(&g).unwrap();
close(&ec, &[1.0, 1.0, 1.0], 1e-9);
}
#[test]
fn star_4_centre_one_leaves_inverse_sqrt_three() {
let mut g = Graph::with_vertices(4);
for v in 1..4 {
g.add_edge(0, v).unwrap();
}
let ec = eigenvector_centrality(&g).unwrap();
let inv_sqrt3 = 1.0 / 3f64.sqrt();
close(&ec, &[1.0, inv_sqrt3, inv_sqrt3, inv_sqrt3], 1e-9);
}
#[test]
fn k4_uniform_one() {
let mut g = Graph::with_vertices(4);
for u in 0..4u32 {
for v in (u + 1)..4 {
g.add_edge(u, v).unwrap();
}
}
let ec = eigenvector_centrality(&g).unwrap();
close(&ec, &[1.0, 1.0, 1.0, 1.0], 1e-9);
}
#[test]
fn directed_graph_returns_unsupported() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
assert!(eigenvector_centrality(&g).is_err());
}
}