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 since = "0.1.0-beta.2",
108 note = "Use `modularity_optimization_result` instead for standardized community detection API"
109)]
110#[allow(dead_code)]
111pub fn modularity_optimization<N, E, Ix>(
112 graph: &Graph<N, E, Ix>,
113 initial_temp: f64,
114 cooling_rate: f64,
115 max_iterations: usize,
116) -> CommunityStructure<N>
117where
118 N: Node + Clone + Hash + Eq + std::fmt::Debug,
119 E: EdgeWeight + Into<f64> + Copy,
120 Ix: IndexType,
121{
122 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
123 let n = nodes.len();
124
125 if n == 0 {
126 return CommunityStructure {
127 node_communities: HashMap::new(),
128 modularity: 0.0,
129 };
130 }
131
132 let mut current_communities: HashMap<N, usize> = nodes
134 .iter()
135 .enumerate()
136 .map(|(i, node)| (node.clone(), i))
137 .collect();
138
139 let mut current_modularity = modularity(graph, ¤t_communities);
140 let mut best_communities = current_communities.clone();
141 let mut best_modularity = current_modularity;
142
143 let mut temp = initial_temp;
144 let mut rng = scirs2_core::random::rng();
145
146 for _iteration in 0..max_iterations {
147 use scirs2_core::random::Rng;
149 let node_idx = rng.gen_range(0..n);
150 let node = &nodes[node_idx];
151 let current_community = current_communities[node];
152
153 let mut candidate_communities = HashSet::new();
155 candidate_communities.insert(n); if let Ok(neighbors) = graph.neighbors(node) {
158 for neighbor in neighbors {
159 if let Some(&comm) = current_communities.get(&neighbor) {
160 candidate_communities.insert(comm);
161 }
162 }
163 }
164
165 let candidates: Vec<usize> = candidate_communities.into_iter().collect();
167 if candidates.is_empty() {
168 continue;
169 }
170
171 let new_community = candidates[rng.gen_range(0..candidates.len())];
172
173 if new_community == current_community {
174 continue;
175 }
176
177 current_communities.insert(node.clone(), new_community);
179 let new_modularity = modularity(graph, ¤t_communities);
180 let delta = new_modularity - current_modularity;
181
182 let accept = if delta > 0.0 {
184 true
185 } else {
186 let prob = (delta / temp).exp();
188 rng.random::<f64>() < prob
189 };
190
191 if accept {
192 current_modularity = new_modularity;
193 if current_modularity > best_modularity {
194 best_modularity = current_modularity;
195 best_communities = current_communities.clone();
196 }
197 } else {
198 current_communities.insert(node.clone(), current_community);
200 }
201
202 temp *= cooling_rate;
204
205 if temp < 1e-8 {
207 break;
208 }
209 }
210
211 let mut community_map: HashMap<usize, usize> = HashMap::new();
213 let mut next_id = 0;
214 for &comm in best_communities.values() {
215 if let std::collections::hash_map::Entry::Vacant(e) = community_map.entry(comm) {
216 e.insert(next_id);
217 next_id += 1;
218 }
219 }
220
221 for (_, comm) in best_communities.iter_mut() {
223 *comm = community_map[comm];
224 }
225
226 CommunityStructure {
227 node_communities: best_communities,
228 modularity: best_modularity,
229 }
230}
231
232#[deprecated(
252 since = "0.1.0-beta.2",
253 note = "Use `greedy_modularity_optimization_result` instead for standardized community detection API"
254)]
255#[allow(dead_code)]
256pub fn greedy_modularity_optimization<N, E, Ix>(
257 graph: &Graph<N, E, Ix>,
258 max_iterations: usize,
259) -> CommunityStructure<N>
260where
261 N: Node + Clone + Hash + Eq + std::fmt::Debug,
262 E: EdgeWeight + Into<f64> + Copy,
263 Ix: IndexType,
264{
265 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
266 let n = nodes.len();
267
268 if n == 0 {
269 return CommunityStructure {
270 node_communities: HashMap::new(),
271 modularity: 0.0,
272 };
273 }
274
275 let mut communities: HashMap<N, usize> = nodes
277 .iter()
278 .enumerate()
279 .map(|(i, node)| (node.clone(), i))
280 .collect();
281
282 let mut improved = true;
283 let mut _iterations = 0;
284
285 while improved && _iterations < max_iterations {
286 improved = false;
287 _iterations += 1;
288
289 let current_modularity = modularity(graph, &communities);
290
291 for node in &nodes {
293 let original_community = communities[node];
294 let mut best_modularity = current_modularity;
295 let mut best_community = original_community;
296
297 let mut neighboring_communities = HashSet::new();
299 if let Ok(neighbors) = graph.neighbors(node) {
300 for neighbor in neighbors {
301 if let Some(&comm) = communities.get(&neighbor) {
302 neighboring_communities.insert(comm);
303 }
304 }
305 }
306
307 for &candidate_community in &neighboring_communities {
309 if candidate_community != original_community {
310 communities.insert(node.clone(), candidate_community);
311 let new_modularity = modularity(graph, &communities);
312
313 if new_modularity > best_modularity {
314 best_modularity = new_modularity;
315 best_community = candidate_community;
316 }
317 }
318 }
319
320 if best_community != original_community {
322 communities.insert(node.clone(), best_community);
323 improved = true;
324 } else {
325 communities.insert(node.clone(), original_community);
327 }
328 }
329 }
330
331 let mut community_map: HashMap<usize, usize> = HashMap::new();
333 let mut next_id = 0;
334 for &comm in communities.values() {
335 if let std::collections::hash_map::Entry::Vacant(e) = community_map.entry(comm) {
336 e.insert(next_id);
337 next_id += 1;
338 }
339 }
340
341 for (_, comm) in communities.iter_mut() {
343 *comm = community_map[comm];
344 }
345
346 let final_modularity = modularity(graph, &communities);
347
348 CommunityStructure {
349 node_communities: communities,
350 modularity: final_modularity,
351 }
352}
353
354#[allow(dead_code)]
368pub fn modularity_optimization_result<N, E, Ix>(
369 graph: &Graph<N, E, Ix>,
370 initial_temp: f64,
371 cooling_rate: f64,
372 max_iterations: usize,
373) -> CommunityResult<N>
374where
375 N: Node + Clone + Hash + Eq + std::fmt::Debug,
376 E: EdgeWeight + Into<f64> + Copy,
377 Ix: IndexType,
378{
379 #[allow(deprecated)]
380 let structure = modularity_optimization(graph, initial_temp, cooling_rate, max_iterations);
381 CommunityResult::from_community_structure(structure)
382}
383
384#[allow(dead_code)]
396pub fn greedy_modularity_optimization_result<N, E, Ix>(
397 graph: &Graph<N, E, Ix>,
398 max_iterations: usize,
399) -> CommunityResult<N>
400where
401 N: Node + Clone + Hash + Eq + std::fmt::Debug,
402 E: EdgeWeight + Into<f64> + Copy,
403 Ix: IndexType,
404{
405 #[allow(deprecated)]
406 let structure = greedy_modularity_optimization(graph, max_iterations);
407 CommunityResult::from_community_structure(structure)
408}