use super::types::{CommunityResult, CommunityStructure};
use crate::base::{EdgeWeight, Graph, Node};
use petgraph::visit::EdgeRef;
use std::collections::HashMap;
use std::hash::Hash;
#[allow(dead_code)]
pub fn louvain_communities_result<N, E, Ix>(graph: &Graph<N, E, Ix>) -> CommunityResult<N>
where
N: Node + Clone + Hash + Eq + std::fmt::Debug,
E: EdgeWeight + Into<f64> + scirs2_core::numeric::Zero + Copy,
Ix: petgraph::graph::IndexType,
{
let structure = louvain_communities_legacy(graph);
CommunityResult::from_community_structure(structure)
}
#[deprecated(note = "Use `louvain_communities_result` instead")]
#[allow(dead_code)]
pub fn louvain_communities<N, E, Ix>(graph: &Graph<N, E, Ix>) -> CommunityStructure<N>
where
N: Node + std::fmt::Debug,
E: EdgeWeight + Into<f64> + scirs2_core::numeric::Zero + Copy,
Ix: petgraph::graph::IndexType,
{
louvain_communities_legacy(graph)
}
#[allow(dead_code)]
fn louvain_communities_legacy<N, E, Ix>(graph: &Graph<N, E, Ix>) -> CommunityStructure<N>
where
N: Node + std::fmt::Debug,
E: EdgeWeight + Into<f64> + scirs2_core::numeric::Zero + Copy,
Ix: petgraph::graph::IndexType,
{
let n = graph.node_count();
if n == 0 {
return CommunityStructure {
node_communities: HashMap::new(),
modularity: 0.0,
};
}
let mut communities: HashMap<petgraph::graph::NodeIndex<Ix>, usize> = HashMap::new();
let mut node_degrees: HashMap<petgraph::graph::NodeIndex<Ix>, f64> = HashMap::new();
let mut m = 0.0; for edge in graph.inner().edge_references() {
m += (*edge.weight()).into();
}
if m == 0.0 {
m = 1.0;
}
for node_idx in graph.inner().node_indices() {
let mut degree = 0.0;
for edge in graph.inner().edges(node_idx) {
degree += (*edge.weight()).into();
}
node_degrees.insert(node_idx, degree);
communities.insert(node_idx, node_idx.index());
}
let mut improved = true;
let mut iterations = 0;
const MAX_ITERATIONS: usize = 100;
while improved && iterations < MAX_ITERATIONS {
improved = false;
iterations += 1;
for node_idx in graph.inner().node_indices() {
let current_community = communities[&node_idx];
let k_i = node_degrees[&node_idx];
communities.insert(node_idx, node_idx.index());
let mut community_weights: HashMap<usize, f64> = HashMap::new();
for edge in graph.inner().edges(node_idx) {
let neighbor_idx = edge.target();
let neighbor_community = communities[&neighbor_idx];
let edge_weight: f64 = (*edge.weight()).into();
*community_weights.entry(neighbor_community).or_insert(0.0) += edge_weight;
}
community_weights.entry(node_idx.index()).or_insert(0.0);
let mut best_community = node_idx.index();
let mut best_delta_q = 0.0;
for (&community, &k_i_in) in &community_weights {
let mut sigma_tot = 0.0;
for (&other_node, &other_community) in &communities {
if other_community == community && other_node != node_idx {
sigma_tot += node_degrees[&other_node];
}
}
let delta_q = k_i_in / m - (sigma_tot * k_i) / (2.0 * m * m);
if delta_q > best_delta_q {
best_delta_q = delta_q;
best_community = community;
}
}
if best_community != current_community {
improved = true;
}
communities.insert(node_idx, best_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.values_mut() {
*comm = community_map[comm];
}
let modularity = calculate_modularity(graph, &communities, m);
let node_communities: HashMap<N, usize> = communities
.into_iter()
.map(|(idx, comm)| (graph.inner()[idx].clone(), comm))
.collect();
CommunityStructure {
node_communities,
modularity,
}
}
#[allow(dead_code)]
pub fn calculate_modularity<N, E, Ix>(
graph: &Graph<N, E, Ix>,
communities: &HashMap<petgraph::graph::NodeIndex<Ix>, usize>,
m: f64,
) -> f64
where
N: Node + std::fmt::Debug,
E: EdgeWeight + Into<f64> + Copy,
Ix: petgraph::graph::IndexType,
{
let mut modularity = 0.0;
let mut node_degrees: HashMap<petgraph::graph::NodeIndex<Ix>, f64> = HashMap::new();
for node_idx in graph.inner().node_indices() {
let degree: f64 = graph
.inner()
.edges(node_idx)
.map(|e| (*e.weight()).into())
.sum();
node_degrees.insert(node_idx, degree);
}
for node_i in graph.inner().node_indices() {
for node_j in graph.inner().node_indices() {
if communities[&node_i] == communities[&node_j] {
let mut a_ij = 0.0;
for edge in graph.inner().edges(node_i) {
if edge.target() == node_j {
a_ij = (*edge.weight()).into();
break;
}
}
let k_i = node_degrees[&node_i];
let k_j = node_degrees[&node_j];
modularity += a_ij - (k_i * k_j) / (2.0 * m);
}
}
}
modularity / (2.0 * m)
}