Coverage Report

Created: 2025-10-12 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/igraph/src/community/fast_modularity.c
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
#include "igraph_community.h"
24
25
#include "igraph_memory.h"
26
#include "igraph_iterators.h"
27
#include "igraph_interface.h"
28
#include "igraph_progress.h"
29
#include "igraph_structural.h"
30
#include "igraph_vector_ptr.h"
31
32
#include "core/interruption.h"
33
34
/* #define IGRAPH_FASTCOMM_DEBUG */
35
36
#ifdef IGRAPH_FASTCOMM_DEBUG
37
    #define debug(...) fprintf(stderr, __VA_ARGS__)
38
#else
39
    #define debug(...)
40
#endif
41
42
/*
43
 * Implementation of the community structure algorithm originally published
44
 * by Clauset et al in:
45
 *
46
 * A. Clauset, M.E.J. Newman and C. Moore, "Finding community structure in
47
 * very large networks.". Phys. Rev. E 70, 066111 (2004).
48
 *
49
 * The data structures being used are slightly different and they are described
50
 * most closely in:
51
 *
52
 * K. Wakita, T. Tsurumi, "Finding community structure in mega-scale social
53
 * networks.". arXiv:cs/0702048v1.
54
 *
55
 * We maintain a vector of communities, each of which containing a list of
56
 * pointers to their neighboring communities along with the increase in the
57
 * modularity score that could be achieved by joining the two communities.
58
 * Each community has a pointer to one of its neighbors - the one which would
59
 * result in the highest increase in modularity after a join. The local
60
 * (community-level) maximums are also stored in an indexed max-heap. The
61
 * max-heap itself stores its elements in an array which satisfies the heap
62
 * property, but to allow us to access any of the elements in the array based
63
 * on the community index (and not based on the array index - which depends on
64
 * the element's actual position in the heap), we also maintain an index
65
 * vector in the heap: the ith element of the index vector contains the
66
 * position of community i in the array of the max-heap. When we perform
67
 * sifting operations on the heap to restore the heap property, we also maintain
68
 * the index vector.
69
 */
70
71
/* Structure storing a pair of communities along with their dQ values */
72
typedef struct s_igraph_i_fastgreedy_commpair {
73
    igraph_int_t first;  /* first member of the community pair */
74
    igraph_int_t second; /* second member of the community pair */
75
    igraph_real_t *dq;    /* pointer to a member of the dq vector storing the */
76
    /* increase in modularity achieved when joining */
77
    struct s_igraph_i_fastgreedy_commpair *opposite;
78
} igraph_i_fastgreedy_commpair;
79
80
/* Structure storing a community */
81
typedef struct {
82
    igraph_int_t id;      /* Identifier of the community (for merges matrix) */
83
    igraph_int_t size;    /* Size of the community */
84
    igraph_vector_ptr_t neis; /* references to neighboring communities */
85
    igraph_i_fastgreedy_commpair *maxdq; /* community pair with maximal dq */
86
} igraph_i_fastgreedy_community;
87
88
/* Global community list structure */
89
typedef struct {
90
    igraph_int_t no_of_communities, n; /* number of communities, number of vertices */
91
    igraph_i_fastgreedy_community *e;     /* list of communities */
92
    igraph_i_fastgreedy_community **heap; /* heap of communities */
93
    igraph_int_t *heapindex; /* heap index to speed up lookup by community idx */
94
} igraph_i_fastgreedy_community_list;
95
96
/* Scans the community neighborhood list for the new maximal dq value.
97
 * Returns true if the maximum is different from the previous one,
98
 * false otherwise. */
99
476k
static igraph_bool_t igraph_i_fastgreedy_community_rescan_max(igraph_i_fastgreedy_community *comm) {
100
476k
    igraph_int_t i, n;
101
476k
    igraph_i_fastgreedy_commpair *p, *best;
102
476k
    igraph_real_t bestdq, currdq;
103
104
476k
    n = igraph_vector_ptr_size(&comm->neis);
105
476k
    if (n == 0) {
106
6.27k
        comm->maxdq = NULL;
107
6.27k
        return true;
108
6.27k
    }
109
110
470k
    best = (igraph_i_fastgreedy_commpair*)VECTOR(comm->neis)[0];
111
470k
    bestdq = *best->dq;
112
2.12M
    for (i = 1; i < n; i++) {
113
1.65M
        p = (igraph_i_fastgreedy_commpair*)VECTOR(comm->neis)[i];
114
1.65M
        currdq = *p->dq;
115
1.65M
        if (currdq > bestdq) {
116
178k
            best = p;
117
178k
            bestdq = currdq;
118
178k
        }
119
1.65M
    }
120
121
470k
    if (best != comm->maxdq) {
122
159k
        comm->maxdq = best;
123
159k
        return true;
124
311k
    } else {
125
311k
        return false;
126
311k
    }
127
470k
}
128
129
/* Destroys the global community list object */
130
static void igraph_i_fastgreedy_community_list_destroy(
131
6.54k
        igraph_i_fastgreedy_community_list *list) {
132
6.54k
    igraph_int_t i;
133
308k
    for (i = 0; i < list->n; i++) {
134
301k
        igraph_vector_ptr_destroy(&list->e[i].neis);
135
301k
    }
136
6.54k
    IGRAPH_FREE(list->e);
137
6.54k
    if (list->heapindex != NULL) {
138
6.54k
        IGRAPH_FREE(list->heapindex);
139
6.54k
    }
140
6.54k
    if (list->heap != NULL) {
141
6.54k
        IGRAPH_FREE(list->heap);
142
6.54k
    }
143
6.54k
}
144
145
/* Community list heap maintenance: sift down */
146
static void igraph_i_fastgreedy_community_list_sift_down(
147
650k
        igraph_i_fastgreedy_community_list *list, igraph_int_t idx) {
148
650k
    igraph_int_t root, child, c1, c2;
149
650k
    igraph_i_fastgreedy_community *dummy;
150
650k
    igraph_int_t dummy2;
151
650k
    igraph_i_fastgreedy_community** heap = list->heap;
152
650k
    igraph_int_t *heapindex = list->heapindex;
153
154
650k
    root = idx;
155
974k
    while (root * 2 + 1 < list->no_of_communities) {
156
653k
        child = root * 2 + 1;
157
653k
        if (child + 1 < list->no_of_communities &&
158
611k
            *heap[child]->maxdq->dq < *heap[child + 1]->maxdq->dq) {
159
189k
            child++;
160
189k
        }
161
653k
        if (*heap[root]->maxdq->dq < *heap[child]->maxdq->dq) {
162
324k
            c1 = heap[root]->maxdq->first;
163
324k
            c2 = heap[child]->maxdq->first;
164
165
324k
            dummy = heap[root];
166
324k
            heap[root] = heap[child];
167
324k
            heap[child] = dummy;
168
169
324k
            dummy2 = heapindex[c1];
170
324k
            heapindex[c1] = heapindex[c2];
171
324k
            heapindex[c2] = dummy2;
172
173
324k
            root = child;
174
329k
        } else {
175
329k
            break;
176
329k
        }
177
653k
    }
178
650k
}
179
180
/* Community list heap maintenance: sift up */
181
static void igraph_i_fastgreedy_community_list_sift_up(
182
11.6k
        igraph_i_fastgreedy_community_list *list, igraph_int_t idx) {
183
11.6k
    igraph_int_t root, parent, c1, c2;
184
11.6k
    igraph_i_fastgreedy_community *dummy;
185
11.6k
    igraph_int_t dummy2;
186
11.6k
    igraph_i_fastgreedy_community** heap = list->heap;
187
11.6k
    igraph_int_t *heapindex = list->heapindex;
188
189
11.6k
    root = idx;
190
23.7k
    while (root > 0) {
191
18.6k
        parent = (root - 1) / 2;
192
18.6k
        if (*heap[parent]->maxdq->dq < *heap[root]->maxdq->dq) {
193
12.1k
            c1 = heap[root]->maxdq->first;
194
12.1k
            c2 = heap[parent]->maxdq->first;
195
196
12.1k
            dummy = heap[parent];
197
12.1k
            heap[parent] = heap[root];
198
12.1k
            heap[root] = dummy;
199
200
12.1k
            dummy2 = heapindex[c1];
201
12.1k
            heapindex[c1] = heapindex[c2];
202
12.1k
            heapindex[c2] = dummy2;
203
204
12.1k
            root = parent;
205
12.1k
        } else {
206
6.52k
            break;
207
6.52k
        }
208
18.6k
    }
209
11.6k
}
210
211
/* Builds the community heap for the first time */
212
static void igraph_i_fastgreedy_community_list_build_heap(
213
6.54k
        igraph_i_fastgreedy_community_list *list) {
214
6.54k
    igraph_int_t i;
215
54.8k
    for (i = list->no_of_communities / 2 - 1; i >= 0; i--) {
216
48.2k
        igraph_i_fastgreedy_community_list_sift_down(list, i);
217
48.2k
    }
218
6.54k
}
219
220
/* Finds the element belonging to a given community in the heap and return its
221
 * index in the heap array */
