scirs2_cluster/community/
mod.rs1pub mod label_propagation;
11pub mod leiden;
12pub mod sbm;
13
14use serde::{Deserialize, Serialize};
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct CommunityResult {
19 pub labels: Vec<usize>,
21 pub num_communities: usize,
23 pub quality_score: Option<f64>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct AdjacencyGraph {
34 pub n_nodes: usize,
36 pub adjacency: Vec<Vec<(usize, f64)>>,
39}
40
41impl AdjacencyGraph {
42 pub fn new(n: usize) -> Self {
44 Self {
45 n_nodes: n,
46 adjacency: vec![Vec::new(); n],
47 }
48 }
49
50 pub fn from_dense(matrix: &[f64], n: usize) -> crate::error::Result<Self> {
53 if matrix.len() != n * n {
54 return Err(crate::error::ClusteringError::InvalidInput(
55 "Matrix length must equal n*n".to_string(),
56 ));
57 }
58 let mut g = Self::new(n);
59 for i in 0..n {
60 for j in (i + 1)..n {
61 let w = matrix[i * n + j];
62 if w > 0.0 {
63 g.adjacency[i].push((j, w));
64 g.adjacency[j].push((i, w));
65 }
66 }
67 }
68 Ok(g)
69 }
70
71 pub fn from_nested(adj: &[Vec<f64>]) -> crate::error::Result<Self> {
73 let n = adj.len();
74 for row in adj {
75 if row.len() != n {
76 return Err(crate::error::ClusteringError::InvalidInput(
77 "Adjacency matrix must be square".to_string(),
78 ));
79 }
80 }
81 let mut g = Self::new(n);
82 for i in 0..n {
83 for j in (i + 1)..n {
84 let w = adj[i][j];
85 if w > 0.0 {
86 g.adjacency[i].push((j, w));
87 g.adjacency[j].push((i, w));
88 }
89 }
90 }
91 Ok(g)
92 }
93
94 pub fn add_edge(&mut self, u: usize, v: usize, weight: f64) -> crate::error::Result<()> {
96 if u >= self.n_nodes || v >= self.n_nodes {
97 return Err(crate::error::ClusteringError::InvalidInput(
98 "Node index out of bounds".to_string(),
99 ));
100 }
101 if u != v {
102 self.adjacency[u].push((v, weight));
103 self.adjacency[v].push((u, weight));
104 }
105 Ok(())
106 }
107
108 pub fn weighted_degree(&self, node: usize) -> f64 {
110 self.adjacency
111 .get(node)
112 .map(|nbrs| nbrs.iter().map(|(_, w)| *w).sum())
113 .unwrap_or(0.0)
114 }
115
116 pub fn total_edge_weight(&self) -> f64 {
118 let sum: f64 = self
119 .adjacency
120 .iter()
121 .flat_map(|nbrs| nbrs.iter().map(|(_, w)| *w))
122 .sum();
123 sum / 2.0
124 }
125
126 pub fn edge_weight(&self, u: usize, v: usize) -> f64 {
128 if let Some(nbrs) = self.adjacency.get(u) {
129 for &(nb, w) in nbrs {
130 if nb == v {
131 return w;
132 }
133 }
134 }
135 0.0
136 }
137
138 pub fn modularity(&self, labels: &[usize]) -> f64 {
142 let m2 = self.total_edge_weight() * 2.0; if m2 == 0.0 {
144 return 0.0;
145 }
146 let mut q = 0.0;
147 for i in 0..self.n_nodes {
148 let ki = self.weighted_degree(i);
149 for &(j, w) in &self.adjacency[i] {
150 if labels[i] == labels[j] {
151 q += w - ki * self.weighted_degree(j) / m2;
152 }
153 }
154 }
155 q / m2
156 }
157}
158
159pub use label_propagation::{
161 label_propagation_community, LabelPropagationConfig, LabelPropagationResult,
162};
163pub use leiden::{leiden, LeidenConfig, QualityFunction};
164pub use sbm::{StochasticBlockModel, StochasticBlockModelConfig};