use super::traits::CommunityDetection;
use crate::error::{Error, Result};
use graphops::{louvain_seeded, louvain_weighted_seeded, Graph, PetgraphRef, WeightedGraph};
use petgraph::graph::UnGraph;
use petgraph::visit::EdgeRef;
#[derive(Debug, Clone)]
pub struct Louvain {
resolution: f64,
#[allow(dead_code)]
max_iter: usize,
#[allow(dead_code)]
max_levels: usize,
#[allow(dead_code)]
min_modularity_gain: f64,
}
impl Louvain {
pub fn new() -> Self {
Self {
resolution: 1.0,
max_iter: 100,
max_levels: 10,
min_modularity_gain: 1e-7,
}
}
pub fn with_resolution(mut self, resolution: f64) -> Self {
self.resolution = resolution;
self
}
pub fn with_max_iter(mut self, max_iter: usize) -> Self {
self.max_iter = max_iter;
self
}
pub fn with_max_levels(mut self, levels: usize) -> Self {
self.max_levels = levels;
self
}
pub fn detect_weighted<N>(&self, graph: &UnGraph<N, f32>) -> Result<Vec<usize>> {
let n = graph.node_count();
if n == 0 {
return Err(Error::EmptyInput);
}
if graph.edge_count() == 0 {
return Ok((0..n).collect());
}
let adapter = F32WeightedAdapter::from_graph(graph);
Ok(louvain_weighted_seeded(&adapter, self.resolution, 0))
}
}
impl Default for Louvain {
fn default() -> Self {
Self::new()
}
}
impl CommunityDetection for Louvain {
fn detect<N, E>(&self, graph: &UnGraph<N, E>) -> Result<Vec<usize>> {
let n = graph.node_count();
if n == 0 {
return Err(Error::EmptyInput);
}
if graph.edge_count() == 0 {
return Ok((0..n).collect());
}
let adapter = PetgraphRef::from_graph(graph);
Ok(louvain_seeded(&adapter, self.resolution, 0))
}
fn resolution(&self) -> f64 {
self.resolution
}
}
struct F32WeightedAdapter {
adj: Vec<Vec<(usize, f64)>>,
}
impl F32WeightedAdapter {
fn from_graph<N>(graph: &UnGraph<N, f32>) -> Self {
let n = graph.node_count();
let mut adj: Vec<Vec<(usize, f64)>> = vec![Vec::new(); n];
for edge in graph.edge_references() {
let i = edge.source().index();
let j = edge.target().index();
let w = *edge.weight() as f64;
adj[i].push((j, w));
adj[j].push((i, w));
}
Self { adj }
}
}
impl Graph for F32WeightedAdapter {
fn node_count(&self) -> usize {
self.adj.len()
}
fn neighbors(&self, node: usize) -> Vec<usize> {
self.adj[node].iter().map(|(v, _)| *v).collect()
}
}
impl WeightedGraph for F32WeightedAdapter {
fn edge_weight(&self, source: usize, target: usize) -> f64 {
self.adj[source]
.iter()
.find(|(v, _)| *v == target)
.map(|(_, w)| *w)
.unwrap_or(0.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use petgraph::graph::UnGraph;
#[test]
fn test_louvain_triangle() {
let mut graph = UnGraph::<(), ()>::new_undirected();
let n0 = graph.add_node(());
let n1 = graph.add_node(());
let n2 = graph.add_node(());
let _ = graph.add_edge(n0, n1, ());
let _ = graph.add_edge(n1, n2, ());
let _ = graph.add_edge(n0, n2, ());
let louvain = Louvain::new();
let communities = louvain.detect(&graph).unwrap();
assert_eq!(communities.len(), 3);
assert_eq!(communities[0], communities[1]);
assert_eq!(communities[1], communities[2]);
}
#[test]
fn test_louvain_two_cliques() {
let mut graph = UnGraph::<(), ()>::new_undirected();
let a0 = graph.add_node(());
let a1 = graph.add_node(());
let a2 = graph.add_node(());
let _ = graph.add_edge(a0, a1, ());
let _ = graph.add_edge(a1, a2, ());
let _ = graph.add_edge(a0, a2, ());
let b0 = graph.add_node(());
let b1 = graph.add_node(());
let b2 = graph.add_node(());
let _ = graph.add_edge(b0, b1, ());
let _ = graph.add_edge(b1, b2, ());
let _ = graph.add_edge(b0, b2, ());
let _ = graph.add_edge(a2, b0, ());
let louvain = Louvain::new();
let communities = louvain.detect(&graph).unwrap();
assert_eq!(communities.len(), 6);
assert_eq!(communities[0], communities[1]);
assert_eq!(communities[1], communities[2]);
assert_eq!(communities[3], communities[4]);
assert_eq!(communities[4], communities[5]);
assert_ne!(communities[0], communities[3]);
}
#[test]
fn test_louvain_empty_graph() {
let graph = UnGraph::<(), ()>::new_undirected();
let louvain = Louvain::new();
let result = louvain.detect(&graph);
assert!(result.is_err());
}
#[test]
fn test_louvain_single_node() {
let mut graph = UnGraph::<(), ()>::new_undirected();
let _ = graph.add_node(());
let louvain = Louvain::new();
let communities = louvain.detect(&graph).unwrap();
assert_eq!(communities.len(), 1);
assert_eq!(communities[0], 0);
}
#[test]
fn test_louvain_disconnected() {
let mut graph = UnGraph::<(), ()>::new_undirected();
let _ = graph.add_node(());
let _ = graph.add_node(());
let louvain = Louvain::new();
let communities = louvain.detect(&graph).unwrap();
assert_eq!(communities.len(), 2);
assert_ne!(communities[0], communities[1]);
}
}