pub mod label_propagation;
pub mod leiden;
pub mod sbm;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommunityResult {
pub labels: Vec<usize>,
pub num_communities: usize,
pub quality_score: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdjacencyGraph {
pub n_nodes: usize,
pub adjacency: Vec<Vec<(usize, f64)>>,
}
impl AdjacencyGraph {
pub fn new(n: usize) -> Self {
Self {
n_nodes: n,
adjacency: vec![Vec::new(); n],
}
}
pub fn from_dense(matrix: &[f64], n: usize) -> crate::error::Result<Self> {
if matrix.len() != n * n {
return Err(crate::error::ClusteringError::InvalidInput(
"Matrix length must equal n*n".to_string(),
));
}
let mut g = Self::new(n);
for i in 0..n {
for j in (i + 1)..n {
let w = matrix[i * n + j];
if w > 0.0 {
g.adjacency[i].push((j, w));
g.adjacency[j].push((i, w));
}
}
}
Ok(g)
}
pub fn from_nested(adj: &[Vec<f64>]) -> crate::error::Result<Self> {
let n = adj.len();
for row in adj {
if row.len() != n {
return Err(crate::error::ClusteringError::InvalidInput(
"Adjacency matrix must be square".to_string(),
));
}
}
let mut g = Self::new(n);
for i in 0..n {
for j in (i + 1)..n {
let w = adj[i][j];
if w > 0.0 {
g.adjacency[i].push((j, w));
g.adjacency[j].push((i, w));
}
}
}
Ok(g)
}
pub fn add_edge(&mut self, u: usize, v: usize, weight: f64) -> crate::error::Result<()> {
if u >= self.n_nodes || v >= self.n_nodes {
return Err(crate::error::ClusteringError::InvalidInput(
"Node index out of bounds".to_string(),
));
}
if u != v {
self.adjacency[u].push((v, weight));
self.adjacency[v].push((u, weight));
}
Ok(())
}
pub fn weighted_degree(&self, node: usize) -> f64 {
self.adjacency
.get(node)
.map(|nbrs| nbrs.iter().map(|(_, w)| *w).sum())
.unwrap_or(0.0)
}
pub fn total_edge_weight(&self) -> f64 {
let sum: f64 = self
.adjacency
.iter()
.flat_map(|nbrs| nbrs.iter().map(|(_, w)| *w))
.sum();
sum / 2.0
}
pub fn edge_weight(&self, u: usize, v: usize) -> f64 {
if let Some(nbrs) = self.adjacency.get(u) {
for &(nb, w) in nbrs {
if nb == v {
return w;
}
}
}
0.0
}
pub fn modularity(&self, labels: &[usize]) -> f64 {
let m2 = self.total_edge_weight() * 2.0; if m2 == 0.0 {
return 0.0;
}
let mut q = 0.0;
for i in 0..self.n_nodes {
let ki = self.weighted_degree(i);
for &(j, w) in &self.adjacency[i] {
if labels[i] == labels[j] {
q += w - ki * self.weighted_degree(j) / m2;
}
}
}
q / m2
}
}
pub use label_propagation::{
label_propagation_community, LabelPropagationConfig, LabelPropagationResult,
};
pub use leiden::{leiden, LeidenConfig, QualityFunction};
pub use sbm::{StochasticBlockModel, StochasticBlockModelConfig};