/src/igraph/src/community/walktrap/walktrap.cpp
Line | Count | Source |
1 | | /* |
2 | | igraph library. |
3 | | Copyright (C) 2007-2012 Gabor Csardi <csardi.gabor@gmail.com> |
4 | | 334 Harvard street, Cambridge, MA 02139 USA |
5 | | |
6 | | This program is free software; you can redistribute it and/or modify |
7 | | it under the terms of the GNU General Public License as published by |
8 | | the Free Software Foundation; either version 2 of the License, or |
9 | | (at your option) any later version. |
10 | | |
11 | | This program is distributed in the hope that it will be useful, |
12 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | GNU General Public License for more details. |
15 | | |
16 | | You should have received a copy of the GNU General Public License |
17 | | along with this program; if not, write to the Free Software |
18 | | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
19 | | 02110-1301 USA |
20 | | |
21 | | */ |
22 | | |
23 | | /* The original version of this file was written by Pascal Pons |
24 | | The original copyright notice follows here. The FSF address was |
25 | | fixed by Tamas Nepusz */ |
26 | | |
27 | | // File: walktrap.cpp |
28 | | //----------------------------------------------------------------------------- |
29 | | // Walktrap v0.2 -- Finds community structure of networks using random walks |
30 | | // Copyright (C) 2004-2005 Pascal Pons |
31 | | // |
32 | | // This program is free software; you can redistribute it and/or modify |
33 | | // it under the terms of the GNU General Public License as published by |
34 | | // the Free Software Foundation; either version 2 of the License, or |
35 | | // (at your option) any later version. |
36 | | // |
37 | | // This program is distributed in the hope that it will be useful, |
38 | | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
39 | | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
40 | | // GNU General Public License for more details. |
41 | | // |
42 | | // You should have received a copy of the GNU General Public License |
43 | | // along with this program; if not, write to the Free Software |
44 | | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
45 | | // 02110-1301 USA |
46 | | //----------------------------------------------------------------------------- |
47 | | // Author : Pascal Pons |
48 | | // Email : pascal.pons@gmail.com |
49 | | // Web page : http://www-rp.lip6.fr/~latapy/PP/walktrap.html |
50 | | // Location : Paris, France |
51 | | // Time : June 2005 |
52 | | //----------------------------------------------------------------------------- |
53 | | // see readme.txt for more details |
54 | | |
55 | | #include "walktrap_graph.h" |
56 | | #include "walktrap_communities.h" |
57 | | |
58 | | #include "igraph_community.h" |
59 | | #include "igraph_components.h" |
60 | | #include "igraph_interface.h" |
61 | | |
62 | | #include "core/exceptions.h" |
63 | | #include "core/interruption.h" |
64 | | |
65 | | #include <climits> |
66 | | #include <cmath> |
67 | | |
68 | | // This is necessary for GCC 5 and earlier, where including <cmath> |
69 | | // makes isnan() unusable without the std:: prefix, even if <math.h> |
70 | | // was included as well. |
71 | | using std::isnan; |
72 | | |
73 | | using namespace igraph::walktrap; |
74 | | |
75 | | /** |
76 | | * \function igraph_community_walktrap |
77 | | * \brief Community finding using a random walk based similarity measure. |
78 | | * |
79 | | * This function is the implementation of the Walktrap community |
80 | | * finding algorithm, see Pascal Pons, Matthieu Latapy: Computing |
81 | | * communities in large networks using random walks, |
82 | | * https://arxiv.org/abs/physics/0512106 |
83 | | * |
84 | | * </para><para> |
85 | | * Currently the original C++ implementation is used in igraph, |
86 | | * see https://www-complexnetworks.lip6.fr/~latapy/PP/walktrap.html |
87 | | * We are grateful to Matthieu Latapy and Pascal Pons for providing this |
88 | | * source code. |
89 | | * |
90 | | * </para><para> |
91 | | * In contrast to the original implementation, isolated vertices are allowed |
92 | | * in the graph and they are assumed to have a single incident loop edge with |
93 | | * weight 1. |
94 | | * |
95 | | * \param graph The input graph, edge directions are ignored. |
96 | | * \param weights Numeric vector giving the weights of the edges. |
97 | | * If it is a NULL pointer then all edges will have equal |
98 | | * weights. The weights are expected to be positive. |
99 | | * \param steps Integer constant, the length of the random walks. |
100 | | * Typically, good results are obtained with values between |
101 | | * 3-8 with 4-5 being a reasonable default. |
102 | | * \param merges Pointer to a matrix, the merges performed by the |
103 | | * algorithm will be stored here (if not \c NULL). Each merge is a |
104 | | * row in a two-column matrix and contains the IDs of the merged |
105 | | * clusters. Clusters are numbered from zero and cluster numbers |
106 | | * smaller than the number of nodes in the network belong to the |
107 | | * individual vertices as singleton clusters. In each step a new |
108 | | * cluster is created from two other clusters and its id will be |
109 | | * one larger than the largest cluster id so far. This means that |
110 | | * before the first merge we have \c n clusters (the number of |
111 | | * vertices in the graph) numbered from zero to <code>n - 1</code>. |
112 | | * The first merge creates cluster \c n, the second cluster |
113 | | * <code>n + 1</code>, etc. |
114 | | * \param modularity Pointer to a vector. If not \c NULL then the |
115 | | * modularity score of the current clustering is stored here after |
116 | | * each merge operation. |
117 | | * \param membership Pointer to a vector. If not a \c NULL pointer, then |
118 | | * the membership vector corresponding to the maximal modularity |
119 | | * score is stored here. |
120 | | * \return Error code. |
121 | | * |
122 | | * \sa \ref igraph_community_spinglass(), \ref |
123 | | * igraph_community_edge_betweenness(). |
124 | | * |
125 | | * Time complexity: O(|E||V|^2) in the worst case, O(|V|^2 log|V|) typically, |
126 | | * |V| is the number of vertices, |E| is the number of edges. |
127 | | * |
128 | | * \example examples/simple/walktrap.c |
129 | | */ |
130 | | |
131 | | igraph_error_t igraph_community_walktrap(const igraph_t *graph, |
132 | | const igraph_vector_t *weights, |
133 | | igraph_int_t steps, |
134 | | igraph_matrix_int_t *merges, |
135 | | igraph_vector_t *modularity, |
136 | 6.93k | igraph_vector_int_t *membership) { |
137 | | |
138 | 6.93k | igraph_int_t no_of_nodes = igraph_vcount(graph); |
139 | 6.93k | igraph_int_t no_of_edges = igraph_ecount(graph); |
140 | 6.93k | igraph_int_t comp_count; |
141 | 6.93k | igraph_matrix_int_t imerges, *pmerges = merges; |
142 | 6.93k | igraph_vector_t imodularity, *pmodularity = modularity; |
143 | | |
144 | 6.93k | if (steps <= 0) { |
145 | 0 | IGRAPH_ERROR("Length of random walks must be positive for walktrap community detection.", IGRAPH_EINVAL); |
146 | 0 | } |
147 | | |
148 | 6.93k | if (steps > INT_MAX) { |
149 | 0 | IGRAPH_ERROR("Length of random walks too large for walktrap community detection.", IGRAPH_EINVAL); |
150 | 0 | } |
151 | | |
152 | 6.93k | int length = steps; |
153 | | |
154 | 6.93k | if (weights) { |
155 | 3.51k | if (igraph_vector_size(weights) != no_of_edges) { |
156 | 0 | IGRAPH_ERROR("Invalid weight vector length.", IGRAPH_EINVAL); |
157 | 0 | } |
158 | | |
159 | 3.51k | if (no_of_edges > 0) { |
160 | 3.47k | igraph_real_t minweight = igraph_vector_min(weights); |
161 | 3.47k | if (minweight < 0) { |
162 | 0 | IGRAPH_ERROR("Weight vector must be non-negative.", IGRAPH_EINVAL); |
163 | 3.47k | } else if (isnan(minweight)) { |
164 | 0 | IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL); |
165 | 0 | } |
166 | 3.47k | } |
167 | 3.51k | } |
168 | | |
169 | 6.93k | if (membership) { |
170 | | /* We need both 'modularity' and 'merges' to compute 'membership'. |
171 | | * If they were not provided by the called, we allocate these here. */ |
172 | | |
173 | 6.93k | if (! modularity) { |
174 | 0 | IGRAPH_VECTOR_INIT_FINALLY(&imodularity, 0); |
175 | 0 | pmodularity = &imodularity; |
176 | 0 | } |
177 | | |
178 | 6.93k | if (! merges) { |
179 | 0 | IGRAPH_MATRIX_INT_INIT_FINALLY(&imerges, 0, 0); |
180 | 0 | pmerges = &imerges; |
181 | 0 | } |
182 | 6.93k | } |
183 | | |
184 | 13.8k | IGRAPH_HANDLE_EXCEPTIONS( |
185 | 13.8k | Graph G; |
186 | 13.8k | IGRAPH_CHECK(G.convert_from_igraph(graph, weights)); |
187 | | |
188 | 13.8k | if (pmerges || pmodularity) { |
189 | 13.8k | IGRAPH_CHECK(igraph_connected_components(graph, /*membership=*/ NULL, /*csize=*/ NULL, |
190 | 13.8k | &comp_count, IGRAPH_WEAK)); |
191 | 13.8k | } |
192 | 13.8k | if (pmerges) { |
193 | 13.8k | IGRAPH_CHECK(igraph_matrix_int_resize(pmerges, no_of_nodes - comp_count, 2)); |
194 | 13.8k | } |
195 | 13.8k | if (pmodularity) { |
196 | 13.8k | IGRAPH_CHECK(igraph_vector_resize(pmodularity, no_of_nodes - comp_count + 1)); |
197 | 13.8k | igraph_vector_null(pmodularity); |
198 | 13.8k | } |
199 | 13.8k | Communities C(&G, length, pmerges, pmodularity); |
200 | | |
201 | 13.8k | while (!C.H->is_empty()) { |
202 | 13.8k | IGRAPH_ALLOW_INTERRUPTION(); |
203 | 13.8k | C.merge_nearest_communities(); |
204 | 13.8k | } |
205 | 13.8k | ); |
206 | | |
207 | 13.8k | if (membership) { |
208 | 6.93k | igraph_int_t m; |
209 | 6.93k | m = no_of_nodes > 0 ? igraph_vector_which_max(pmodularity) : 0; |
210 | 6.93k | IGRAPH_CHECK(igraph_community_to_membership(pmerges, no_of_nodes, |
211 | 6.93k | /*steps=*/ m, |
212 | 6.93k | membership, |
213 | 6.93k | /*csize=*/ NULL)); |
214 | | |
215 | 6.93k | if (! merges) { |
216 | 0 | igraph_matrix_int_destroy(&imerges); |
217 | 0 | IGRAPH_FINALLY_CLEAN(1); |
218 | 0 | } |
219 | 6.93k | if (! modularity) { |
220 | 0 | igraph_vector_destroy(&imodularity); |
221 | 0 | IGRAPH_FINALLY_CLEAN(1); |
222 | 0 | } |
223 | 6.93k | } |
224 | | |
225 | | /* The walktrap implementation cannot work with NaN values internally, |
226 | | * and produces 0 for the modularity of edgeless graphs. We correct |
227 | | * this to NaN in the last step for consistency. */ |
228 | 6.93k | if (modularity && no_of_edges == 0) { |
229 | 84 | VECTOR(*modularity)[0] = IGRAPH_NAN; |
230 | 84 | } |
231 | | |
232 | 6.93k | return IGRAPH_SUCCESS; |
233 | 13.8k | } |