scirs2_graph/algorithms/community/
parallel.rs1use super::louvain::calculate_modularity;
4use super::types::{CommunityResult, CommunityStructure};
5use crate::base::{EdgeWeight, Graph, IndexType, Node};
6use scirs2_core::random::seq::SliceRandom;
7use std::collections::{HashMap, HashSet};
8use std::hash::Hash;
9
10#[cfg(feature = "parallel")]
11use scirs2_core::parallel_ops::*;
12
13#[deprecated(
34 note = "Use `parallel_louvain_communities_result` instead for standardized community detection API"
35)]
36#[allow(dead_code)]
37pub fn parallel_louvain_communities<N, E, Ix>(
38 graph: &Graph<N, E, Ix>,
39 _max_iterations: usize,
40) -> CommunityStructure<N>
41where
42 N: Node + Send + Sync + std::fmt::Debug,
43 E: EdgeWeight + Into<f64> + Send + Sync + Copy,
44 Ix: IndexType + Send + Sync,
45{
46 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
47
48 let m: f64 = graph
50 .edges()
51 .into_iter()
52 .map(|edge| edge.weight.into())
53 .sum::<f64>()
54 / 2.0;
55
56 if m == 0.0 {
57 let node_communities: HashMap<N, usize> = nodes
59 .into_iter()
60 .enumerate()
61 .map(|(i, node)| (node, i))
62 .collect();
63
64 return CommunityStructure {
65 node_communities,
66 modularity: 0.0,
67 };
68 }
69
70 let mut communities: HashMap<N, usize> = HashMap::new();
72 let mut node_degrees: HashMap<N, f64> = HashMap::new();
73
74 for (i, node) in nodes.iter().enumerate() {
76 communities.insert(node.clone(), i);
77
78 let degree = if let Ok(neighbors) = graph.neighbors(node) {
80 neighbors
81 .iter()
82 .filter_map(|neighbor| graph.edge_weight(node, neighbor).ok())
83 .map(|w| w.into())
84 .sum()
85 } else {
86 0.0
87 };
88 node_degrees.insert(node.clone(), degree);
89 }
90
91 let mut communities_by_index: HashMap<petgraph::graph::NodeIndex<Ix>, usize> = HashMap::new();
93 for (node, community) in &communities {
94 if let Some(node_idx) = graph.node_index(node) {
95 communities_by_index.insert(node_idx, *community);
96 }
97 }
98
99 let final_modularity = calculate_modularity(graph, &communities_by_index, m);
101
102 CommunityStructure {
103 node_communities: communities,
104 modularity: final_modularity,
105 }
106}
107
108#[allow(dead_code)]
141pub fn parallel_louvain_communities_result<N, E, Ix>(
142 graph: &Graph<N, E, Ix>,
143 max_iterations: usize,
144) -> CommunityResult<N>
145where
146 N: Node + Send + Sync + Clone + Hash + Eq + std::fmt::Debug,
147 E: EdgeWeight + Into<f64> + Send + Sync + Copy,
148 Ix: IndexType + Send + Sync,
149{
150 #[allow(deprecated)]
151 let structure = parallel_louvain_communities(graph, max_iterations);
152 CommunityResult::from_node_map(structure.node_communities)
153}
154
155#[cfg(feature = "parallel")]
167#[allow(dead_code)]
168pub fn parallel_label_propagation_result<N, E, Ix>(
169 graph: &Graph<N, E, Ix>,
170 max_iterations: Option<usize>,
171) -> CommunityResult<N>
172where
173 N: Node + Clone + Hash + Eq + Send + Sync + std::fmt::Debug,
174 E: EdgeWeight + Into<f64> + Copy + Send + Sync,
175 Ix: petgraph::graph::IndexType + Send + Sync,
176{
177 let max_iter = max_iterations.unwrap_or(100);
178 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
179
180 let mut labels: HashMap<N, usize> = nodes
182 .par_iter()
183 .enumerate()
184 .map(|(i, node)| (node.clone(), i))
185 .collect();
186
187 let mut rng = scirs2_core::random::rng();
188
189 for _ in 0..max_iter {
190 let mut shuffled_nodes = nodes.clone();
192 shuffled_nodes.shuffle(&mut rng);
193
194 let updates: Vec<(N, usize)> = shuffled_nodes
196 .par_iter()
197 .filter_map(|node| {
198 if let Ok(neighbors) = graph.neighbors(node) {
199 let mut label_counts: HashMap<usize, usize> = HashMap::new();
201
202 for neighbor in neighbors {
203 if let Some(&label) = labels.get(&neighbor) {
204 *label_counts.entry(label).or_insert(0) += 1;
205 }
206 }
207
208 if let Some((&most_frequent_label_, _)) =
210 label_counts.iter().max_by_key(|&(_, count)| count)
211 {
212 let current_label = labels.get(node).copied().unwrap_or(0);
213 if most_frequent_label_ != current_label {
214 return Some((node.clone(), most_frequent_label_));
215 }
216 }
217 }
218 None
219 })
220 .collect();
221
222 if updates.is_empty() {
224 break; }
226
227 for (node, new_label) in updates {
228 labels.insert(node, new_label);
229 }
230 }
231
232 let mut communities: HashMap<usize, HashSet<N>> = HashMap::new();
234 for (node, label) in &labels {
235 communities.entry(*label).or_default().insert(node.clone());
236 }
237
238 let communities_vec: Vec<HashSet<N>> = communities.into_values().collect();
239 let num_communities = communities_vec.len();
240
241 CommunityResult {
242 node_communities: labels,
243 communities: communities_vec,
244 num_communities,
245 quality_score: None, metadata: HashMap::new(),
247 }
248}
249
250#[cfg(feature = "parallel")]
262#[allow(dead_code)]
263pub fn parallel_modularity<N, E, Ix>(
264 graph: &Graph<N, E, Ix>,
265 communities: &HashMap<N, usize>,
266) -> f64
267where
268 N: Node + Clone + Hash + Eq + Send + Sync + std::fmt::Debug,
269 E: EdgeWeight + Into<f64> + Copy + Send + Sync,
270 Ix: petgraph::graph::IndexType + Send + Sync,
271{
272 let total_edges = graph.edge_count() as f64;
273 if total_edges == 0.0 {
274 return 0.0;
275 }
276
277 let two_m = 2.0 * total_edges;
278 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
279
280 let modularity_sum: f64 = nodes
282 .par_iter()
283 .flat_map(|node_i| {
284 nodes.par_iter().map(move |node_j| {
285 let comm_i = communities.get(node_i).copied().unwrap_or(0);
286 let comm_j = communities.get(node_j).copied().unwrap_or(0);
287
288 if comm_i == comm_j {
289 let a_ij = if graph.has_edge(node_i, node_j) {
290 1.0
291 } else {
292 0.0
293 };
294 let degree_i = graph.degree(node_i) as f64;
295 let degree_j = graph.degree(node_j) as f64;
296 let expected = (degree_i * degree_j) / two_m;
297
298 a_ij - expected
299 } else {
300 0.0
301 }
302 })
303 })
304 .sum();
305
306 modularity_sum / two_m
307}