Coverage Report

Created: 2026-09-03 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/igraph/src/centrality/closeness.c
Line
Count
Source
1
/*
2
   igraph library.
3
   Copyright (C) 2007-2020  The igraph development team <igraph@igraph.org>
4
5
   This program is free software; you can redistribute it and/or modify
6
   it under the terms of the GNU General Public License as published by
7
   the Free Software Foundation; either version 2 of the License, or
8
   (at your option) any later version.
9
10
   This program is distributed in the hope that it will be useful,
11
   but WITHOUT ANY WARRANTY; without even the implied warranty of
12
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
   GNU General Public License for more details.
14
15
   You should have received a copy of the GNU General Public License
16
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
17
*/
18
19
#include "igraph_centrality.h"
20
21
#include "igraph_adjlist.h"
22
#include "igraph_interface.h"
23
#include "igraph_progress.h"
24
#include "igraph_dqueue.h"
25
26
#include "core/indheap.h"
27
#include "core/interruption.h"
28
29
/***** Closeness centrality *****/
30
31
/**
32
 * \ingroup structural
33
 * \function igraph_closeness
34
 * \brief Closeness centrality calculations for some vertices.
35
 *
36
 * The closeness centrality of a vertex measures how easily other
37
 * vertices can be reached from it (or the other way: how easily it
38
 * can be reached from the other vertices). It is defined as
39
 * the inverse of the mean distance to (or from) all other vertices.
40
 *
41
 * </para><para>
42
 * Closeness centrality is meaningful only for connected graphs.
43
 * If the graph is not connected, igraph computes the inverse of the
44
 * mean distance to (or from) all \em reachable vertices. In undirected
45
 * graphs, this is equivalent to computing the closeness separately in
46
 * each connected component. The optional \p all_reachable output
47
 * parameter is provided to help detect when the graph is disconnected.
48
 *
49
 * </para><para>
50
 * While there is no universally adopted definition of closeness centrality
51
 * for disconnected graphs, there have been some attempts for generalizing
52
 * the concept to the disconnected case. One type of approach considers the mean distance
53
 * only to reachable vertices, then re-scales the obtained certrality score
54
 * by a factor that depends on the number of reachable vertices
55
 * (i.e. the size of the component in the undirected case).
56
 * To facilitate computing these generalizations of closeness centrality,
57
 * the number of reachable vertices (not including the starting vertex)
58
 * is returned in \p reachable_count.
59
 *
60
 * </para><para>
61
 * In disconnected graphs, consider using the harmonic centrality,
62
 * computable using \ref igraph_harmonic_centrality().
63
 *
64
 * </para><para>
65
 * For isolated vertices, i.e. those having no associated paths, NaN is returned.
66
 *
67
 * \param graph The graph object.
68
 * \param res The result of the computation, a vector containing the
69
 *        closeness centrality scores for the given vertices.
70
 * \param reachable_count If not \c NULL, this vector will contain the number of
71
 *        vertices reachable from each vertex for which the closeness is calculated
72
 *        (not including that vertex).
73
 * \param all_reachable Pointer to a Boolean. If not \c NULL, it indicates if all
74
 *        vertices of the graph were reachable from each vertex in \p vids.
75
 *        If false, the graph is non-connected. If true, and the graph is undirected,
76
 *        or if the graph is directed and \p vids contains all vertices, then the
77
 *        graph is connected.
78
 * \param vids The vertices for which the closeness centrality will be computed.
79
 * \param mode The type of shortest paths to be used for the
80
 *        calculation in directed graphs. Possible values:
81
 *        \clist
82
 *        \cli IGRAPH_OUT
83
 *          the lengths of the outgoing paths are calculated.
84
 *        \cli IGRAPH_IN
85
 *          the lengths of the incoming paths are calculated.
86
 *        \cli IGRAPH_ALL
87
 *          the directed graph is considered as an
88
 *          undirected one for the computation.
89
 *        \endclist
90
 * \param weights An optional vector containing edge weights for weighted
91
 *        closeness. NaN values re not allowed as weights. Supply a null
92
 *        pointer here for traditional, unweighted closeness.
93
 * \param normalized If true, the inverse of the mean distance to reachable
94
 *        vertices is returned. If false, the inverse of the sum of distances
95
 *        is returned.
96
 * \return Error code:
97
 *        \clist
98
 *        \cli IGRAPH_ENOMEM
99
 *           not enough memory for temporary data.
100
 *        \cli IGRAPH_EINVVID
101
 *           invalid vertex ID passed.
102
 *        \cli IGRAPH_EINVMODE
103
 *           invalid mode argument.
104
 *        \endclist
105
 *
106
 * Time complexity: O(n|E|) for the unweighted case and O(n|E|log|V|+|V|)
107
 * for the weighted case, where n is the number
108
 * of vertices for which the calculation is done, |V| is the number of vertices
109
 * and |E| is the number of edges in the graph.
110
 *
111
 * \sa Other centrality types: \ref igraph_degree(), \ref igraph_betweenness(),
112
 *   \ref igraph_harmonic_centrality().
113
 *   See \ref igraph_closeness_cutoff() for the range-limited closeness centrality.
114
 */
