use std::collections::{HashMap, HashSet};
use std::hash::Hash;
use scirs2_core::ndarray::{Array1, Array2};
use crate::algorithms::{dijkstra_path, dijkstra_path_digraph};
use crate::base::{DiGraph, EdgeWeight, Graph, Node};
use crate::error::{GraphError, Result};
use petgraph::graph::IndexType;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CentralityType {
Degree,
Betweenness,
Closeness,
Eigenvector,
Katz,
PageRank,
WeightedDegree,
WeightedBetweenness,
WeightedCloseness,
}
#[allow(dead_code)]
pub fn centrality<N, E, Ix>(
graph: &Graph<N, E, Ix>,
centrality_type: CentralityType,
) -> Result<HashMap<N, f64>>
where
N: Node + std::fmt::Debug,
E: EdgeWeight
+ scirs2_core::numeric::Zero
+ scirs2_core::numeric::One
+ std::ops::Add<Output = E>
+ PartialOrd
+ Into<f64>
+ std::marker::Copy
+ std::fmt::Debug
+ std::default::Default,
Ix: petgraph::graph::IndexType,
{
let nodes = graph.nodes();
let n = nodes.len();
if n == 0 {
return Err(GraphError::InvalidGraph("Empty graph".to_string()));
}
match centrality_type {
CentralityType::Degree => degree_centrality(graph),
CentralityType::Betweenness => betweenness_centrality(graph),
CentralityType::Closeness => closeness_centrality(graph),
CentralityType::Eigenvector => eigenvector_centrality(graph),
CentralityType::Katz => katz_centrality(graph, 0.1, 1.0), CentralityType::PageRank => pagerank_centrality(graph, 0.85, 1e-6), CentralityType::WeightedDegree => weighted_degree_centrality(graph),
CentralityType::WeightedBetweenness => weighted_betweenness_centrality(graph),
CentralityType::WeightedCloseness => weighted_closeness_centrality(graph),
}
}
#[allow(dead_code)]
pub fn centrality_digraph<N, E, Ix>(
graph: &DiGraph<N, E, Ix>,
centrality_type: CentralityType,
) -> Result<HashMap<N, f64>>
where
N: Node + std::fmt::Debug,
E: EdgeWeight
+ scirs2_core::numeric::Zero
+ scirs2_core::numeric::One
+ std::ops::Add<Output = E>
+ PartialOrd
+ Into<f64>
+ std::marker::Copy
+ std::fmt::Debug
+ std::default::Default,
Ix: petgraph::graph::IndexType,
{
let nodes = graph.nodes();
let n = nodes.len();
if n == 0 {
return Err(GraphError::InvalidGraph("Empty graph".to_string()));
}
match centrality_type {
CentralityType::Degree => degree_centrality_digraph(graph),
CentralityType::Betweenness => betweenness_centrality_digraph(graph),
CentralityType::Closeness => closeness_centrality_digraph(graph),
CentralityType::Eigenvector => eigenvector_centrality_digraph(graph),
CentralityType::Katz => katz_centrality_digraph(graph, 0.1, 1.0), CentralityType::PageRank => pagerank_centrality_digraph(graph, 0.85, 1e-6), CentralityType::WeightedDegree => weighted_degree_centrality_digraph(graph),
CentralityType::WeightedBetweenness => weighted_betweenness_centrality_digraph(graph),
CentralityType::WeightedCloseness => weighted_closeness_centrality_digraph(graph),
}
}
#[allow(dead_code)]
fn degree_centrality<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<HashMap<N, f64>>
where
N: Node + std::fmt::Debug,
E: EdgeWeight,
Ix: petgraph::graph::IndexType,
{
let nodes = graph.nodes();
let n = nodes.len() as f64;
let degrees = graph.degree_vector();
let mut centrality = HashMap::new();
let normalization = if n <= 1.0 { 1.0 } else { n - 1.0 };
for (i, node) in nodes.iter().enumerate() {
let degree = degrees[i] as f64;
centrality.insert((*node).clone(), degree / normalization);
}
Ok(centrality)
}
#[allow(dead_code)]
fn degree_centrality_digraph<N, E, Ix>(graph: &DiGraph<N, E, Ix>) -> Result<HashMap<N, f64>>
where
N: Node + std::fmt::Debug,
E: EdgeWeight,
Ix: petgraph::graph::IndexType,
{
let nodes = graph.nodes();
let n = nodes.len() as f64;
let in_degrees = graph.in_degree_vector();
let out_degrees = graph.out_degree_vector();
let mut centrality = HashMap::new();
let normalization = if n <= 1.0 { 1.0 } else { n - 1.0 };
for (i, node) in nodes.iter().enumerate() {
let degree = (in_degrees[i] + out_degrees[i]) as f64;
centrality.insert((*node).clone(), degree / normalization);
}
Ok(centrality)
}
#[allow(dead_code)]
fn betweenness_centrality<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<HashMap<N, f64>>
where
N: Node + std::fmt::Debug,
E: EdgeWeight
+ scirs2_core::numeric::Zero
+ scirs2_core::numeric::One
+ std::ops::Add<Output = E>
+ PartialOrd
+ Into<f64>
+ std::marker::Copy
+ std::fmt::Debug
+ std::default::Default,
Ix: petgraph::graph::IndexType,
{
let nodes = graph.nodes();
let n = nodes.len();
if n <= 1 {
let mut result = HashMap::new();
for node in nodes {
result.insert(node.clone(), 0.0);
}
return Ok(result);
}
let mut betweenness = HashMap::new();
for node in nodes.iter() {
betweenness.insert((*node).clone(), 0.0);
}
for (i, &s) in nodes.iter().enumerate() {
for (j, &t) in nodes.iter().enumerate() {
if i == j {
continue;
}
if let Ok(Some(path)) = dijkstra_path(graph, s, t) {
for node in &path.nodes[1..path.nodes.len() - 1] {
*betweenness.entry(node.clone()).or_insert(0.0) += 1.0;
}
}
}
}
let scale = 1.0 / ((n - 1) * (n - 2)) as f64;
for val in betweenness.values_mut() {
*val *= scale;
}
Ok(betweenness)
}
#[allow(dead_code)]
fn betweenness_centrality_digraph<N, E, Ix>(graph: &DiGraph<N, E, Ix>) -> Result<HashMap<N, f64>>
where
N: Node + std::fmt::Debug,
E: EdgeWeight
+ scirs2_core::numeric::Zero
+ scirs2_core::numeric::One
+ std::ops::Add<Output = E>
+ PartialOrd
+ Into<f64>
+ std::marker::Copy
+ std::fmt::Debug
+ std::default::Default,
Ix: petgraph::graph::IndexType,
{
let nodes = graph.nodes();
let n = nodes.len();
if n <= 1 {
let mut result = HashMap::new();
for node in nodes {
result.insert(node.clone(), 0.0);
}
return Ok(result);
}
let mut betweenness = HashMap::new();
for node in nodes.iter() {
betweenness.insert((*node).clone(), 0.0);
}
for (i, &s) in nodes.iter().enumerate() {
for (j, &t) in nodes.iter().enumerate() {
if i == j {
continue;
}
if let Ok(Some(path)) = dijkstra_path_digraph(graph, s, t) {
for node in &path.nodes[1..path.nodes.len() - 1] {
*betweenness.entry(node.clone()).or_insert(0.0) += 1.0;
}
}
}
}
let scale = 1.0 / ((n - 1) * (n - 2)) as f64;
for val in betweenness.values_mut() {
*val *= scale;
}
Ok(betweenness)
}
#[allow(dead_code)]
fn closeness_centrality<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<HashMap<N, f64>>
where
N: Node + std::fmt::Debug,
E: EdgeWeight
+ scirs2_core::numeric::Zero
+ scirs2_core::numeric::One
+ std::ops::Add<Output = E>
+ PartialOrd
+ Into<f64>
+ std::marker::Copy
+ std::fmt::Debug
+ std::default::Default,
Ix: petgraph::graph::IndexType,
{
let nodes = graph.nodes();
let n = nodes.len();
if n <= 1 {
let mut result = HashMap::new();
for node in nodes {
result.insert(node.clone(), 1.0);
}
return Ok(result);
}
let mut closeness = HashMap::new();
for &node in nodes.iter() {
let mut sum_distances = 0.0;
let mut reachable_nodes = 0;
for &other in nodes.iter() {
if node == other {
continue;
}
if let Ok(Some(path)) = dijkstra_path(graph, node, other) {
sum_distances += path.total_weight.into();
reachable_nodes += 1;
}
}
if reachable_nodes > 0 {
let closeness_value = reachable_nodes as f64 / sum_distances;
let normalized = closeness_value * (reachable_nodes as f64 / (n - 1) as f64);
closeness.insert(node.clone(), normalized);
} else {
closeness.insert(node.clone(), 0.0);
}
}
Ok(closeness)
}
#[allow(dead_code)]
fn closeness_centrality_digraph<N, E, Ix>(graph: &DiGraph<N, E, Ix>) -> Result<HashMap<N, f64>>
where
N: Node + std::fmt::Debug,
E: EdgeWeight
+ scirs2_core::numeric::Zero
+ scirs2_core::numeric::One
+ std::ops::Add<Output = E>
+ PartialOrd
+ Into<f64>
+ std::marker::Copy
+ std::fmt::Debug
+ std::default::Default,
Ix: petgraph::graph::IndexType,
{
let nodes = graph.nodes();
let n = nodes.len();
if n <= 1 {
let mut result = HashMap::new();
for node in nodes {
result.insert(node.clone(), 1.0);
}
return Ok(result);
}
let mut closeness = HashMap::new();
for &node in nodes.iter() {
let mut sum_distances = 0.0;
let mut reachable_nodes = 0;
for &other in nodes.iter() {
if node == other {
continue;
}
if let Ok(Some(path)) = dijkstra_path_digraph(graph, node, other) {
sum_distances += path.total_weight.into();
reachable_nodes += 1;
}
}
if reachable_nodes > 0 {
let closeness_value = reachable_nodes as f64 / sum_distances;
let normalized = closeness_value * (reachable_nodes as f64 / (n - 1) as f64);
closeness.insert(node.clone(), normalized);
} else {
closeness.insert(node.clone(), 0.0);
}
}
Ok(closeness)
}
#[allow(dead_code)]
fn eigenvector_centrality<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<HashMap<N, f64>>
where
N: Node + std::fmt::Debug,
E: EdgeWeight
+ scirs2_core::numeric::Zero
+ scirs2_core::numeric::One
+ PartialOrd
+ Into<f64>
+ std::marker::Copy,
Ix: petgraph::graph::IndexType,
{
let nodes = graph.nodes();
let n = nodes.len();
if n == 0 {
return Err(GraphError::InvalidGraph("Empty _graph".to_string()));
}
let adj_mat = graph.adjacency_matrix();
let mut adj_f64 = Array2::<f64>::zeros((n, n));
for i in 0..n {
for j in 0..n {
adj_f64[[i, j]] = adj_mat[[i, j]].into();
}
}
let mut x = Array1::<f64>::ones(n);
x.mapv_inplace(|v| v / (n as f64).sqrt());
let max_iter = 100;
let tol = 1e-6;
for _ in 0..max_iter {
let y = adj_f64.dot(&x);
let norm = (y.iter().map(|v| v * v).sum::<f64>()).sqrt();
if norm < 1e-10 {
return Err(GraphError::AlgorithmError(
"Eigenvector centrality computation failed: zero eigenvalue".to_string(),
));
}
let y_norm = y / norm;
let diff = (&y_norm - &x).iter().map(|v| v.abs()).sum::<f64>();
if diff < tol {
let mut result = HashMap::new();
for (i, &node) in nodes.iter().enumerate() {
result.insert(node.clone(), y_norm[i]);
}
return Ok(result);
}
x = y_norm;
}
Err(GraphError::AlgorithmError(
"Eigenvector centrality computation did not converge".to_string(),
))
}
#[allow(dead_code)]
fn eigenvector_centrality_digraph<N, E, Ix>(graph: &DiGraph<N, E, Ix>) -> Result<HashMap<N, f64>>
where
N: Node + std::fmt::Debug,
E: EdgeWeight
+ scirs2_core::numeric::Zero
+ scirs2_core::numeric::One
+ PartialOrd
+ Into<f64>
+ std::marker::Copy,
Ix: petgraph::graph::IndexType,
{
let nodes = graph.nodes();
let n = nodes.len();
if n == 0 {
return Err(GraphError::InvalidGraph("Empty _graph".to_string()));
}
let adj_mat = graph.adjacency_matrix();
let mut adj_f64 = Array2::<f64>::zeros((n, n));
for i in 0..n {
for j in 0..n {
adj_f64[[i, j]] = adj_mat[[i, j]];
}
}
let mut x = Array1::<f64>::ones(n);
x.mapv_inplace(|v| v / (n as f64).sqrt());
let max_iter = 100;
let tol = 1e-6;
for _ in 0..max_iter {
let y = adj_f64.dot(&x);
let norm = (y.iter().map(|v| v * v).sum::<f64>()).sqrt();
if norm < 1e-10 {
return Err(GraphError::AlgorithmError(
"Eigenvector centrality computation failed: zero eigenvalue".to_string(),
));
}
let y_norm = y / norm;
let diff = (&y_norm - &x).iter().map(|v| v.abs()).sum::<f64>();
if diff < tol {
let mut result = HashMap::new();
for (i, &node) in nodes.iter().enumerate() {
result.insert(node.clone(), y_norm[i]);
}
return Ok(result);
}
x = y_norm;
}
Err(GraphError::AlgorithmError(
"Eigenvector centrality computation did not converge".to_string(),
))
}
#[allow(dead_code)]
pub fn clustering_coefficient<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<HashMap<N, f64>>
where
N: Node + std::fmt::Debug,
E: EdgeWeight,
Ix: petgraph::graph::IndexType,
{
let mut coefficients = HashMap::new();
for (idx, &node) in graph.nodes().iter().enumerate() {
let node_idx = petgraph::graph::NodeIndex::new(idx);
let neighbors: HashSet<_> = graph.inner().neighbors(node_idx).collect();
let k = neighbors.len();
if k < 2 {
coefficients.insert(node.clone(), 0.0);
continue;
}
let mut edge_count = 0;
for &n1 in &neighbors {
for &n2 in &neighbors {
if n1 != n2 && graph.inner().contains_edge(n1, n2) {
edge_count += 1;
}
}
}
edge_count /= 2;
let possible_edges = k * (k - 1) / 2;
let coefficient = edge_count as f64 / possible_edges as f64;
coefficients.insert(node.clone(), coefficient);
}
Ok(coefficients)
}
#[allow(dead_code)]
pub fn graph_density<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<f64>
where
N: Node + std::fmt::Debug,
E: EdgeWeight,
Ix: petgraph::graph::IndexType,
{
let n = graph.node_count();
if n <= 1 {
return Err(GraphError::InvalidGraph(
"Graph density undefined for graphs with 0 or 1 nodes".to_string(),
));
}
let m = graph.edge_count();
let possible_edges = n * (n - 1) / 2;
Ok(m as f64 / possible_edges as f64)
}
#[allow(dead_code)]
pub fn graph_density_digraph<N, E, Ix>(graph: &DiGraph<N, E, Ix>) -> Result<f64>
where
N: Node + std::fmt::Debug,
E: EdgeWeight,
Ix: petgraph::graph::IndexType,
{
let n = graph.node_count();
if n <= 1 {
return Err(GraphError::InvalidGraph(
"Graph density undefined for graphs with 0 or 1 nodes".to_string(),
));
}
let m = graph.edge_count();
let possible_edges = n * (n - 1);
Ok(m as f64 / possible_edges as f64)
}
#[allow(dead_code)]
pub fn katz_centrality<N, E, Ix>(
graph: &Graph<N, E, Ix>,
alpha: f64,
beta: f64,
) -> Result<HashMap<N, f64>>
where
N: Node + std::fmt::Debug,
E: EdgeWeight + scirs2_core::numeric::Zero + scirs2_core::numeric::One + Into<f64> + Copy,
Ix: petgraph::graph::IndexType,
{
let nodes = graph.nodes();
let n = nodes.len();
if n == 0 {
return Err(GraphError::InvalidGraph("Empty graph".to_string()));
}
let adj_mat = graph.adjacency_matrix();
let mut adj_f64 = Array2::<f64>::zeros((n, n));
for i in 0..n {
for j in 0..n {
adj_f64[[i, j]] = adj_mat[[i, j]].into();
}
}
let mut identity = Array2::<f64>::zeros((n, n));
for i in 0..n {
identity[[i, i]] = 1.0;
}
let _factor_matrix = &identity - &(alpha * &adj_f64);
let beta_vec = Array1::<f64>::from_elem(n, beta);
let mut centrality_vec = Array1::<f64>::ones(n);
let max_iter = 100;
let tol = 1e-6;
for _ in 0..max_iter {
let new_centrality = alpha * adj_f64.dot(¢rality_vec) + &beta_vec;
let diff = (&new_centrality - ¢rality_vec)
.iter()
.map(|v| v.abs())
.sum::<f64>();
if diff < tol {
centrality_vec = new_centrality;
break;
}
centrality_vec = new_centrality;
}
let mut result = HashMap::new();
for (i, &node) in nodes.iter().enumerate() {
result.insert(node.clone(), centrality_vec[i]);
}
Ok(result)
}
#[allow(dead_code)]
pub fn katz_centrality_digraph<N, E, Ix>(
graph: &DiGraph<N, E, Ix>,
alpha: f64,
beta: f64,
) -> Result<HashMap<N, f64>>
where
N: Node + std::fmt::Debug,
E: EdgeWeight + scirs2_core::numeric::Zero + scirs2_core::numeric::One + Into<f64> + Copy,
Ix: petgraph::graph::IndexType,
{
let nodes = graph.nodes();
let n = nodes.len();
if n == 0 {
return Err(GraphError::InvalidGraph("Empty graph".to_string()));
}
let adj_mat = graph.adjacency_matrix();
let mut adj_f64 = Array2::<f64>::zeros((n, n));
for i in 0..n {
for j in 0..n {
adj_f64[[i, j]] = adj_mat[[i, j]];
}
}
let beta_vec = Array1::<f64>::from_elem(n, beta);
let mut centrality_vec = Array1::<f64>::ones(n);
let max_iter = 100;
let tol = 1e-6;
for _ in 0..max_iter {
let new_centrality = alpha * adj_f64.t().dot(¢rality_vec) + &beta_vec;
let diff = (&new_centrality - ¢rality_vec)
.iter()
.map(|v| v.abs())
.sum::<f64>();
if diff < tol {
centrality_vec = new_centrality;
break;
}
centrality_vec = new_centrality;
}
let mut result = HashMap::new();
for (i, &node) in nodes.iter().enumerate() {
result.insert(node.clone(), centrality_vec[i]);
}
Ok(result)
}
#[allow(dead_code)]
pub fn pagerank_centrality<N, E, Ix>(
graph: &Graph<N, E, Ix>,
damping: f64,
tolerance: f64,
) -> Result<HashMap<N, f64>>
where
N: Node + std::fmt::Debug,
E: EdgeWeight,
Ix: petgraph::graph::IndexType,
{
let nodes = graph.nodes();
let n = nodes.len();
if n == 0 {
return Err(GraphError::InvalidGraph("Empty graph".to_string()));
}
let mut pagerank = Array1::<f64>::from_elem(n, 1.0 / n as f64);
let max_iter = 100;
let degrees = graph.degree_vector();
for _ in 0..max_iter {
let mut dangling_sum = 0.0;
for (i, _) in graph.inner().node_indices().enumerate() {
if degrees[i] == 0 {
dangling_sum += pagerank[i];
}
}
let dangling_contrib = damping * dangling_sum / n as f64;
let mut new_pagerank =
Array1::<f64>::from_elem(n, (1.0 - damping) / n as f64 + dangling_contrib);
for (i, node_idx) in graph.inner().node_indices().enumerate() {
let node_degree = degrees[i] as f64;
if node_degree > 0.0 {
let contribution = damping * pagerank[i] / node_degree;
for neighbor_idx in graph.inner().neighbors(node_idx) {
let neighbor_i = neighbor_idx.index();
new_pagerank[neighbor_i] += contribution;
}
}
}
let diff = (&new_pagerank - &pagerank)
.iter()
.map(|v| v.abs())
.sum::<f64>();
if diff < tolerance {
pagerank = new_pagerank;
break;
}
pagerank = new_pagerank;
}
let mut result = HashMap::new();
for (i, &node) in nodes.iter().enumerate() {
result.insert(node.clone(), pagerank[i]);
}
Ok(result)
}
#[allow(dead_code)]
pub fn parallel_pagerank_centrality<N, E, Ix>(
graph: &Graph<N, E, Ix>,
damping: f64,
tolerance: f64,
max_iterations: Option<usize>,
) -> Result<HashMap<N, f64>>
where
N: Node + Send + Sync + std::fmt::Debug,
E: EdgeWeight + Send + Sync,
Ix: petgraph::graph::IndexType + Send + Sync,
{
use scirs2_core::parallel_ops::*;
use std::sync::{Arc, Mutex};
let nodes = graph.nodes();
let n = nodes.len();
if n == 0 {
return Err(GraphError::InvalidGraph("Empty graph".to_string()));
}
if n < 1000 {
return pagerank_centrality(graph, damping, tolerance);
}
let max_iter = max_iterations.unwrap_or(100);
let mut pagerank = Array1::<f64>::from_elem(n, 1.0 / n as f64);
let degrees = graph.degree_vector();
let neighbor_lists: Vec<Vec<usize>> = (0..n)
.map(|i| {
let node_idx = petgraph::graph::NodeIndex::new(i);
graph
.inner()
.neighbors(node_idx)
.map(|neighbor_idx| neighbor_idx.index())
.collect()
})
.collect();
for _iteration in 0..max_iter {
let new_pagerank = Arc::new(Mutex::new(Array1::<f64>::from_elem(
n,
(1.0 - damping) / n as f64,
)));
(0..n).into_par_iter().for_each(|i| {
let node_degree = degrees[i] as f64;
if node_degree > 0.0 {
let contribution = damping * pagerank[i] / node_degree;
let mut local_updates = Vec::new();
for &neighbor_i in &neighbor_lists[i] {
local_updates.push((neighbor_i, contribution));
}
if !local_updates.is_empty() {
let mut new_pr = new_pagerank.lock().expect("Failed to acquire lock");
for (neighbor_i, contrib) in local_updates {
new_pr[neighbor_i] += contrib;
}
}
}
});
let new_pagerank = Arc::try_unwrap(new_pagerank)
.expect("Failed to unwrap Arc")
.into_inner()
.expect("Failed to get inner value");
let diff = pagerank
.iter()
.zip(new_pagerank.iter())
.map(|(old, new)| (new - old).abs())
.sum::<f64>();
if diff < tolerance {
pagerank = new_pagerank;
break;
}
pagerank = new_pagerank;
}
let result: HashMap<N, f64> = nodes
.iter()
.enumerate()
.map(|(i, node)| ((*node).clone(), pagerank[i]))
.collect();
Ok(result)
}
#[derive(Debug, Clone)]
pub struct HitsScores<N: Node> {
pub authorities: HashMap<N, f64>,
pub hubs: HashMap<N, f64>,
}
#[allow(dead_code)]
pub fn hits_algorithm<N, E, Ix>(
graph: &DiGraph<N, E, Ix>,
max_iter: usize,
tolerance: f64,
) -> Result<HitsScores<N>>
where
N: Node + Clone + Hash + Eq + std::fmt::Debug,
E: EdgeWeight,
Ix: IndexType,
{
let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
let n = nodes.len();
if n == 0 {
return Ok(HitsScores {
authorities: HashMap::new(),
hubs: HashMap::new(),
});
}
let mut authorities = vec![1.0 / (n as f64).sqrt(); n];
let mut hubs = vec![1.0 / (n as f64).sqrt(); n];
let mut new_authorities = vec![0.0; n];
let mut new_hubs = vec![0.0; n];
let node_to_idx: HashMap<N, usize> = nodes
.iter()
.enumerate()
.map(|(i, n)| (n.clone(), i))
.collect();
for _ in 0..max_iter {
new_authorities.fill(0.0);
for (i, node) in nodes.iter().enumerate() {
if let Ok(predecessors) = graph.predecessors(node) {
for pred in predecessors {
if let Some(&pred_idx) = node_to_idx.get(&pred) {
new_authorities[i] += hubs[pred_idx];
}
}
}
}
new_hubs.fill(0.0);
for (i, node) in nodes.iter().enumerate() {
if let Ok(successors) = graph.successors(node) {
for succ in successors {
if let Some(&succ_idx) = node_to_idx.get(&succ) {
new_hubs[i] += authorities[succ_idx];
}
}
}
}
let auth_norm: f64 = new_authorities.iter().map(|x| x * x).sum::<f64>().sqrt();
let hub_norm: f64 = new_hubs.iter().map(|x| x * x).sum::<f64>().sqrt();
if auth_norm > 0.0 {
for score in &mut new_authorities {
*score /= auth_norm;
}
}
if hub_norm > 0.0 {
for score in &mut new_hubs {
*score /= hub_norm;
}
}
let auth_diff: f64 = authorities
.iter()
.zip(&new_authorities)
.map(|(old, new)| (old - new).abs())
.sum();
let hub_diff: f64 = hubs
.iter()
.zip(&new_hubs)
.map(|(old, new)| (old - new).abs())
.sum();
if auth_diff < tolerance && hub_diff < tolerance {
break;
}
authorities.copy_from_slice(&new_authorities);
hubs.copy_from_slice(&new_hubs);
}
let authority_map = nodes
.iter()
.enumerate()
.map(|(i, n)| (n.clone(), authorities[i]))
.collect();
let hub_map = nodes
.iter()
.enumerate()
.map(|(i, n)| (n.clone(), hubs[i]))
.collect();
Ok(HitsScores {
authorities: authority_map,
hubs: hub_map,
})
}
#[allow(dead_code)]
pub fn pagerank_centrality_digraph<N, E, Ix>(
graph: &DiGraph<N, E, Ix>,
damping: f64,
tolerance: f64,
) -> Result<HashMap<N, f64>>
where
N: Node + std::fmt::Debug,
E: EdgeWeight,
Ix: petgraph::graph::IndexType,
{
let nodes = graph.nodes();
let n = nodes.len();
if n == 0 {
return Err(GraphError::InvalidGraph("Empty graph".to_string()));
}
let mut pagerank = Array1::<f64>::from_elem(n, 1.0 / n as f64);
let max_iter = 100;
let out_degrees = graph.out_degree_vector();
for _ in 0..max_iter {
let mut new_pagerank = Array1::<f64>::from_elem(n, (1.0 - damping) / n as f64);
for (i, node_idx) in graph.inner().node_indices().enumerate() {
let node_out_degree = out_degrees[i] as f64;
if node_out_degree > 0.0 {
let contribution = damping * pagerank[i] / node_out_degree;
for neighbor_idx in graph
.inner()
.neighbors_directed(node_idx, petgraph::Direction::Outgoing)
{
let neighbor_i = neighbor_idx.index();
new_pagerank[neighbor_i] += contribution;
}
} else {
let contribution = damping * pagerank[i] / n as f64;
for j in 0..n {
new_pagerank[j] += contribution;
}
}
}
let diff = (&new_pagerank - &pagerank)
.iter()
.map(|v| v.abs())
.sum::<f64>();
if diff < tolerance {
pagerank = new_pagerank;
break;
}
pagerank = new_pagerank;
}
let mut result = HashMap::new();
for (i, &node) in nodes.iter().enumerate() {
result.insert(node.clone(), pagerank[i]);
}
Ok(result)
}
#[allow(dead_code)]
fn weighted_degree_centrality<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<HashMap<N, f64>>
where
N: Node + std::fmt::Debug,
E: EdgeWeight + Into<f64> + Clone,
Ix: IndexType,
{
let mut centrality = HashMap::new();
for node in graph.nodes() {
let mut weight_sum = 0.0;
if let Ok(neighbors) = graph.neighbors(node) {
for neighbor in neighbors {
if let Ok(weight) = graph.edge_weight(node, &neighbor) {
weight_sum += weight.into();
}
}
}
centrality.insert(node.clone(), weight_sum);
}
Ok(centrality)
}
#[allow(dead_code)]
fn weighted_degree_centrality_digraph<N, E, Ix>(
graph: &DiGraph<N, E, Ix>,
) -> Result<HashMap<N, f64>>
where
N: Node + std::fmt::Debug,
E: EdgeWeight + Into<f64> + Clone,
Ix: IndexType,
{
let mut centrality = HashMap::new();
for node in graph.nodes() {
let mut in_weight = 0.0;
let mut out_weight = 0.0;
if let Ok(predecessors) = graph.predecessors(node) {
for pred in predecessors {
if let Ok(weight) = graph.edge_weight(&pred, node) {
in_weight += weight.into();
}
}
}
if let Ok(successors) = graph.successors(node) {
for succ in successors {
if let Ok(weight) = graph.edge_weight(node, &succ) {
out_weight += weight.into();
}
}
}
centrality.insert(node.clone(), in_weight + out_weight);
}
Ok(centrality)
}
#[allow(dead_code)]
fn weighted_betweenness_centrality<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<HashMap<N, f64>>
where
N: Node + std::fmt::Debug,
E: EdgeWeight
+ Into<f64>
+ scirs2_core::numeric::Zero
+ scirs2_core::numeric::One
+ std::ops::Add<Output = E>
+ PartialOrd
+ Copy
+ std::fmt::Debug
+ Default,
Ix: IndexType,
{
let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
let n = nodes.len();
let mut centrality: HashMap<N, f64> = nodes.iter().map(|n| (n.clone(), 0.0)).collect();
for source in &nodes {
for target in &nodes {
if source != target {
if let Ok(Some(path)) = dijkstra_path(graph, source, target) {
for intermediate in &path.nodes[1..path.nodes.len() - 1] {
*centrality
.get_mut(intermediate)
.expect("Failed to get mutable reference") += 1.0;
}
}
}
}
}
if n > 2 {
let normalization = ((n - 1) * (n - 2)) as f64;
for value in centrality.values_mut() {
*value /= normalization;
}
}
Ok(centrality)
}
#[allow(dead_code)]
fn weighted_betweenness_centrality_digraph<N, E, Ix>(
graph: &DiGraph<N, E, Ix>,
) -> Result<HashMap<N, f64>>
where
N: Node + std::fmt::Debug,
E: EdgeWeight
+ Into<f64>
+ scirs2_core::numeric::Zero
+ scirs2_core::numeric::One
+ std::ops::Add<Output = E>
+ PartialOrd
+ Copy
+ std::fmt::Debug
+ Default,
Ix: IndexType,
{
let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
let n = nodes.len();
let mut centrality: HashMap<N, f64> = nodes.iter().map(|n| (n.clone(), 0.0)).collect();
for source in &nodes {
for target in &nodes {
if source != target {
if let Ok(Some(path)) = dijkstra_path_digraph(graph, source, target) {
for intermediate in &path.nodes[1..path.nodes.len() - 1] {
*centrality
.get_mut(intermediate)
.expect("Failed to get mutable reference") += 1.0;
}
}
}
}
}
if n > 2 {
let normalization = ((n - 1) * (n - 2)) as f64;
for value in centrality.values_mut() {
*value /= normalization;
}
}
Ok(centrality)
}
#[allow(dead_code)]
fn weighted_closeness_centrality<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<HashMap<N, f64>>
where
N: Node + std::fmt::Debug,
E: EdgeWeight
+ Into<f64>
+ scirs2_core::numeric::Zero
+ scirs2_core::numeric::One
+ std::ops::Add<Output = E>
+ PartialOrd
+ Copy
+ std::fmt::Debug
+ Default,
Ix: IndexType,
{
let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
let n = nodes.len();
let mut centrality = HashMap::new();
for node in &nodes {
let mut total_distance = 0.0;
let mut reachable_count = 0;
for other in &nodes {
if node != other {
if let Ok(Some(path)) = dijkstra_path(graph, node, other) {
let distance: f64 = path.total_weight.into();
total_distance += distance;
reachable_count += 1;
}
}
}
if reachable_count > 0 && total_distance > 0.0 {
let closeness = reachable_count as f64 / total_distance;
let normalized_closeness = if n > 1 {
closeness * (reachable_count as f64 / (n - 1) as f64)
} else {
closeness
};
centrality.insert(node.clone(), normalized_closeness);
} else {
centrality.insert(node.clone(), 0.0);
}
}
Ok(centrality)
}
#[allow(dead_code)]
fn weighted_closeness_centrality_digraph<N, E, Ix>(
graph: &DiGraph<N, E, Ix>,
) -> Result<HashMap<N, f64>>
where
N: Node + std::fmt::Debug,
E: EdgeWeight
+ Into<f64>
+ scirs2_core::numeric::Zero
+ scirs2_core::numeric::One
+ std::ops::Add<Output = E>
+ PartialOrd
+ Copy
+ std::fmt::Debug
+ Default,
Ix: IndexType,
{
let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
let n = nodes.len();
let mut centrality = HashMap::new();
for node in &nodes {
let mut total_distance = 0.0;
let mut reachable_count = 0;
for other in &nodes {
if node != other {
if let Ok(Some(path)) = dijkstra_path_digraph(graph, node, other) {
let distance: f64 = path.total_weight.into();
total_distance += distance;
reachable_count += 1;
}
}
}
if reachable_count > 0 && total_distance > 0.0 {
let closeness = reachable_count as f64 / total_distance;
let normalized_closeness = if n > 1 {
closeness * (reachable_count as f64 / (n - 1) as f64)
} else {
closeness
};
centrality.insert(node.clone(), normalized_closeness);
} else {
centrality.insert(node.clone(), 0.0);
}
}
Ok(centrality)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_degree_centrality() {
let mut graph: Graph<char, f64> = Graph::new();
graph.add_edge('A', 'B', 1.0).expect("Test failed");
graph.add_edge('A', 'C', 1.0).expect("Test failed");
graph.add_edge('C', 'D', 1.0).expect("Test failed");
let centrality = centrality(&graph, CentralityType::Degree).expect("Test failed");
assert_eq!(centrality[&'A'], 2.0 / 3.0);
assert_eq!(centrality[&'B'], 1.0 / 3.0);
assert_eq!(centrality[&'C'], 2.0 / 3.0);
assert_eq!(centrality[&'D'], 1.0 / 3.0);
}
#[test]
fn test_clustering_coefficient() {
let mut graph: Graph<i32, f64> = Graph::new();
graph.add_edge(1, 2, 1.0).expect("Test failed");
graph.add_edge(2, 3, 1.0).expect("Test failed");
graph.add_edge(3, 4, 1.0).expect("Test failed");
graph.add_edge(4, 1, 1.0).expect("Test failed");
graph.add_edge(4, 5, 1.0).expect("Test failed");
let coefficients = clustering_coefficient(&graph).expect("Test failed");
assert_eq!(coefficients[&1], 0.0);
assert_eq!(coefficients[&2], 0.0);
assert_eq!(coefficients[&3], 0.0);
assert_eq!(coefficients[&4], 0.0);
assert_eq!(coefficients[&5], 0.0);
graph.add_edge(1, 3, 1.0).expect("Test failed");
let coefficients = clustering_coefficient(&graph).expect("Test failed");
assert!((coefficients[&4] - 1.0 / 3.0).abs() < 1e-10);
}
#[test]
fn test_graph_density() {
let mut graph: Graph<i32, f64> = Graph::new();
assert!(graph_density(&graph).is_err());
graph.add_node(1);
assert!(graph_density(&graph).is_err());
graph.add_node(2);
graph.add_node(3);
graph.add_node(4);
graph.add_edge(1, 2, 1.0).expect("Test failed");
graph.add_edge(2, 3, 1.0).expect("Test failed");
graph.add_edge(3, 4, 1.0).expect("Test failed");
let density = graph_density(&graph).expect("Test failed");
assert_eq!(density, 0.5);
graph.add_edge(1, 3, 1.0).expect("Test failed");
graph.add_edge(1, 4, 1.0).expect("Test failed");
graph.add_edge(2, 4, 1.0).expect("Test failed");
let density = graph_density(&graph).expect("Test failed");
assert_eq!(density, 1.0);
}
#[test]
fn test_katz_centrality() {
let mut graph: Graph<char, f64> = Graph::new();
graph.add_edge('A', 'B', 1.0).expect("Test failed");
graph.add_edge('A', 'C', 1.0).expect("Test failed");
graph.add_edge('A', 'D', 1.0).expect("Test failed");
let centrality = katz_centrality(&graph, 0.1, 1.0).expect("Test failed");
assert!(centrality[&'A'] > centrality[&'B']);
assert!(centrality[&'A'] > centrality[&'C']);
assert!(centrality[&'A'] > centrality[&'D']);
assert!((centrality[&'B'] - centrality[&'C']).abs() < 0.1);
assert!((centrality[&'B'] - centrality[&'D']).abs() < 0.1);
}
#[test]
fn test_pagerank_centrality() {
let mut graph: Graph<char, f64> = Graph::new();
graph.add_edge('A', 'B', 1.0).expect("Test failed");
graph.add_edge('B', 'C', 1.0).expect("Test failed");
graph.add_edge('C', 'A', 1.0).expect("Test failed");
let centrality = pagerank_centrality(&graph, 0.85, 1e-6).expect("Test failed");
let values: Vec<f64> = centrality.values().cloned().collect();
let expected = 1.0 / 3.0;
for &value in &values {
assert!((value - expected).abs() < 0.1);
}
let sum: f64 = values.iter().sum();
assert!((sum - 1.0).abs() < 0.01);
}
#[test]
fn test_pagerank_centrality_digraph() {
let mut graph: DiGraph<char, f64> = DiGraph::new();
graph.add_edge('A', 'B', 1.0).expect("Test failed");
graph.add_edge('B', 'C', 1.0).expect("Test failed");
let centrality = pagerank_centrality_digraph(&graph, 0.85, 1e-6).expect("Test failed");
assert!(centrality[&'C'] > centrality[&'B']);
assert!(centrality[&'B'] > centrality[&'A']);
}
#[test]
fn test_centrality_enum_katz_pagerank() {
let mut graph: Graph<char, f64> = Graph::new();
graph.add_edge('A', 'B', 1.0).expect("Test failed");
graph.add_edge('A', 'C', 1.0).expect("Test failed");
let katz_result = centrality(&graph, CentralityType::Katz).expect("Test failed");
let pagerank_result = centrality(&graph, CentralityType::PageRank).expect("Test failed");
assert_eq!(katz_result.len(), 3);
assert_eq!(pagerank_result.len(), 3);
for value in katz_result.values() {
assert!(*value > 0.0);
}
for value in pagerank_result.values() {
assert!(*value > 0.0);
}
}
#[test]
fn test_hits_algorithm() {
let mut graph: DiGraph<char, f64> = DiGraph::new();
graph.add_edge('A', 'C', 1.0).expect("Test failed");
graph.add_edge('A', 'D', 1.0).expect("Test failed");
graph.add_edge('B', 'C', 1.0).expect("Test failed");
graph.add_edge('B', 'D', 1.0).expect("Test failed");
graph.add_edge('E', 'C', 1.0).expect("Test failed");
graph.add_edge('B', 'E', 1.0).expect("Test failed");
let hits = hits_algorithm(&graph, 100, 1e-6).expect("Test failed");
assert_eq!(hits.authorities.len(), 5);
assert_eq!(hits.hubs.len(), 5);
assert!(hits.authorities[&'C'] > hits.authorities[&'A']);
assert!(hits.authorities[&'D'] > hits.authorities[&'A']);
assert!(hits.hubs[&'A'] > hits.hubs[&'C']);
assert!(hits.hubs[&'B'] > hits.hubs[&'C']);
let auth_norm: f64 = hits.authorities.values().map(|&x| x * x).sum::<f64>();
let hub_norm: f64 = hits.hubs.values().map(|&x| x * x).sum::<f64>();
assert!((auth_norm - 1.0).abs() < 0.01);
assert!((hub_norm - 1.0).abs() < 0.01);
}
#[test]
fn test_weighted_degree_centrality() {
let mut graph = crate::generators::create_graph::<&str, f64>();
graph.add_edge("A", "B", 2.0).expect("Test failed");
graph.add_edge("A", "C", 3.0).expect("Test failed");
graph.add_edge("B", "C", 1.0).expect("Test failed");
let centrality = centrality(&graph, CentralityType::WeightedDegree).expect("Test failed");
assert_eq!(centrality[&"A"], 5.0);
assert_eq!(centrality[&"B"], 3.0);
assert_eq!(centrality[&"C"], 4.0);
}
#[test]
fn test_weighted_closeness_centrality() {
let mut graph = crate::generators::create_graph::<&str, f64>();
graph.add_edge("A", "B", 1.0).expect("Test failed");
graph.add_edge("B", "C", 2.0).expect("Test failed");
let centrality =
centrality(&graph, CentralityType::WeightedCloseness).expect("Test failed");
for value in centrality.values() {
assert!(*value > 0.0);
}
assert!(centrality[&"B"] > centrality[&"A"]);
assert!(centrality[&"B"] > centrality[&"C"]);
}
#[test]
fn test_weighted_betweenness_centrality() {
let mut graph = crate::generators::create_graph::<&str, f64>();
graph.add_edge("A", "B", 1.0).expect("Test failed");
graph.add_edge("B", "C", 1.0).expect("Test failed");
let centrality =
centrality(&graph, CentralityType::WeightedBetweenness).expect("Test failed");
assert!(centrality[&"B"] > 0.0);
assert_eq!(centrality[&"A"], 0.0);
assert_eq!(centrality[&"C"], 0.0);
}
#[test]
fn test_weighted_centrality_digraph() {
let mut graph = crate::generators::create_digraph::<&str, f64>();
graph.add_edge("A", "B", 2.0).expect("Test failed");
graph.add_edge("B", "C", 3.0).expect("Test failed");
graph.add_edge("A", "C", 1.0).expect("Test failed");
let degree_centrality =
centrality_digraph(&graph, CentralityType::WeightedDegree).expect("Test failed");
let closeness_centrality =
centrality_digraph(&graph, CentralityType::WeightedCloseness).expect("Test failed");
for value in degree_centrality.values() {
assert!(*value >= 0.0);
}
for value in closeness_centrality.values() {
assert!(*value >= 0.0);
}
assert!(degree_centrality[&"B"] > degree_centrality[&"A"]);
assert!(degree_centrality[&"B"] > degree_centrality[&"C"]);
}
}