use super::types::{CommunityResult, CommunityStructure};
use crate::base::{EdgeWeight, Graph, IndexType, Node};
use std::collections::{HashMap, HashSet};
use std::hash::Hash;
#[allow(dead_code)]
pub fn modularity<N, E, Ix>(graph: &Graph<N, E, Ix>, communities: &HashMap<N, usize>) -> f64
where
N: Node + std::fmt::Debug,
E: EdgeWeight + Into<f64> + Copy,
Ix: IndexType,
{
let n = graph.node_count();
if n == 0 || communities.is_empty() {
return 0.0;
}
let mut m = 0.0;
for edge in graph.inner().edge_references() {
m += (*edge.weight()).into();
}
if m == 0.0 {
return 0.0;
}
let mut node_degrees: HashMap<N, f64> = HashMap::new();
for node in graph.nodes() {
let mut degree = 0.0;
if let Ok(neighbors) = graph.neighbors(node) {
for neighbor in neighbors {
if let Ok(weight) = graph.edge_weight(node, &neighbor) {
degree += weight.into();
}
}
}
node_degrees.insert(node.clone(), degree);
}
let mut q = 0.0;
for node_i in graph.nodes() {
for node_j in graph.nodes() {
if communities.get(node_i) == communities.get(node_j) {
let a_ij = if let Ok(weight) = graph.edge_weight(node_i, node_j) {
weight.into()
} else {
0.0
};
let k_i = node_degrees.get(node_i).unwrap_or(&0.0);
let k_j = node_degrees.get(node_j).unwrap_or(&0.0);
q += a_ij - (k_i * k_j) / (2.0 * m);
}
}
}
q / (2.0 * m)
}
#[deprecated(
note = "Use `modularity_optimization_result` instead for standardized community detection API"
)]
#[allow(dead_code)]
pub fn modularity_optimization<N, E, Ix>(
graph: &Graph<N, E, Ix>,
initial_temp: f64,
cooling_rate: f64,
max_iterations: usize,
) -> CommunityStructure<N>
where
N: Node + Clone + Hash + Eq + std::fmt::Debug,
E: EdgeWeight + Into<f64> + Copy,
Ix: IndexType,
{
let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
let n = nodes.len();
if n == 0 {
return CommunityStructure {
node_communities: HashMap::new(),
modularity: 0.0,
};
}
let mut current_communities: HashMap<N, usize> = nodes
.iter()
.enumerate()
.map(|(i, node)| (node.clone(), i))
.collect();
let mut current_modularity = modularity(graph, ¤t_communities);
let mut best_communities = current_communities.clone();
let mut best_modularity = current_modularity;
let mut temp = initial_temp;
let mut rng = scirs2_core::random::rng();
for _iteration in 0..max_iterations {
use scirs2_core::random::{Rng, RngExt};
let node_idx = rng.random_range(0..n);
let node = &nodes[node_idx];
let current_community = current_communities[node];
let mut candidate_communities = HashSet::new();
candidate_communities.insert(n);
if let Ok(neighbors) = graph.neighbors(node) {
for neighbor in neighbors {
if let Some(&comm) = current_communities.get(&neighbor) {
candidate_communities.insert(comm);
}
}
}
let candidates: Vec<usize> = candidate_communities.into_iter().collect();
if candidates.is_empty() {
continue;
}
let new_community = candidates[rng.random_range(0..candidates.len())];
if new_community == current_community {
continue;
}
current_communities.insert(node.clone(), new_community);
let new_modularity = modularity(graph, ¤t_communities);
let delta = new_modularity - current_modularity;
let accept = if delta > 0.0 {
true
} else {
let prob = (delta / temp).exp();
rng.random::<f64>() < prob
};
if accept {
current_modularity = new_modularity;
if current_modularity > best_modularity {
best_modularity = current_modularity;
best_communities = current_communities.clone();
}
} else {
current_communities.insert(node.clone(), current_community);
}
temp *= cooling_rate;
if temp < 1e-8 {
break;
}
}
let mut community_map: HashMap<usize, usize> = HashMap::new();
let mut next_id = 0;
for &comm in best_communities.values() {
if let std::collections::hash_map::Entry::Vacant(e) = community_map.entry(comm) {
e.insert(next_id);
next_id += 1;
}
}
for (_, comm) in best_communities.iter_mut() {
*comm = community_map[comm];
}
CommunityStructure {
node_communities: best_communities,
modularity: best_modularity,
}
}
#[deprecated(
note = "Use `greedy_modularity_optimization_result` instead for standardized community detection API"
)]
#[allow(dead_code)]
pub fn greedy_modularity_optimization<N, E, Ix>(
graph: &Graph<N, E, Ix>,
max_iterations: usize,
) -> CommunityStructure<N>
where
N: Node + Clone + Hash + Eq + std::fmt::Debug,
E: EdgeWeight + Into<f64> + Copy,
Ix: IndexType,
{
let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
let n = nodes.len();
if n == 0 {
return CommunityStructure {
node_communities: HashMap::new(),
modularity: 0.0,
};
}
let mut communities: HashMap<N, usize> = nodes
.iter()
.enumerate()
.map(|(i, node)| (node.clone(), i))
.collect();
let mut improved = true;
let mut _iterations = 0;
while improved && _iterations < max_iterations {
improved = false;
_iterations += 1;
let current_modularity = modularity(graph, &communities);
for node in &nodes {
let original_community = communities[node];
let mut best_modularity = current_modularity;
let mut best_community = original_community;
let mut neighboring_communities = HashSet::new();
if let Ok(neighbors) = graph.neighbors(node) {
for neighbor in neighbors {
if let Some(&comm) = communities.get(&neighbor) {
neighboring_communities.insert(comm);
}
}
}
for &candidate_community in &neighboring_communities {
if candidate_community != original_community {
communities.insert(node.clone(), candidate_community);
let new_modularity = modularity(graph, &communities);
if new_modularity > best_modularity {
best_modularity = new_modularity;
best_community = candidate_community;
}
}
}
if best_community != original_community {
communities.insert(node.clone(), best_community);
improved = true;
} else {
communities.insert(node.clone(), original_community);
}
}
}
let mut community_map: HashMap<usize, usize> = HashMap::new();
let mut next_id = 0;
for &comm in communities.values() {
if let std::collections::hash_map::Entry::Vacant(e) = community_map.entry(comm) {
e.insert(next_id);
next_id += 1;
}
}
for (_, comm) in communities.iter_mut() {
*comm = community_map[comm];
}
let final_modularity = modularity(graph, &communities);
CommunityStructure {
node_communities: communities,
modularity: final_modularity,
}
}
#[allow(dead_code)]
pub fn modularity_optimization_result<N, E, Ix>(
graph: &Graph<N, E, Ix>,
initial_temp: f64,
cooling_rate: f64,
max_iterations: usize,
) -> CommunityResult<N>
where
N: Node + Clone + Hash + Eq + std::fmt::Debug,
E: EdgeWeight + Into<f64> + Copy,
Ix: IndexType,
{
#[allow(deprecated)]
let structure = modularity_optimization(graph, initial_temp, cooling_rate, max_iterations);
CommunityResult::from_community_structure(structure)
}
#[allow(dead_code)]
pub fn greedy_modularity_optimization_result<N, E, Ix>(
graph: &Graph<N, E, Ix>,
max_iterations: usize,
) -> CommunityResult<N>
where
N: Node + Clone + Hash + Eq + std::fmt::Debug,
E: EdgeWeight + Into<f64> + Copy,
Ix: IndexType,
{
#[allow(deprecated)]
let structure = greedy_modularity_optimization(graph, max_iterations);
CommunityResult::from_community_structure(structure)
}