115
igraph_error_t igraph_closeness(const igraph_t *graph, igraph_vector_t *res,
116
                     igraph_vector_int_t *reachable_count, igraph_bool_t *all_reachable,
117
                     const igraph_vs_t vids, igraph_neimode_t mode,
118
                     const igraph_vector_t *weights,
119
0
                     igraph_bool_t normalized) {
120
0
    return igraph_closeness_cutoff(graph, res, reachable_count, all_reachable, vids, mode, weights, normalized, -1);
121
0
}
122
123
static igraph_error_t igraph_i_closeness_cutoff_weighted(const igraph_t *graph,
124
                                                igraph_vector_t *res,
125
                                                igraph_vector_int_t *reachable_count,
126
                                                igraph_bool_t *all_reachable,
127
                                                const igraph_vs_t vids,
128
                                                igraph_neimode_t mode,
129
                                                igraph_real_t cutoff,
130
                                                const igraph_vector_t *weights,
131
1.50k
                                                igraph_bool_t normalized) {
132
133
    /* See igraph_distances_dijkstra() for the implementation
134
       details and the dirty tricks. */
135
136
1.50k
    igraph_int_t no_of_nodes = igraph_vcount(graph);
137
1.50k
    igraph_int_t no_of_edges = igraph_ecount(graph);
138
139
1.50k
    igraph_2wheap_t Q;
140
1.50k
    igraph_vit_t vit;
141
1.50k
    igraph_int_t nodes_to_calc;
142
143
1.50k
    igraph_lazy_inclist_t inclist;
144
1.50k
    igraph_int_t i, j;
145
146
1.50k
    igraph_vector_t dist;
147
1.50k
    igraph_vector_int_t which;
148
1.50k
    igraph_int_t nodes_reached;
149
150
1.50k
    igraph_real_t mindist = 0;
151
152
1.50k
    if (igraph_vector_size(weights) != no_of_edges) {
153
0
        IGRAPH_ERROR("Invalid weight vector length.", IGRAPH_EINVAL);
154
0
    }
155
156
1.50k
    if (no_of_edges > 0) {
157
1.42k
        igraph_real_t minweight = igraph_vector_min(weights);
158
1.42k
        if (minweight <= 0) {
159
0
            IGRAPH_ERROR("Weight vector must be positive.", IGRAPH_EINVAL);
160
1.42k
        } else if (isnan(minweight)) {
161
0
            IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL);
162
0
        }
163
1.42k
    }
164
165
1.50k
    IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit));
166
1.50k
    IGRAPH_FINALLY(igraph_vit_destroy, &vit);
167
168
1.50k
    nodes_to_calc = IGRAPH_VIT_SIZE(vit);
169
170
1.50k
    if (reachable_count) {
171
1.50k
        IGRAPH_CHECK(igraph_vector_int_resize(reachable_count, nodes_to_calc));
172
1.50k
    }
173
174
1.50k
    if (all_reachable) {
175
1.50k
        *all_reachable = true; /* be optimistic */
176
1.50k
    }
177
178
1.50k
    IGRAPH_CHECK(igraph_2wheap_init(&Q, no_of_nodes));
179
1.50k
    IGRAPH_FINALLY(igraph_2wheap_destroy, &Q);
180
1.50k
    IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist, mode, IGRAPH_LOOPS));
181
1.50k
    IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist);
182
183
1.50k
    IGRAPH_VECTOR_INIT_FINALLY(&dist, no_of_nodes);
184
1.50k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&which, no_of_nodes);
185
186
1.50k
    IGRAPH_CHECK(igraph_vector_resize(res, nodes_to_calc));
187
1.50k
    igraph_vector_null(res);
