Coverage Report

Created: 2026-03-15 06:34

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
472k
static igraph_bool_t igraph_i_fastgreedy_community_rescan_max(igraph_i_fastgreedy_community *comm) {
100
472k
    igraph_int_t i, n;
101
472k
    igraph_i_fastgreedy_commpair *p, *best;
102
472k
    igraph_real_t bestdq, currdq;
103
104
472k
    n = igraph_vector_ptr_size(&comm->neis);
105
472k
    if (n == 0) {
106
6.29k
        comm->maxdq = NULL;
107
6.29k
        return true;
108
6.29k
    }
109
110
465k
    best = (igraph_i_fastgreedy_commpair*)VECTOR(comm->neis)[0];
111
465k
    bestdq = *best->dq;
112
2.07M
    for (i = 1; i < n; i++) {
113
1.61M
        p = (igraph_i_fastgreedy_commpair*)VECTOR(comm->neis)[i];
114
1.61M
        currdq = *p->dq;
115
1.61M
        if (currdq > bestdq) {
116
173k
            best = p;
117
173k
            bestdq = currdq;
118
173k
        }
119
1.61M
    }
120
121
465k
    if (best != comm->maxdq) {
122
157k
        comm->maxdq = best;
123
157k
        return true;
124
308k
    } else {
125
308k
        return false;
126
308k
    }
127
465k
}
128
129
/* Destroys the global community list object */
130
static void igraph_i_fastgreedy_community_list_destroy(
131
6.67k
        igraph_i_fastgreedy_community_list *list) {
132
6.67k
    igraph_int_t i;
133
312k
    for (i = 0; i < list->n; i++) {
134
305k
        igraph_vector_ptr_destroy(&list->e[i].neis);
135
305k
    }
136
6.67k
    IGRAPH_FREE(list->e);
137
6.67k
    if (list->heapindex != NULL) {
138
6.67k
        IGRAPH_FREE(list->heapindex);
139
6.67k
    }
140
6.67k
    if (list->heap != NULL) {
141
6.67k
        IGRAPH_FREE(list->heap);
142
6.67k
    }
143
6.67k
}
144
145
/* Community list heap maintenance: sift down */
146
static void igraph_i_fastgreedy_community_list_sift_down(
147
642k
        igraph_i_fastgreedy_community_list *list, igraph_int_t idx) {
148
642k
    igraph_int_t root, child, c1, c2;
149
642k
    igraph_i_fastgreedy_community *dummy;
150
642k
    igraph_int_t dummy2;
151
642k
    igraph_i_fastgreedy_community** heap = list->heap;
152
642k
    igraph_int_t *heapindex = list->heapindex;
153
154
642k
    root = idx;
155
959k
    while (root * 2 + 1 < list->no_of_communities) {
156
642k
        child = root * 2 + 1;
157
642k
        if (child + 1 < list->no_of_communities &&
158
600k
            *heap[child]->maxdq->dq < *heap[child + 1]->maxdq->dq) {
159
184k
            child++;
160
184k
        }
161
642k
        if (*heap[root]->maxdq->dq < *heap[child]->maxdq->dq) {
162
317k
            c1 = heap[root]->maxdq->first;
163
317k
            c2 = heap[child]->maxdq->first;
164
165
317k
            dummy = heap[root];
166
317k
            heap[root] = heap[child];
167
317k
            heap[child] = dummy;
168
169
317k
            dummy2 = heapindex[c1];
170
317k
            heapindex[c1] = heapindex[c2];
171
317k
            heapindex[c2] = dummy2;
172
173
317k
            root = child;
174
325k
        } else {
175
325k
            break;
176
325k
        }
177
642k
    }
178
642k
}
179
180
/* Community list heap maintenance: sift up */
181
static void igraph_i_fastgreedy_community_list_sift_up(
182
11.5k
        igraph_i_fastgreedy_community_list *list, igraph_int_t idx) {
183
11.5k
    igraph_int_t root, parent, c1, c2;
184
11.5k
    igraph_i_fastgreedy_community *dummy;
185
11.5k
    igraph_int_t dummy2;
186
11.5k
    igraph_i_fastgreedy_community** heap = list->heap;
187
11.5k
    igraph_int_t *heapindex = list->heapindex;
188
189
11.5k
    root = idx;
190
23.3k
    while (root > 0) {
191
18.1k
        parent = (root - 1) / 2;
192
18.1k
        if (*heap[parent]->maxdq->dq < *heap[root]->maxdq->dq) {
193
11.8k
            c1 = heap[root]->maxdq->first;
194
11.8k
            c2 = heap[parent]->maxdq->first;
195
196
11.8k
            dummy = heap[parent];
197
11.8k
            heap[parent] = heap[root];
198
11.8k
            heap[root] = dummy;
199
200
11.8k
            dummy2 = heapindex[c1];
201
11.8k
            heapindex[c1] = heapindex[c2];
202
11.8k
            heapindex[c2] = dummy2;
203
204
11.8k
            root = parent;
205
11.8k
        } else {
206
6.38k
            break;
207
6.38k
        }
208
18.1k
    }
209
11.5k
}
210
211
/* Builds the community heap for the first time */
212
static void igraph_i_fastgreedy_community_list_build_heap(
213
6.67k
        igraph_i_fastgreedy_community_list *list) {
214
6.67k
    igraph_int_t i;
215
54.4k
    for (i = list->no_of_communities / 2 - 1; i >= 0; i--) {
216
47.8k
        igraph_i_fastgreedy_community_list_sift_down(list, i);
217
47.8k
    }
218
6.67k
}
219
220
/* Finds the element belonging to a given community in the heap and return its
221
 * index in the heap array */
