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 rand::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    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    // Calculate total edge weight
50    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        // No edges, each node is its own community
59        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    // Initialize communities using parallel operations
72    let mut communities: HashMap<N, usize> = HashMap::new();
73    let mut node_degrees: HashMap<N, f64> = HashMap::new();
74
75    // Parallel initialization
76    for (i, node) in nodes.iter().enumerate() {
77        communities.insert(node.clone(), i);
78
79        // Calculate node degree
80        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    // Convert communities to NodeIndex-based map for modularity calculation
93    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    // Calculate final modularity with the initial communities
101    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/// Detects communities using parallel Louvain method (modern API)
110///
111/// This function returns the standardized `CommunityResult` type that provides
112/// multiple ways to access the community structure. Uses parallel processing
113/// to accelerate community detection on large graphs.
114///
115/// # Arguments
116/// * `graph` - The undirected graph to analyze
117/// * `max_iterations` - Maximum number of optimization iterations
118///
119/// # Returns
120/// * A `CommunityResult` with community structure from parallel Louvain
121///
122/// # Time Complexity
123/// O(m * log n / p) where m is the number of edges, n is the number of nodes,
124/// and p is the number of parallel threads. Actual speedup depends on graph structure.
125///
126/// # Space Complexity
127/// O(n) for storing community assignments and auxiliary data structures.
128///
129/// # Example
130/// ```rust,ignore
131/// // This requires the "parallel" feature to be enabled
132/// use scirs2_graph::{Graph, parallel_louvain_communities_result};
133///
134/// let mut graph: Graph<i32, f64> = Graph::new();
135/// // ... add nodes and edges ...
136/// let result = parallel_louvain_communities_result(&graph, 50);
137///
138/// println!("Parallel Louvain modularity: {:.4}", result.quality_score.unwrap_or(0.0));
139/// println!("Found {} communities", result.num_communities);
140/// ```
141#[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/// Parallel implementation of label propagation community detection
157///
158/// Uses parallel processing to speed up the label propagation algorithm
159/// for large graphs.
160///
161/// # Arguments
162/// * `graph` - The graph to analyze
163/// * `max_iterations` - Maximum number of iterations to run
164///
165/// # Returns
166/// * A `CommunityResult` containing the detected communities
167#[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    // Initialize labels (parallel)
182    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        // Create a shuffled order for processing nodes
192        let mut shuffled_nodes = nodes.clone();
193        shuffled_nodes.shuffle(&mut rng);
194
195        // Parallel label updates
196        let updates: Vec<(N, usize)> = shuffled_nodes
197            .par_iter()
198            .filter_map(|node| {
199                if let Ok(neighbors) = graph.neighbors(node) {
200                    // Count neighbor labels in parallel
201                    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                    // Find most frequent label
210                    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        // Apply updates
224        if updates.is_empty() {
225            break; // Converged
226        }
227
228        for (node, new_label) in updates {
229            labels.insert(node, new_label);
230        }
231    }
232
233    // Convert to communities
234    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, // Could compute modularity here
247        metadata: HashMap::new(),
248    }
249}
250
251/// Parallel implementation of modularity computation
252///
253/// Computes graph modularity using parallel processing for better performance
254/// on large graphs.
255///
256/// # Arguments
257/// * `graph` - The graph to analyze
258/// * `communities` - Node-to-community mapping
259///
260/// # Returns
261/// * The modularity score
262#[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    // Parallel computation of modularity
282    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}