samyama_graph_algorithms/
cdlp.rs1use super::common::{GraphView, NodeId};
8use std::collections::HashMap;
9use rayon::prelude::*;
10
11#[derive(Debug, Clone)]
13pub struct CdlpResult {
14 pub labels: HashMap<NodeId, NodeId>,
16 pub iterations: usize,
18}
19
20pub struct CdlpConfig {
22 pub max_iterations: usize,
24}
25
26impl Default for CdlpConfig {
27 fn default() -> Self {
28 Self {
29 max_iterations: 100,
30 }
31 }
32}
33
34pub fn cdlp(view: &GraphView, config: &CdlpConfig) -> CdlpResult {
39 let n = view.node_count;
40 if n == 0 {
41 return CdlpResult { labels: HashMap::new(), iterations: 0 };
42 }
43
44 let mut labels: Vec<NodeId> = (0..n).map(|i| view.index_to_node[i]).collect();
46 let mut new_labels = labels.clone();
47 let mut converged = false;
48 let mut iterations = 0;
49
50 let use_parallel = n >= 1000;
51
52 for _iter in 0..config.max_iterations {
53 iterations += 1;
54
55 if use_parallel {
56 let results: Vec<(NodeId, bool)> = (0..n).into_par_iter().map(|idx| {
57 let mut label_counts: HashMap<NodeId, usize> = HashMap::new();
58 for &neighbor in view.successors(idx) {
59 *label_counts.entry(labels[neighbor]).or_insert(0) += 1;
60 }
61 for &neighbor in view.predecessors(idx) {
62 *label_counts.entry(labels[neighbor]).or_insert(0) += 1;
63 }
64 if label_counts.is_empty() {
65 return (labels[idx], true);
66 }
67 let max_count = *label_counts.values().max().unwrap();
68 let best_label = label_counts.into_iter()
69 .filter(|(_, count)| *count == max_count)
70 .map(|(label, _)| label)
71 .min()
72 .unwrap();
73 (best_label, best_label == labels[idx])
74 }).collect();
75
76 converged = true;
77 for (idx, (label, same)) in results.into_iter().enumerate() {
78 new_labels[idx] = label;
79 if !same { converged = false; }
80 }
81 } else {
82 converged = true;
83 for idx in 0..n {
84 let mut label_counts: HashMap<NodeId, usize> = HashMap::new();
85 for &neighbor in view.successors(idx) {
86 *label_counts.entry(labels[neighbor]).or_insert(0) += 1;
87 }
88 for &neighbor in view.predecessors(idx) {
89 *label_counts.entry(labels[neighbor]).or_insert(0) += 1;
90 }
91 if label_counts.is_empty() {
92 new_labels[idx] = labels[idx];
93 continue;
94 }
95 let max_count = *label_counts.values().max().unwrap();
96 let best_label = label_counts.into_iter()
97 .filter(|(_, count)| *count == max_count)
98 .map(|(label, _)| label)
99 .min()
100 .unwrap();
101 if best_label != labels[idx] {
102 converged = false;
103 }
104 new_labels[idx] = best_label;
105 }
106 }
107
108 std::mem::swap(&mut labels, &mut new_labels);
109
110 if converged {
111 break;
112 }
113 }
114
115 let result_labels: HashMap<NodeId, NodeId> = (0..n)
116 .map(|idx| (view.index_to_node[idx], labels[idx]))
117 .collect();
118
119 CdlpResult { labels: result_labels, iterations }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use std::collections::HashMap;
126 use crate::common::GraphView;
127
128 fn make_triangle_graph() -> GraphView {
129 let index_to_node = vec![1, 2, 3];
131 let mut node_to_index = HashMap::new();
132 node_to_index.insert(1, 0);
133 node_to_index.insert(2, 1);
134 node_to_index.insert(3, 2);
135
136 let outgoing = vec![vec![1, 2], vec![0, 2], vec![0, 1]];
137 let incoming = vec![vec![1, 2], vec![0, 2], vec![0, 1]];
138
139 GraphView::from_adjacency_list(3, index_to_node, node_to_index, outgoing, incoming, None)
140 }
141
142 #[test]
143 fn test_cdlp_triangle() {
144 let view = make_triangle_graph();
145 let result = cdlp(&view, &CdlpConfig::default());
146 let labels: Vec<NodeId> = result.labels.values().cloned().collect();
148 assert!(labels.iter().all(|l| *l == labels[0]),
149 "All nodes in a triangle should have the same community label");
150 }
151
152 #[test]
153 fn test_cdlp_disconnected() {
154 let index_to_node = vec![1, 2];
156 let mut node_to_index = HashMap::new();
157 node_to_index.insert(1, 0);
158 node_to_index.insert(2, 1);
159
160 let view = GraphView::from_adjacency_list(
161 2, index_to_node, node_to_index,
162 vec![vec![], vec![]], vec![vec![], vec![]], None,
163 );
164 let result = cdlp(&view, &CdlpConfig::default());
165 assert_ne!(result.labels[&1], result.labels[&2]);
166 }
167
168 #[test]
169 fn test_cdlp_empty() {
170 let view = GraphView::from_adjacency_list(
171 0, vec![], HashMap::new(), vec![], vec![], None,
172 );
173 let result = cdlp(&view, &CdlpConfig::default());
174 assert!(result.labels.is_empty());
175 }
176}