222
560k
#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
79.3k
        igraph_i_fastgreedy_community_list *list, igraph_int_t idx) {
267
79.3k
    igraph_real_t old;
268
79.3k
    igraph_int_t commidx;
269
270
    /* First adjust the index */
271
79.3k
    commidx = list->heap[list->no_of_communities - 1]->maxdq->first;
272
79.3k
    list->heapindex[commidx] = idx;
273
79.3k
    commidx = list->heap[idx]->maxdq->first;
274
79.3k
    list->heapindex[commidx] = -1;
275
276
    /* Now remove the element */
277
79.3k
    old = *list->heap[idx]->maxdq->dq;
278
79.3k
    list->heap[idx] = list->heap[list->no_of_communities - 1];
279
79.3k
    list->no_of_communities--;
280
281
    /* Recover heap property */
282
79.3k
    if (old > *list->heap[idx]->maxdq->dq) {
283
77.4k
        igraph_i_fastgreedy_community_list_sift_down(list, idx);
284
77.4k
    } else {
285
1.92k
        igraph_i_fastgreedy_community_list_sift_up(list, idx);
286
1.92k
    }
287
79.3k
}
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.29k
        igraph_i_fastgreedy_community_list *list, igraph_int_t idx, igraph_int_t comm) {
293
6.29k
    igraph_int_t i;
294
295
6.29k
    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
244
        list->heapindex[comm] = -1;
299
244
        list->no_of_communities--;
300
244
        return;
301
244
    }
302
303
    /* First adjust the index */
304
6.05k
    i = list->heap[list->no_of_communities - 1]->maxdq->first;
305
6.05k
    list->heapindex[i] = idx;
306
6.05k
    list->heapindex[comm] = -1;
307
308
    /* Now remove the element */
309
6.05k
    list->heap[idx] = list->heap[list->no_of_communities - 1];
310
6.05k
    list->no_of_communities--;
311
312
    /* Recover heap property */
313
58.2k
    for (i = list->no_of_communities / 2 - 1; i >= 0; i--) {
314
52.1k
        igraph_i_fastgreedy_community_list_sift_down(list, i);
315
52.1k
    }
316
6.05k
}
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
109k
        igraph_i_fastgreedy_community_list *list, igraph_int_t c, igraph_int_t k) {
322
109k
    igraph_int_t i, n;
323
109k
    igraph_bool_t rescan = false;
324
109k
    igraph_i_fastgreedy_commpair *p;
325
109k
    igraph_i_fastgreedy_community *comm;
326
109k
    igraph_real_t olddq;
327
328
109k
    comm = &list->e[c];
329
109k
    n = igraph_vector_ptr_size(&comm->neis);
330
445k
    for (i = 0; i < n; i++) {
331
445k
        p = (igraph_i_fastgreedy_commpair*)VECTOR(comm->neis)[i];
332
445k
        if (p->second == k) {
333
            /* Check current maxdq */
334
109k
            if (comm->maxdq == p) {
335
81.7k
                rescan = true;
336
81.7k
            }
337
109k
            break;
338
109k
        }
339
445k
    }
340
109k
    if (i < n) {
341
109k
        olddq = *comm->maxdq->dq;
342
109k
        igraph_vector_ptr_remove(&comm->neis, i);
343
109k
        if (rescan) {
344
81.7k
            igraph_i_fastgreedy_community_rescan_max(comm);
345
81.7k
            i = igraph_i_fastgreedy_community_list_find_in_heap(list, c);
346
81.7k
            if (comm->maxdq) {
347
75.4k
                if (*comm->maxdq->dq > olddq) {
348
830
                    igraph_i_fastgreedy_community_list_sift_up(list, i);
349
74.6k
                } else {
350
74.6k
                    igraph_i_fastgreedy_community_list_sift_down(list, i);
351
74.6k
                }
352
75.4k
            } else {
353
                /* no more neighbors for this community. we should remove this
354
                 * community from the heap and restore the heap property */
355
6.29k
                debug("REMOVING (NO MORE NEIS): %" IGRAPH_PRId "\n", i);
356
6.29k
                igraph_i_fastgreedy_community_list_remove2(list, i, c);
357
6.29k
            }
358
81.7k
        }
359
109k
    }
360
109k
}
361
362
/* Auxiliary function to sort a community pair list with respect to the
363
 * `second` field */
