Coverage Report

Created: 2025-11-09 06:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/igraph/src/paths/floyd_warshall.c
Line
Count
Source
1
/*
2
   igraph library.
3
   Copyright (C) 2022-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
#include "igraph_interface.h"
21
#include "igraph_stack.h"
22
23
#include "core/interruption.h"
24
#include "internal/utils.h"
25
#include "paths/paths_internal.h"
26
27
0
static igraph_error_t distances_floyd_warshall_original(igraph_matrix_t *res) {
28
29
0
    igraph_int_t no_of_nodes = igraph_matrix_nrow(res);
30
31
0
    for (igraph_int_t k = 0; k < no_of_nodes; k++) {
32
0
        IGRAPH_ALLOW_INTERRUPTION();
33
34
        /* Iteration order matters for performance!
35
         * First j, then i, because matrices are stored as column-major. */
36
0
        for (igraph_int_t j = 0; j < no_of_nodes; j++) {
37
0
            igraph_real_t dkj = MATRIX(*res, k, j);
38
0
            if (dkj == IGRAPH_INFINITY) {
39
0
                continue;
40
0
            }
41
42
0
            for (igraph_int_t i = 0; i < no_of_nodes; i++) {
43
0
                igraph_real_t di = MATRIX(*res, i, k) + dkj;
44
0
                igraph_real_t dd = MATRIX(*res, i, j);
45
0
                if (di < dd) {
46
0
                    MATRIX(*res, i, j) = di;
47
0
                }
48
0
                if (i == j && MATRIX(*res, i, i) < 0) {
49
0
                    IGRAPH_ERROR("Negative cycle found while calculating distances with Floyd-Warshall.",
50
0
                                 IGRAPH_ENEGCYCLE);
51
0
                }
52
0
            }
53
0
        }
54
0
    }
55
56
0
    return IGRAPH_SUCCESS;
57
0
}
58
59
60
0
static igraph_error_t distances_floyd_warshall_tree(igraph_matrix_t *res) {
61
62
    /* This is the "Tree" algorithm of Brodnik et al.
63
     * A difference from the paper is that instead of using the OUT_k tree of shortest
64
     * paths _starting_ in k, we use the IN_k tree of shortest paths _ending_ in k.
65
     * This makes it easier to iterate through matrices in column-major order,
66
     * i.e. storage order, thus increasing performance. */
67
68
0
    igraph_int_t no_of_nodes = igraph_matrix_nrow(res);
69
70
    /* successors[v][u] is the second vertex on the shortest path from v to u,
71
       i.e. the parent of v in the IN_u tree. */
72
0
    igraph_matrix_int_t successors;
73
0
    IGRAPH_MATRIX_INT_INIT_FINALLY(&successors, no_of_nodes, no_of_nodes);
74
75
    /* children[children_start[u] + i] is the i-th child of u in a tree of shortest paths
76
       rooted at k, and ending in k, in the main loop below (IN_k). There are no_of_nodes-1
77
       child vertices in total, as the root vertex is excluded. This is essentially a contiguously
78
       stored adjacency list representation of IN_k. */
79
0
    igraph_vector_int_t children;
80
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&children, no_of_nodes-1);
81
82
    /* children_start[u] indicates where the children of u are stored in children[].
83
       These are effectively the cumulative sums of no_of_children[], with the first
84
       element being 0. The last element, children_start[no_of_nodes], is equal to the
85
       total number of children in the tree, i.e. no_of_nodes-1. */
86
0
    igraph_vector_int_t children_start;
87
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&children_start, no_of_nodes+1);
88
89
    /* no_of_children[u] is the number of children that u has in IN_k in the main loop below. */
90
0
    igraph_vector_int_t no_of_children;
91
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&no_of_children, no_of_nodes);
92
93
    /* dfs_traversal and dfs_skip arrays for running time optimization,
94
       see "Practical improvement" in Section 3.1 of the paper */
95
0
    igraph_vector_int_t dfs_traversal;
96
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&dfs_traversal, no_of_nodes);
97
0
    igraph_vector_int_t dfs_skip;
98
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&dfs_skip, no_of_nodes);
99
100
0
    igraph_stack_int_t stack;
101
0
    IGRAPH_STACK_INT_INIT_FINALLY(&stack, no_of_nodes);
102
103
0
    for (igraph_int_t u = 0; u < no_of_nodes; u++) {
104
0
        for (igraph_int_t v = 0; v < no_of_nodes; v++) {
105
0
            MATRIX(successors, v, u) = u;
106
0
        }
107
0
    }
