Coverage Report

Created: 2025-11-21 06:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/igraph/src/paths/all_shortest_paths.c
Line
Count
Source
1
/*
2
   igraph library.
3
   Copyright (C) 2005-2025  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_paths.h"
20
21
#include "igraph_dqueue.h"
22
#include "igraph_interface.h"
23
#include "igraph_memory.h"
24
25
#include "core/interruption.h"
26
#include "paths/paths_internal.h"
27
28
#include <string.h>  /* memset */
29
30
/**
31
 * \function igraph_get_all_shortest_paths
32
 * \brief All shortest paths (geodesics) from a vertex.
33
 *
34
 * When there is more than one shortest path between two vertices,
35
 * all of them will be returned. Every edge is considered separately,
36
 * therefore in graphs with multi-edges, this function may produce
37
 * a very large number of results.
38
 *
39
 * \param graph The graph object.
40
 * \param vertices The result, the IDs of the vertices along the paths.
41
 *   This is a list of integer vectors where each element is an
42
 *   \ref igraph_vector_int_t object. Each vector object contains the vertices
43
 *   along a shortest path from \p from to another vertex. The vectors are
44
 *   ordered according to their target vertex: first the shortest paths to
45
 *   vertex 0, then to vertex 1, etc. No data is included for unreachable
46
 *   vertices. The list will be resized as needed. Supply a null pointer here
47
 *   if you don't need these vectors.
48
 * \param edges The result, the IDs of the edges along the paths.
49
 *   This is a list of integer vectors where each element is an
50
 *   \ref igraph_vector_int_t object. Each vector object contains the edges
51
 *   along a shortest path from \p from to another vertex. The vectors are
52
 *   ordered according to their target vertex: first the shortest paths to
53
 *   vertex 0, then to vertex 1, etc. No data is included for unreachable
54
 *   vertices. The list will be resized as needed. Supply a null pointer here
55
 *   if you don't need these vectors.
56
 * \param nrgeo Pointer to an initialized \ref igraph_vector_int_t object or
57
 *   \c NULL. If not \c NULL the number of shortest paths from \p from are
58
 *   stored here for every vertex in the graph. Note that the values
59
 *   will be accurate only for those vertices that are in the target
60
 *   vertex sequence (see \p to), since the search terminates as soon
61
 *   as all the target vertices have been found.
62
 * \param from The id of the vertex from/to which the geodesics are
63
 *        calculated.
64
 * \param to Vertex sequence with the IDs of the vertices to/from which the
65
 *        shortest paths will be calculated. A vertex might be given multiple
66
 *        times.
67
 * \param mode The type of shortest paths to be use for the
68
 *        calculation in directed graphs. Possible values:
69
 *        \clist
70
 *        \cli IGRAPH_OUT
71
 *          the lengths of the outgoing paths are calculated.
72
 *        \cli IGRAPH_IN
73
 *          the lengths of the incoming paths are calculated.
74
 *        \cli IGRAPH_ALL
75
 *          the directed graph is considered as an
76
 *          undirected one for the computation.
77
 *        \endclist
78
 * \return Error code:
79
 *        \clist
80
 *        \cli IGRAPH_ENOMEM
81
 *           not enough memory for temporary data.
82
 *        \cli IGRAPH_EINVVID
83
 *           \p from is invalid vertex ID.
84
 *        \cli IGRAPH_EINVMODE
85
 *           invalid mode argument.
86
 *        \endclist
87
 *
88
 * Added in version 0.2.</para><para>
89
 *
90
 * Time complexity: O(|V|+|E|) for most graphs, O(|V|^2) in the worst
91
 * case.
92
 */