364
259k
static int igraph_i_fastgreedy_commpair_cmp(const void *p1, const void *p2) {
365
259k
    igraph_i_fastgreedy_commpair *cp1, *cp2;
366
259k
    igraph_int_t diff;
367
259k
    cp1 = *(igraph_i_fastgreedy_commpair**)p1;
368
259k
    cp2 = *(igraph_i_fastgreedy_commpair**)p2;
369
259k
    diff = cp1->second - cp2->second;
370
259k
    return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
371
259k
}
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
474k
        igraph_i_fastgreedy_commpair *changed_pair) {
379
474k
    igraph_vector_ptr_t *vec;
380
474k
    igraph_int_t i, n;
381
474k
    igraph_bool_t can_skip_sort = false;
382
474k
    igraph_i_fastgreedy_commpair *other_pair;
383
384
474k
    vec = &list->e[index].neis;
385
474k
    if (changed_pair != NULL) {
386
        /* Optimized sorting */
387
388
        /* First we look for changed_pair in vec */
389
168k
        n = igraph_vector_ptr_size(vec);
390
354k
        for (i = 0; i < n; i++) {
391
354k
            if (VECTOR(*vec)[i] == changed_pair) {
392
168k
                break;
393
168k
            }
394
354k
        }
395
396
        /* Did we find it? We should have -- otherwise it's a bug */
397
168k
        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
221k
        while (i > 0) {
407
104k
            other_pair = VECTOR(*vec)[i - 1];
408
104k
            if (other_pair->second > changed_pair->second) {
409
52.5k
                VECTOR(*vec)[i] = other_pair;
410
52.5k
                i--;
411
52.5k
            } else {
412
51.9k
                break;
413
51.9k
            }
414
104k
        }
415
168k
        VECTOR(*vec)[i] = changed_pair;
416
417
        /* Shifting to the right */
418
240k
        while (i < n - 1) {
419
135k
            other_pair = VECTOR(*vec)[i + 1];
420
135k
            if (other_pair->second < changed_pair->second) {
421
71.0k
                VECTOR(*vec)[i] = other_pair;
422
71.0k
                i++;
423
71.0k
            } else {
424
64.4k
                break;
425
64.4k
            }
426
135k
        }
427
168k
        VECTOR(*vec)[i] = changed_pair;
428
429
        /* Mark that we don't need a full sort */
430
168k
        can_skip_sort = true;
431
168k
    }
432
433
474k
    if (!can_skip_sort) {
434
        /* Fallback to full sorting */
435
305k
        igraph_vector_ptr_sort(vec, igraph_i_fastgreedy_commpair_cmp);
436
305k
    }
437
474k
}
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
498k
        igraph_i_fastgreedy_commpair *p, igraph_real_t newdq) {
446
447
498k
    igraph_int_t i, j, to, from;
448
498k
    igraph_real_t olddq;
449
498k
    igraph_i_fastgreedy_community *comm_to, *comm_from;
450
498k
    to = p->first; from = p->second;
451
498k
    comm_to = &list->e[to];
452
498k
    comm_from = &list->e[from];
453
498k
    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
453
        *p->dq = newdq;
457
        /* The maximum was increased, so perform a sift-up in the heap */
458
453
        i = igraph_i_fastgreedy_community_list_find_in_heap(list, to);
459
453
        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
453
        if (comm_from->maxdq != p->opposite) {
463
163
            if (*comm_from->maxdq->dq < newdq) {
464
                /* ...and it will become the maximal, we need to adjust and sift up */
465
139
                comm_from->maxdq = p->opposite;
466
139
                j = igraph_i_fastgreedy_community_list_find_in_heap(list, from);
467
139
                igraph_i_fastgreedy_community_list_sift_up(list, j);
468
139
            } 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
24
            }
472
290
        } else {
473
            /* The pair was maximal in the opposite side, so we need to sift it up
474
             * with the new value */
475
290
            j = igraph_i_fastgreedy_community_list_find_in_heap(list, from);
476
290
            igraph_i_fastgreedy_community_list_sift_up(list, j);
477
290
        }
478
453
        return true;
479
497k
    } 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
438k
        olddq = *p->dq;
484
438k
        *p->dq = newdq;
485
        /* However, if the item was the maximum on the opposite side, we'd better
486
         * re-scan it */
487
438k
        if (comm_from->maxdq == p->opposite) {
488
279k
            if (olddq > newdq) {
489
                /* Decreased the maximum on the other side, we have to re-scan for the
490
                 * new maximum */
491
279k
                igraph_i_fastgreedy_community_rescan_max(comm_from);
492
279k
                j = igraph_i_fastgreedy_community_list_find_in_heap(list, from);
493
279k
                igraph_i_fastgreedy_community_list_sift_down(list, j);
494
279k
            } 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
926
                j = igraph_i_fastgreedy_community_list_find_in_heap(list, from);
498
926
                igraph_i_fastgreedy_community_list_sift_up(list, j);
499
926
            }