188
189
67.8k
    for (i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) {
190
191
66.3k
        igraph_int_t source = IGRAPH_VIT_GET(vit);
192
66.3k
        igraph_2wheap_clear(&Q);
193
66.3k
        igraph_2wheap_push_with_index(&Q, source, -1.0); /* reserved */
194
66.3k
        VECTOR(which)[source] = i + 1;
195
66.3k
        VECTOR(dist)[source] = 1.0;     /* actual distance is zero but we need to store distance + 1 */
196
66.3k
        nodes_reached = 0;
197
198
348k
        while (!igraph_2wheap_empty(&Q)) {
199
281k
            igraph_int_t minnei = igraph_2wheap_max_index(&Q);
200
            /* Now check all neighbors of minnei for a shorter path */
201
281k
            igraph_vector_int_t *neis = igraph_lazy_inclist_get(&inclist, minnei);
202
281k
            igraph_int_t nlen;
203
204
281k
            IGRAPH_CHECK_OOM(neis, "Failed to query incident edges.");
205
206
281k
            nlen = igraph_vector_int_size(neis);
207
281k
            mindist = -igraph_2wheap_delete_max(&Q);
208
209
281k
            if (cutoff >= 0 && (mindist - 1.0) > cutoff) {
210
7.73k
                continue;    /* NOT break!!! */
211
7.73k
            }
212
213
274k
            VECTOR(*res)[i] += (mindist - 1.0);
214
274k
            nodes_reached++;
215
216
984k
            for (j = 0; j < nlen; j++) {
217
710k
                igraph_int_t edge = VECTOR(*neis)[j];
218
710k
                igraph_int_t to = IGRAPH_OTHER(graph, edge, minnei);
219
710k
                igraph_real_t altdist = mindist + VECTOR(*weights)[edge];
220
710k
                igraph_real_t curdist = VECTOR(dist)[to];
221
222
710k
                if (VECTOR(which)[to] != i + 1) {
223
                    /* First non-infinite distance */
224
215k
                    VECTOR(which)[to] = i + 1;
225
215k
                    VECTOR(dist)[to] = altdist;
226
215k
                    IGRAPH_CHECK(igraph_2wheap_push_with_index(&Q, to, -altdist));
227
494k
                } else if (curdist == 0 /* this means curdist is infinity */ || altdist < curdist) {
228
                    /* This is a shorter path */
229
41.6k
                    VECTOR(dist)[to] = altdist;
230
41.6k
                    igraph_2wheap_modify(&Q, to, -altdist);
231
41.6k
                }
232
710k
            }
233
234
274k
        } /* !igraph_2wheap_empty(&Q) */
235
236
66.3k
        if (reachable_count) {
237
66.3k
            VECTOR(*reachable_count)[i] = nodes_reached - 1;
238
66.3k
        }
239
240
66.3k
        if (normalized) {
241
            /* compute the inverse of the average distance, considering only reachable nodes */
242
66.3k
            VECTOR(*res)[i] = VECTOR(*res)[i] == 0 ? IGRAPH_NAN : ((igraph_real_t) (nodes_reached-1)) / VECTOR(*res)[i];
243
66.3k
        } else {
244
            /* compute the inverse of the sum of distances */
245
0
            VECTOR(*res)[i] = VECTOR(*res)[i] == 0 ? IGRAPH_NAN : 1.0 / VECTOR(*res)[i];
246
0
        }
247
248
66.3k
        if (all_reachable) {
249
66.3k
            if (nodes_reached < no_of_nodes) {
250
65.9k
                *all_reachable = false;
251
65.9k
            }
252
66.3k
        }
253
66.3k
    } /* !IGRAPH_VIT_END(vit) */
254
255
1.50k
    igraph_vector_int_destroy(&which);
256
1.50k
    igraph_vector_destroy(&dist);
257
1.50k
    igraph_lazy_inclist_destroy(&inclist);
258
1.50k
    igraph_2wheap_destroy(&Q);
259
1.50k
    igraph_vit_destroy(&vit);
260
1.50k
    IGRAPH_FINALLY_CLEAN(5);
261
262
1.50k
    return IGRAPH_SUCCESS;
263
1.50k
}
264
265
/**
266
 * \ingroup structural
267
 * \function igraph_closeness_cutoff
268
 * \brief Range limited closeness centrality.
269
 *
270
 * This function computes a range-limited version of closeness centrality
271
 * by considering only those shortest paths whose length is no greater
272
 * then the given cutoff value.
273
 *
274
 * \param graph The graph object.
275
 * \param res The result of the computation, a vector containing the
276
 *        range-limited closeness centrality scores for the given vertices.
277
 * \param reachable_count If not \c NULL, this vector will contain the number of
278
 *        vertices reachable within the cutoff distance from each vertex for which
279
 *        the range-limited closeness is calculated (not including that vertex).
280
 * \param all_reachable Pointer to a Boolean. If not \c NULL, it indicates if all
281
 *        vertices of the graph were reachable from each vertex in \p vids within
282
 *        the given cutoff distance.
283
 * \param vids The vertices for which the range limited closeness centrality
284
 *             will be computed.
285
 * \param mode The type of shortest paths to be used for the
286
 *        calculation in directed graphs. Possible values:
287
 *        \clist
288
 *        \cli IGRAPH_OUT
289
 *          the lengths of the outgoing paths are calculated.
290
 *        \cli IGRAPH_IN
291
 *          the lengths of the incoming paths are calculated.
292
 *        \cli IGRAPH_ALL
293
 *          the directed graph is considered as an
294
 *          undirected one for the computation.
295
 *        \endclist
296
 * \param weights An optional vector containing edge weights for
297
 *        weighted closeness. No edge weight may be NaN. Supply a null
298
 *        pointer here for traditional, unweighted closeness.
299
 * \param normalized If true, the inverse of the mean distance to vertices
300
 *        reachable within the cutoff is returned. If false, the inverse
301
 *        of the sum of distances is returned.
302
 * \param cutoff The maximal length of paths that will be considered.
303
 *        If negative or \ref IGRAPH_UNLIMITED, the exact closeness will be
304
 *        calculated (no upper limit on path lengths).
305
 * \return Error code:
306
 *        \clist
307
 *        \cli IGRAPH_ENOMEM
308
 *           not enough memory for temporary data.
309
 *        \cli IGRAPH_EINVVID
310
 *           invalid vertex ID passed.
311
 *        \cli IGRAPH_EINVMODE
312
 *           invalid mode argument.
313
 *        \endclist
314
 *
315
 * Time complexity: At most O(n|E|) for the unweighted case and O(n|E|log|V|+|V|)
316
 * for the weighted case, where n is the number
317
 * of vertices for which the calculation is done, |V| is the number of vertices
318
 * and |E| is the number of edges in the graph. The timing decreases with smaller
319
 * cutoffs in a way that depends on the graph structure.
320
 *
321
 * \sa \ref igraph_closeness() to calculate the exact closeness centrality.
322
 */