222
566k
#define igraph_i_fastgreedy_community_list_find_in_heap(list, idx) (list)->heapindex[idx]
223
224
/* Dumps the heap - for debugging purposes */
225
/*
226
static void igraph_i_fastgreedy_community_list_dump_heap(
227
        igraph_i_fastgreedy_community_list *list) {
228
    igraph_int_t i;
229
    debug("Heap:\n");
230
    for (i = 0; i < list->no_of_communities; i++) {
231
        debug("(%ld, %p, %p)", i, list->heap[i],
232
              list->heap[i]->maxdq);
233
        if (list->heap[i]->maxdq) {
234
            debug(" (%" IGRAPH_PRId ", %" IGRAPH_PRId ", %.7f)", list->heap[i]->maxdq->first,
235
                  list->heap[i]->maxdq->second, *list->heap[i]->maxdq->dq);
236
        }
237
        debug("\n");
238
    }
239
    debug("Heap index:\n");
240
    for (i = 0; i < list->no_of_communities; i++) {
241
        debug("%" IGRAPH_PRId " ", list->heapindex[i]);
242
    }
243
    debug("\nEND\n");
244
}
245
*/
246
247
/* Checks if the community heap satisfies the heap property.
248
 * Only useful for debugging. */
249
/*
250
static void igraph_i_fastgreedy_community_list_check_heap(
251
        igraph_i_fastgreedy_community_list *list) {
252
    igraph_int_t i;
253
    for (i = 0; i < list->no_of_communities / 2; i++) {
254
        if ((2 * i + 1 < list->no_of_communities && *list->heap[i]->maxdq->dq < *list->heap[2 * i + 1]->maxdq->dq) ||
255
            (2 * i + 2 < list->no_of_communities && *list->heap[i]->maxdq->dq < *list->heap[2 * i + 2]->maxdq->dq)) {
256
            IGRAPH_WARNING("Heap property violated");
257
            debug("Position: %" IGRAPH_PRId ", %" IGRAPH_PRId " and %" IGRAPH_PRId "\n", i, 2 * i + 1, 2 * i + 2);
258
            igraph_i_fastgreedy_community_list_dump_heap(list);
259
        }
260
    }
261
}
262
*/
263
264
/* Removes a given element from the heap */
265
static void igraph_i_fastgreedy_community_list_remove(
266
80.4k
        igraph_i_fastgreedy_community_list *list, igraph_int_t idx) {
267
80.4k
    igraph_real_t old;
268
80.4k
    igraph_int_t commidx;
269
270
    /* First adjust the index */
271
80.4k
    commidx = list->heap[list->no_of_communities - 1]->maxdq->first;
272
80.4k
    list->heapindex[commidx] = idx;
273
80.4k
    commidx = list->heap[idx]->maxdq->first;
274
80.4k
    list->heapindex[commidx] = -1;
275
276
    /* Now remove the element */
277
80.4k
    old = *list->heap[idx]->maxdq->dq;
278
80.4k
    list->heap[idx] = list->heap[list->no_of_communities - 1];
279
80.4k
    list->no_of_communities--;
280
281
    /* Recover heap property */
282
80.4k
    if (old > *list->heap[idx]->maxdq->dq) {
283
78.7k
        igraph_i_fastgreedy_community_list_sift_down(list, idx);
284
78.7k
    } else {
285
1.75k
        igraph_i_fastgreedy_community_list_sift_up(list, idx);
286
1.75k
    }
287
80.4k
}
288
289
/* Removes a given element from the heap when there are no more neighbors
290
 * for it (comm->maxdq is NULL) */
291
static void igraph_i_fastgreedy_community_list_remove2(
292
6.27k
        igraph_i_fastgreedy_community_list *list, igraph_int_t idx, igraph_int_t comm) {
293
6.27k
    igraph_int_t i;
294
295
6.27k
    if (idx == list->no_of_communities - 1) {
296
        /* We removed the rightmost element on the bottom level, no problem,
297
         * there's nothing to be done */
298
201
        list->heapindex[comm] = -1;
299
201
        list->no_of_communities--;
300
201
        return;
301
201
    }
302
303
    /* First adjust the index */
304
6.07k
    i = list->heap[list->no_of_communities - 1]->maxdq->first;
305
6.07k
    list->heapindex[i] = idx;
306
6.07k
    list->heapindex[comm] = -1;
307
308
    /* Now remove the element */
309
6.07k
    list->heap[idx] = list->heap[list->no_of_communities - 1];
310
6.07k
    list->no_of_communities--;
311
312
    /* Recover heap property */
313
59.4k
    for (i = list->no_of_communities / 2 - 1; i >= 0; i--) {
314
53.3k
        igraph_i_fastgreedy_community_list_sift_down(list, i);
315
53.3k
    }
316
6.07k
}
317
318
/* Removes the pair belonging to community k from the neighborhood list
319
 * of community c (that is, clist[c]) and recalculates maxdq */