500
279k
        }
501
438k
        return false;
502
438k
    } 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
59.3k
        *p->dq = newdq;
511
59.3k
        if (comm_to->maxdq != p) {
512
            /* case (2) */
513
3.34k
            comm_to->maxdq = p;
514
            /* The maximum was increased, so perform a sift-up in the heap */
515
3.34k
            i = igraph_i_fastgreedy_community_list_find_in_heap(list, to);
516
3.34k
            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.34k
            if (comm_from->maxdq != p->opposite) {
520
2.25k
                if (*comm_from->maxdq->dq < newdq) {
521
                    /* Yes, it will become the new maximum */
522
2.20k
                    comm_from->maxdq = p->opposite;
523
2.20k
                    j = igraph_i_fastgreedy_community_list_find_in_heap(list, from);
524
2.20k
                    igraph_i_fastgreedy_community_list_sift_up(list, j);
525
2.20k
                } else {
526
                    /* No, nothing to do there */
527
45
                }
528
2.25k
            } else {
529
                /* Already increased the maximum on the opposite side, so sift it up */
530
1.09k
                j = igraph_i_fastgreedy_community_list_find_in_heap(list, from);
531
1.09k
                igraph_i_fastgreedy_community_list_sift_up(list, j);
532
1.09k
            }
533
55.9k
        } 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
55.9k
            igraph_i_fastgreedy_community_rescan_max(comm_to);
538
            /* The maximum was decreased, so perform a sift-down in the heap */
539
55.9k
            i = igraph_i_fastgreedy_community_list_find_in_heap(list, to);
540
55.9k
            igraph_i_fastgreedy_community_list_sift_down(list, i);
541
55.9k
            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
55.3k
            } else {
545
                /* We decreased the maximal on the opposite side as well. Re-scan
546
                 * and sift down */
547
55.3k
                igraph_i_fastgreedy_community_rescan_max(comm_from);
548
55.3k
                j = igraph_i_fastgreedy_community_list_find_in_heap(list, from);
549
55.3k
                igraph_i_fastgreedy_community_list_sift_down(list, j);
550
55.3k
            }
551
55.9k
        }
552
59.3k
    }
553
59.3k
    return true;