323
324
igraph_error_t igraph_closeness_cutoff(const igraph_t *graph, igraph_vector_t *res,
325
                            igraph_vector_int_t *reachable_count, igraph_bool_t *all_reachable,
326
                            const igraph_vs_t vids, igraph_neimode_t mode,
327
                            const igraph_vector_t *weights,
328
                            igraph_bool_t normalized,
329
4.16k
                            igraph_real_t cutoff) {
330
331
4.16k
    igraph_int_t no_of_nodes = igraph_vcount(graph);
332
4.16k
    igraph_vector_int_t already_counted;
333
4.16k
    igraph_vector_int_t *neis;
334
4.16k
    igraph_int_t i, j;
335
4.16k
    igraph_int_t nodes_reached;
336
4.16k
    igraph_adjlist_t allneis;
337
338
4.16k
    igraph_int_t actdist = 0;
339
340
4.16k
    igraph_dqueue_int_t q;
341
342
4.16k
    igraph_int_t nodes_to_calc;
343
4.16k
    igraph_vit_t vit;
344
345
4.16k
    if (weights) {
346
1.50k
        return igraph_i_closeness_cutoff_weighted(graph, res, reachable_count, all_reachable, vids, mode, cutoff,
347
1.50k
                weights, normalized);
348
1.50k
    }
349
350
2.66k
    IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit));
351
2.66k
    IGRAPH_FINALLY(igraph_vit_destroy, &vit);
352
353
2.66k
    nodes_to_calc = IGRAPH_VIT_SIZE(vit);
354
355
2.66k
    if (reachable_count) {
356
2.66k
        IGRAPH_CHECK(igraph_vector_int_resize(reachable_count, nodes_to_calc));
357
2.66k
    }
358
359
2.66k
    if (all_reachable) {
360
2.66k
        *all_reachable = true; /* be optimistic */
361
2.66k
    }
362
363
2.66k
    if (mode != IGRAPH_OUT && mode != IGRAPH_IN && mode != IGRAPH_ALL) {
364
0
        IGRAPH_ERROR("Invalid mode for closeness.", IGRAPH_EINVMODE);
365
0
    }
366
367
2.66k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&already_counted, no_of_nodes);
368
2.66k
    IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100);
369
370
2.66k
    IGRAPH_CHECK(igraph_adjlist_init(graph, &allneis, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE));
371
2.66k
    IGRAPH_FINALLY(igraph_adjlist_destroy, &allneis);
372
373
2.66k
    IGRAPH_CHECK(igraph_vector_resize(res, nodes_to_calc));
374
2.66k
    igraph_vector_null(res);
375
376
2.66k
    for (IGRAPH_VIT_RESET(vit), i = 0;
377
118k
         !IGRAPH_VIT_END(vit);
378
115k
         IGRAPH_VIT_NEXT(vit), i++) {
379
115k
        nodes_reached = 0;
380
381
115k
        igraph_dqueue_int_clear(&q);
382
115k
        IGRAPH_CHECK(igraph_dqueue_int_push(&q, IGRAPH_VIT_GET(vit)));
383
115k
        IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0));
384
115k
        VECTOR(already_counted)[IGRAPH_VIT_GET(vit)] = i + 1;
385
386
115k
        IGRAPH_PROGRESS("Closeness: ", 100.0 * i / nodes_to_calc, NULL);
387
115k
        IGRAPH_ALLOW_INTERRUPTION();
388
389
881k
        while (!igraph_dqueue_int_empty(&q)) {
390
765k
            igraph_int_t act = igraph_dqueue_int_pop(&q);
391
765k
            actdist = igraph_dqueue_int_pop(&q);
392
393
765k
            if (cutoff >= 0 && actdist > cutoff) {
394
41.8k
                continue;    /* NOT break!!! */
395
41.8k
            }
396
397
723k
            VECTOR(*res)[i] += actdist;
398
723k
            nodes_reached++;
399
400
            /* check the neighbors */
401
723k
            neis = igraph_adjlist_get(&allneis, act);
402
723k
            igraph_int_t nei_count = igraph_vector_int_size(neis);
403
3.11M
            for (j = 0; j < nei_count; j++) {
404
2.39M
                igraph_int_t neighbor = VECTOR(*neis)[j];
405
2.39M
                if (VECTOR(already_counted)[neighbor] == i + 1) {
406
1.74M
                    continue;
407
1.74M
                }
408
650k
                VECTOR(already_counted)[neighbor] = i + 1;
409
650k
                IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor));
