scirs2_graph/algorithms/community/
louvain.rs1use super::types::{CommunityResult, CommunityStructure};
4use crate::base::{EdgeWeight, Graph, Node};
5use petgraph::visit::EdgeRef;
6use std::collections::HashMap;
7use std::hash::Hash;
8
9#[allow(dead_code)]
42pub fn louvain_communities_result<N, E, Ix>(graph: &Graph<N, E, Ix>) -> CommunityResult<N>
43where
44 N: Node + Clone + Hash + Eq + std::fmt::Debug,
45 E: EdgeWeight + Into<f64> + scirs2_core::numeric::Zero + Copy,
46 Ix: petgraph::graph::IndexType,
47{
48 let structure = louvain_communities_legacy(graph);
49 CommunityResult::from_community_structure(structure)
50}
51
52#[deprecated(
57 since = "0.1.0-beta.1",
58 note = "Use `louvain_communities_result` instead"
59)]
60#[allow(dead_code)]
61pub fn louvain_communities<N, E, Ix>(graph: &Graph<N, E, Ix>) -> CommunityStructure<N>
62where
63 N: Node + std::fmt::Debug,
64 E: EdgeWeight + Into<f64> + scirs2_core::numeric::Zero + Copy,
65 Ix: petgraph::graph::IndexType,
66{
67 louvain_communities_legacy(graph)
68}
69
70#[allow(dead_code)]
72fn louvain_communities_legacy<N, E, Ix>(graph: &Graph<N, E, Ix>) -> CommunityStructure<N>
73where
74 N: Node + std::fmt::Debug,
75 E: EdgeWeight + Into<f64> + scirs2_core::numeric::Zero + Copy,
76 Ix: petgraph::graph::IndexType,
77{
78 let n = graph.node_count();
79 if n == 0 {
80 return CommunityStructure {
81 node_communities: HashMap::new(),
82 modularity: 0.0,
83 };
84 }
85
86 let mut communities: HashMap<petgraph::graph::NodeIndex<Ix>, usize> = HashMap::new();
88 let mut node_degrees: HashMap<petgraph::graph::NodeIndex<Ix>, f64> = HashMap::new();
89
90 let mut m = 0.0; for edge in graph.inner().edge_references() {
93 m += (*edge.weight()).into();
94 }
95
96 if m == 0.0 {
98 m = 1.0;
99 }
100
101 for node_idx in graph.inner().node_indices() {
103 let mut degree = 0.0;
104 for edge in graph.inner().edges(node_idx) {
105 degree += (*edge.weight()).into();
106 }
107 node_degrees.insert(node_idx, degree);
108 communities.insert(node_idx, node_idx.index());
109 }
110
111 let mut improved = true;
113 let mut iterations = 0;
114 const MAX_ITERATIONS: usize = 100;
115
116 while improved && iterations < MAX_ITERATIONS {
117 improved = false;
118 iterations += 1;
119
120 for node_idx in graph.inner().node_indices() {
122 let current_community = communities[&node_idx];
123 let k_i = node_degrees[&node_idx]; communities.insert(node_idx, node_idx.index());
127
128 let mut community_weights: HashMap<usize, f64> = HashMap::new();
130 for edge in graph.inner().edges(node_idx) {
131 let neighbor_idx = edge.target();
132 let neighbor_community = communities[&neighbor_idx];
133 let edge_weight: f64 = (*edge.weight()).into();
134 *community_weights.entry(neighbor_community).or_insert(0.0) += edge_weight;
135 }
136
137 community_weights.entry(node_idx.index()).or_insert(0.0);
139
140 let mut best_community = node_idx.index();
142 let mut best_delta_q = 0.0;
143
144 for (&community, &k_i_in) in &community_weights {
145 let mut sigma_tot = 0.0;
147 for (&other_node, &other_community) in &communities {
148 if other_community == community && other_node != node_idx {
149 sigma_tot += node_degrees[&other_node];
150 }
151 }
152
153 let delta_q = k_i_in / m - (sigma_tot * k_i) / (2.0 * m * m);
155
156 if delta_q > best_delta_q {
157 best_delta_q = delta_q;
158 best_community = community;
159 }
160 }
161
162 if best_community != current_community {
164 improved = true;
165 }
166 communities.insert(node_idx, best_community);
167 }
168 }
169
170 let mut community_map: HashMap<usize, usize> = HashMap::new();
172 let mut next_id = 0;
173 for &comm in communities.values() {
174 if let std::collections::hash_map::Entry::Vacant(e) = community_map.entry(comm) {
175 e.insert(next_id);
176 next_id += 1;
177 }
178 }
179
180 for comm in communities.values_mut() {
182 *comm = community_map[comm];
183 }
184
185 let modularity = calculate_modularity(graph, &communities, m);
187
188 let node_communities: HashMap<N, usize> = communities
190 .into_iter()
191 .map(|(idx, comm)| (graph.inner()[idx].clone(), comm))
192 .collect();
193
194 CommunityStructure {
195 node_communities,
196 modularity,
197 }
198}
199
200#[allow(dead_code)]
202pub fn calculate_modularity<N, E, Ix>(
203 graph: &Graph<N, E, Ix>,
204 communities: &HashMap<petgraph::graph::NodeIndex<Ix>, usize>,
205 m: f64,
206) -> f64
207where
208 N: Node + std::fmt::Debug,
209 E: EdgeWeight + Into<f64> + Copy,
210 Ix: petgraph::graph::IndexType,
211{
212 let mut modularity = 0.0;
213
214 let mut node_degrees: HashMap<petgraph::graph::NodeIndex<Ix>, f64> = HashMap::new();
216 for node_idx in graph.inner().node_indices() {
217 let degree: f64 = graph
218 .inner()
219 .edges(node_idx)
220 .map(|e| (*e.weight()).into())
221 .sum();
222 node_degrees.insert(node_idx, degree);
223 }
224
225 for node_i in graph.inner().node_indices() {
227 for node_j in graph.inner().node_indices() {
228 if communities[&node_i] == communities[&node_j] {
229 let mut a_ij = 0.0;
231 for edge in graph.inner().edges(node_i) {
232 if edge.target() == node_j {
233 a_ij = (*edge.weight()).into();
234 break;
235 }
236 }
237
238 let k_i = node_degrees[&node_i];
239 let k_j = node_degrees[&node_j];
240
241 modularity += a_ij - (k_i * k_j) / (2.0 * m);
242 }
243 }
244 }
245
246 modularity / (2.0 * m)
247}