scirs2_graph/algorithms/community/
modularity.rs1use super::types::{CommunityResult, CommunityStructure};
4use crate::base::{EdgeWeight, Graph, IndexType, Node};
5use std::collections::{HashMap, HashSet};
6use std::hash::Hash;
7
8#[allow(dead_code)]
27pub fn modularity<N, E, Ix>(graph: &Graph<N, E, Ix>, communities: &HashMap<N, usize>) -> f64
28where
29 N: Node + std::fmt::Debug,
30 E: EdgeWeight + Into<f64> + Copy,
31 Ix: IndexType,
32{
33 let n = graph.node_count();
34 if n == 0 || communities.is_empty() {
35 return 0.0;
36 }
37
38 let mut m = 0.0;
40 for edge in graph.inner().edge_references() {
41 m += (*edge.weight()).into();
42 }
43
44 if m == 0.0 {
45 return 0.0;
46 }
47
48 let mut node_degrees: HashMap<N, f64> = HashMap::new();
50 for node in graph.nodes() {
51 let mut degree = 0.0;
52 if let Ok(neighbors) = graph.neighbors(node) {
53 for neighbor in neighbors {
54 if let Ok(weight) = graph.edge_weight(node, &neighbor) {
55 degree += weight.into();
56 }
57 }
58 }
59 node_degrees.insert(node.clone(), degree);
60 }
61
62 let mut q = 0.0;
64 for node_i in graph.nodes() {
65 for node_j in graph.nodes() {
66 if communities.get(node_i) == communities.get(node_j) {
67 let a_ij = if let Ok(weight) = graph.edge_weight(node_i, node_j) {
69 weight.into()
70 } else {
71 0.0
72 };
73
74 let k_i = node_degrees.get(node_i).unwrap_or(&0.0);
75 let k_j = node_degrees.get(node_j).unwrap_or(&0.0);
76
77 q += a_ij - (k_i * k_j) / (2.0 * m);
78 }
79 }
80 }
81
82 q / (2.0 * m)
83}
84
85#[deprecated(
107 note = "Use `modularity_optimization_result` instead for standardized community detection API"
108)]
109#[allow(dead_code)]
110pub fn modularity_optimization<N, E, Ix>(
111 graph: &Graph<N, E, Ix>,
112 initial_temp: f64,
113 cooling_rate: f64,
114 max_iterations: usize,
115) -> CommunityStructure<N>
116where
117 N: Node + Clone + Hash + Eq + std::fmt::Debug,
118 E: EdgeWeight + Into<f64> + Copy,
119 Ix: IndexType,
120{
121 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
122 let n = nodes.len();
123
124 if n == 0 {
125 return CommunityStructure {
126 node_communities: HashMap::new(),
127 modularity: 0.0,
128 };
129 }
130
131 let mut current_communities: HashMap<N, usize> = nodes
133 .iter()
134 .enumerate()
135 .map(|(i, node)| (node.clone(), i))
136 .collect();
137
138 let mut current_modularity = modularity(graph, ¤t_communities);
139 let mut best_communities = current_communities.clone();
140 let mut best_modularity = current_modularity;
141
142 let mut temp = initial_temp;
143 let mut rng = scirs2_core::random::rng();
144
145 for _iteration in 0..max_iterations {
146 use scirs2_core::random::Rng;
148 let node_idx = rng.random_range(0..n);
149 let node = &nodes[node_idx];
150 let current_community = current_communities[node];
151
152 let mut candidate_communities = HashSet::new();
154 candidate_communities.insert(n); if let Ok(neighbors) = graph.neighbors(node) {
157 for neighbor in neighbors {
158 if let Some(&comm) = current_communities.get(&neighbor) {
159 candidate_communities.insert(comm);
160 }
161 }
162 }
163
164 let candidates: Vec<usize> = candidate_communities.into_iter().collect();
166 if candidates.is_empty() {
167 continue;
168 }
169
170 let new_community = candidates[rng.random_range(0..candidates.len())];
171
172 if new_community == current_community {
173 continue;
174 }
175
176 current_communities.insert(node.clone(), new_community);
178 let new_modularity = modularity(graph, ¤t_communities);
179 let delta = new_modularity - current_modularity;
180
181 let accept = if delta > 0.0 {
183 true
184 } else {
185 let prob = (delta / temp).exp();
187 rng.random::<f64>() < prob
188 };
189
190 if accept {
191 current_modularity = new_modularity;
192 if current_modularity > best_modularity {
193 best_modularity = current_modularity;
194 best_communities = current_communities.clone();
195 }
196 } else {
197 current_communities.insert(node.clone(), current_community);
199 }
200
201 temp *= cooling_rate;
203
204 if temp < 1e-8 {
206 break;
207 }
208 }
209
210 let mut community_map: HashMap<usize, usize> = HashMap::new();
212 let mut next_id = 0;
213 for &comm in best_communities.values() {
214 if let std::collections::hash_map::Entry::Vacant(e) = community_map.entry(comm) {
215 e.insert(next_id);
216 next_id += 1;
217 }
218 }
219
220 for (_, comm) in best_communities.iter_mut() {
222 *comm = community_map[comm];
223 }
224
225 CommunityStructure {
226 node_communities: best_communities,
227 modularity: best_modularity,
228 }
229}
230
231#[deprecated(
251 note = "Use `greedy_modularity_optimization_result` instead for standardized community detection API"
252)]
253#[allow(dead_code)]
254pub fn greedy_modularity_optimization<N, E, Ix>(
255 graph: &Graph<N, E, Ix>,
256 max_iterations: usize,
257) -> CommunityStructure<N>
258where
259 N: Node + Clone + Hash + Eq + std::fmt::Debug,
260 E: EdgeWeight + Into<f64> + Copy,
261 Ix: IndexType,
262{
263 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
264 let n = nodes.len();
265
266 if n == 0 {
267 return CommunityStructure {
268 node_communities: HashMap::new(),
269 modularity: 0.0,
270 };
271 }
272
273 let mut communities: HashMap<N, usize> = nodes
275 .iter()
276 .enumerate()
277 .map(|(i, node)| (node.clone(), i))
278 .collect();
279
280 let mut improved = true;
281 let mut _iterations = 0;
282
283 while improved && _iterations < max_iterations {
284 improved = false;
285 _iterations += 1;
286
287 let current_modularity = modularity(graph, &communities);
288
289 for node in &nodes {
291 let original_community = communities[node];
292 let mut best_modularity = current_modularity;
293 let mut best_community = original_community;
294
295 let mut neighboring_communities = HashSet::new();
297 if let Ok(neighbors) = graph.neighbors(node) {
298 for neighbor in neighbors {
299 if let Some(&comm) = communities.get(&neighbor) {
300 neighboring_communities.insert(comm);
301 }
302 }
303 }
304
305 for &candidate_community in &neighboring_communities {
307 if candidate_community != original_community {
308 communities.insert(node.clone(), candidate_community);
309 let new_modularity = modularity(graph, &communities);
310
311 if new_modularity > best_modularity {
312 best_modularity = new_modularity;
313 best_community = candidate_community;
314 }
315 }
316 }
317
318 if best_community != original_community {
320 communities.insert(node.clone(), best_community);
321 improved = true;
322 } else {
323 communities.insert(node.clone(), original_community);
325 }
326 }
327 }
328
329 let mut community_map: HashMap<usize, usize> = HashMap::new();
331 let mut next_id = 0;
332 for &comm in communities.values() {
333 if let std::collections::hash_map::Entry::Vacant(e) = community_map.entry(comm) {
334 e.insert(next_id);
335 next_id += 1;
336 }
337 }
338
339 for (_, comm) in communities.iter_mut() {
341 *comm = community_map[comm];
342 }
343
344 let final_modularity = modularity(graph, &communities);
345
346 CommunityStructure {
347 node_communities: communities,
348 modularity: final_modularity,
349 }
350}
351
352#[allow(dead_code)]
366pub fn modularity_optimization_result<N, E, Ix>(
367 graph: &Graph<N, E, Ix>,
368 initial_temp: f64,
369 cooling_rate: f64,
370 max_iterations: usize,
371) -> CommunityResult<N>
372where
373 N: Node + Clone + Hash + Eq + std::fmt::Debug,
374 E: EdgeWeight + Into<f64> + Copy,
375 Ix: IndexType,
376{
377 #[allow(deprecated)]
378 let structure = modularity_optimization(graph, initial_temp, cooling_rate, max_iterations);
379 CommunityResult::from_community_structure(structure)
380}
381
382#[allow(dead_code)]
394pub fn greedy_modularity_optimization_result<N, E, Ix>(
395 graph: &Graph<N, E, Ix>,
396 max_iterations: usize,
397) -> CommunityResult<N>
398where
399 N: Node + Clone + Hash + Eq + std::fmt::Debug,
400 E: EdgeWeight + Into<f64> + Copy,
401 Ix: IndexType,
402{
403 #[allow(deprecated)]
404 let structure = greedy_modularity_optimization(graph, max_iterations);
405 CommunityResult::from_community_structure(structure)
406}