410
650k
                IGRAPH_CHECK(igraph_dqueue_int_push(&q, actdist + 1));
411
650k
            }
412
723k
        }
413
414
115k
        if (reachable_count) {
415
115k
            VECTOR(*reachable_count)[i] = nodes_reached - 1;
416
115k
        }
417
418
115k
        if (normalized) {
419
            /* compute the inverse of the average distance, considering only reachable nodes */
420
115k
            VECTOR(*res)[i] = VECTOR(*res)[i] == 0 ? IGRAPH_NAN : ((igraph_real_t) (nodes_reached-1)) / VECTOR(*res)[i];
421
115k
        } else {
422
            /* compute the inverse of the sum of distances */
423
0
            VECTOR(*res)[i] = VECTOR(*res)[i] == 0 ? IGRAPH_NAN : 1.0 / VECTOR(*res)[i];
424
0
        }
425
426
115k
        if (all_reachable) {
427
115k
            if (nodes_reached < no_of_nodes) {
428
114k
                *all_reachable = false;
429
114k
            }
430
115k
        }
431
115k
    }
432
433
2.66k
    IGRAPH_PROGRESS("Closeness: ", 100.0, NULL);
434
435
    /* Clean */
436
2.66k
    igraph_dqueue_int_destroy(&q);
437
2.66k
    igraph_vector_int_destroy(&already_counted);
438
2.66k
    igraph_vit_destroy(&vit);
439
2.66k
    igraph_adjlist_destroy(&allneis);
440
2.66k
    IGRAPH_FINALLY_CLEAN(4);
441
442
2.66k
    return IGRAPH_SUCCESS;
443
2.66k
}
444
445
446
/***** Harmonic centrality *****/
447
448
static igraph_error_t igraph_i_harmonic_centrality_unweighted(const igraph_t *graph, igraph_vector_t *res,
449
                                                   const igraph_vs_t vids, igraph_neimode_t mode,
450
                                                   igraph_bool_t normalized,
451
2.66k
                                                   igraph_real_t cutoff) {
452
453
2.66k
    igraph_int_t no_of_nodes = igraph_vcount(graph);
454
2.66k
    igraph_vector_int_t already_counted;
455
2.66k
    igraph_vector_int_t *neis;
456
2.66k
    igraph_int_t i, j;
457
2.66k
    igraph_adjlist_t allneis;
458
459
2.66k
    igraph_int_t actdist = 0;
460
461
2.66k
    igraph_dqueue_int_t q;
462
463
2.66k
    igraph_int_t nodes_to_calc;
464
2.66k
    igraph_vit_t vit;
465
466
2.66k
    IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit));
467
2.66k
    IGRAPH_FINALLY(igraph_vit_destroy, &vit);
468
469
2.66k
    nodes_to_calc = IGRAPH_VIT_SIZE(vit);
470
471
2.66k
    if (mode != IGRAPH_OUT && mode != IGRAPH_IN &&
472
1.33k
        mode != IGRAPH_ALL) {
473
0
        IGRAPH_ERROR("Invalid mode for harmonic centrality.", IGRAPH_EINVMODE);
474
0
    }
475
476
2.66k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&already_counted, no_of_nodes);
477
2.66k
    IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100);
478
479
2.66k
    IGRAPH_CHECK(igraph_adjlist_init(graph, &allneis, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE));
480
2.66k
    IGRAPH_FINALLY(igraph_adjlist_destroy, &allneis);
481
482
2.66k
    IGRAPH_CHECK(igraph_vector_resize(res, nodes_to_calc));
483
2.66k
    igraph_vector_null(res);
484
485
2.66k
    for (IGRAPH_VIT_RESET(vit), i = 0;
486
118k
         !IGRAPH_VIT_END(vit);
487
115k
         IGRAPH_VIT_NEXT(vit), i++)
488
115k
    {
489
115k
        igraph_int_t source = IGRAPH_VIT_GET(vit);
490
491
115k
        igraph_dqueue_int_clear(&q);
492
115k
        IGRAPH_CHECK(igraph_dqueue_int_push(&q, source));
493
115k
        IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0));
494
115k
        VECTOR(already_counted)[source] = i + 1;
495
496
115k
        IGRAPH_PROGRESS("Harmonic centrality: ", 100.0 * i / nodes_to_calc, NULL);
497
115k
        IGRAPH_ALLOW_INTERRUPTION();