320
static void igraph_i_fastgreedy_community_remove_nei(
321
111k
        igraph_i_fastgreedy_community_list *list, igraph_int_t c, igraph_int_t k) {
322
111k
    igraph_int_t i, n;
323
111k
    igraph_bool_t rescan = false;
324
111k
    igraph_i_fastgreedy_commpair *p;
325
111k
    igraph_i_fastgreedy_community *comm;
326
111k
    igraph_real_t olddq;
327
328
111k
    comm = &list->e[c];
329
111k
    n = igraph_vector_ptr_size(&comm->neis);
330
451k
    for (i = 0; i < n; i++) {
331
451k
        p = (igraph_i_fastgreedy_commpair*)VECTOR(comm->neis)[i];
332
451k
        if (p->second == k) {
333
            /* Check current maxdq */
334
111k
            if (comm->maxdq == p) {
335
83.0k
                rescan = true;
336
83.0k
            }
337
111k
            break;
338
111k
        }
339
451k
    }
340
111k
    if (i < n) {
341
111k
        olddq = *comm->maxdq->dq;
342
111k
        igraph_vector_ptr_remove(&comm->neis, i);
343
111k
        if (rescan) {
344
83.0k
            igraph_i_fastgreedy_community_rescan_max(comm);
345
83.0k
            i = igraph_i_fastgreedy_community_list_find_in_heap(list, c);
346
83.0k
            if (comm->maxdq) {
347
76.7k
                if (*comm->maxdq->dq > olddq) {
348
908
                    igraph_i_fastgreedy_community_list_sift_up(list, i);
349
75.8k
                } else {
350
75.8k
                    igraph_i_fastgreedy_community_list_sift_down(list, i);
351
75.8k
                }
352
76.7k
            } else {
353
                /* no more neighbors for this community. we should remove this
354
                 * community from the heap and restore the heap property */
355
6.27k
                debug("REMOVING (NO MORE NEIS): %" IGRAPH_PRId "\n", i);
356
6.27k
                igraph_i_fastgreedy_community_list_remove2(list, i, c);
357
6.27k
            }
358
83.0k
        }
359
111k
    }
360
111k
}
361
362
/* Auxiliary function to sort a community pair list with respect to the
363
 * `second` field */
364
265k
static int igraph_i_fastgreedy_commpair_cmp(const void *p1, const void *p2) {
365
265k
    igraph_i_fastgreedy_commpair *cp1, *cp2;
366
265k
    igraph_int_t diff;
367
265k
    cp1 = *(igraph_i_fastgreedy_commpair**)p1;
368
265k
    cp2 = *(igraph_i_fastgreedy_commpair**)p2;
369
265k
    diff = cp1->second - cp2->second;
370
265k
    return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
371
265k
}
372
373
/* Sorts the neighbor list of the community with the given index, optionally
374
 * optimizing the process if we know that the list is nearly sorted and only
375
 * a given pair is in the wrong place. */
376
static void igraph_i_fastgreedy_community_sort_neighbors_of(
377
        igraph_i_fastgreedy_community_list *list, igraph_int_t index,
378
471k
        igraph_i_fastgreedy_commpair *changed_pair) {
379
471k
    igraph_vector_ptr_t *vec;
380
471k
    igraph_int_t i, n;
381
471k
    igraph_bool_t can_skip_sort = false;
382
471k
    igraph_i_fastgreedy_commpair *other_pair;
383
384
471k
    vec = &list->e[index].neis;
385
471k
    if (changed_pair != NULL) {
386
        /* Optimized sorting */
387
388
        /* First we look for changed_pair in vec */
389
170k
        n = igraph_vector_ptr_size(vec);
390
359k
        for (i = 0; i < n; i++) {
391
359k
            if (VECTOR(*vec)[i] == changed_pair) {
392
170k
                break;
393
170k
            }
394
359k
        }
395
396
        /* Did we find it? We should have -- otherwise it's a bug */
397
170k
        IGRAPH_ASSERT(i < n);
398
399
        /* Okay, the pair that changed is at index i. We need to figure out where
400
         * its new place should be. We can simply try moving the item all the way
401
         * to the left as long as the comparison function tells so (since the
402
         * rest of the vector is sorted), and then move all the way to the right
403
         * as long as the comparison function tells so, and we will be okay. */
404
405
        /* Shifting to the left */
406
223k
        while (i > 0) {
407
106k
            other_pair = VECTOR(*vec)[i - 1];
408
106k
            if (other_pair->second > changed_pair->second) {
409
52.9k
                VECTOR(*vec)[i] = other_pair;
410
52.9k
                i--;
411
53.2k
            } else {
412
53.2k
                break;
413
53.2k
            }
414
106k
        }
415
170k
        VECTOR(*vec)[i] = changed_pair;
416
417
        /* Shifting to the right */
418
242k
        while (i < n - 1) {
419
137k
            other_pair = VECTOR(*vec)[i + 1];
420
137k
            if (other_pair->second < changed_pair->second) {
421
72.7k
                VECTOR(*vec)[i] = other_pair;
422
72.7k
                i++;
423
72.7k
            } else {
424
65.1k
                break;
425
65.1k
            }
426
137k
        }
427
170k
        VECTOR(*vec)[i] = changed_pair;
428
429
        /* Mark that we don't need a full sort */
430
170k
        can_skip_sort = true;
431
170k
    }
432
433
471k
    if (!can_skip_sort) {
434
        /* Fallback to full sorting */
435
301k
        igraph_vector_ptr_sort(vec, igraph_i_fastgreedy_commpair_cmp);
436
301k
    }
437
471k
}
438
439
/* Updates the dq value of community pair p in the community with index p->first
440
 * of the community list clist to newdq and restores the heap property
441
 * in community c if necessary. Returns 1 if the maximum in the row had
442
 * to be updated, zero otherwise */
