use std::collections::{HashMap, HashSet, VecDeque};
use super::super::graph_store::GraphStore;
pub struct BetweennessCentrality;
#[derive(Debug, Clone)]
pub struct BetweennessResult {
pub scores: HashMap<String, f64>,
pub normalized: bool,
}
impl BetweennessResult {
pub fn top(&self, n: usize) -> Vec<(String, f64)> {
let mut sorted: Vec<_> = self.scores.iter().map(|(k, v)| (k.clone(), *v)).collect();
sorted.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
sorted.truncate(n);
sorted
}
pub fn score(&self, node_id: &str) -> Option<f64> {
self.scores.get(node_id).copied()
}
}
impl BetweennessCentrality {
pub fn compute(graph: &GraphStore, normalize: bool) -> BetweennessResult {
let nodes: Vec<String> = graph.iter_nodes().map(|n| n.id.clone()).collect();
let n = nodes.len();
if n < 2 {
return BetweennessResult {
scores: nodes.into_iter().map(|id| (id, 0.0)).collect(),
normalized: normalize,
};
}
let mut centrality: HashMap<String, f64> =
nodes.iter().map(|id| (id.clone(), 0.0)).collect();
for source in &nodes {
let mut stack: Vec<String> = Vec::new();
let mut predecessors: HashMap<String, Vec<String>> = HashMap::new();
let mut sigma: HashMap<String, f64> =
nodes.iter().map(|id| (id.clone(), 0.0)).collect();
let mut dist: HashMap<String, i64> = nodes.iter().map(|id| (id.clone(), -1)).collect();
sigma.insert(source.clone(), 1.0);
dist.insert(source.clone(), 0);
let mut queue: VecDeque<String> = VecDeque::new();
queue.push_back(source.clone());
while let Some(v) = queue.pop_front() {
stack.push(v.clone());
let v_dist = *dist.get(&v).unwrap_or(&0);
for (_, w, _) in graph.outgoing_edges(&v) {
if *dist.get(&w).unwrap_or(&-1) < 0 {
queue.push_back(w.clone());
dist.insert(w.clone(), v_dist + 1);
}
if *dist.get(&w).unwrap_or(&0) == v_dist + 1 {
let sigma_v = *sigma.get(&v).unwrap_or(&0.0);
let sigma_w = sigma.entry(w.clone()).or_insert(0.0);
*sigma_w += sigma_v;
predecessors.entry(w.clone()).or_default().push(v.clone());
}
}
}
let mut delta: HashMap<String, f64> =
nodes.iter().map(|id| (id.clone(), 0.0)).collect();
while let Some(w) = stack.pop() {
if let Some(preds) = predecessors.get(&w) {
let sigma_w = *sigma.get(&w).unwrap_or(&1.0);
let delta_w = *delta.get(&w).unwrap_or(&0.0);
for v in preds {
let sigma_v = *sigma.get(v).unwrap_or(&1.0);
let d = (sigma_v / sigma_w) * (1.0 + delta_w);
*delta.entry(v.clone()).or_insert(0.0) += d;
}
}
if w != *source {
let c = centrality.entry(w.clone()).or_insert(0.0);
*c += *delta.get(&w).unwrap_or(&0.0);
}
}
}
if normalize && n > 2 {
let norm_factor = 1.0 / ((n - 1) * (n - 2)) as f64;
for score in centrality.values_mut() {
*score *= norm_factor;
}
}
BetweennessResult {
scores: centrality,
normalized: normalize,
}
}
}
pub struct ClosenessCentrality;
#[derive(Debug, Clone)]
pub struct ClosenessResult {
pub scores: HashMap<String, f64>,
}
impl ClosenessResult {
pub fn top(&self, n: usize) -> Vec<(String, f64)> {
let mut sorted: Vec<_> = self.scores.iter().map(|(k, v)| (k.clone(), *v)).collect();
sorted.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
sorted.truncate(n);
sorted
}
}
impl ClosenessCentrality {
pub fn compute(graph: &GraphStore) -> ClosenessResult {
let nodes: Vec<String> = graph.iter_nodes().map(|n| n.id.clone()).collect();
let n = nodes.len();
if n <= 1 {
return ClosenessResult {
scores: nodes.into_iter().map(|id| (id, 1.0)).collect(),
};
}
let mut scores: HashMap<String, f64> = HashMap::new();
for source in &nodes {
let mut distances: HashMap<String, usize> = HashMap::new();
let mut queue: VecDeque<(String, usize)> = VecDeque::new();
queue.push_back((source.clone(), 0));
distances.insert(source.clone(), 0);
while let Some((current, dist)) = queue.pop_front() {
for (_, neighbor, _) in graph.outgoing_edges(¤t) {
if !distances.contains_key(&neighbor) {
distances.insert(neighbor.clone(), dist + 1);
queue.push_back((neighbor, dist + 1));
}
}
}
let sum_reciprocal: f64 = distances
.iter()
.filter(|(k, _)| *k != source)
.map(|(_, d)| 1.0 / (*d as f64))
.sum();
let closeness = sum_reciprocal / (n - 1) as f64;
scores.insert(source.clone(), closeness);
}
ClosenessResult { scores }
}
}
pub struct DegreeCentrality;
#[derive(Debug, Clone)]
pub struct DegreeCentralityResult {
pub in_degree: HashMap<String, usize>,
pub out_degree: HashMap<String, usize>,
pub total_degree: HashMap<String, usize>,
}
impl DegreeCentralityResult {
pub fn top_by_total(&self, n: usize) -> Vec<(String, usize)> {
let mut sorted: Vec<_> = self
.total_degree
.iter()
.map(|(k, v)| (k.clone(), *v))
.collect();
sorted.sort_by_key(|b| std::cmp::Reverse(b.1));
sorted.truncate(n);
sorted
}
pub fn top_by_in_degree(&self, n: usize) -> Vec<(String, usize)> {
let mut sorted: Vec<_> = self
.in_degree
.iter()
.map(|(k, v)| (k.clone(), *v))
.collect();
sorted.sort_by_key(|b| std::cmp::Reverse(b.1));
sorted.truncate(n);
sorted
}
pub fn top_by_out_degree(&self, n: usize) -> Vec<(String, usize)> {
let mut sorted: Vec<_> = self
.out_degree
.iter()
.map(|(k, v)| (k.clone(), *v))
.collect();
sorted.sort_by_key(|b| std::cmp::Reverse(b.1));
sorted.truncate(n);
sorted
}
}
impl DegreeCentrality {
pub fn compute(graph: &GraphStore) -> DegreeCentralityResult {
let mut in_degree: HashMap<String, usize> = HashMap::new();
let mut out_degree: HashMap<String, usize> = HashMap::new();
for node in graph.iter_nodes() {
in_degree.insert(node.id.clone(), 0);
out_degree.insert(node.id.clone(), 0);
}
for node in graph.iter_nodes() {
let out_count = graph.outgoing_edges(&node.id).len();
out_degree.insert(node.id.clone(), out_count);
for (_, target, _) in graph.outgoing_edges(&node.id) {
*in_degree.entry(target).or_insert(0) += 1;
}
}
let total_degree: HashMap<String, usize> = in_degree
.keys()
.map(|k| {
let total = in_degree.get(k).unwrap_or(&0) + out_degree.get(k).unwrap_or(&0);
(k.clone(), total)
})
.collect();
DegreeCentralityResult {
in_degree,
out_degree,
total_degree,
}
}
}
pub struct EigenvectorCentrality {
pub epsilon: f64,
pub max_iterations: usize,
}
impl Default for EigenvectorCentrality {
fn default() -> Self {
Self {
epsilon: 1e-6,
max_iterations: 100,
}
}
}
#[derive(Debug, Clone)]
pub struct EigenvectorResult {
pub scores: HashMap<String, f64>,
pub iterations: usize,
pub converged: bool,
}
impl EigenvectorResult {
pub fn top(&self, n: usize) -> Vec<(String, f64)> {
let mut sorted: Vec<_> = self.scores.iter().map(|(k, v)| (k.clone(), *v)).collect();
sorted.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
sorted.truncate(n);
sorted
}
}
impl EigenvectorCentrality {
pub fn new() -> Self {
Self::default()
}
pub fn compute(&self, graph: &GraphStore) -> EigenvectorResult {
let nodes: Vec<String> = graph.iter_nodes().map(|n| n.id.clone()).collect();
let n = nodes.len();
if n == 0 {
return EigenvectorResult {
scores: HashMap::new(),
iterations: 0,
converged: true,
};
}
let mut neighbors: HashMap<String, Vec<String>> = HashMap::new();
for node in &nodes {
let mut node_neighbors: HashSet<String> = HashSet::new();
for (_, target, _) in graph.outgoing_edges(node) {
node_neighbors.insert(target);
}
for (_, source, _) in graph.incoming_edges(node) {
node_neighbors.insert(source);
}
neighbors.insert(node.clone(), node_neighbors.into_iter().collect());
}
let init_score = 1.0 / (n as f64).sqrt();
let mut scores: HashMap<String, f64> =
nodes.iter().map(|id| (id.clone(), init_score)).collect();
let mut converged = false;
let mut iterations = 0;
for iter in 0..self.max_iterations {
iterations = iter + 1;
let mut new_scores: HashMap<String, f64> = HashMap::new();
for node in &nodes {
let sum: f64 = neighbors
.get(node)
.map(|nbrs| {
nbrs.iter()
.map(|n| scores.get(n).copied().unwrap_or(0.0))
.sum()
})
.unwrap_or(0.0);
new_scores.insert(node.clone(), sum);
}
let norm: f64 = new_scores.values().map(|v| v * v).sum::<f64>().sqrt();
if norm > 0.0 {
for score in new_scores.values_mut() {
*score /= norm;
}
}
let diff: f64 = nodes
.iter()
.map(|id| {
let old = scores.get(id).copied().unwrap_or(0.0);
let new = new_scores.get(id).copied().unwrap_or(0.0);
(old - new).abs()
})
.sum();
scores = new_scores;
if diff < self.epsilon {
converged = true;
break;
}
}
EigenvectorResult {
scores,
iterations,
converged,
}
}
}