554
498k
}
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.67k
                                igraph_vector_int_t *membership) {
609
6.67k
    igraph_int_t no_of_edges, no_of_nodes, no_of_joins, total_joins;
610
6.67k
    igraph_int_t i, j, k, n, m, from, to, dummy, best_no_of_joins;
611
6.67k
    igraph_eit_t edgeit;
612
6.67k
    igraph_i_fastgreedy_commpair *pairs, *p1, *p2;
613
6.67k
    igraph_i_fastgreedy_community_list communities;
614
6.67k
    igraph_vector_t a;
615
6.67k
    igraph_vector_int_t degrees;
616
6.67k
    igraph_real_t q, *dq, bestq, weight_sum, loop_weight_sum;
617
6.67k
    igraph_bool_t has_multiple;
618
6.67k
    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.67k
    no_of_nodes = igraph_vcount(graph);
624
6.67k
    no_of_edges = igraph_ecount(graph);
625
626
6.67k
    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.67k
    total_joins = no_of_nodes > 0 ? no_of_nodes - 1 : 0;
631
632
6.67k
    if (weights) {
633
3.38k
        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.38k
        if (no_of_edges > 0) {
637
3.30k
            igraph_real_t minweight = igraph_vector_min(weights);
638
3.30k
            if (minweight < 0) {
639
0
                IGRAPH_ERROR("Weights must not be negative.", IGRAPH_EINVAL);
640
0
            }
641
3.30k
            if (isnan(minweight)) {
642
0
                IGRAPH_ERROR("Weights must not be NaN.", IGRAPH_EINVAL);
643
0
            }
644
3.30k
        }
645
3.38k
        weight_sum = igraph_vector_sum(weights);
646
3.38k
    } else {
647
3.28k
        weight_sum = no_of_edges;
648
3.28k
    }
649
650
6.67k
    IGRAPH_CHECK(igraph_has_multiple(graph, &has_multiple));
651
6.67k
    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.67k
    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.67k
    if (merges != NULL) {
664
6.67k
        IGRAPH_CHECK(igraph_matrix_int_resize(merges, total_joins, 2));
665
6.67k
        igraph_matrix_int_null(merges);
666
6.67k
    }
667
668
6.67k
    if (modularity != NULL) {
669
6.67k
        IGRAPH_CHECK(igraph_vector_resize(modularity, total_joins + 1));
670
6.67k
    }
671
672
    /* Create degree vector */
673
6.67k
    IGRAPH_VECTOR_INIT_FINALLY(&a, no_of_nodes);
674
6.67k
    if (weights) {
675
3.38k
        debug("Calculating weighted degrees\n");
676
58.1k
        for (i = 0; i < no_of_edges; i++) {
677
54.8k
            VECTOR(a)[IGRAPH_FROM(graph, i)] += VECTOR(*weights)[i];
678
54.8k
            VECTOR(a)[IGRAPH_TO(graph, i)] += VECTOR(*weights)[i];
679
54.8k
        }
680
3.38k
    } else {
681
3.28k
        debug("Calculating degrees\n");
682
3.28k
        IGRAPH_VECTOR_INT_INIT_FINALLY(&degrees, no_of_nodes);
683
3.28k
        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.28k
        igraph_vector_int_destroy(&degrees);
688
3.28k
        IGRAPH_FINALLY_CLEAN(1);
689
3.28k
    }
690
691
    /* Create list of communities */
692
6.67k
    debug("Creating community list\n");
693
6.67k
    communities.n = no_of_nodes;
694
6.67k
    communities.no_of_communities = no_of_nodes;
695
6.67k
    communities.e = IGRAPH_CALLOC(no_of_nodes, igraph_i_fastgreedy_community);
696
6.67k
    IGRAPH_CHECK_OOM(communities.e, "Insufficient memory for fast greedy community detection.");
697
6.67k
    IGRAPH_FINALLY(igraph_free, communities.e);
698
699
6.67k
    communities.heap = IGRAPH_CALLOC(no_of_nodes, igraph_i_fastgreedy_community*);
700
6.67k
    IGRAPH_CHECK_OOM(communities.heap, "Insufficient memory for fast greedy community detection.");
701
6.67k
    IGRAPH_FINALLY(igraph_free, communities.heap);
702
703
6.67k
    communities.heapindex = IGRAPH_CALLOC(no_of_nodes, igraph_int_t);
704
6.67k
    IGRAPH_CHECK_OOM(communities.heapindex, "Insufficient memory for fast greedy community detection.");
705
706
6.67k
    IGRAPH_FINALLY_CLEAN(2);
707
6.67k
    IGRAPH_FINALLY(igraph_i_fastgreedy_community_list_destroy, &communities);
708
709
312k
    for (i = 0; i < no_of_nodes; i++) {
710
305k
        IGRAPH_CHECK(igraph_vector_ptr_init(&communities.e[i].neis, 0));
711
305k
        communities.e[i].id = i;
712
305k
        communities.e[i].size = 1;
713
305k
    }
714
715
    /* Create list of community pairs from edges */
716
6.67k
    debug("Allocating dq vector\n");
717
6.67k
    dq = IGRAPH_CALLOC(no_of_edges, igraph_real_t);
718
6.67k
    IGRAPH_CHECK_OOM(dq, "Insufficient memory for fast greedy community detection.");
719
6.67k
    IGRAPH_FINALLY(igraph_free, dq);
720
721
6.67k
    debug("Creating community pair list\n");
722
6.67k
    IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_ID), &edgeit));
723
6.67k
    IGRAPH_FINALLY(igraph_eit_destroy, &edgeit);
724
6.67k
    pairs = IGRAPH_CALLOC(2 * no_of_edges, igraph_i_fastgreedy_commpair);
725
6.67k
    IGRAPH_CHECK_OOM(pairs, "Insufficient memory for fast greedy community detection.");
726
6.67k
    IGRAPH_FINALLY(igraph_free, pairs);
727
728
6.67k
    loop_weight_sum = 0;
729
122k
    for (i = 0, j = 0; !IGRAPH_EIT_END(edgeit); i += 2, j++, IGRAPH_EIT_NEXT(edgeit)) {
730
116k
        igraph_int_t eidx = IGRAPH_EIT_GET(edgeit);
731
732
        /* Create the pairs themselves */
733
116k
        from = IGRAPH_FROM(graph, eidx); to = IGRAPH_TO(graph, eidx);
734
116k
        if (from == to) {
735
0
            loop_weight_sum += weights ? 2 * VECTOR(*weights)[eidx] : 2;
736
0
            continue;
737
0
        }
738
739
116k
        if (from > to) {
740
116k
            dummy = from; from = to; to = dummy;
741
116k
        }
742
116k
        if (weights) {
743
54.8k
            dq[j] = 2 * (VECTOR(*weights)[eidx] / (weight_sum * 2.0) - VECTOR(a)[from] * VECTOR(a)[to] / (4.0 * weight_sum * weight_sum));
744
61.3k
        } else {
745
61.3k
            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
61.3k
        }
747
116k
        pairs[i].first = from;
748
116k
        pairs[i].second = to;
749
116k
        pairs[i].dq = &dq[j];
750
116k
        pairs[i].opposite = &pairs[i + 1];
751
116k
        pairs[i + 1].first = to;
752
116k
        pairs[i + 1].second = from;
753
116k
        pairs[i + 1].dq = pairs[i].dq;
754
116k
        pairs[i + 1].opposite = &pairs[i];
755
        /* Link the pair to the communities */
756
116k
        IGRAPH_CHECK(igraph_vector_ptr_push_back(&communities.e[from].neis, &pairs[i]));
757
116k
        IGRAPH_CHECK(igraph_vector_ptr_push_back(&communities.e[to].neis, &pairs[i + 1]));
758
        /* Update maximums */
759
116k
        if (communities.e[from].maxdq == NULL || *communities.e[from].maxdq->dq < *pairs[i].dq) {
760
47.0k
            communities.e[from].maxdq = &pairs[i];
761
47.0k
        }
762
116k
        if (communities.e[to].maxdq == NULL || *communities.e[to].maxdq->dq < *pairs[i + 1].dq) {
763
90.9k
            communities.e[to].maxdq = &pairs[i + 1];
764
90.9k
        }
765
116k
    }