498
499
881k
        while (!igraph_dqueue_int_empty(&q)) {
500
765k
            igraph_int_t act = igraph_dqueue_int_pop(&q);
501
765k
            actdist = igraph_dqueue_int_pop(&q);
502
503
765k
            if (cutoff >= 0 && actdist > cutoff) {
504
41.8k
                continue;    /* NOT break!!! */
505
41.8k
            }
506
507
            /* Exclude self-distance, which is zero. */
508
723k
            if (source != act) {
509
608k
                VECTOR(*res)[i] += 1.0/actdist;
510
608k
            }
511
512
            /* check the neighbors */
513
723k
            neis = igraph_adjlist_get(&allneis, act);
514
723k
            igraph_int_t nei_count = igraph_vector_int_size(neis);
515
3.10M
            for (j = 0; j < nei_count; j++) {
516
2.38M
                igraph_int_t neighbor = VECTOR(*neis)[j];
517
2.38M
                if (VECTOR(already_counted)[neighbor] == i + 1) {
518
1.73M
                    continue;
519
1.73M
                }
520
650k
                VECTOR(already_counted)[neighbor] = i + 1;
521
650k
                IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor));
522
650k
                IGRAPH_CHECK(igraph_dqueue_int_push(&q, actdist + 1));
523
650k
            }
524
723k
        }
525
115k
    }
526
527
2.66k
    if (normalized && no_of_nodes > 1 /* not a null graph or singleton graph */) {
528
2.64k
        igraph_vector_scale(res, 1.0 / (no_of_nodes - 1));
529
2.64k
    }
530
531
2.66k
    IGRAPH_PROGRESS("Harmonic centrality: ", 100.0, NULL);
532
533
    /* Clean */
534
2.66k
    igraph_dqueue_int_destroy(&q);
535
2.66k
    igraph_vector_int_destroy(&already_counted);
536
2.66k
    igraph_vit_destroy(&vit);
537
2.66k
    igraph_adjlist_destroy(&allneis);
538
2.66k
    IGRAPH_FINALLY_CLEAN(4);
539
540
2.66k
    return IGRAPH_SUCCESS;
541
2.66k
}
542
543
544
static igraph_error_t igraph_i_harmonic_centrality_weighted(const igraph_t *graph,
545
                                                 igraph_vector_t *res,
546
                                                 const igraph_vs_t vids,
547
                                                 igraph_neimode_t mode,
548
                                                 const igraph_vector_t *weights,
549
                                                 igraph_bool_t normalized,
550
1.50k
                                                 igraph_real_t cutoff) {
551
552
    /* See igraph_distances_dijkstra() for the implementation
553
       details and the dirty tricks. */
554
555
1.50k
    igraph_int_t no_of_nodes = igraph_vcount(graph);
556
1.50k
    igraph_int_t no_of_edges = igraph_ecount(graph);
557
558
1.50k
    igraph_2wheap_t Q;
559
1.50k
    igraph_vit_t vit;
560
1.50k
    igraph_int_t nodes_to_calc;
561
562
1.50k
    igraph_lazy_inclist_t inclist;
563
1.50k
    igraph_int_t i, j;
564
565
1.50k
    igraph_vector_t dist;
566
1.50k
    igraph_vector_int_t which;
567
568
1.50k
    igraph_real_t mindist = 0;
569
570
1.50k
    if (igraph_vector_size(weights) != no_of_edges) {
571
0
        IGRAPH_ERROR("Invalid weight vector length.", IGRAPH_EINVAL);
572
0
    }
573
574
1.50k
    if (no_of_edges > 0) {
575
1.42k
        igraph_real_t minweight = igraph_vector_min(weights);
576
1.42k
        if (minweight <= 0) {
577
0
            IGRAPH_ERROR("Weight vector must be positive.", IGRAPH_EINVAL);
578
1.42k
        } else if (isnan(minweight)) {
579
0
            IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL);
580
0
        }
581
1.42k
    }
582
583
1.50k
    IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit));
584
1.50k
    IGRAPH_FINALLY(igraph_vit_destroy, &vit);
585
586
1.50k
    nodes_to_calc = IGRAPH_VIT_SIZE(vit);
587
588
1.50k
    IGRAPH_CHECK(igraph_2wheap_init(&Q, no_of_nodes));
589
1.50k
    IGRAPH_FINALLY(igraph_2wheap_destroy, &Q);
590
1.50k
    IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist, mode, IGRAPH_LOOPS));
591
1.50k
    IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist);
592
593
1.50k
    IGRAPH_VECTOR_INIT_FINALLY(&dist, no_of_nodes);
594
1.50k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&which, no_of_nodes);
595
596
1.50k
    IGRAPH_CHECK(igraph_vector_resize(res, nodes_to_calc));
597
1.50k
    igraph_vector_null(res);
