/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 | } |