766
6.67k
    igraph_eit_destroy(&edgeit);
767
6.67k
    IGRAPH_FINALLY_CLEAN(1);
768
769
    /* Sorting community neighbor lists by community IDs */
770
6.67k
    debug("Sorting community neighbor lists\n");
771
312k
    for (i = 0, j = 0; i < no_of_nodes; i++) {
772
305k
        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
305k
        if (communities.e[i].maxdq != NULL) {
776
98.6k
            communities.heap[j] = &communities.e[i];
777
98.6k
            communities.heapindex[i] = j;
778
98.6k
            j++;
779
206k
        } else {
780
206k
            communities.heapindex[i] = -1;
781
206k
        }
782
305k
    }
783
6.67k
    communities.no_of_communities = j;
784
785
    /* Calculate proper vector a (see paper) and initial modularity */
786
6.67k
    q = 2.0 * (weights ? weight_sum : no_of_edges);
787
6.67k
    if (q == 0) {
788
        /* All the weights are zero */
789
6.50k
    } else {
790
6.50k
        igraph_vector_scale(&a, 1.0 / q);
791
6.50k
        q = loop_weight_sum / q;
792
307k
        for (i = 0; i < no_of_nodes; i++) {
793
300k
            q -= VECTOR(a)[i] * VECTOR(a)[i];
794
300k
        }
795
6.50k
    }
796
797
    /* Initialize "best modularity" value and best merge counter */
798
6.67k
    bestq = q;
799
6.67k
    best_no_of_joins = 0;
800
801
    /* Initializing community heap */
802
6.67k
    debug("Initializing community heap\n");
803
6.67k
    igraph_i_fastgreedy_community_list_build_heap(&communities);
804
805
6.67k
    debug("Initial modularity: %.4f\n", q);
806
807
    /* Let's rock ;) */
808
6.67k
    no_of_joins = 0;
