/src/igraph/src/community/modularity.c
Line | Count | Source |
1 | | /* |
2 | | igraph library. |
3 | | Copyright (C) 2007-2020 The igraph development team |
4 | | |
5 | | This program is free software; you can redistribute it and/or modify |
6 | | it under the terms of the GNU General Public License as published by |
7 | | the Free Software Foundation; either version 2 of the License, or |
8 | | (at your option) any later version. |
9 | | |
10 | | This program is distributed in the hope that it will be useful, |
11 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | | GNU General Public License for more details. |
14 | | |
15 | | You should have received a copy of the GNU General Public License |
16 | | along with this program; if not, write to the Free Software |
17 | | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
18 | | 02110-1301 USA |
19 | | |
20 | | */ |
21 | | |
22 | | #include "igraph_community.h" |
23 | | |
24 | | #include "igraph_interface.h" |
25 | | #include "igraph_structural.h" |
26 | | |
27 | | #include "community/community_internal.h" |
28 | | |
29 | | /** |
30 | | * \function igraph_modularity |
31 | | * \brief Calculates the modularity of a graph with respect to some clusters or vertex types. |
32 | | * |
33 | | * The modularity of a graph with respect to some clustering of the vertices |
34 | | * (or assignment of vertex types) |
35 | | * measures how strongly separated the different clusters are from each |
36 | | * other compared to a random null model. It is defined as |
37 | | * |
38 | | * </para><para> |
39 | | * <code>Q = 1/(2m) sum_ij (A_ij - γ k_i k_j / (2m)) δ(c_i,c_j)</code>, |
40 | | * |
41 | | * </para><para> |
42 | | * where \c m is the number of edges, <code>A_ij</code> is the adjacency matrix, |
43 | | * \c k_i is the degree of vertex \c i, \c c_i is the cluster that vertex \c i belongs to |
44 | | * (or its vertex type), <code>δ(i,j)=1</code> if <code>i=j</code> and 0 otherwise, |
45 | | * and the sum goes over all \c i, \c j pairs of vertices. Note that in this formula, |
46 | | * the diagonal of the adjacency matrix contains twice the number of self-loops. |
47 | | * |
48 | | * </para><para> |
49 | | * The resolution parameter \c γ allows weighting the random null model, which |
50 | | * might be useful when finding partitions with a high modularity. Maximizing modularity |
51 | | * with higher values of the resolution parameter typically results in more, smaller clusters |
52 | | * when finding partitions with a high modularity. Lower values typically result in |
53 | | * fewer, larger clusters. The original definition of modularity is retrieved |
54 | | * when setting <code>γ = 1</code>. |
55 | | * |
56 | | * </para><para> |
57 | | * Modularity can also be calculated on directed graphs. This only requires a relatively |
58 | | * modest change, |
59 | | * |
60 | | * </para><para> |
61 | | * <code>Q = 1/m sum_ij (A_ij - γ k^out_i k^in_j / m) δ(c_i,c_j)</code>, |
62 | | * |
63 | | * </para><para> |
64 | | * where \c k^out_i is the out-degree of node \c i and \c k^in_j is the in-degree of node \c j. |
65 | | * |
66 | | * </para><para> |
67 | | * Modularity on weighted graphs is also meaningful. When taking |
68 | | * edge weights into account, \c A_ij equals the weight of the corresponding edge |
69 | | * (or 0 if there is no edge), \c k_i is the strength (i.e. the weighted degree) of |
70 | | * vertex \c i, with similar counterparts for a directed graph, and \c m is the total |
71 | | * weight of all edges. |
72 | | * |
73 | | * </para><para> |
74 | | * Note that the modularity is not well-defined for graphs with no edges. |
75 | | * igraph returns \c NaN for graphs with no edges; see |
76 | | * https://github.com/igraph/igraph/issues/1539 for |
77 | | * a detailed discussion. |
78 | | * |
79 | | * </para><para> |
80 | | * For the original definition of modularity, see Newman, M. E. J., and Girvan, M. |
81 | | * (2004). Finding and evaluating community structure in networks. |
82 | | * Physical Review E 69, 026113. https://doi.org/10.1103/PhysRevE.69.026113 |
83 | | * |
84 | | * </para><para> |
85 | | * For the directed definition of modularity, see Leicht, E. A., and Newman, M. E. |
86 | | * J. (2008). Community Structure in Directed Networks. Physical Review Letters 100, |
87 | | * 118703. https://doi.org/10.1103/PhysRevLett.100.118703 |
88 | | * |
89 | | * </para><para> |
90 | | * For the introduction of the resolution parameter \c γ, see Reichardt, J., and |
91 | | * Bornholdt, S. (2006). Statistical mechanics of community detection. Physical |
92 | | * Review E 74, 016110. https://doi.org/10.1103/PhysRevE.74.016110 |
93 | | * |
94 | | * \param graph The input graph. |
95 | | * \param membership Numeric vector of integer values which gives the type of each |
96 | | * vertex, i.e. the cluster to which it belongs. |
97 | | * It does not have to be consecutive, i.e. empty communities |
98 | | * are allowed. For better performance, ensure that community |
99 | | * indices are nonnegative and smaller than the vertex count. |
100 | | * This can be ensured using \ref igraph_reindex_membership(). |
101 | | * \param weights Weight vector or \c NULL if no weights are specified. |
102 | | * \param resolution The resolution parameter \c γ. Must not be negative. |
103 | | * Set it to 1 to use the classical definition of modularity. |
104 | | * \param directed Whether to use the directed or undirected version of modularity. |
105 | | * Ignored for undirected graphs. |
106 | | * \param modularity Pointer to a real number, the result will be |
107 | | * stored here. |
108 | | * \return Error code. |
109 | | * |
110 | | * \sa \ref igraph_modularity_matrix(). |
111 | | * |
112 | | * Time complexity: O(|V|+|E|), the number of vertices plus the number |
113 | | * of edges, assuming that community indices are nonnegative and smaller |
114 | | * than the vertex count. Otherwise, O(|V| log |V| + |E|). |
115 | | */ |
116 | | igraph_error_t igraph_modularity(const igraph_t *graph, |
117 | | const igraph_vector_int_t *membership, |
118 | | const igraph_vector_t *weights, |
119 | | const igraph_real_t resolution, |
120 | | const igraph_bool_t directed, |
121 | 52.6k | igraph_real_t *modularity) { |
122 | | |
123 | 52.6k | const igraph_int_t vcount = igraph_vcount(graph); |
124 | 52.6k | const igraph_int_t ecount = igraph_ecount(graph); |
125 | 52.6k | const igraph_vector_int_t *p_membership; |
126 | 52.6k | igraph_vector_int_t i_membership; |
127 | 52.6k | igraph_bool_t using_i_membership = false; |
128 | 52.6k | igraph_vector_t k_out, k_in; |
129 | 52.6k | igraph_int_t min_cluster_id, max_cluster_id, no_of_partitions; |
130 | 52.6k | igraph_real_t e; /* count/fraction of edges/weights within partitions */ |
131 | 52.6k | igraph_real_t m; /* edge count / weight sum */ |
132 | 52.6k | igraph_int_t c1, c2; |
133 | | /* Only consider the graph as directed if it actually is directed */ |
134 | 52.6k | igraph_bool_t use_directed = directed && igraph_is_directed(graph); |
135 | 52.6k | igraph_real_t directed_multiplier = (use_directed ? 1 : 2); |
136 | | |
137 | 52.6k | if (igraph_vector_int_size(membership) != vcount) { |
138 | 0 | IGRAPH_ERROR("Membership vector size differs from number of vertices.", |
139 | 0 | IGRAPH_EINVAL); |
140 | 0 | } |
141 | 52.6k | if (resolution < 0.0) { |
142 | 0 | IGRAPH_ERROR("The resolution parameter must not be negative.", IGRAPH_EINVAL); |
143 | 0 | } |
144 | | |
145 | 52.6k | if (ecount == 0) { |
146 | | /* Special case: the modularity of graphs with no edges is not |
147 | | * well-defined */ |
148 | 205 | *modularity = IGRAPH_NAN; |
149 | 205 | return IGRAPH_SUCCESS; |
150 | 205 | } |
151 | | |
152 | | /* At this point, the 'membership' vector does not have length zero, |
153 | | thus it is safe to call igraph_vector_int_minmax(). */ |
154 | | |
155 | | /* If community indices are outside of the standard range, automatically |
156 | | * reindex them. */ |
157 | 52.4k | igraph_vector_int_minmax(membership, &min_cluster_id, &max_cluster_id); |
158 | 52.4k | if (min_cluster_id < 0 || max_cluster_id >= vcount) { |
159 | 41.8k | IGRAPH_CHECK(igraph_vector_int_init_copy(&i_membership, membership)); |
160 | 41.8k | IGRAPH_FINALLY(igraph_vector_int_destroy, &i_membership); |
161 | 41.8k | IGRAPH_CHECK(igraph_i_reindex_membership_large(&i_membership, NULL, &no_of_partitions)); |
162 | 41.8k | p_membership = &i_membership; |
163 | 41.8k | using_i_membership = true; |
164 | 41.8k | } else { |
165 | 10.5k | no_of_partitions = max_cluster_id + 1; |
166 | 10.5k | p_membership = membership; |
167 | 10.5k | } |
168 | | |
169 | 52.4k | IGRAPH_VECTOR_INIT_FINALLY(&k_out, no_of_partitions); |
170 | 52.4k | IGRAPH_VECTOR_INIT_FINALLY(&k_in, no_of_partitions); |
171 | | |
172 | 52.4k | e = 0.0; |
173 | 52.4k | if (weights) { |
174 | 52.4k | if (igraph_vector_size(weights) != ecount) |
175 | 0 | IGRAPH_ERROR("Weight vector size differs from number of edges.", |
176 | 52.4k | IGRAPH_EINVAL); |
177 | 52.4k | m = 0.0; |
178 | 2.57M | for (igraph_int_t i = 0; i < ecount; i++) { |
179 | 2.52M | igraph_real_t w = VECTOR(*weights)[i]; |
180 | 2.52M | if (w < 0) { |
181 | 0 | IGRAPH_ERROR("Negative weight in weight vector.", IGRAPH_EINVAL); |
182 | 0 | } |
183 | 2.52M | c1 = VECTOR(*p_membership)[ IGRAPH_FROM(graph, i) ]; |
184 | 2.52M | c2 = VECTOR(*p_membership)[ IGRAPH_TO(graph, i) ]; |
185 | 2.52M | if (c1 == c2) { |
186 | 1.30M | e += directed_multiplier * w; |
187 | 1.30M | } |
188 | 2.52M | VECTOR(k_out)[c1] += w; |
189 | 2.52M | VECTOR(k_in)[c2] += w; |
190 | 2.52M | m += w; |
191 | 2.52M | } |
192 | 52.4k | } else { |
193 | 0 | m = ecount; |
194 | 0 | for (igraph_int_t i = 0; i < ecount; i++) { |
195 | 0 | c1 = VECTOR(*p_membership)[ IGRAPH_FROM(graph, i) ]; |
196 | 0 | c2 = VECTOR(*p_membership)[ IGRAPH_TO(graph, i) ]; |
197 | 0 | if (c1 == c2) { |
198 | 0 | e += directed_multiplier; |
199 | 0 | } |
200 | 0 | VECTOR(k_out)[c1] += 1; |
201 | 0 | VECTOR(k_in)[c2] += 1; |
202 | 0 | } |
203 | 0 | } |
204 | | |
205 | 52.4k | if (!use_directed) { |
206 | | /* Graph is undirected, simply add vectors. |
207 | | * Vectors are the same size, calls below can't fail. */ |
208 | 3.54k | igraph_vector_add(&k_out, &k_in); |
209 | 3.54k | igraph_vector_update(&k_in, &k_out); |
210 | 3.54k | } |
211 | | |
212 | | /* Divide all vectors by total weight. */ |
213 | 52.4k | igraph_vector_scale(&k_out, 1.0/( directed_multiplier * m ) ); |
214 | 52.4k | igraph_vector_scale(&k_in, 1.0/( directed_multiplier * m ) ); |
215 | 52.4k | e /= directed_multiplier * m; |
216 | | |
217 | 52.4k | if (m > 0) { |
218 | 52.4k | *modularity = e; |
219 | 2.24M | for (igraph_int_t i = 0; i < no_of_partitions; i++) { |
220 | 2.19M | *modularity -= resolution * VECTOR(k_out)[i] * VECTOR(k_in)[i]; |
221 | 2.19M | } |
222 | 52.4k | } else { |
223 | 0 | *modularity = IGRAPH_NAN; |
224 | 0 | } |
225 | | |
226 | 52.4k | igraph_vector_destroy(&k_out); |
227 | 52.4k | igraph_vector_destroy(&k_in); |
228 | 52.4k | IGRAPH_FINALLY_CLEAN(2); |
229 | | |
230 | 52.4k | if (using_i_membership) { |
231 | 41.8k | igraph_vector_int_destroy(&i_membership); |
232 | 41.8k | IGRAPH_FINALLY_CLEAN(1); |
233 | 41.8k | } |
234 | | |
235 | 52.4k | return IGRAPH_SUCCESS; |
236 | 52.4k | } |
237 | | |
238 | | static igraph_error_t igraph_i_modularity_matrix_get_adjacency( |
239 | | const igraph_t *graph, igraph_matrix_t *res, |
240 | 3.59k | const igraph_vector_t *weights, igraph_bool_t directed) { |
241 | | |
242 | | /* Specifically used to handle weights and/or ignore direction */ |
243 | 3.59k | igraph_eit_t edgeit; |
244 | 3.59k | igraph_int_t no_of_nodes = igraph_vcount(graph); |
245 | 3.59k | igraph_int_t from, to; |
246 | | |
247 | 3.59k | IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, no_of_nodes)); |
248 | 3.59k | igraph_matrix_null(res); |
249 | 3.59k | IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_ID), &edgeit)); |
250 | 3.59k | IGRAPH_FINALLY(igraph_eit_destroy, &edgeit); |
251 | | |
252 | 3.59k | if (weights) { |
253 | 98.1k | for (; !IGRAPH_EIT_END(edgeit); IGRAPH_EIT_NEXT(edgeit)) { |
254 | 94.5k | igraph_int_t edge = IGRAPH_EIT_GET(edgeit); |
255 | 94.5k | from = IGRAPH_FROM(graph, edge); |
256 | 94.5k | to = IGRAPH_TO(graph, edge); |
257 | 94.5k | MATRIX(*res, from, to) += VECTOR(*weights)[edge]; |
258 | 94.5k | if (!directed) { |
259 | 0 | MATRIX(*res, to, from) += VECTOR(*weights)[edge]; |
260 | 0 | } |
261 | 94.5k | } |
262 | 3.59k | } else { |
263 | 0 | for (; !IGRAPH_EIT_END(edgeit); IGRAPH_EIT_NEXT(edgeit)) { |
264 | 0 | igraph_int_t edge = IGRAPH_EIT_GET(edgeit); |
265 | 0 | from = IGRAPH_FROM(graph, edge); |
266 | 0 | to = IGRAPH_TO(graph, edge); |
267 | 0 | MATRIX(*res, from, to) += 1; |
268 | 0 | if (!directed) { |
269 | 0 | MATRIX(*res, to, from) += 1; |
270 | 0 | } |
271 | 0 | } |
272 | 0 | } |
273 | | |
274 | 3.59k | igraph_eit_destroy(&edgeit); |
275 | 3.59k | IGRAPH_FINALLY_CLEAN(1); |
276 | 3.59k | return IGRAPH_SUCCESS; |
277 | 3.59k | } |
278 | | |
279 | | /** |
280 | | * \function igraph_modularity_matrix |
281 | | * \brief Calculates the modularity matrix. |
282 | | * |
283 | | * This function returns the modularity matrix, which is defined as |
284 | | * |
285 | | * </para><para> |
286 | | * <code>B_ij = A_ij - γ k_i k_j / (2m)</code> |
287 | | * |
288 | | * </para><para> |
289 | | * for undirected graphs, where \c A_ij is the adjacency matrix, \c γ is the |
290 | | * resolution parameter, \c k_i is the degree of vertex \c i, and \c m is the |
291 | | * number of edges in the graph. When there are no edges, or the weights add up |
292 | | * to zero, the result is undefined. |
293 | | * |
294 | | * </para><para> |
295 | | * For directed graphs the modularity matrix is changed to |
296 | | * |
297 | | * </para><para> |
298 | | * <code>B_ij = A_ij - γ k^out_i k^in_j / m</code> |
299 | | * |
300 | | * </para><para> |
301 | | * where <code>k^out_i</code> is the out-degree of node \c i and <code>k^in_j</code> is the |
302 | | * in-degree of node \c j. |
303 | | * |
304 | | * </para><para> |
305 | | * Note that self-loops in undirected graphs are multiplied by 2 in this |
306 | | * implementation. If weights are specified, the weighted counterparts of the adjacency |
307 | | * matrix and degrees are used. |
308 | | * |
309 | | * \param graph The input graph. |
310 | | * \param weights Edge weights, pointer to a vector. If this is a null pointer |
311 | | * then every edge is assumed to have a weight of 1. |
312 | | * \param resolution The resolution parameter \c γ. Must not be negative. |
313 | | * Default is 1. Lower values favor fewer, larger communities; |
314 | | * higher values favor more, smaller communities. |
315 | | * \param modmat Pointer to an initialized matrix in which the modularity |
316 | | * matrix is stored. |
317 | | * \param directed For directed graphs: if the edges should be treated as |
318 | | * undirected. For undirected graphs this is ignored. |
319 | | * \return Error code. |
320 | | * |
321 | | * \sa \ref igraph_modularity(). |
322 | | */ |
323 | | igraph_error_t igraph_modularity_matrix(const igraph_t *graph, |
324 | | const igraph_vector_t *weights, |
325 | | const igraph_real_t resolution, |
326 | | igraph_matrix_t *modmat, |
327 | 3.59k | igraph_bool_t directed) { |
328 | | |
329 | 3.59k | const igraph_int_t no_of_nodes = igraph_vcount(graph); |
330 | 3.59k | const igraph_int_t no_of_edges = igraph_ecount(graph); |
331 | 3.59k | const igraph_real_t sw = weights ? igraph_vector_sum(weights) : no_of_edges; |
332 | 3.59k | igraph_vector_t deg, in_deg, out_deg; |
333 | 3.59k | igraph_real_t scaling_factor; |
334 | | |
335 | 3.59k | if (weights && igraph_vector_size(weights) != no_of_edges) { |
336 | 0 | IGRAPH_ERROR("Invalid weight vector length.", IGRAPH_EINVAL); |
337 | 0 | } |
338 | | |
339 | 3.59k | if (resolution < 0.0) { |
340 | 0 | IGRAPH_ERROR("The resolution parameter must not be negative.", IGRAPH_EINVAL); |
341 | 0 | } |
342 | | |
343 | 3.59k | if (!igraph_is_directed(graph)) { |
344 | 0 | directed = false; |
345 | 0 | } |
346 | 3.59k | IGRAPH_CHECK(igraph_i_modularity_matrix_get_adjacency(graph, modmat, weights, directed)); |
347 | | |
348 | | /* Performance notes: |
349 | | * - Iterating in column-major order makes a large difference. |
350 | | * - Applying the scaling_factor to in_deg (or out_deg) first to reduce the |
351 | | * number of multiplications does not make an appreciable performance |
352 | | * difference. However, doing this in the undirected case causes the result |
353 | | * matrix to sometimes not be strictly symmetric due to the non-associativity |
354 | | * of floating point multiplication. |
355 | | */ |
356 | | |
357 | 3.59k | if (directed) { |
358 | 3.59k | IGRAPH_VECTOR_INIT_FINALLY(&in_deg, no_of_nodes); |
359 | 3.59k | IGRAPH_VECTOR_INIT_FINALLY(&out_deg, no_of_nodes); |
360 | | |
361 | 3.59k | IGRAPH_CHECK(igraph_strength(graph, &in_deg, igraph_vss_all(), IGRAPH_IN, |
362 | 3.59k | IGRAPH_LOOPS, weights)); |
363 | 3.59k | IGRAPH_CHECK(igraph_strength(graph, &out_deg, igraph_vss_all(), IGRAPH_OUT, |
364 | 3.59k | IGRAPH_LOOPS, weights)); |
365 | | |
366 | 3.59k | scaling_factor = resolution / sw; |
367 | | |
368 | 162k | for (igraph_int_t j = 0; j < no_of_nodes; j++) { |
369 | 8.99M | for (igraph_int_t i = 0; i < no_of_nodes; i++) { |
370 | 8.84M | MATRIX(*modmat, i, j) -= VECTOR(out_deg)[i] * VECTOR(in_deg)[j] * scaling_factor; |
371 | 8.84M | } |
372 | 159k | } |
373 | 3.59k | igraph_vector_destroy(&in_deg); |
374 | 3.59k | igraph_vector_destroy(&out_deg); |
375 | 3.59k | IGRAPH_FINALLY_CLEAN(2); |
376 | 3.59k | } else { |
377 | 0 | IGRAPH_VECTOR_INIT_FINALLY(°, no_of_nodes); |
378 | | |
379 | 0 | IGRAPH_CHECK(igraph_strength(graph, °, igraph_vss_all(), IGRAPH_ALL, |
380 | 0 | IGRAPH_LOOPS, weights)); |
381 | | |
382 | 0 | scaling_factor = resolution / 2.0 / sw; |
383 | |
|
384 | 0 | for (igraph_int_t j = 0; j < no_of_nodes; j++) { |
385 | 0 | for (igraph_int_t i = 0; i < no_of_nodes; i++) { |
386 | 0 | MATRIX(*modmat, i, j) -= VECTOR(deg)[i] * VECTOR(deg)[j] * scaling_factor; |
387 | 0 | } |
388 | 0 | } |
389 | 0 | igraph_vector_destroy(°); |
390 | 0 | IGRAPH_FINALLY_CLEAN(1); |
391 | 0 | } |
392 | | |
393 | 3.59k | return IGRAPH_SUCCESS; |
394 | 3.59k | } |