443
static igraph_bool_t igraph_i_fastgreedy_community_update_dq(
444
        igraph_i_fastgreedy_community_list *list,
445
503k
        igraph_i_fastgreedy_commpair *p, igraph_real_t newdq) {
446
447
503k
    igraph_int_t i, j, to, from;
448
503k
    igraph_real_t olddq;
449
503k
    igraph_i_fastgreedy_community *comm_to, *comm_from;
450
503k
    to = p->first; from = p->second;
451
503k
    comm_to = &list->e[to];
452
503k
    comm_from = &list->e[from];
453
503k
    if (comm_to->maxdq == p && newdq >= *p->dq) {
454
        /* If we are adjusting the current maximum and it is increased, we don't
455
         * have to re-scan for the new maximum */
456
420
        *p->dq = newdq;
457
        /* The maximum was increased, so perform a sift-up in the heap */
458
420
        i = igraph_i_fastgreedy_community_list_find_in_heap(list, to);
459
420
        igraph_i_fastgreedy_community_list_sift_up(list, i);
460
        /* Let's check the opposite side. If the pair was not the maximal in
461
         * the opposite side (the other community list)... */
462
420
        if (comm_from->maxdq != p->opposite) {
463
164
            if (*comm_from->maxdq->dq < newdq) {
464
                /* ...and it will become the maximal, we need to adjust and sift up */
465
143
                comm_from->maxdq = p->opposite;
466
143
                j = igraph_i_fastgreedy_community_list_find_in_heap(list, from);
467
143
                igraph_i_fastgreedy_community_list_sift_up(list, j);
468
143
            } else {
469
                /* The pair was not the maximal in the opposite side and it will
470
                 * NOT become the maximal, there's nothing to do there */
471
21
            }
472
256
        } else {
473
            /* The pair was maximal in the opposite side, so we need to sift it up
474
             * with the new value */
475
256
            j = igraph_i_fastgreedy_community_list_find_in_heap(list, from);
476
256
            igraph_i_fastgreedy_community_list_sift_up(list, j);
477
256
        }
478
420
        return true;
479
502k
    } else if (comm_to->maxdq != p && (newdq <= *comm_to->maxdq->dq)) {
480
        /* If we are modifying an item which is not the current maximum, and the
481
         * new value is less than the current maximum, we don't
482
         * have to re-scan for the new maximum */
483
442k
        olddq = *p->dq;
484
442k
        *p->dq = newdq;
485
        /* However, if the item was the maximum on the opposite side, we'd better
486
         * re-scan it */
487
442k
        if (comm_from->maxdq == p->opposite) {
488
281k
            if (olddq > newdq) {
489
                /* Decreased the maximum on the other side, we have to re-scan for the
490
                 * new maximum */
491
280k
                igraph_i_fastgreedy_community_rescan_max(comm_from);
492
280k
                j = igraph_i_fastgreedy_community_list_find_in_heap(list, from);
493
280k
                igraph_i_fastgreedy_community_list_sift_down(list, j);
494
280k
            } else {
495
                /* Increased the maximum on the other side, we don't have to re-scan
496
                 * but we might have to sift up */
497
951
                j = igraph_i_fastgreedy_community_list_find_in_heap(list, from);
498
951
                igraph_i_fastgreedy_community_list_sift_up(list, j);
499
951
            }
500
281k
        }
501
442k
        return false;
502
442k
    } else {
503
        /* We got here in two cases:
504
         (1) the pair we are modifying right now is the maximum in the given
505
             community and we are decreasing it
506
         (2) the pair we are modifying right now is NOT the maximum in the
507
             given community, but we increase it so much that it will become
508
             the new maximum
509
         */
510
60.5k
        *p->dq = newdq;
511
60.5k
        if (comm_to->maxdq != p) {
512
            /* case (2) */
513
3.44k
            comm_to->maxdq = p;
514
            /* The maximum was increased, so perform a sift-up in the heap */
515
3.44k
            i = igraph_i_fastgreedy_community_list_find_in_heap(list, to);
516
3.44k
            igraph_i_fastgreedy_community_list_sift_up(list, i);
517
            /* Opposite side. Chances are that the new value became the maximum
518
             * in the opposite side, but check it first */
519
3.44k
            if (comm_from->maxdq != p->opposite) {
520
2.32k
                if (*comm_from->maxdq->dq < newdq) {
521
                    /* Yes, it will become the new maximum */
522
2.28k
                    comm_from->maxdq = p->opposite;
523
2.28k
                    j = igraph_i_fastgreedy_community_list_find_in_heap(list, from);
524
2.28k
                    igraph_i_fastgreedy_community_list_sift_up(list, j);
525
2.28k
                } else {
526
                    /* No, nothing to do there */
527
38
                }
528
2.32k
            } else {
529
                /* Already increased the maximum on the opposite side, so sift it up */
530
1.12k
                j = igraph_i_fastgreedy_community_list_find_in_heap(list, from);
531
1.12k
                igraph_i_fastgreedy_community_list_sift_up(list, j);
532
1.12k
            }
533
57.1k
        } else {
534
            /* case (1) */
535
            /* This is the worst, we have to re-scan the whole community to find
536
             * the new maximum and update the global maximum as well if necessary */
537
57.1k
            igraph_i_fastgreedy_community_rescan_max(comm_to);
538
            /* The maximum was decreased, so perform a sift-down in the heap */
539
57.1k
            i = igraph_i_fastgreedy_community_list_find_in_heap(list, to);
540
57.1k
            igraph_i_fastgreedy_community_list_sift_down(list, i);
541
57.1k
            if (comm_from->maxdq != p->opposite) {
542
                /* The one that we decreased on the opposite side is not the
543
                 * maximal one. Nothing to do. */
544
56.5k
            } else {
545
                /* We decreased the maximal on the opposite side as well. Re-scan
546
                 * and sift down */
547
56.5k
                igraph_i_fastgreedy_community_rescan_max(comm_from);
548
56.5k
                j = igraph_i_fastgreedy_community_list_find_in_heap(list, from);
549
56.5k
                igraph_i_fastgreedy_community_list_sift_down(list, j);
550
56.5k
            }
551
57.1k
        }
552
60.5k
    }
553
60.5k
    return true;
554
503k
}
555
556
/**
557
 * \function igraph_community_fastgreedy
558
 * \brief Finding community structure by greedy optimization of modularity.
559
 *
560
 * This function implements the fast greedy modularity optimization
561
 * algorithm for finding community structure, see
562
 * A Clauset, MEJ Newman, C Moore: Finding community structure in very
563
 * large networks, http://www.arxiv.org/abs/cond-mat/0408187 for the
564
 * details.
565
 *
566
 * </para><para>
567
 * Some improvements proposed in K Wakita, T Tsurumi: Finding community
568
 * structure in mega-scale social networks,
569
 * http://www.arxiv.org/abs/cs.CY/0702048v1 have also been implemented.
570
 *
571
 * \param graph The input graph. It must be a graph without multiple edges.
572
 *    This is checked and an error message is given for graphs with multiple
573
 *    edges.
574
 * \param weights Potentially a numeric vector containing edge
575
 *    weights. Supply a null pointer here for unweighted graphs. The
576
 *    weights are expected to be non-negative.
577
 * \param merges Pointer to an initialized matrix or \c NULL, the result of the
578
 *    computation is stored here as a merges matrix representing a dendrogram.
579
 *    The matrix has two columns and each merge corresponds to one merge, the
580
 *    IDs of the two merged components are stored. The component IDs are numbered
581
 *    from zero and the first \c n components are the individual vertices, \c n is
582
 *    the number of vertices in the graph. Component \c n is created
583
 *    in the first merge, component <code>n+1</code> in the second merge, etc.
584
 *    The matrix will be resized as needed. If this argument is \c NULL
585
 *    then it is ignored completely.
586
 * \param modularity Pointer to an initialized vector or \c NULL pointer,
587
 *    in the former case the modularity scores along the stages of the
588
 *    computation are recorded here. The vector will be resized as
589
 *    needed.
590
 * \param membership Pointer to a vector. If not a null pointer, then
591
 *    the membership vector corresponding to the best split (in terms
592
 *    of modularity) is stored here.
593
 * \return Error code.
594
 *
595
 * \sa \ref igraph_community_to_membership() to cut the dendrogram at
596
 * an arbitrary number of steps.
597
 *
598
 * Time complexity: O(|E||V|log|V|) in the worst case,
599
 * O(|E|+|V|log^2|V|) typically, |V| is the number of vertices, |E| is
600
 * the number of edges.
601
 *
602
 * \example examples/simple/igraph_community_fastgreedy.c
603
 */