809
92.5k
    while (no_of_joins < total_joins) {
810
91.8k
        IGRAPH_ALLOW_INTERRUPTION();
811
91.8k
        IGRAPH_PROGRESS("Fast greedy community detection", no_of_joins * 100.0 / total_joins, 0);
812
813
        /* Store the modularity */
814
91.8k
        if (modularity) {
815
91.8k
            VECTOR(*modularity)[no_of_joins] = q;
816
91.8k
        }
817
818
        /* Update best modularity if needed */
819
91.8k
        if (q >= bestq) {
820
83.7k
            bestq = q;
821
83.7k
            best_no_of_joins = no_of_joins;
822
83.7k
        }
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
91.8k
        if (communities.heap[0] == NULL) {
849
139
            break;    /* no more communities */
850
139
        }
851
91.6k
        if (communities.heap[0]->maxdq == NULL) {
852
5.83k
            break;    /* there are only isolated comms */
853
5.83k
        }
854
85.8k
        to = communities.heap[0]->maxdq->second;
855
85.8k
        from = communities.heap[0]->maxdq->first;
856
857
85.8k
        debug("Q[%ld] = %.7f\tdQ = %.7f\t |H| = %ld\n",
858
85.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
85.8k
        n = igraph_vector_ptr_size(&communities.e[to].neis);
869
85.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
85.8k
        debug("  joining: %ld <- %ld\n", to, from);
875
85.8k
        q += *communities.heap[0]->maxdq->dq;
876
877
        /* Merge the second community into the first */
878
85.8k
        i = j = 0;
879
424k
        while (i < n && j < m) {
880
338k
            p1 = (igraph_i_fastgreedy_commpair*)VECTOR(communities.e[to].neis)[i];
881
338k
            p2 = (igraph_i_fastgreedy_commpair*)VECTOR(communities.e[from].neis)[j];
882
338k
            debug("Pairs: %" IGRAPH_PRId "-%" IGRAPH_PRId " and %" IGRAPH_PRId "-%" IGRAPH_PRId "\n", p1->first, p1->second,
883
338k
                  p2->first, p2->second);
884
338k
            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.3k
                    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
176k
            } else if (p1->second == p2->second) {
897
                /* p1->first, p1->second and p2->first form a triangle */
898
30.2k
                debug("    Considering: %" IGRAPH_PRId "-%" IGRAPH_PRId " and %" IGRAPH_PRId "-%" IGRAPH_PRId "\n", p1->first, p1->second,
899
30.2k
                      p2->first, p2->second);
900
                /* Update dq value */
901
30.2k
                debug("    TRIANGLE: %ld-%ld-%ld, now=%.7f, adding=%.7f, newdq(%ld,%ld)=%.7f\n",
902
30.2k
                      to, p1->second, from, *p1->dq, *p2->dq, p1->first, p1->second, *p1->dq + *p2->dq);
903
30.2k
                igraph_i_fastgreedy_community_update_dq(&communities, p1, *p1->dq + *p2->dq);
904
30.2k
                igraph_i_fastgreedy_community_remove_nei(&communities, p1->second, from);
905
30.2k
                i++;
906
30.2k
                j++;
907
145k
            } else {
908
145k
                debug("    Considering: %" IGRAPH_PRId "-%" IGRAPH_PRId "\n", p2->first, p2->second);
909
145k
                if (p2->second == to) {
910
57.2k
                    debug("    WILL REMOVE: %" IGRAPH_PRId "-%" IGRAPH_PRId "\n", p2->second, p2->first);
911
88.6k
                } else {
912
                    /* chain, case 2 */
913
88.6k
                    debug("    CHAIN(2): %ld %ld-%ld, newdq(%ld,%ld)=%.7f\n",
914
88.6k
                          to, p2->second, from, to, p2->second, *p2->dq - 2 * VECTOR(a)[to]*VECTOR(a)[p2->second]);
915
88.6k
                    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
88.6k
                    igraph_i_fastgreedy_community_sort_neighbors_of(
924
88.6k
                        &communities, p2->second, p2->opposite);
925
                    /* link from.neis[j] to the current place in to.neis if
926
                     * from.neis[j] != to */
927
88.6k
                    p2->first = to;
928
88.6k
                    IGRAPH_CHECK(igraph_vector_ptr_insert(&communities.e[to].neis, i, p2));
929
88.6k
                    n++; i++;
930
88.6k
                    if (*p2->dq > *communities.e[to].maxdq->dq) {
931
280
                        communities.e[to].maxdq = p2;
932
280
                        k = igraph_i_fastgreedy_community_list_find_in_heap(&communities, to);
933
280
                        igraph_i_fastgreedy_community_list_sift_up(&communities, k);
934
280
                    }
935
88.6k
                    igraph_i_fastgreedy_community_update_dq(&communities, p2, *p2->dq - 2 * VECTOR(a)[to]*VECTOR(a)[p2->second]);
936
88.6k
                }
937
145k
                j++;
938
145k
            }
939
338k
        }
940
941
85.8k
        p1 = NULL;
942
308k
        while (i < n) {
943
222k
            p1 = (igraph_i_fastgreedy_commpair*)VECTOR(communities.e[to].neis)[i];
944
222k
            if (p1->second == from) {
945
33.4k
                debug("    WILL REMOVE: %" IGRAPH_PRId "-%" IGRAPH_PRId "\n", p1->first, from);
946
188k
            } else {
947
                /* chain, case 1 */
948
188k
                debug("    CHAIN(1): %ld-%ld %ld, now=%.7f, adding=%.7f, newdq(%ld,%ld)=%.7f\n",
949
188k
                      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
188k
                igraph_i_fastgreedy_community_update_dq(&communities, p1, *p1->dq - 2 * VECTOR(a)[from]*VECTOR(a)[p1->second]);
951
188k
            }
952
222k
            i++;
953
222k
        }
954
194k
        while (j < m) {
955
108k
            p2 = (igraph_i_fastgreedy_commpair*)VECTOR(communities.e[from].neis)[j];
956
108k
            if (to == p2->second) {
957
28.6k
                j++;
958
28.6k
                continue;
959
28.6k
            }
960
            /* chain, case 2 */
961
80.3k
            debug("    CHAIN(2): %ld %ld-%ld, newdq(%ld,%ld)=%.7f\n",
962
80.3k
                  to, p2->second, from, p1 ? p1->first : -1, p2->second, *p2->dq - 2 * VECTOR(a)[to]*VECTOR(a)[p2->second]);
963
80.3k
            p2->opposite->second = to;
964
            /* need to re-sort community nei list `p2->second` */
965
80.3k
            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.3k
            p2->first = to;
969
80.3k
            IGRAPH_CHECK(igraph_vector_ptr_push_back(&communities.e[to].neis, p2));
970
80.3k
            if (*p2->dq > *communities.e[to].maxdq->dq) {
971
70
                communities.e[to].maxdq = p2;
972
70
                k = igraph_i_fastgreedy_community_list_find_in_heap(&communities, to);
973
70
                igraph_i_fastgreedy_community_list_sift_up(&communities, k);
974
70
            }
975
80.3k
            igraph_i_fastgreedy_community_update_dq(&communities, p2, *p2->dq - 2 * VECTOR(a)[to]*VECTOR(a)[p2->second]);
976
80.3k
            j++;
977
80.3k
        }
978
979
        /* Now, remove community `from` from the neighbors of community `to` */
980
85.8k
        if (communities.no_of_communities > 2) {
981
79.3k
            debug("    REMOVING: %" IGRAPH_PRId "-%" IGRAPH_PRId "\n", to, from);
982
79.3k
            igraph_i_fastgreedy_community_remove_nei(&communities, to, from);
983
79.3k
            i = igraph_i_fastgreedy_community_list_find_in_heap(&communities, from);
984
79.3k
            igraph_i_fastgreedy_community_list_remove(&communities, i);
985
79.3k
        }
986
85.8k
        communities.e[from].maxdq = NULL;
987
988
        /* Update community sizes */
989
85.8k
        communities.e[to].size += communities.e[from].size;
990
85.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
85.8k
        igraph_vector_ptr_destroy(&communities.e[from].neis);
997
85.8k
        if (merges) {
998
85.8k
            MATRIX(*merges, no_of_joins, 0) = communities.e[to].id;
999
85.8k
            MATRIX(*merges, no_of_joins, 1) = communities.e[from].id;
1000
85.8k
            communities.e[to].id = no_of_nodes + no_of_joins;
1001
85.8k
        }
1002
1003
        /* Update vector a */
1004
85.8k
        VECTOR(a)[to] += VECTOR(a)[from];
1005
85.8k
        VECTOR(a)[from] = 0.0;
1006
1007
85.8k
        no_of_joins++;
1008
85.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.67k
    if (merges != NULL) {
1014
6.67k
        if (no_of_joins < total_joins) {
1015
5.97k
            igraph_int_t *ivec;
1016
5.97k
            igraph_int_t merges_nrow = igraph_matrix_int_nrow(merges);
1017
1018
5.97k
            ivec = IGRAPH_CALLOC(merges_nrow, igraph_int_t);
1019
5.97k
            IGRAPH_CHECK_OOM(ivec, "Insufficient memory for fast greedy community detection.");
1020
5.97k
            IGRAPH_FINALLY(igraph_free, ivec);
1021
1022
83.9k
            for (i = 0; i < no_of_joins; i++) {
1023
77.9k
                ivec[i] = i + 1;
1024
77.9k
            }
1025
1026
5.97k
            igraph_matrix_int_permdelete_rows(merges, ivec, total_joins - no_of_joins);
1027
1028
5.97k
            IGRAPH_FREE(ivec);
1029
5.97k
            IGRAPH_FINALLY_CLEAN(1);
1030
5.97k
        }
1031
6.67k
    }
1032
6.67k
    IGRAPH_PROGRESS("Fast greedy community detection", 100.0, 0);
1033
1034
6.67k
    if (modularity) {
1035
6.67k
        VECTOR(*modularity)[no_of_joins] = q;
1036
6.67k
        IGRAPH_CHECK(igraph_vector_resize(modularity, no_of_joins + 1));
1037
6.67k
    }
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.67k
    if (modularity && no_of_edges == 0) {
1043
165
        IGRAPH_ASSERT(no_of_joins == 0);
1044
165
        VECTOR(*modularity)[0] = IGRAPH_NAN;
1045
165
    }
1046
1047
6.67k
    debug("Freeing memory\n");
1048
6.67k
    IGRAPH_FREE(pairs);
1049
6.67k
    IGRAPH_FREE(dq);
1050
6.67k
    igraph_i_fastgreedy_community_list_destroy(&communities);
1051
6.67k
    igraph_vector_destroy(&a);
1052
6.67k
    IGRAPH_FINALLY_CLEAN(4);
1053
1054
6.67k
    if (membership) {
1055
6.67k
        IGRAPH_CHECK(igraph_community_to_membership(merges,
1056
6.67k
                     no_of_nodes,
1057
6.67k
                     /*steps=*/ best_no_of_joins,
1058
6.67k
                     membership,
1059
6.67k
                     /*csize=*/ NULL));
1060
6.67k
    }
1061
1062
6.67k
    if (merges == &merges_local) {
1063
0
        igraph_matrix_int_destroy(&merges_local);
1064
0
        IGRAPH_FINALLY_CLEAN(1);
1065
0
    }
1066
1067
6.67k
    return IGRAPH_SUCCESS;
1068
6.67k
}
1069
1070
#ifdef IGRAPH_FASTCOMM_DEBUG
1071
    #undef IGRAPH_FASTCOMM_DEBUG
1072
#endif