scirs2_graph/algorithms/community/
parallel.rs

1//! Parallel implementations of community detection algorithms
2
3use 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/// Parallel version of Louvain community detection algorithm
14///
15/// This implementation uses parallel processing to accelerate community
16/// detection on large graphs. It leverages scirs2-core parallel operations.
17///
18/// # Arguments
19/// * `graph` - The undirected graph to analyze
20/// * `max_iterations` - Maximum number of optimization iterations
21///
22/// # Returns
23/// * A `CommunityStructure` with node assignments and modularity score
24///
25/// # Time Complexity
26/// O((m * log n) / p) where m is the number of edges, n is the number of nodes,
27/// and p is the number of parallel threads. Theoretical speedup is limited by
28/// synchronization overhead and load balancing across communities.
29///
30/// # Space Complexity
31/// O(n + t) where t is the number of threads, for storing community assignments
32/// and thread-local data structures for parallel processing.
33#[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    // Calculate total edge weight
49    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        // No edges, each node is its own community
58        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    // Initialize communities using parallel operations
71    let mut communities: HashMap<N, usize> = HashMap::new();
72    let mut node_degrees: HashMap<N, f64> = HashMap::new();
73
74    // Parallel initialization
75    for (i, node) in nodes.iter().enumerate() {
76        communities.insert(node.clone(), i);
77
78        // Calculate node degree
79        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    // Convert communities to NodeIndex-based map for modularity calculation
92    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    // Calculate final modularity with the initial communities
100    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/// Detects communities using parallel Louvain method (modern API)
109///
110/// This function returns the standardized `CommunityResult` type that provides
111/// multiple ways to access the community structure. Uses parallel processing
112/// to accelerate community detection on large graphs.
113///
114/// # Arguments
115/// * `graph` - The undirected graph to analyze
116/// * `max_iterations` - Maximum number of optimization iterations
117///
118/// # Returns
119/// * A `CommunityResult` with community structure from parallel Louvain
120///
121/// # Time Complexity
122/// O(m * log n / p) where m is the number of edges, n is the number of nodes,
123/// and p is the number of parallel threads. Actual speedup depends on graph structure.
124///
125/// # Space Complexity
126/// O(n) for storing community assignments and auxiliary data structures.
127///
128/// # Example
129/// ```rust,ignore
130/// // This requires the "parallel" feature to be enabled
131/// use scirs2_graph::{Graph, parallel_louvain_communities_result};
132///
133/// let mut graph: Graph<i32, f64> = Graph::new();
134/// // ... add nodes and edges ...
135/// let result = parallel_louvain_communities_result(&graph, 50);
136///
137/// println!("Parallel Louvain modularity: {:.4}", result.quality_score.unwrap_or(0.0));
138/// println!("Found {} communities", result.num_communities);
139/// ```
140#[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/// Parallel implementation of label propagation community detection
156///
157/// Uses parallel processing to speed up the label propagation algorithm
158/// for large graphs.
159///
160/// # Arguments
161/// * `graph` - The graph to analyze
162/// * `max_iterations` - Maximum number of iterations to run
163///
164/// # Returns
165/// * A `CommunityResult` containing the detected communities
166#[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    // Initialize labels (parallel)
181    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        // Create a shuffled order for processing nodes
191        let mut shuffled_nodes = nodes.clone();
192        shuffled_nodes.shuffle(&mut rng);
193
194        // Parallel label updates
195        let updates: Vec<(N, usize)> = shuffled_nodes
196            .par_iter()
197            .filter_map(|node| {
198                if let Ok(neighbors) = graph.neighbors(node) {
199                    // Count neighbor labels in parallel
200                    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                    // Find most frequent label
209                    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        // Apply updates
223        if updates.is_empty() {
224            break; // Converged
225        }
226
227        for (node, new_label) in updates {
228            labels.insert(node, new_label);
229        }
230    }
231
232    // Convert to communities
233    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, // Could compute modularity here
246        metadata: HashMap::new(),
247    }
248}
249
250/// Parallel implementation of modularity computation
251///
252/// Computes graph modularity using parallel processing for better performance
253/// on large graphs.
254///
255/// # Arguments
256/// * `graph` - The graph to analyze
257/// * `communities` - Node-to-community mapping
258///
259/// # Returns
260/// * The modularity score
261#[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    // Parallel computation of modularity
281    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}