604
igraph_error_t igraph_community_fastgreedy(const igraph_t *graph,
605
                                const igraph_vector_t *weights,
606
                                igraph_matrix_int_t *merges,
607
                                igraph_vector_t *modularity,
608
6.54k
                                igraph_vector_int_t *membership) {
609
6.54k
    igraph_int_t no_of_edges, no_of_nodes, no_of_joins, total_joins;
610
6.54k
    igraph_int_t i, j, k, n, m, from, to, dummy, best_no_of_joins;
611
6.54k
    igraph_eit_t edgeit;
612
6.54k
    igraph_i_fastgreedy_commpair *pairs, *p1, *p2;
613
6.54k
    igraph_i_fastgreedy_community_list communities;
614
6.54k
    igraph_vector_t a;
615
6.54k
    igraph_vector_int_t degrees;
616
6.54k
    igraph_real_t q, *dq, bestq, weight_sum, loop_weight_sum;
617
6.54k
    igraph_bool_t has_multiple;
618
6.54k
    igraph_matrix_int_t merges_local;
619
620
    /*igraph_int_t join_order[] = { 16,5, 5,6, 6,0, 4,0, 10,0, 26,29, 29,33, 23,33, 27,33, 25,24, 24,31, 12,3, 21,1, 30,8, 8,32, 9,2, 17,1, 11,0, 7,3, 3,2, 13,2, 1,2, 28,31, 31,33, 22,32, 18,32, 20,32, 32,33, 15,33, 14,33, 0,19, 19,2, -1,-1 };*/
621
    /*igraph_int_t join_order[] = { 43,42, 42,41, 44,41, 41,36, 35,36, 37,36, 36,29, 38,29, 34,29, 39,29, 33,29, 40,29, 32,29, 14,29, 30,29, 31,29, 6,18, 18,4, 23,4, 21,4, 19,4, 27,4, 20,4, 22,4, 26,4, 25,4, 24,4, 17,4, 0,13, 13,2, 1,2, 11,2, 8,2, 5,2, 3,2, 10,2, 9,2, 7,2, 2,28, 28,15, 12,15, 29,16, 4,15, -1,-1 };*/
622
623
6.54k
    no_of_nodes = igraph_vcount(graph);
624
6.54k
    no_of_edges = igraph_ecount(graph);
625
626
6.54k
    if (igraph_is_directed(graph)) {
627
0
        IGRAPH_ERROR("Fast greedy community detection works on undirected graphs only.", IGRAPH_UNIMPLEMENTED);
628
0
    }
629
630
6.54k
    total_joins = no_of_nodes > 0 ? no_of_nodes - 1 : 0;
631
632
6.54k
    if (weights) {
633
3.29k
        if (igraph_vector_size(weights) != no_of_edges) {
634
0
            IGRAPH_ERROR("Length of weight vector must agree with number of edges.", IGRAPH_EINVAL);
635
0
        }
636
3.29k
        if (no_of_edges > 0) {
637
3.21k
            igraph_real_t minweight = igraph_vector_min(weights);
638
3.21k
            if (minweight < 0) {
639
0
                IGRAPH_ERROR("Weights must not be negative.", IGRAPH_EINVAL);
640
0
            }
641
3.21k
            if (isnan(minweight)) {
642
0
                IGRAPH_ERROR("Weights must not be NaN.", IGRAPH_EINVAL);
643
0
            }
644
3.21k
        }
645
3.29k
        weight_sum = igraph_vector_sum(weights);
646
3.29k
    } else {
647
3.24k
        weight_sum = no_of_edges;
648
3.24k
    }
649
650
6.54k
    IGRAPH_CHECK(igraph_has_multiple(graph, &has_multiple));
651
6.54k
    if (has_multiple) {
652
0
        IGRAPH_ERROR("Fast greedy community detection works only on graphs without multi-edges.", IGRAPH_EINVAL);
653
0
    }
654
655
6.54k
    if (membership != NULL && merges == NULL) {
656
        /* We need the merge matrix because the user wants the membership
657
         * vector, so we allocate one on our own */
658
0
        IGRAPH_CHECK(igraph_matrix_int_init(&merges_local, total_joins, 2));
659
0
        IGRAPH_FINALLY(igraph_matrix_int_destroy, &merges_local);
660
0
        merges = &merges_local;
661
0
    }
662
663
6.54k
    if (merges != NULL) {
664
6.54k
        IGRAPH_CHECK(igraph_matrix_int_resize(merges, total_joins, 2));
665
6.54k
        igraph_matrix_int_null(merges);
666
6.54k
    }
667
668
6.54k
    if (modularity != NULL) {
669
6.54k
        IGRAPH_CHECK(igraph_vector_resize(modularity, total_joins + 1));
670
6.54k
    }
671
672
    /* Create degree vector */
673
6.54k
    IGRAPH_VECTOR_INIT_FINALLY(&a, no_of_nodes);
674
6.54k
    if (weights) {
675
3.29k
        debug("Calculating weighted degrees\n");
676
58.9k
        for (i = 0; i < no_of_edges; i++) {
677
55.6k
            VECTOR(a)[IGRAPH_FROM(graph, i)] += VECTOR(*weights)[i];
678
55.6k
            VECTOR(a)[IGRAPH_TO(graph, i)] += VECTOR(*weights)[i];
679
55.6k
        }
680
3.29k
    } else {
681
3.24k
        debug("Calculating degrees\n");
682
3.24k
        IGRAPH_VECTOR_INT_INIT_FINALLY(&degrees, no_of_nodes);
683
3.24k
        IGRAPH_CHECK(igraph_degree(graph, &degrees, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS));
684
157k
        for (i = 0; i < no_of_nodes; i++) {
685
154k
            VECTOR(a)[i] = VECTOR(degrees)[i];
686
154k
        }
687
3.24k
        igraph_vector_int_destroy(&degrees);
688
3.24k
        IGRAPH_FINALLY_CLEAN(1);
689
3.24k
    }
690
691
    /* Create list of communities */
692
6.54k
    debug("Creating community list\n");
693
6.54k
    communities.n = no_of_nodes;
694
6.54k
    communities.no_of_communities = no_of_nodes;
695
6.54k
    communities.e = IGRAPH_CALLOC(no_of_nodes, igraph_i_fastgreedy_community);
696
6.54k
    IGRAPH_CHECK_OOM(communities.e, "Insufficient memory for fast greedy community detection.");
697
6.54k
    IGRAPH_FINALLY(igraph_free, communities.e);
698
699
6.54k
    communities.heap = IGRAPH_CALLOC(no_of_nodes, igraph_i_fastgreedy_community*);
700
6.54k
    IGRAPH_CHECK_OOM(communities.heap, "Insufficient memory for fast greedy community detection.");
701
6.54k
    IGRAPH_FINALLY(igraph_free, communities.heap);
702
703
6.54k
    communities.heapindex = IGRAPH_CALLOC(no_of_nodes, igraph_int_t);
704
6.54k
    IGRAPH_CHECK_OOM(communities.heapindex, "Insufficient memory for fast greedy community detection.");
705
706
6.54k
    IGRAPH_FINALLY_CLEAN(2);
707
6.54k
    IGRAPH_FINALLY(igraph_i_fastgreedy_community_list_destroy, &communities);
708
709
308k
    for (i = 0; i < no_of_nodes; i++) {
710
301k
        IGRAPH_CHECK(igraph_vector_ptr_init(&communities.e[i].neis, 0));
711
301k
        communities.e[i].id = i;
712
301k
        communities.e[i].size = 1;
713
301k
    }
714
715
    /* Create list of community pairs from edges */
716
6.54k
    debug("Allocating dq vector\n");
717
6.54k
    dq = IGRAPH_CALLOC(no_of_edges, igraph_real_t);
718
6.54k
    IGRAPH_CHECK_OOM(dq, "Insufficient memory for fast greedy community detection.");
719
6.54k
    IGRAPH_FINALLY(igraph_free, dq);
720
721
6.54k
    debug("Creating community pair list\n");
722
6.54k
    IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_ID), &edgeit));