598
599
67.8k
    for (i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) {
600
601
66.3k
        igraph_int_t source = IGRAPH_VIT_GET(vit);
602
66.3k
        igraph_2wheap_clear(&Q);
603
66.3k
        igraph_2wheap_push_with_index(&Q, source, -1.0); /* reserved */
604
66.3k
        VECTOR(which)[source] = i + 1;
605
66.3k
        VECTOR(dist)[source] = 1.0;     /* actual distance is zero but we need to store distance + 1 */
606
607
348k
        while (!igraph_2wheap_empty(&Q)) {
608
281k
            igraph_int_t minnei = igraph_2wheap_max_index(&Q);
609
            /* Now check all neighbors of minnei for a shorter path */
610
281k
            igraph_vector_int_t *neis = igraph_lazy_inclist_get(&inclist, minnei);
611
281k
            igraph_int_t nlen;
612
613
281k
            IGRAPH_CHECK_OOM(neis, "Failed to query incident edges.");
614
615
281k
            nlen = igraph_vector_int_size(neis);
616
281k
            mindist = -igraph_2wheap_delete_max(&Q);
617
618
281k
            if (cutoff >= 0 && (mindist - 1.0) > cutoff) {
619
7.75k
                continue;    /* NOT break!!! */
620
7.75k
            }
621
622
            /* Exclude self-distance, which is zero. */
623
274k
            if (source != minnei) {
624
207k
                VECTOR(*res)[i] += 1.0 / (mindist - 1.0);
625
207k
            }
626
627
986k
            for (j = 0; j < nlen; j++) {
628
711k
                igraph_int_t edge = VECTOR(*neis)[j];
629
711k
                igraph_int_t to = IGRAPH_OTHER(graph, edge, minnei);
630
711k
                igraph_real_t altdist = mindist + VECTOR(*weights)[edge];
631
711k
                igraph_real_t curdist = VECTOR(dist)[to];
632
633
711k
                if (VECTOR(which)[to] != i + 1) {
634
                    /* First non-infinite distance */
635
215k
                    VECTOR(which)[to] = i + 1;
636
215k
                    VECTOR(dist)[to] = altdist;
637
215k
                    IGRAPH_CHECK(igraph_2wheap_push_with_index(&Q, to, -altdist));
638
496k
                } else if (curdist == 0 /* this means curdist is infinity */ || altdist < curdist) {
639
                    /* This is a shorter path */
640
41.6k
                    VECTOR(dist)[to] = altdist;
641
41.6k
                    igraph_2wheap_modify(&Q, to, -altdist);
642
41.6k
                }
643
711k
            }
644
645
274k
        } /* !igraph_2wheap_empty(&Q) */
646
647
66.3k
    } /* !IGRAPH_VIT_END(vit) */
648
649
1.50k
    if (normalized && no_of_nodes > 1 /* not a null graph or singleton graph */) {
650
1.48k
        igraph_vector_scale(res, 1.0 / (no_of_nodes - 1));
651
1.48k
    }
652
653
1.50k
    igraph_vector_int_destroy(&which);
654
1.50k
    igraph_vector_destroy(&dist);
655
1.50k
    igraph_lazy_inclist_destroy(&inclist);
656
1.50k
    igraph_2wheap_destroy(&Q);
657
1.50k
    igraph_vit_destroy(&vit);
658
1.50k
    IGRAPH_FINALLY_CLEAN(5);
659
660
1.50k
    return IGRAPH_SUCCESS;
661
1.50k
}
662
663
664
/**
665
 * \ingroup structural
666
 * \function igraph_harmonic_centrality_cutoff
667
 * \brief Range limited harmonic centrality.
668
 *
669
 * This function computes the range limited version of harmonic centrality:
670
 * only those shortest paths are considered whose length is not above the given cutoff.
671
 * The inverse distance to vertices not reachable within the cutoff is considered
672
 * to be zero.
673
 *
674
 * \param graph The graph object.
675
 * \param res The result of the computation, a vector containing the
676
 *        range limited harmonic centrality scores for the given vertices.
677
 * \param vids The vertices for which the harmonic centrality will be computed.
678
 * \param mode The type of shortest paths to be used for the
679
 *        calculation in directed graphs. Possible values:
680
 *        \clist
681
 *        \cli IGRAPH_OUT
682
 *          the lengths of the outgoing paths are calculated.
683
 *        \cli IGRAPH_IN
684
 *          the lengths of the incoming paths are calculated.
685
 *        \cli IGRAPH_ALL
686
 *          the directed graph is considered as an
687
 *          undirected one for the computation.
688
 *        \endclist
689
 * \param weights An optional vector containing edge weights for
690
 *        weighted harmonic centrality. No edge weight may be NaN.
691
 *        If \c NULL, all weights are considered to be one.
692
 * \param normalized Boolean, whether to normalize the result. If true,
693
 *        the result is the mean inverse path length to other vertices.
694
 *        i.e. it is normalized by the number of vertices minus one.
695
 *        If false, the result is the sum of inverse path lengths to other
696
 *        vertices.
697
 * \param cutoff The maximal length of paths that will be considered.
698
 *        The inverse distance to vertices that are not reachable within
699
 *        the cutoff path length is considered to be zero.
700
 *        Supply a negative value to compute the exact harmonic centrality,
701
 *        without any upper limit on the length of paths.
702
 * \return Error code:
703
 *        \clist
704
 *        \cli IGRAPH_ENOMEM
705
 *           not enough memory for temporary data.
706
 *        \cli IGRAPH_EINVVID
707
 *           invalid vertex ID passed.
708
 *        \cli IGRAPH_EINVMODE
709
 *           invalid mode argument.
710
 *        \endclist
711
 *
712
 * Time complexity: At most O(n|E|) for the unweighted case and O(n|E|log|V|+|V|)
713
 * for the weighted case, where n is the number
714
 * of vertices for which the calculation is done, |V| is the number of vertices
715
 * and |E| is the number of edges in the graph. The timing decreases with smaller
716
 * cutoffs in a way that depends on the graph structure.
717
 *
718
 * \sa \ref igraph_harmonic_centrality() to calculate the exact harmonic centrality.
719
 * Other centrality types: \ref igraph_closeness(), \ref igraph_betweenness().
720
 */
