use crate::core::error::Result;
use crate::core::types::{BaseGraph, GraphConstructor, NodeId, NodeMap};
use std::collections::HashMap;
use std::collections::HashSet;
pub fn local_reaching_centrality<A, W, Ty>(
graph: &BaseGraph<A, W, Ty>,
distance: usize,
) -> Result<NodeMap<f64>>
where
Ty: GraphConstructor<A, W>,
{
let mut centrality = NodeMap::default();
for (node, _) in graph.nodes() {
let mut reached = HashSet::new();
let mut current = HashSet::new();
current.insert(node);
reached.insert(node);
for _ in 0..distance {
let mut next = HashSet::new();
for &n in ¤t {
for neighbor in graph.neighbors(n) {
if !reached.contains(&neighbor) {
reached.insert(neighbor);
next.insert(neighbor);
}
}
}
current = next;
}
centrality.insert(node, reached.len() as f64);
}
Ok(centrality)
}
pub fn global_reaching_centrality<A, W, Ty>(graph: &BaseGraph<A, W, Ty>) -> Result<NodeMap<f64>>
where
Ty: GraphConstructor<A, W>,
{
local_reaching_centrality(graph, graph.node_count())
}
pub fn voterank<A, W, Ty>(graph: &BaseGraph<A, W, Ty>, num_seeds: usize) -> Vec<NodeId>
where
Ty: GraphConstructor<A, W>,
{
let node_list: Vec<NodeId> = graph.nodes().map(|(u, _)| u).collect();
let n = node_list.len();
let mut influential = Vec::new();
if n == 0 {
return influential;
}
let mut node_to_idx: HashMap<NodeId, usize> = HashMap::new();
for (i, &nid) in node_list.iter().enumerate() {
node_to_idx.insert(nid, i);
}
let directed = graph.is_directed();
let total_degree: usize = node_list
.iter()
.map(|&v| {
if directed {
graph.in_degree(v).unwrap_or(0)
} else {
graph.degree(v).unwrap_or(0)
}
})
.sum();
let avg_degree = total_degree as f64 / n as f64;
let decay = if avg_degree > 0.0 {
1.0 / avg_degree
} else {
0.0
};
let mut ability = vec![1.0f64; n];
let mut selected = vec![false; n];
for _ in 0..num_seeds.min(n) {
let mut score = vec![0.0f64; n];
for (u, v, _) in graph.edges() {
let (ui, vi) = (node_to_idx[&u], node_to_idx[&v]);
score[vi] += ability[ui];
if !directed {
score[ui] += ability[vi];
}
}
for (i, &sel) in selected.iter().enumerate() {
if sel {
score[i] = 0.0;
}
}
let mut best = 0usize;
let mut best_score = -1.0;
for (i, &s) in score.iter().enumerate() {
if s > best_score {
best_score = s;
best = i;
}
}
if best_score <= 0.0 {
break;
}
selected[best] = true;
ability[best] = 0.0;
influential.push(node_list[best]);
for neighbor in graph.neighbors(node_list[best]) {
if let Some(&j) = node_to_idx.get(&neighbor) {
ability[j] = (ability[j] - decay).max(0.0);
}
}
}
influential
}
pub fn laplacian_centrality<A, W, Ty>(graph: &BaseGraph<A, W, Ty>) -> Result<NodeMap<f64>>
where
W: Copy + PartialOrd + Into<f64>,
Ty: GraphConstructor<A, W>,
{
let degrees: HashMap<NodeId, f64> = graph
.nodes()
.map(|(node, _)| (node, graph.neighbors(node).count() as f64))
.collect();
let mut centrality = NodeMap::default();
for (node, _) in graph.nodes() {
let degree = degrees[&node];
let mut sum = degree * degree + degree;
for neighbor in graph.neighbors(node) {
sum += 2.0 * degrees[&neighbor];
}
centrality.insert(node, sum);
}
Ok(centrality)
}
#[cfg(test)]
mod tests {
#[test]
fn test_laplacian_centrality_formula() {
use crate::centrality::other::laplacian_centrality;
use crate::core::types::Graph;
use ordered_float::OrderedFloat;
let mut g = Graph::<i32, OrderedFloat<f64>>::new();
let n0 = g.add_node(0);
let n1 = g.add_node(1);
let n2 = g.add_node(2);
g.add_edge(n0, n1, OrderedFloat(1.0));
g.add_edge(n1, n2, OrderedFloat(1.0));
g.add_edge(n2, n0, OrderedFloat(1.0));
let lc = laplacian_centrality(&g).expect("laplacian should succeed");
for n in [n0, n1, n2] {
assert!(
(lc[&n] - 14.0).abs() < 1e-9,
"expected 14.0, got {}",
lc[&n]
);
}
}
#[test]
fn test_voterank_stops_and_is_deterministic() {
use crate::centrality::other::voterank;
use crate::core::types::Graph;
let mut g = Graph::<i32, f64>::new();
let center = g.add_node(0);
let leaves: Vec<_> = (1..=4).map(|i| g.add_node(i)).collect();
for &leaf in &leaves {
g.add_edge(center, leaf, 1.0);
}
let first = voterank(&g, 4);
assert_eq!(
first,
vec![center],
"star elects only the center, then stops"
);
for _ in 0..5 {
assert_eq!(voterank(&g, 4), first);
}
}
}