Coverage Report

Created: 2026-08-31 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}