use super::model::Graph;
use rand::rngs::StdRng;
use rand::seq::SliceRandom;
use rand::SeedableRng;
use std::collections::HashMap;
pub fn louvain(graph: &Graph, resolution: f64, seed: u64) -> Vec<usize> {
let n = graph.node_ids.len();
if n == 0 {
return Vec::new();
}
if graph.total_weight <= 0.0 {
return (0..n).collect();
}
let mut level = Level::from_graph(graph);
let mut orig_to_super: Vec<usize> = (0..n).collect();
loop {
let comms = level.local_moving(resolution, seed);
let (comm_of_super, k) = compact(&comms);
for s in orig_to_super.iter_mut() {
*s = comm_of_super[*s];
}
if k == level.n {
break; }
level = level.aggregate(&comm_of_super, k);
if level.n <= 1 {
break;
}
}
orig_to_super
}
struct Level {
n: usize,
adj: Vec<Vec<(usize, f64)>>, k: Vec<f64>, m2: f64, }
impl Level {
fn from_graph(g: &Graph) -> Self {
let n = g.node_ids.len();
let adj = g.adjacency.clone();
let k = g.degrees.clone();
let m2: f64 = k.iter().sum();
Level { n, adj, k, m2 }
}
fn local_moving(&self, resolution: f64, seed: u64) -> Vec<usize> {
let mut comm: Vec<usize> = (0..self.n).collect();
let mut sigma_tot: Vec<f64> = self.k.clone(); let mut rng = StdRng::seed_from_u64(seed);
let mut order: Vec<usize> = (0..self.n).collect();
order.shuffle(&mut rng);
let mut improved = true;
let mut iterations = 0;
while improved && iterations < 100 {
improved = false;
iterations += 1;
for &i in &order {
let ci = comm[i];
let mut k_i_to: HashMap<usize, f64> = HashMap::new();
for &(j, w) in &self.adj[i] {
if j == i {
continue;
}
*k_i_to.entry(comm[j]).or_insert(0.0) += w;
}
sigma_tot[ci] -= self.k[i];
let mut best_comm = ci;
let mut best_gain = k_i_to.get(&ci).copied().unwrap_or(0.0)
- resolution * sigma_tot[ci] * self.k[i] / self.m2;
for (&c, &k_i_in) in &k_i_to {
let gain = k_i_in - resolution * sigma_tot[c] * self.k[i] / self.m2;
if gain > best_gain {
best_gain = gain;
best_comm = c;
}
}
sigma_tot[best_comm] += self.k[i];
if best_comm != ci {
comm[i] = best_comm;
improved = true;
}
}
}
comm
}
fn aggregate(&self, comm_of: &[usize], kcount: usize) -> Level {
let mut edge_map: HashMap<(usize, usize), f64> = HashMap::new();
for i in 0..self.n {
let ci = comm_of[i];
for &(j, w) in &self.adj[i] {
if j <= i {
continue; }
let cj = comm_of[j];
if ci != cj {
let key = if ci < cj { (ci, cj) } else { (cj, ci) };
*edge_map.entry(key).or_insert(0.0) += w;
}
}
}
let mut adj = vec![Vec::new(); kcount];
for ((a, b), w) in edge_map {
adj[a].push((b, w));
adj[b].push((a, w));
}
let mut k = vec![0.0; kcount];
for i in 0..self.n {
k[comm_of[i]] += self.k[i];
}
Level {
n: kcount,
adj,
k,
m2: self.m2,
}
}
}
fn compact(comms: &[usize]) -> (Vec<usize>, usize) {
let mut map: HashMap<usize, usize> = HashMap::new();
let mut out = vec![0usize; comms.len()];
let mut next = 0;
for (i, &c) in comms.iter().enumerate() {
let id = *map.entry(c).or_insert_with(|| {
let v = next;
next += 1;
v
});
out[i] = id;
}
(out, next)
}
#[cfg(test)]
mod tests {
use super::super::model::GraphBuilder;
use super::*;
fn community_count(labels: &[usize]) -> usize {
let mut s: Vec<usize> = labels.to_vec();
s.sort_unstable();
s.dedup();
s.len()
}
#[test]
fn two_triangles_with_a_bridge_yield_two_communities() {
let mut b = GraphBuilder::new();
for (u, v) in [("0", "1"), ("1", "2"), ("2", "0")] {
b.add_edge(u, v, 1.0);
}
for (u, v) in [("3", "4"), ("4", "5"), ("5", "3")] {
b.add_edge(u, v, 1.0);
}
b.add_edge("2", "3", 1.0); let g = b.build();
let labels = louvain(&g, 1.0, 42);
assert_eq!(community_count(&labels), 2);
assert_eq!(labels[0], labels[1]);
assert_eq!(labels[1], labels[2]);
assert_eq!(labels[3], labels[4]);
assert_eq!(labels[4], labels[5]);
assert_ne!(labels[0], labels[3]);
}
#[test]
fn complete_graph_is_one_community() {
let mut b = GraphBuilder::new();
let nodes = ["a", "b", "c", "d"];
for i in 0..nodes.len() {
for j in (i + 1)..nodes.len() {
b.add_edge(nodes[i], nodes[j], 1.0);
}
}
let g = b.build();
assert_eq!(community_count(&louvain(&g, 1.0, 7)), 1);
}
#[test]
fn disconnected_components_are_separate() {
let mut b = GraphBuilder::new();
b.add_edge("a", "b", 1.0);
b.add_edge("c", "d", 1.0);
let g = b.build();
let labels = louvain(&g, 1.0, 1);
assert_eq!(community_count(&labels), 2);
assert_eq!(labels[0], labels[1]);
assert_ne!(labels[0], labels[2]);
}
#[test]
fn empty_and_edgeless_graphs_are_safe() {
let empty = GraphBuilder::new().build();
assert!(louvain(&empty, 1.0, 1).is_empty());
}
}