93
igraph_error_t igraph_get_all_shortest_paths(
94
        const igraph_t *graph,
95
        const igraph_vector_t *weights,
96
        igraph_vector_int_list_t *vertices,
97
        igraph_vector_int_list_t *edges,
98
        igraph_vector_int_t *nrgeo,
99
        igraph_int_t from, const igraph_vs_t to,
100
3.62k
        igraph_neimode_t mode) {
101
102
3.62k
    if (weights == NULL) {
103
        /* Unweighted version */
104
3.62k
        return igraph_i_get_all_shortest_paths_unweighted(
105
3.62k
            graph, vertices, edges, nrgeo, from, to, mode
106
3.62k
        );
107
3.62k
    } else {
108
        /* Weighted version */
109
0
        return igraph_get_all_shortest_paths_dijkstra(
110
0
            graph, vertices, edges, nrgeo, from, to, weights, mode
111
0
        );
112
0
    }
113
3.62k
}
114
115
igraph_error_t igraph_i_get_all_shortest_paths_unweighted(
116
        const igraph_t *graph,
117
        igraph_vector_int_list_t *vertices,
118
        igraph_vector_int_list_t *edges,
119
        igraph_vector_int_t *nrgeo,
120
        igraph_int_t from, const igraph_vs_t to,
121
3.62k
        igraph_neimode_t mode) {
122
123
3.62k
    const igraph_int_t no_of_nodes = igraph_vcount(graph);
124
3.62k
    igraph_int_t *geodist;
125
3.62k
    igraph_vector_int_list_t paths;
126
3.62k
    igraph_vector_int_list_t path_edge;
127
3.62k
    igraph_dqueue_int_t q;
128
3.62k
    igraph_vector_int_t *vptr;
129
3.62k
    igraph_vector_int_t *vptr_e;
130
3.62k
    igraph_vector_int_t neis;
131
3.62k
    igraph_vector_int_t ptrlist;
132
3.62k
    igraph_vector_int_t ptrhead;
133
3.62k
    igraph_int_t n;
134
3.62k
    igraph_int_t to_reach, reached = 0, maxdist = 0;
135
136
3.62k
    igraph_vit_t vit;
137
138
3.62k
    if (from < 0 || from >= no_of_nodes) {
139
0
        IGRAPH_ERROR("Index of source vertex is out of range.", IGRAPH_EINVVID);
140
0
    }
141
3.62k
    if (mode != IGRAPH_OUT && mode != IGRAPH_IN &&
142
3.62k
        mode != IGRAPH_ALL) {
143
0
        IGRAPH_ERROR("Invalid mode argument.", IGRAPH_EINVMODE);
144
0
    }
145
146
3.62k
    IGRAPH_CHECK(igraph_vit_create(graph, to, &vit));
147
3.62k
    IGRAPH_FINALLY(igraph_vit_destroy, &vit);
148
149
    /* paths will store the shortest paths during the search */
150
3.62k
    IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&paths, 0);
151
    /* path_edge will store the shortest paths during the search */
152
3.62k
    IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&path_edge, 0);
153
    /* neis is a temporary vector holding the neighbors of the
154
     * node being examined */
155
3.62k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0);
156
    /* ptrlist stores indices into the paths vector, in the order
157
     * of how they were found. ptrhead is a second-level index that
158
     * will be used to find paths that terminate in a given vertex */
159
3.62k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&ptrlist, 0);
160
    /* ptrhead contains indices into ptrlist.
161
     * ptrhead[i] = j means that element #j-1 in ptrlist contains
162
     * the shortest path from the root to node i. ptrhead[i] = 0
163
     * means that node i was not reached so far */
164
3.62k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&ptrhead, no_of_nodes);
165
    /* geodist[i] == 0 if i was not reached yet and it is not in the
166
     * target vertex sequence, or -1 if i was not reached yet and it
167
     * is in the target vertex sequence. Otherwise it is
168
     * one larger than the length of the shortest path from the
169
     * source */
170
3.62k
    geodist = IGRAPH_CALLOC(no_of_nodes, igraph_int_t);
171
3.62k
    IGRAPH_CHECK_OOM(geodist, "Insufficient memory for calculating shortest paths.");
172
3.62k
    IGRAPH_FINALLY(igraph_free, geodist);
173
    /* dequeue to store the BFS queue -- odd elements are the vertex indices,
174
     * even elements are the distances from the root */
175
3.62k
    IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100);
176
177
3.62k
    if (nrgeo) {
178
3.62k
        IGRAPH_CHECK(igraph_vector_int_resize(nrgeo, no_of_nodes));
179
3.62k
        igraph_vector_int_null(nrgeo);
180
3.62k
    }
181
182
    /* use geodist to count how many vertices we have to reach */
183
3.62k
    to_reach = IGRAPH_VIT_SIZE(vit);
184
630k
    for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) {
185
627k
        if (geodist[ IGRAPH_VIT_GET(vit) ] == 0) {
186
627k
            geodist[ IGRAPH_VIT_GET(vit) ] = -1;
187
627k
        } else {
188
0
            to_reach--;       /* this node was given multiple times */
189
0
        }
190
627k
    }
191
192
3.62k
    if (geodist[ from ] < 0) {
193
3.62k
        reached++;
194
3.62k
    }