723
6.54k
    IGRAPH_FINALLY(igraph_eit_destroy, &edgeit);
724
6.54k
    pairs = IGRAPH_CALLOC(2 * no_of_edges, igraph_i_fastgreedy_commpair);
725
6.54k
    IGRAPH_CHECK_OOM(pairs, "Insufficient memory for fast greedy community detection.");
726
6.54k
    IGRAPH_FINALLY(igraph_free, pairs);
727
728
6.54k
    loop_weight_sum = 0;
729
124k
    for (i = 0, j = 0; !IGRAPH_EIT_END(edgeit); i += 2, j++, IGRAPH_EIT_NEXT(edgeit)) {
730
117k
        igraph_int_t eidx = IGRAPH_EIT_GET(edgeit);
731
732
        /* Create the pairs themselves */
733
117k
        from = IGRAPH_FROM(graph, eidx); to = IGRAPH_TO(graph, eidx);
734
117k
        if (from == to) {
735
0
            loop_weight_sum += weights ? 2 * VECTOR(*weights)[eidx] : 2;
736
0
            continue;
737
0
        }
738
739
117k
        if (from > to) {
740
117k
            dummy = from; from = to; to = dummy;
741
117k
        }
742
117k
        if (weights) {
743
55.6k
            dq[j] = 2 * (VECTOR(*weights)[eidx] / (weight_sum * 2.0) - VECTOR(a)[from] * VECTOR(a)[to] / (4.0 * weight_sum * weight_sum));
744
62.2k
        } else {
745
62.2k
            dq[j] = 2 * (1.0 / (no_of_edges * 2.0) - VECTOR(a)[from] * VECTOR(a)[to] / (4.0 * no_of_edges * no_of_edges));
746
62.2k
        }
747
117k
        pairs[i].first = from;
748
117k
        pairs[i].second = to;
749
117k
        pairs[i].dq = &dq[j];
750
117k
        pairs[i].opposite = &pairs[i + 1];
751
117k
        pairs[i + 1].first = to;
752
117k
        pairs[i + 1].second = from;
753
117k
        pairs[i + 1].dq = pairs[i].dq;
754
117k
        pairs[i + 1].opposite = &pairs[i];
755
        /* Link the pair to the communities */
756
117k
        IGRAPH_CHECK(igraph_vector_ptr_push_back(&communities.e[from].neis, &pairs[i]));
757
117k
        IGRAPH_CHECK(igraph_vector_ptr_push_back(&communities.e[to].neis, &pairs[i + 1]));
758
        /* Update maximums */
759
117k
        if (communities.e[from].maxdq == NULL || *communities.e[from].maxdq->dq < *pairs[i].dq) {
760
47.3k
            communities.e[from].maxdq = &pairs[i];
761
47.3k
        }
762
117k
        if (communities.e[to].maxdq == NULL || *communities.e[to].maxdq->dq < *pairs[i + 1].dq) {
763
92.3k
            communities.e[to].maxdq = &pairs[i + 1];
764
92.3k
        }
765
117k
    }
766
6.54k
    igraph_eit_destroy(&edgeit);
767
6.54k
    IGRAPH_FINALLY_CLEAN(1);
768
769
    /* Sorting community neighbor lists by community IDs */
770
6.54k
    debug("Sorting community neighbor lists\n");
771
308k
    for (i = 0, j = 0; i < no_of_nodes; i++) {
772
301k
        igraph_i_fastgreedy_community_sort_neighbors_of(&communities, i, NULL);
773
        /* Isolated vertices and vertices with loop edges only won't be stored in
774
         * the heap (to avoid maxdq == NULL) */
775
301k
        if (communities.e[i].maxdq != NULL) {
776
99.5k
            communities.heap[j] = &communities.e[i];
777
99.5k
            communities.heapindex[i] = j;
778
99.5k
            j++;
779
202k
        } else {
780
202k
            communities.heapindex[i] = -1;
781
202k
        }
782
301k
    }
783
6.54k
    communities.no_of_communities = j;
784
785
    /* Calculate proper vector a (see paper) and initial modularity */
786
6.54k
    q = 2.0 * (weights ? weight_sum : no_of_edges);
787
6.54k
    if (q == 0) {
788
        /* All the weights are zero */
789
6.37k
    } else {
790
6.37k
        igraph_vector_scale(&a, 1.0 / q);
791
6.37k
        q = loop_weight_sum / q;
792
303k
        for (i = 0; i < no_of_nodes; i++) {
793
296k
            q -= VECTOR(a)[i] * VECTOR(a)[i];
794
296k
        }
795
6.37k
    }
796
797
    /* Initialize "best modularity" value and best merge counter */
798
6.54k
    bestq = q;
799
6.54k
    best_no_of_joins = 0;
800
801
    /* Initializing community heap */
802
6.54k
    debug("Initializing community heap\n");
803
6.54k
    igraph_i_fastgreedy_community_list_build_heap(&communities);
804
805
6.54k
    debug("Initial modularity: %.4f\n", q);
806
807
    /* Let's rock ;) */
808
6.54k
    no_of_joins = 0;