108
109
0
    for (igraph_int_t k = 0; k < no_of_nodes; k++) {
110
0
        IGRAPH_ALLOW_INTERRUPTION();
111
112
        /* Count the children of each node in the shortest path tree, assuming that at
113
           this point all elements of no_of_children[] are zeros. */
114
0
        for (igraph_int_t v = 0; v < no_of_nodes; v++) {
115
0
            if (v == k) continue;
116
0
            igraph_int_t parent = MATRIX(successors, v, k);
117
0
            VECTOR(no_of_children)[parent]++;
118
0
        }
119
120
        /* Note: we do not use igraph_vector_int_cumsum() here as that function produces
121
           an output vector of the same length as the input vector. Here we need an output
122
           one longer, with a 0 being prepended to what vector_cumsum() would produce. */
123
0
        igraph_int_t cumsum = 0;
124
0
        for (igraph_int_t v = 0; v < no_of_nodes; v++) {
125
0
            VECTOR(children_start)[v] = cumsum;
126
0
            cumsum += VECTOR(no_of_children)[v];
127
0
        }
128
0
        VECTOR(children_start)[no_of_nodes] = cumsum;
129
130
        /* Constructing the tree IN_k (as in the paper) and representing it
131
           as a contiguously stored adjacency list. The entries of the no_of_children
132
           vector as re-used as an index of where to insert child node indices.
133
           At the end of the calculation, all elements of no_of_children[] will be zeros,
134
           making this vector ready for the next iteration of the outer loop. */
135
0
        for (igraph_int_t v = 0; v < no_of_nodes; v++) {
136
0
            if (v == k) continue;
137
0
            igraph_int_t parent = MATRIX(successors, v, k);
138
0
            VECTOR(no_of_children)[parent]--;
139
0
            VECTOR(children)[ VECTOR(children_start)[parent] + VECTOR(no_of_children)[parent] ] = v;
140
0
        }
141
142
        /* constructing dfs-traversal and dfs-skip arrays for the IN_k tree */
143
0
        IGRAPH_CHECK(igraph_stack_int_push(&stack, k));
144
0
        igraph_int_t counter = 0;
145
0
        while (!igraph_stack_int_empty(&stack)) {
146
0
            igraph_int_t parent = igraph_stack_int_pop(&stack);
147
0
            if (parent >= 0) {
148
0
                VECTOR(dfs_traversal)[counter] = parent;
149
0
                counter++;
150
                /* a negative marker -parent - 1 that is popped right after
151
                   all the descendants of the parent were processed */
152
0
                IGRAPH_CHECK(igraph_stack_int_push(&stack, -parent - 1));
153
0
                for (igraph_int_t l = VECTOR(children_start)[parent]; l < VECTOR(children_start)[parent + 1]; l++) {
154
0
                    IGRAPH_CHECK(igraph_stack_int_push(&stack, VECTOR(children)[l]));
155
0
                }
156
0
            } else {
157
0
                VECTOR(dfs_skip)[-(parent + 1)] = counter;
158
0
            }
159
0
        }
160
161
        /* main inner loop */
162
0
        for (igraph_int_t i = 0; i < no_of_nodes; i++) {
163
0
            igraph_real_t dki = MATRIX(*res, k, i);
164
0
            if (dki == IGRAPH_INFINITY || i == k) {
165
0
                continue;
166
0
            }
167
0
            igraph_int_t counter = 1;
168
0
            while (counter < no_of_nodes) {
169
0
                igraph_int_t j = VECTOR(dfs_traversal)[counter];
170
0
                igraph_real_t di = MATRIX(*res, j, k) + dki;
171
0
                igraph_real_t dd = MATRIX(*res, j, i);
172
0
                if (di < dd) {
173
0
                    MATRIX(*res, j, i) = di;
174
0
                    MATRIX(successors, j, i) = MATRIX(successors, j, k);
175
0
                    counter++;
176
0
                } else {
177
0
                    counter = VECTOR(dfs_skip)[j];
178
0
                }
179
0
                if (i == j && MATRIX(*res, i, i) < 0) {
180
0
                    IGRAPH_ERROR("Negative cycle found while calculating distances with Floyd-Warshall.",
181
0
                                 IGRAPH_ENEGCYCLE);
182
0
                }
183
0
            }
184
0
        }
185
0
    }
