use crate::algorithms::shortest_path::dijkstra;
use crate::{Error, ErrorKind, Graph};
use std::collections::{HashMap, HashSet};
use std::fmt::{Debug, Display};
use std::hash::Hash;
pub fn group_closeness_centrality<T, A>(
graph: &Graph<T, A>,
group: &HashSet<T>,
weighted: bool,
) -> Result<f64, Error>
where
T: Hash + Eq + Clone + Ord + Debug + Display + Send + Sync,
A: Clone + Send + Sync,
{
let mut all_nodes: Vec<T> = graph
.get_all_nodes()
.iter()
.map(|n| n.name.clone())
.collect();
all_nodes.sort();
let group_vec: Vec<T> = group.iter().cloned().collect();
let group_set: HashSet<T> = group_vec.iter().cloned().collect();
let all_nodes_set: HashSet<T> = all_nodes.iter().cloned().collect();
let missing_nodes: Vec<T> = group_set.difference(&all_nodes_set).cloned().collect();
if !missing_nodes.is_empty() {
return Err(Error {
kind: ErrorKind::NodeNotFound,
message: format!("The node(s) {:?} are not in the graph", missing_nodes),
});
}
let n = all_nodes.len();
let c = group_set.len();
if c == 0 {
return Err(Error {
kind: ErrorKind::InvalidArgument,
message: "Group cannot be empty".to_string(),
});
}
if c >= n {
return Err(Error {
kind: ErrorKind::InvalidArgument,
message: format!(
"Group size {} cannot be greater than or equal to total nodes {}",
c, n
),
});
}
let working_graph = if graph.specs.directed {
Some(graph.reverse()?)
} else {
None
};
let actual_graph = if let Some(ref reversed_graph) = working_graph {
reversed_graph
} else {
graph
};
let shortest_paths = if weighted {
multi_source_dijkstra_distances(actual_graph, &group_vec)?
} else {
single_source_shortest_paths_minimum_unweighted(actual_graph, &group_vec)?
};
let non_group_nodes: HashSet<T> = all_nodes_set.difference(&group_set).cloned().collect();
let mut sum_distances = 0.0;
for node in &non_group_nodes {
if let Some(&distance) = shortest_paths.get(node) {
sum_distances += distance;
}
}
if sum_distances == 0.0 {
return Ok(0.0);
}
let closeness = non_group_nodes.len() as f64 / sum_distances;
Ok(closeness)
}
fn multi_source_dijkstra_distances<T, A>(
graph: &Graph<T, A>,
sources: &[T],
) -> Result<HashMap<T, f64>, Error>
where
T: Hash + Eq + Clone + Ord + Debug + Display + Send + Sync,
A: Clone + Send + Sync,
{
let multi_source_result = dijkstra::multi_source(
graph,
true, sources.to_vec(),
None, None, false, false, )?;
let mut min_distances: HashMap<T, f64> = HashMap::new();
for (_source, target_distances) in multi_source_result {
for (target, shortest_path_info) in target_distances {
let distance = shortest_path_info.distance;
if let Some(&existing_distance) = min_distances.get(&target) {
if distance < existing_distance {
min_distances.insert(target, distance);
}
} else {
min_distances.insert(target, distance);
}
}
}
Ok(min_distances)
}
fn single_source_shortest_paths_minimum_unweighted<T, A>(
graph: &Graph<T, A>,
sources: &[T],
) -> Result<HashMap<T, f64>, Error>
where
T: Hash + Eq + Clone + Ord + Debug + Display + Send + Sync,
A: Clone + Send + Sync,
{
use crate::algorithms::shortest_path::dijkstra;
let mut min_distances: HashMap<T, f64> = HashMap::new();
for source in sources {
let distances = dijkstra::single_source(
graph,
false, source.clone(),
None, None, false, false, )?;
for (target, path_info) in distances {
let distance = path_info.distance;
if let Some(&existing_dist) = min_distances.get(&target) {
if distance < existing_dist {
min_distances.insert(target, distance);
}
} else {
min_distances.insert(target, distance);
}
}
}
Ok(min_distances)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Edge, Graph, GraphSpecs};
#[test]
fn test_group_closeness_basic() {
let mut graph = Graph::<i32, ()>::new(GraphSpecs::undirected_create_missing());
graph.add_edge(Edge::new(0, 1)).unwrap();
graph.add_edge(Edge::new(1, 2)).unwrap();
graph.add_edge(Edge::new(2, 3)).unwrap();
graph.add_edge(Edge::new(3, 4)).unwrap();
let mut group = HashSet::new();
group.insert(2);
let centrality = group_closeness_centrality(&graph, &group, false).unwrap();
assert!(centrality > 0.0);
let normalized_centrality = group_closeness_centrality(&graph, &group, false).unwrap();
assert!(normalized_centrality > 0.0);
assert!(normalized_centrality <= 1.0);
}
#[test]
fn test_group_closeness_star() {
let mut graph = Graph::<i32, ()>::new(GraphSpecs::undirected_create_missing());
graph.add_edge(Edge::new(0, 1)).unwrap();
graph.add_edge(Edge::new(0, 2)).unwrap();
graph.add_edge(Edge::new(0, 3)).unwrap();
graph.add_edge(Edge::new(0, 4)).unwrap();
let mut group = HashSet::new();
group.insert(0);
let centrality = group_closeness_centrality(&graph, &group, false).unwrap();
assert!((centrality - 1.0).abs() < f64::EPSILON);
}
#[test]
fn test_invalid_group_node() {
let mut graph = Graph::<i32, ()>::new(GraphSpecs::undirected_create_missing());
graph.add_edge(Edge::new(0, 1)).unwrap();
let mut group = HashSet::new();
group.insert(99);
let result = group_closeness_centrality(&graph, &group, false);
assert!(result.is_err());
}
#[test]
fn test_empty_group() {
let mut graph = Graph::<i32, ()>::new(GraphSpecs::undirected_create_missing());
graph.add_edge(Edge::new(0, 1)).unwrap();
let group = HashSet::new();
let result = group_closeness_centrality(&graph, &group, false);
assert!(result.is_err());
}
#[test]
fn test_deterministic_behavior() {
let graph = crate::generators::social::karate_club_graph();
let mut group = HashSet::new();
group.insert(0);
group.insert(33);
let result1 = group_closeness_centrality(&graph, &group, false).unwrap();
let result2 = group_closeness_centrality(&graph, &group, false).unwrap();
assert!((result1 - result2).abs() < f64::EPSILON);
}
}