809
93.4k
    while (no_of_joins < total_joins) {
810
92.7k
        IGRAPH_ALLOW_INTERRUPTION();
811
92.7k
        IGRAPH_PROGRESS("Fast greedy community detection", no_of_joins * 100.0 / total_joins, 0);
812
813
        /* Store the modularity */
814
92.7k
        if (modularity) {
815
92.7k
            VECTOR(*modularity)[no_of_joins] = q;
816
92.7k
        }
817
818
        /* Update best modularity if needed */
819
92.7k
        if (q >= bestq) {
820
84.5k
            bestq = q;
821
84.5k
            best_no_of_joins = no_of_joins;
822
84.5k
        }
823
824
        /* Some debug info if needed */
825
        /* igraph_i_fastgreedy_community_list_check_heap(&communities); */
826
#ifdef IGRAPH_FASTCOMM_DEBUG
827
        debug("===========================================\n");
828
        for (i = 0; i < communities.n; i++) {
829
            if (communities.e[i].maxdq == 0) {
830
                debug("Community #%ld: PASSIVE\n", i);
831
                continue;
832
            }
833
            debug("Community #%ld\n ", i);
834
            for (j = 0; j < igraph_vector_ptr_size(&communities.e[i].neis); j++) {
835
                p1 = (igraph_i_fastgreedy_commpair*)VECTOR(communities.e[i].neis)[j];
836
                debug(" (%ld,%ld,%.4f)", p1->first, p1->second, *p1->dq);
837
            }
838
            p1 = communities.e[i].maxdq;
839
            debug("\n  Maxdq: (%ld,%ld,%.4f)\n", p1->first, p1->second, *p1->dq);
840
        }
841
        debug("Global maxdq is: (%ld,%ld,%.4f)\n", communities.heap[0]->maxdq->first,
842
              communities.heap[0]->maxdq->second, *communities.heap[0]->maxdq->dq);
843
        for (i = 0; i < communities.no_of_communities; i++) {
844
            debug("(%ld,%ld,%.4f) ", communities.heap[i]->maxdq->first, communities.heap[i]->maxdq->second, *communities.heap[0]->maxdq->dq);
845
        }
846
        debug("\n");
847
#endif
848
92.7k
        if (communities.heap[0] == NULL) {
849
144
            break;    /* no more communities */
850
144
        }
851
92.5k
        if (communities.heap[0]->maxdq == NULL) {
852
5.70k
            break;    /* there are only isolated comms */
853
5.70k
        }
854
86.8k
        to = communities.heap[0]->maxdq->second;
855
86.8k
        from = communities.heap[0]->maxdq->first;
856
857
86.8k
        debug("Q[%ld] = %.7f\tdQ = %.7f\t |H| = %ld\n",
858
86.8k
              no_of_joins, q, *communities.heap[0]->maxdq->dq, no_of_nodes - no_of_joins - 1);
859
860
        /* IGRAPH_FASTCOMM_DEBUG */
861
        /* from=join_order[no_of_joins*2]; to=join_order[no_of_joins*2+1];
862
        if (to == -1) break;
863
        for (i=0; i<igraph_vector_ptr_size(&communities.e[to].neis); i++) {
864
          p1=(igraph_i_fastgreedy_commpair*)VECTOR(communities.e[to].neis)[i];
865
          if (p1->second == from) communities.maxdq = p1;
866
        } */
867
868
86.8k
        n = igraph_vector_ptr_size(&communities.e[to].neis);
869
86.8k
        m = igraph_vector_ptr_size(&communities.e[from].neis);
870
        /*if (n>m) {
871
          dummy=n; n=m; m=dummy;
872
          dummy=to; to=from; from=dummy;
873
        }*/
874
86.8k
        debug("  joining: %ld <- %ld\n", to, from);
875
86.8k
        q += *communities.heap[0]->maxdq->dq;
876
877
        /* Merge the second community into the first */
878
86.8k
        i = j = 0;
879
427k
        while (i < n && j < m) {
880
340k
            p1 = (igraph_i_fastgreedy_commpair*)VECTOR(communities.e[to].neis)[i];
881
340k
            p2 = (igraph_i_fastgreedy_commpair*)VECTOR(communities.e[from].neis)[j];
882
340k
            debug("Pairs: %" IGRAPH_PRId "-%" IGRAPH_PRId " and %" IGRAPH_PRId "-%" IGRAPH_PRId "\n", p1->first, p1->second,
883
340k
                  p2->first, p2->second);
884
340k
            if (p1->second < p2->second) {
885
                /* Considering p1 from now on */
886
162k
                debug("    Considering: %" IGRAPH_PRId "-%" IGRAPH_PRId "\n", p1->first, p1->second);
887
162k
                if (p1->second == from) {
888
52.9k
                    debug("    WILL REMOVE: %" IGRAPH_PRId "-%" IGRAPH_PRId "\n", to, from);
889
109k
                } else {
890
                    /* chain, case 1 */
891
109k
                    debug("    CHAIN(1): %ld-%ld %ld, now=%.7f, adding=%.7f, newdq(%ld,%ld)=%.7f\n",
892
109k
                          to, p1->second, from, *p1->dq, -2 * VECTOR(a)[from]*VECTOR(a)[p1->second], p1->first, p1->second, *p1->dq - 2 * VECTOR(a)[from]*VECTOR(a)[p1->second]);
893
109k
                    igraph_i_fastgreedy_community_update_dq(&communities, p1, *p1->dq - 2 * VECTOR(a)[from]*VECTOR(a)[p1->second]);
894
109k
                }
895
162k
                i++;
896
178k
            } else if (p1->second == p2->second) {
897
                /* p1->first, p1->second and p2->first form a triangle */
898
30.9k
                debug("    Considering: %" IGRAPH_PRId "-%" IGRAPH_PRId " and %" IGRAPH_PRId "-%" IGRAPH_PRId "\n", p1->first, p1->second,
899
30.9k
                      p2->first, p2->second);
900
                /* Update dq value */
901
30.9k
                debug("    TRIANGLE: %ld-%ld-%ld, now=%.7f, adding=%.7f, newdq(%ld,%ld)=%.7f\n",
902
30.9k
                      to, p1->second, from, *p1->dq, *p2->dq, p1->first, p1->second, *p1->dq + *p2->dq);
903
30.9k
                igraph_i_fastgreedy_community_update_dq(&communities, p1, *p1->dq + *p2->dq);
904
30.9k
                igraph_i_fastgreedy_community_remove_nei(&communities, p1->second, from);
905
30.9k
                i++;
906
30.9k
                j++;
907
147k
            } else {
908
147k
                debug("    Considering: %" IGRAPH_PRId "-%" IGRAPH_PRId "\n", p2->first, p2->second);
909
147k
                if (p2->second == to) {
910
57.8k
                    debug("    WILL REMOVE: %" IGRAPH_PRId "-%" IGRAPH_PRId "\n", p2->second, p2->first);
911
89.5k
                } else {
912
                    /* chain, case 2 */
913
89.5k
                    debug("    CHAIN(2): %ld %ld-%ld, newdq(%ld,%ld)=%.7f\n",
914
89.5k
                          to, p2->second, from, to, p2->second, *p2->dq - 2 * VECTOR(a)[to]*VECTOR(a)[p2->second]);
915
89.5k
                    p2->opposite->second = to;
916
                    /* p2->opposite->second changed, so it means that
917
                     * communities.e[p2->second].neis (which contains p2->opposite) is
918
                     * not sorted anymore. We have to find the index of p2->opposite in
919
                     * this vector and move it to the correct place. Moving should be an
920
                     * O(n) operation; re-sorting would be O(n*logn) or even worse,
921
                     * depending on the pivoting strategy used by qsort() since the
922
                     * vector is nearly sorted */
923
89.5k
                    igraph_i_fastgreedy_community_sort_neighbors_of(
924
89.5k
                        &communities, p2->second, p2->opposite);
925
                    /* link from.neis[j] to the current place in to.neis if
926
                     * from.neis[j] != to */
927
89.5k
                    p2->first = to;
928
89.5k
                    IGRAPH_CHECK(igraph_vector_ptr_insert(&communities.e[to].neis, i, p2));
929
89.5k
                    n++; i++;
930
89.5k
                    if (*p2->dq > *communities.e[to].maxdq->dq) {
931
265
                        communities.e[to].maxdq = p2;
932
265
                        k = igraph_i_fastgreedy_community_list_find_in_heap(&communities, to);
933
265
                        igraph_i_fastgreedy_community_list_sift_up(&communities, k);
934
265
                    }
935
89.5k
                    igraph_i_fastgreedy_community_update_dq(&communities, p2, *p2->dq - 2 * VECTOR(a)[to]*VECTOR(a)[p2->second]);
936
89.5k
                }
937
147k
                j++;
938
147k
            }
939
340k
        }
940
941
86.8k
        p1 = NULL;
942
313k
        while (i < n) {
943
226k
            p1 = (igraph_i_fastgreedy_commpair*)VECTOR(communities.e[to].neis)[i];
944
226k
            if (p1->second == from) {
945
33.8k
                debug("    WILL REMOVE: %" IGRAPH_PRId "-%" IGRAPH_PRId "\n", p1->first, from);
946
192k
            } else {
947
                /* chain, case 1 */
948
192k
                debug("    CHAIN(1): %ld-%ld %ld, now=%.7f, adding=%.7f, newdq(%ld,%ld)=%.7f\n",
949
192k
                      to, p1->second, from, *p1->dq, -2 * VECTOR(a)[from]*VECTOR(a)[p1->second], p1->first, p1->second, *p1->dq - 2 * VECTOR(a)[from]*VECTOR(a)[p1->second]);
950
192k
                igraph_i_fastgreedy_community_update_dq(&communities, p1, *p1->dq - 2 * VECTOR(a)[from]*VECTOR(a)[p1->second]);
951
192k
            }
952
226k
            i++;
953
226k
        }
954
196k
        while (j < m) {
955
109k
            p2 = (igraph_i_fastgreedy_commpair*)VECTOR(communities.e[from].neis)[j];
956
109k
            if (to == p2->second) {
957
28.9k
                j++;
958
28.9k
                continue;
959
28.9k
            }
960
            /* chain, case 2 */
961
80.5k
            debug("    CHAIN(2): %ld %ld-%ld, newdq(%ld,%ld)=%.7f\n",
962
80.5k
                  to, p2->second, from, p1 ? p1->first : -1, p2->second, *p2->dq - 2 * VECTOR(a)[to]*VECTOR(a)[p2->second]);
963
80.5k
            p2->opposite->second = to;
964
            /* need to re-sort community nei list `p2->second` */
965
80.5k
            igraph_i_fastgreedy_community_sort_neighbors_of(&communities, p2->second, p2->opposite);
966
            /* link from.neis[j] to the current place in to.neis if
967
             * from.neis[j] != to */
968
80.5k
            p2->first = to;
969
80.5k
            IGRAPH_CHECK(igraph_vector_ptr_push_back(&communities.e[to].neis, p2));
970
80.5k
            if (*p2->dq > *communities.e[to].maxdq->dq) {
971
69
                communities.e[to].maxdq = p2;
972
69
                k = igraph_i_fastgreedy_community_list_find_in_heap(&communities, to);
973
69
                igraph_i_fastgreedy_community_list_sift_up(&communities, k);
974
69
            }
975
80.5k
            igraph_i_fastgreedy_community_update_dq(&communities, p2, *p2->dq - 2 * VECTOR(a)[to]*VECTOR(a)[p2->second]);
976
80.5k
            j++;
977
80.5k
        }
978
979
        /* Now, remove community `from` from the neighbors of community `to` */
980
86.8k
        if (communities.no_of_communities > 2) {
981
80.4k
            debug("    REMOVING: %" IGRAPH_PRId "-%" IGRAPH_PRId "\n", to, from);
982
80.4k
            igraph_i_fastgreedy_community_remove_nei(&communities, to, from);
983
80.4k
            i = igraph_i_fastgreedy_community_list_find_in_heap(&communities, from);
984
80.4k
            igraph_i_fastgreedy_community_list_remove(&communities, i);
985
80.4k
        }
986
86.8k
        communities.e[from].maxdq = NULL;
987
988
        /* Update community sizes */
989
86.8k
        communities.e[to].size += communities.e[from].size;
990
86.8k
        communities.e[from].size = 0;
991
992
        /* record what has been merged */
993
        /* igraph_vector_ptr_clear is not enough here as it won't free
994
         * the memory consumed by communities.e[from].neis. Thanks
995
         * to Tom Gregorovic for pointing that out. */
996
86.8k
        igraph_vector_ptr_destroy(&communities.e[from].neis);
997
86.8k
        if (merges) {
998
86.8k
            MATRIX(*merges, no_of_joins, 0) = communities.e[to].id;
999
86.8k
            MATRIX(*merges, no_of_joins, 1) = communities.e[from].id;
1000
86.8k
            communities.e[to].id = no_of_nodes + no_of_joins;
1001
86.8k
        }
1002
1003
        /* Update vector a */
1004
86.8k
        VECTOR(a)[to] += VECTOR(a)[from];
1005
86.8k
        VECTOR(a)[from] = 0.0;
1006
1007
86.8k
        no_of_joins++;
1008
86.8k
    }