186
187
0
    igraph_stack_int_destroy(&stack);
188
0
    igraph_vector_int_destroy(&dfs_traversal);
189
0
    igraph_vector_int_destroy(&dfs_skip);
190
0
    igraph_vector_int_destroy(&no_of_children);
191
0
    igraph_vector_int_destroy(&children_start);
192
0
    igraph_vector_int_destroy(&children);
193
0
    igraph_matrix_int_destroy(&successors);
194
0
    IGRAPH_FINALLY_CLEAN(7);
195
196
0
    return IGRAPH_SUCCESS;
197
0
}
198
199
/**
200
 * \function igraph_distances_floyd_warshall
201
 * \brief Weighted all-pairs shortest path lengths with the Floyd-Warshall algorithm.
202
 *
203
 * The Floyd-Warshall algorithm computes weighted shortest path lengths between
204
 * all pairs of vertices at the same time. It is useful with very dense weighted graphs,
205
 * as its running time is primarily determined by the vertex count, and is not sensitive
206
 * to the graph density. In sparse graphs, other methods such as the Dijkstra or
207
 * Bellman-Ford algorithms will perform significantly better.
208
 *
209
 * </para><para>
210
 * In addition to the original Floyd-Warshall algorithm, igraph contains implementations
211
 * of variants that offer better asymptotic complexity as well as better practical
212
 * running times for most instances. See the reference below for more information.
213
 *
214
 * </para><para>
215
 * Note that internally this function always computes the distance matrix
216
 * for all pairs of vertices. The \p from and \p to parameters only serve
217
 * to subset this matrix, but do not affect the time or memory taken by the
218
 * calculation.
219
 *
220
 * </para><para>
221
 * Reference:
222
 *
223
 * </para><para>
224
 * Brodnik, A., Grgurovič, M., Požar, R.:
225
 * Modifications of the Floyd-Warshall algorithm with nearly quadratic expected-time,
226
 * Ars Mathematica Contemporanea, vol. 22, issue 1, p. #P1.01 (2021).
227
 * https://doi.org/10.26493/1855-3974.2467.497
228
 *
229
 * \param graph The graph object.
230
 * \param res An intialized matrix, the distances will be stored here.
231
 * \param from The source vertices.
232
 * \param to The target vertices.
233
 * \param weights The edge weights. If \c NULL, all weights are assumed to be 1.
234
 *   Negative weights are allowed, but the graph must not contain negative cycles.
235
 *   Edges with positive infinite weights are ignored.
236
 * \param mode The type of shortest paths to be use for the
237
 *        calculation in directed graphs. Possible values:
238
 *        \clist
239
 *        \cli IGRAPH_OUT
240
 *          the outgoing paths are calculated.
241
 *        \cli IGRAPH_IN
242
 *          the incoming paths are calculated.
243
 *        \cli IGRAPH_ALL
244
 *          the directed graph is considered as an
245
 *          undirected one for the computation.
246
 *        \endclist
247
 * \param method The type of the algorithm used.
248
 *        \clist
249
 *        \cli IGRAPH_FLOYD_WARSHALL_AUTOMATIC
250
 *          tries to select the best performing variant for the current graph;
251
 *          presently this option always uses the "Tree" method.
252
 *        \cli IGRAPH_FLOYD_WARSHALL_ORIGINAL
253
 *          the basic Floyd-Warshall algorithm.
254
 *        \cli IGRAPH_FLOYD_WARSHALL_TREE
255
 *          the "Tree" speedup of Brodnik et al., faster than the original algorithm
256
 *          in most cases.
257
 *        \endclist
258
 * \return Error code. \c IGRAPH_ENEGCYCLE is returned if a negative-weight
259
 *   cycle is found.
260
 *
261
 * \sa \ref igraph_distances(), \ref igraph_distances_dijkstra(),
262
 * \ref igraph_distances_bellman_ford(), \ref igraph_distances_johnson()
263
 *
264
 * Time complexity:
265
 * The original variant has complexity O(|V|^3 + |E|).
266
 * The "Tree" variant has expected-case complexity of O(|V|^2 log^2 |V|)
267
 * according to Brodnik et al., while its worst-time complexity remains O(|V|^3).
268
 * Here |V| denotes the number of vertices and |E| is the number of edges.
269
 */