195
196
    /* from -> from */
197
3.62k
    IGRAPH_CHECK(igraph_vector_int_list_push_back_new(&paths, &vptr));
198
3.62k
    IGRAPH_CHECK(igraph_vector_int_push_back(vptr, from));
199
3.62k
    IGRAPH_CHECK(igraph_vector_int_list_push_back_new(&path_edge, &vptr_e));
200
201
3.62k
    geodist[from] = 1;
202
3.62k
    VECTOR(ptrhead)[from] = 1;
203
3.62k
    IGRAPH_CHECK(igraph_vector_int_push_back(&ptrlist, 0));
204
3.62k
    if (nrgeo) {
205
3.62k
        VECTOR(*nrgeo)[from] = 1;
206
3.62k
    }
207
208
    /* Init queue */
209
3.62k
    IGRAPH_CHECK(igraph_dqueue_int_push(&q, from));
210
3.62k
    IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0));
211
63.5k
    while (!igraph_dqueue_int_empty(&q)) {
212
60.2k
        igraph_int_t actnode = igraph_dqueue_int_pop(&q);
213
60.2k
        igraph_int_t actdist = igraph_dqueue_int_pop(&q);
214
215
60.2k
        IGRAPH_ALLOW_INTERRUPTION();
216
217
60.2k
        if (reached >= to_reach) {
218
            /* all nodes were reached. Since we need all the shortest paths
219
             * to all these nodes, we can stop the search only if the distance
220
             * of the current node to the root is larger than the distance of
221
             * any of the nodes we wanted to reach */
222
827
            if (actdist > maxdist) {
223
                /* safety check, maxdist should have been set when we reached the last node */
224
281
                IGRAPH_ASSERT(maxdist >= 0);
225
281
                break;
226
281
            }
227
827
        }
228
229
        /* If we need the edge-paths, we need to use igraph_incident() followed by an
230
         * IGRAPH_OTHER() macro in the main loop. This is going to be slower than
231
         * using igraph_neighbors() due to branch mispredictions in IGRAPH_OTHER(), so we
232
         * use igraph_incident() only if the user needs the edge-paths */
233
59.9k
        if (edges) {
234
59.9k
            IGRAPH_CHECK(igraph_incident(graph, &neis, actnode, mode, IGRAPH_LOOPS));
235
59.9k
        } else {
236
0
            IGRAPH_CHECK(igraph_neighbors(graph, &neis, actnode, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE));
237
0
        }
238
239
59.9k
        n = igraph_vector_int_size(&neis);
240
190k
        for (igraph_int_t j = 0; j < n; j++) {
241
130k
            igraph_int_t neighbor;
242
130k
            igraph_int_t parentptr;
243
244
130k
            if (edges) {
245
                /* user needs the edge-paths, so 'neis' contains edge IDs, we need to resolve
246
                 * the next edge ID into a vertex ID */
247
130k
                neighbor = IGRAPH_OTHER(graph, VECTOR(neis)[j], actnode);
248
130k
            } else {
249
                /* user does not need the edge-paths, so 'neis' contains vertex IDs */
250
0
                neighbor = VECTOR(neis)[j];
251
0
            }
252
253
130k
            if (geodist[neighbor] > 0 &&
254
73.1k
                geodist[neighbor] - 1 < actdist + 1) {
255
                /* this node was reached via a shorter path before */
256
68.0k
                continue;
257
68.0k
            }
258
259
            /* yay, found another shortest path to neighbor */
260
261
61.9k
            if (nrgeo) {
262
                /* the number of geodesics leading to neighbor must be
263
                 * increased by the number of geodesics leading to actnode */
264
61.9k
                VECTOR(*nrgeo)[neighbor] += VECTOR(*nrgeo)[actnode];
265
61.9k
            }
266
61.9k
            if (geodist[neighbor] <= 0) {
267
                /* this node was not reached yet, push it into the queue */
268
56.9k
                IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor));
269
56.9k
                IGRAPH_CHECK(igraph_dqueue_int_push(&q, actdist + 1));
270
56.9k
                if (geodist[neighbor] < 0) {
271
56.9k
                    reached++;
272
56.9k
                }
273
56.9k
                if (reached == to_reach) {
274
281
                    maxdist = actdist;
275
281
                }
276
56.9k
            }
277
61.9k
            geodist[neighbor] = actdist + 2;
278
279
            /* copy all existing paths to the parent */
280
61.9k
            parentptr = VECTOR(ptrhead)[actnode];