1009
    /* TODO: continue merging when some isolated communities remained. Always
1010
     * joining the communities with the least number of nodes results in the
1011
     * smallest decrease in modularity every step. Now we're simply deleting
1012
     * the excess rows from the merge matrix */
1013
6.54k
    if (merges != NULL) {
1014
6.54k
        if (no_of_joins < total_joins) {
1015
5.84k
            igraph_int_t *ivec;
1016
5.84k
            igraph_int_t merges_nrow = igraph_matrix_int_nrow(merges);
1017
1018
5.84k
            ivec = IGRAPH_CALLOC(merges_nrow, igraph_int_t);
1019
5.84k
            IGRAPH_CHECK_OOM(ivec, "Insufficient memory for fast greedy community detection.");
1020
5.84k
            IGRAPH_FINALLY(igraph_free, ivec);
1021
1022
84.8k
            for (i = 0; i < no_of_joins; i++) {
1023
78.9k
                ivec[i] = i + 1;
1024
78.9k
            }
1025
1026
5.84k
            igraph_matrix_int_permdelete_rows(merges, ivec, total_joins - no_of_joins);
1027
1028
5.84k
            IGRAPH_FREE(ivec);
1029
5.84k
            IGRAPH_FINALLY_CLEAN(1);
1030
5.84k
        }
1031
6.54k
    }
1032
6.54k
    IGRAPH_PROGRESS("Fast greedy community detection", 100.0, 0);
1033
1034
6.54k
    if (modularity) {
1035
6.54k
        VECTOR(*modularity)[no_of_joins] = q;
1036
6.54k
        IGRAPH_CHECK(igraph_vector_resize(modularity, no_of_joins + 1));
1037
6.54k
    }
1038
1039
    /* Internally, the algorithm does not create NaN values.
1040
     * If the graph has no edges, the final modularity will be zero.
1041
     * We change this to NaN for consistency. */
1042
6.54k
    if (modularity && no_of_edges == 0) {
1043
168
        IGRAPH_ASSERT(no_of_joins == 0);
1044
168
        VECTOR(*modularity)[0] = IGRAPH_NAN;
1045
168
    }
1046
1047
6.54k
    debug("Freeing memory\n");
1048
6.54k
    IGRAPH_FREE(pairs);
1049
6.54k
    IGRAPH_FREE(dq);
1050
6.54k
    igraph_i_fastgreedy_community_list_destroy(&communities);
1051
6.54k
    igraph_vector_destroy(&a);
1052
6.54k
    IGRAPH_FINALLY_CLEAN(4);
1053
1054
6.54k
    if (membership) {
1055
6.54k
        IGRAPH_CHECK(igraph_community_to_membership(merges,
1056
6.54k
                     no_of_nodes,
1057
6.54k
                     /*steps=*/ best_no_of_joins,
1058
6.54k
                     membership,
1059
6.54k
                     /*csize=*/ NULL));
1060
6.54k
    }
1061
1062
6.54k
    if (merges == &merges_local) {
1063
0
        igraph_matrix_int_destroy(&merges_local);
1064
0
        IGRAPH_FINALLY_CLEAN(1);
1065
0
    }
1066
1067
6.54k
    return IGRAPH_SUCCESS;
1068
6.54k
}
1069
1070
#ifdef IGRAPH_FASTCOMM_DEBUG
1071
    #undef IGRAPH_FASTCOMM_DEBUG
1072
#endif