721
722
igraph_error_t igraph_harmonic_centrality_cutoff(const igraph_t *graph, igraph_vector_t *res,
723
                                      const igraph_vs_t vids, igraph_neimode_t mode,
724
                                      const igraph_vector_t *weights,
725
                                      igraph_bool_t normalized,
726
4.16k
                                      igraph_real_t cutoff) {
727
4.16k
    if (weights) {
728
1.50k
        return igraph_i_harmonic_centrality_weighted(graph, res, vids, mode, weights, normalized, cutoff);
729
2.66k
    } else {
730
2.66k
        return igraph_i_harmonic_centrality_unweighted(graph, res, vids, mode, normalized, cutoff);
731
2.66k
    }
732
4.16k
}
733
734
735
/**
736
 * \ingroup structural
737
 * \function igraph_harmonic_centrality
738
 * \brief Harmonic centrality for some vertices.
739
 *
740
 * The harmonic centrality of a vertex is the mean inverse distance to
741
 * all other vertices. The inverse distance to an unreachable vertex
742
 * is considered to be zero.
743
 *
744
 * </para><para>
745
 * References:
746
 *
747
 * </para><para>
748
 * M. Marchiori and V. Latora, Harmony in the small-world, Physica A 285, pp. 539-546 (2000).
749
 * https://doi.org/10.1016/S0378-4371%2800%2900311-3
750
 *
751
 * </para><para>
752
 * Y. Rochat, Closeness Centrality Extended to Unconnected Graphs: the Harmonic Centrality Index, ASNA 2009.
753
 * https://infoscience.epfl.ch/record/200525
754
 *
755
 * </para><para>
756
 * S. Vigna and P. Boldi, Axioms for Centrality, Internet Mathematics 10, (2014).
757
 * https://doi.org/10.1080/15427951.2013.865686
758
 *
759
 * \param graph The graph object.
760
 * \param res The result of the computation, a vector containing the
761
 *        harmonic centrality scores for the given vertices.
762
 * \param vids The vertices for which the harmonic centrality will be computed.
763
 * \param mode The type of shortest paths to be used for the
764
 *        calculation in directed graphs. Possible values:
765
 *        \clist
766
 *        \cli IGRAPH_OUT
767
 *          the lengths of the outgoing paths are calculated.
768
 *        \cli IGRAPH_IN
769
 *          the lengths of the incoming paths are calculated.
770
 *        \cli IGRAPH_ALL
771
 *          the directed graph is considered as an
772
 *          undirected one for the computation.
773
 *        \endclist
774
 * \param weights An optional vector containing edge weights for
775
 *        weighted harmonic centrality. No edge weight may be NaN.
776
 *        If \c NULL, all weights are considered to be one.
777
 * \param normalized Boolean, whether to normalize the result. If true,
778
 *        the result is the mean inverse path length to other vertices,
779
 *        i.e. it is normalized by the number of vertices minus one.
780
 *        If false, the result is the sum of inverse path lengths to other
781
 *        vertices.
782
 * \return Error code:
783
 *        \clist
784
 *        \cli IGRAPH_ENOMEM
785
 *           not enough memory for temporary data.
786
 *        \cli IGRAPH_EINVVID
787
 *           invalid vertex ID passed.
788
 *        \cli IGRAPH_EINVMODE
789
 *           invalid mode argument.
790
 *        \endclist
791
 *
792
 * Time complexity: O(n|E|) for the unweighted case and O(n*|E|log|V|+|V|)
793
 * for the weighted case, where n is the number
794
 * of vertices for which the calculation is done, |V| is the number of vertices
795
 * and |E| is the number of edges in the graph.
796
 *
797
 * \sa Other centrality types: \ref igraph_closeness(), \ref igraph_degree(), \ref igraph_betweenness().
798
 */
799
800
igraph_error_t igraph_harmonic_centrality(const igraph_t *graph, igraph_vector_t *res,
801
                               const igraph_vs_t vids, igraph_neimode_t mode,
802
                               const igraph_vector_t *weights,
803
0
                               igraph_bool_t normalized) {
804
0
    return igraph_harmonic_centrality_cutoff(graph, res, vids, mode, weights, normalized, /* cutoff= */ -1);
805
0
}