281
686k
            while (parentptr != 0) {
282
                /* allocate a new igraph_vector_int_t at the end of paths */
283
624k
                IGRAPH_CHECK(igraph_vector_int_list_push_back_new(&paths, &vptr));
284
624k
                IGRAPH_CHECK(igraph_vector_int_update(vptr, igraph_vector_int_list_get_ptr(&paths, parentptr - 1)));
285
624k
                IGRAPH_CHECK(igraph_vector_int_push_back(vptr, neighbor));
286
287
624k
                IGRAPH_CHECK(igraph_vector_int_list_push_back_new(&path_edge, &vptr_e));
288
624k
                if (actnode != from) {
289
                    /* If the previous vertex was the source then there is no edge to add*/
290
615k
                    IGRAPH_CHECK(igraph_vector_int_update(vptr_e, igraph_vector_int_list_get_ptr(&path_edge, parentptr - 1)));
291
615k
                }
292
624k
                IGRAPH_CHECK(igraph_vector_int_push_back(vptr_e, VECTOR(neis)[j]));
293
294
624k
                IGRAPH_CHECK(igraph_vector_int_push_back(&ptrlist, VECTOR(ptrhead)[neighbor]));
295
624k
                VECTOR(ptrhead)[neighbor] = igraph_vector_int_size(&ptrlist);
296
297
624k
                parentptr = VECTOR(ptrlist)[parentptr - 1];
298
624k
            }
299
61.9k
        }
300
59.9k
    }
301
302
3.62k
    igraph_dqueue_int_destroy(&q);
303
3.62k
    IGRAPH_FINALLY_CLEAN(1);
304
305
    /* mark the nodes for which we need the result */
306
3.62k
    memset(geodist, 0, sizeof(geodist[0]) * (size_t) no_of_nodes);
307
630k
    for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) {
308
627k
        geodist[ IGRAPH_VIT_GET(vit) ] = 1;
309
627k
    }
310
311
3.62k
    if (vertices) {
312
3.62k
        igraph_vector_int_list_clear(vertices);
313
3.62k
    }
314
3.62k
    if (edges) {
315
3.62k
        igraph_vector_int_list_clear(edges);
316
3.62k
    }
317
318
630k
    for (igraph_int_t i = 0; i < no_of_nodes; i++) {
319
627k
        igraph_int_t parentptr = VECTOR(ptrhead)[i];
320
321
627k
        IGRAPH_ALLOW_INTERRUPTION();
322
323
        /* do we need the paths leading to vertex i? */
324
627k
        if (geodist[i] > 0) {
325
            /* yes, transfer them to the result vector */
326
1.25M
            while (parentptr != 0) {
327
                /* Given two vector lists, list1 and list2, an efficient way to transfer
328
                 * a vector from list1 to the end of list2 is to extend list2 with an
329
                 * empty vector, then swap that empty vector with the given element of
330
                 * list1. This approach avoids creating a full copy of the vector. */
331
627k
                if (vertices) {
332
627k
                    igraph_vector_int_t *p;
333
627k
                    IGRAPH_CHECK(igraph_vector_int_list_push_back_new(vertices, &p));
334
627k
                    igraph_vector_int_swap(p, igraph_vector_int_list_get_ptr(&paths, parentptr - 1));
335
627k
                }
336
627k
                if (edges) {
337
627k
                    igraph_vector_int_t *p;
338
627k
                    IGRAPH_CHECK(igraph_vector_int_list_push_back_new(edges, &p));
339
627k
                    igraph_vector_int_swap(p, igraph_vector_int_list_get_ptr(&path_edge, parentptr - 1));
340
627k
                }
341
627k
                parentptr = VECTOR(ptrlist)[parentptr - 1];
342
627k
            }
343
627k
        }
344
627k
    }
345
346
3.62k
    IGRAPH_FREE(geodist);
347
3.62k
    igraph_vector_int_destroy(&ptrlist);
348
3.62k
    igraph_vector_int_destroy(&ptrhead);
349
3.62k
    igraph_vector_int_destroy(&neis);
350
3.62k
    igraph_vector_int_list_destroy(&paths);
351
3.62k
    igraph_vector_int_list_destroy(&path_edge);
352
3.62k
    igraph_vit_destroy(&vit);
353
3.62k
    IGRAPH_FINALLY_CLEAN(7);
354
355
3.62k
    return IGRAPH_SUCCESS;
356
3.62k
}