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