270
igraph_error_t igraph_distances_floyd_warshall(
271
        const igraph_t *graph, igraph_matrix_t *res,
272
        igraph_vs_t from, igraph_vs_t to,
273
        const igraph_vector_t *weights, igraph_neimode_t mode,
274
0
        const igraph_floyd_warshall_algorithm_t method) {
275
276
0
    igraph_bool_t negative_weights;
277
0
    IGRAPH_CHECK(igraph_i_validate_distance_weights(graph, weights, &negative_weights));
278
0
    return igraph_i_distances_floyd_warshall(graph, res, from, to, weights, mode, method);
279
0
}
280
281
igraph_error_t igraph_i_distances_floyd_warshall(
282
        const igraph_t *graph, igraph_matrix_t *res,
283
        igraph_vs_t from, igraph_vs_t to,
284
        const igraph_vector_t *weights, igraph_neimode_t mode,
285
0
        const igraph_floyd_warshall_algorithm_t method) {
286
287
0
    igraph_int_t no_of_nodes = igraph_vcount(graph);
288
0
    igraph_int_t no_of_edges = igraph_ecount(graph);
289
0
    igraph_bool_t in = false, out = false;
290
291
0
    if (! igraph_is_directed(graph)) {
292
0
        mode = IGRAPH_ALL;
293
0
    }
294
295
0
    switch (mode) {
296
0
    case IGRAPH_ALL:
297
0
        in = out = true;
298
0
        break;
299
0
    case IGRAPH_OUT:
300
0
        out = true;
301
0
        break;
302
0
    case IGRAPH_IN:
303
0
        in = true;
304
0
        break;
305
0
    default:
306
0
        IGRAPH_ERROR("Invalid mode for Floyd-Warshall shortest path calculation.", IGRAPH_EINVMODE);
307
0
    }
308
309
0
    IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, no_of_nodes));
310
0
    igraph_matrix_fill(res, IGRAPH_INFINITY);
311
312
0
    for (igraph_int_t v = 0; v < no_of_nodes; v++) {
313
0
        MATRIX(*res, v, v) = 0;
314
0
    }
315
316
0
    for (igraph_int_t e = 0; e < no_of_edges; e++) {
317
0
        igraph_int_t from = IGRAPH_FROM(graph, e);
318
0
        igraph_int_t to = IGRAPH_TO(graph, e);
319
0
        igraph_real_t w = weights ? VECTOR(*weights)[e] : 1;
320
321
0
        if (w < 0) {
322
0
            if (mode == IGRAPH_ALL) {
323
0
                IGRAPH_ERRORF("Negative edge weight (%g) found in undirected graph "
324
0
                              "while calculating distances with Floyd-Warshall.",
325
0
                              IGRAPH_ENEGCYCLE, w);
326
0
            } else if (to == from) {
327
0
                IGRAPH_ERRORF("Self-loop with negative weight (%g) found "
328
0
                              "while calculating distances with Floyd-Warshall.",
329
0
                              IGRAPH_ENEGCYCLE, w);
330
0
            }
331
0
        } else if (w == IGRAPH_INFINITY) {
332
            /* Ignore edges with infinite weight */
333
0
            continue;
334
0
        }
335
336
0
        if (out && MATRIX(*res, from, to) > w) {
337
0
            MATRIX(*res, from, to) = w;
338
0
        }
339
0
        if (in  && MATRIX(*res, to, from) > w) {
340
0
            MATRIX(*res, to, from) = w;
341
0
        }
342
0
    }
343
344
    /* If there are zero or one vertices, nothing needs to be done.
345
     * This is special-cased so that at later stages we can rely on no_of_nodes - 1 >= 0. */
346
0
    if (no_of_nodes <= 1) {
347
0
        return IGRAPH_SUCCESS;
348
0
    }
349
350
0
    switch (method) {
351
0
    case IGRAPH_FLOYD_WARSHALL_ORIGINAL:
352
0
        IGRAPH_CHECK(distances_floyd_warshall_original(res));
353
0
        break;
354
0
    case IGRAPH_FLOYD_WARSHALL_AUTOMATIC:
355
0
    case IGRAPH_FLOYD_WARSHALL_TREE:
356
0
        IGRAPH_CHECK(distances_floyd_warshall_tree(res));
357
0
        break;
358
0
    default:
359
0
        IGRAPH_ERROR("Invalid method.", IGRAPH_EINVAL);
360
0
    }
361
362
0
    IGRAPH_CHECK(igraph_i_matrix_subset_vertices(res, graph, from, to));
363
364
0
    return IGRAPH_SUCCESS;
365
0
}