Coverage Report

Created: 2025-10-12 06:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/igraph/src/paths/johnson.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_conversion.h"
22
#include "igraph_interface.h"
23
24
#include "math/safe_intop.h"
25
#include "paths/paths_internal.h"
26
27
28
/**
29
 * \function igraph_distances_johnson
30
 * \brief Weighted shortest path lengths between vertices, using Johnson's algorithm.
31
 *
32
 * This algorithm supports directed graphs with negative edge weights, and performs
33
 * better than the Bellman-Ford method when distances are calculated from many different
34
 * sources, the typical use case being all-pairs distance calculations. It works by using
35
 * a single-source Bellman-Ford run to transform all edge weights to non-negative ones,
36
 * then invoking Dijkstra's algorithm with the new weights. See the Wikipedia page
37
 * for more details: http://en.wikipedia.org/wiki/Johnson's_algorithm.
38
 *
39
 * </para><para>
40
 * If no edge weights are supplied, then the unweighted version, \ref igraph_distances()
41
 * is called. If none of the supplied edge weights are negative, then Dijkstra's algorithm
42
 * is used by calling \ref igraph_distances_dijkstra().
43
 *
44
 * </para><para>
45
 * Note that Johnson's algorithm applies only to directed graphs. This function rejects
46
 * undirected graphs with \em any negative edge weights, even when the \p from and \p to
47
 * vertices are all in connected components that are free of negative weights.
48
 *
49
 * </para><para>
50
 * References:
51
 *
52
 * </para><para>
53
 * Donald B. Johnson: Efficient Algorithms for Shortest Paths in Sparse Networks.
54
 * J. ACM 24, 1 (1977), 1–13.
55
 * https://doi.org/10.1145/321992.321993
56
 *
57
 * \param graph The input graph. If negative weights are present, it
58
 *   should be directed.
59
 * \param res Pointer to an initialized matrix, the result will be
60
 *   stored here, one line for each source vertex, one column for each
61
 *   target vertex.
62
 * \param from The source vertices.
63
 * \param to The target vertices. It is not allowed to include a
64
 *   vertex twice or more.
65
 * \param weights Optional edge weights. If it is a null-pointer, then
66
 *   the unweighted breadth-first search based \ref igraph_distances() will
67
 *   be called.  Edges with positive infinite weights are ignored.
68
 * \param mode For directed graphs; whether to follow paths along edge
69
 *    directions (\c IGRAPH_OUT), or the opposite (\c IGRAPH_IN), or
70
 *    ignore edge directions completely (\c IGRAPH_ALL). It is ignored
71
 *    for undirected graphs. \c IGRAPH_ALL should not be used with
72
 *    negative weights.
73
 * \return Error code.
74
 *
75
 * Time complexity: O(s|V|log|V|+|V||E|), |V| and |E| are the number
76
 * of vertices and edges, s is the number of source vertices.
77
 *
78
 * \sa \ref igraph_distances() for a faster unweighted version,
79
 * \ref igraph_distances_dijkstra() if you do not have negative
80
 * edge weights, \ref igraph_distances_bellman_ford() if you only
81
 * need to calculate shortest paths from a couple of sources.
82
 */
83
igraph_error_t igraph_distances_johnson(
84
        const igraph_t *graph,
85
        igraph_matrix_t *res,
86
        igraph_vs_t from, igraph_vs_t to,
87
        const igraph_vector_t *weights,
88
0
        igraph_neimode_t mode) {
89
90
0
    igraph_bool_t negative_weights;
91
0
    IGRAPH_CHECK(igraph_i_validate_distance_weights(graph, weights, &negative_weights));
92
0
    if (!negative_weights) {
93
        /* If no negative weights, then we can run Dijkstra's algorithm directly, without
94
         * needing to go through Johnson's procedure to eliminate negative weights. */
95
0
        return igraph_i_distances_dijkstra_cutoff(graph, res, from, to, weights, mode, -1);
96
0
    } else {
97
0
        return igraph_i_distances_johnson(graph, res, from, to, weights, mode);
98
0
    }
99
0
}
100
101
igraph_error_t igraph_i_distances_johnson(
102
        const igraph_t *graph,
103
        igraph_matrix_t *res,
104
        igraph_vs_t from, igraph_vs_t to,
105
        const igraph_vector_t *weights,
106
0
        igraph_neimode_t mode) {
107
108
0
    igraph_int_t no_of_nodes = igraph_vcount(graph);
109
0
    igraph_int_t no_of_edges = igraph_ecount(graph);
110
0
    igraph_t newgraph;
111
0
    igraph_vector_int_t edges;
112
0
    igraph_vector_t newweights;
113
0
    igraph_matrix_t bfres;
114
0
    igraph_int_t i, ptr;
115
0
    igraph_int_t nr, nc;
116
0
    igraph_vit_t fromvit;
117
0
    igraph_int_t no_edges_reserved;
118
119
    /* If no weights or no edges, then we can just run the unweighted version */
120
0
    if (!weights || no_of_edges == 0) {
121
0
        return igraph_i_distances_unweighted_cutoff(graph, res, from, to, mode, -1);
122
0
    }
123
124
0
    if (!igraph_is_directed(graph) || mode == IGRAPH_ALL) {
125
0
        IGRAPH_ERROR("Undirected graph with negative weight.",
126
0
                     IGRAPH_ENEGCYCLE);
127
0
    }
128
129
    /* ------------------------------------------------------------ */
130
    /* -------------------- Otherwise proceed --------------------- */
131
132
0
    IGRAPH_MATRIX_INIT_FINALLY(&bfres, 0, 0);
133
0
    IGRAPH_VECTOR_INIT_FINALLY(&newweights, 0);
134
135
0
    IGRAPH_CHECK(igraph_empty(&newgraph, no_of_nodes + 1, igraph_is_directed(graph)));
136
0
    IGRAPH_FINALLY(igraph_destroy, &newgraph);
137
138
0
    IGRAPH_SAFE_MULT(no_of_nodes, 2, &no_edges_reserved);
139
0
    IGRAPH_SAFE_ADD(no_edges_reserved, no_of_edges * 2, &no_edges_reserved);
140
141
    /* Add a new node to the graph, plus edges from it to all the others. */
142
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_edges_reserved);
143
0
    igraph_get_edgelist(graph, &edges, /*bycol=*/ 0); /* reserved */
144
0
    igraph_vector_int_resize(&edges, no_edges_reserved); /* reserved */
145
0
    if (mode == IGRAPH_OUT) {
146
0
        for (i = 0, ptr = no_of_edges * 2; i < no_of_nodes; i++) {
147
0
            VECTOR(edges)[ptr++] = no_of_nodes;
148
0
            VECTOR(edges)[ptr++] = i;
149
0
        }
150
0
    } else {
151
0
        for (i = 0, ptr = no_of_edges * 2; i < no_of_nodes; i++) {
152
0
            VECTOR(edges)[ptr++] = i;
153
0
            VECTOR(edges)[ptr++] = no_of_nodes;
154
0
        }
155
0
    }
156
0
    IGRAPH_CHECK(igraph_add_edges(&newgraph, &edges, 0));
157
0
    igraph_vector_int_destroy(&edges);
158
0
    IGRAPH_FINALLY_CLEAN(1);
159
160
0
    IGRAPH_CHECK(igraph_vector_reserve(&newweights, no_of_edges + no_of_nodes));
161
0
    igraph_vector_update(&newweights, weights); /* reserved */
162
0
    igraph_vector_resize(&newweights, no_of_edges + no_of_nodes); /* reserved */
163
0
    for (i = no_of_edges; i < no_of_edges + no_of_nodes; i++) {
164
0
        VECTOR(newweights)[i] = 0;
165
0
    }
166
167
    /* Run Bellman-Ford algorithm on the new graph, starting from the
168
       new vertex.  */
169
170
0
    IGRAPH_CHECK(igraph_i_distances_bellman_ford(
171
0
            &newgraph, &bfres,
172
0
            igraph_vss_1(no_of_nodes), igraph_vss_all(),
173
0
            &newweights, mode));
174
175
0
    igraph_destroy(&newgraph);
176
0
    IGRAPH_FINALLY_CLEAN(1);
177
178
    /* Now the edges of the original graph are reweighted, using the
179
       values from the BF algorithm. Instead of w(u,v) we will have
180
       w(u,v) + h(u) - h(v) */
181
182
0
    igraph_vector_resize(&newweights, no_of_edges); /* reserved */
183
0
    for (i = 0; i < no_of_edges; i++) {
184
0
        igraph_int_t ffrom = IGRAPH_FROM(graph, i);
185
0
        igraph_int_t tto = IGRAPH_TO(graph, i);
186
0
        if (mode == IGRAPH_OUT) {
187
0
            VECTOR(newweights)[i] += MATRIX(bfres, 0, ffrom) - MATRIX(bfres, 0, tto);
188
0
        } else {
189
0
            VECTOR(newweights)[i] += MATRIX(bfres, 0, tto) - MATRIX(bfres, 0, ffrom);
190
0
        }
191
192
        /* If a weight becomes slightly negative due to roundoff errors,
193
           snap it to exact zero. */
194
0
        if (VECTOR(newweights)[i] < 0) VECTOR(newweights)[i] = 0;
195
0
    }
196
197
    /* Run Dijkstra's algorithm on the new weights */
198
0
    IGRAPH_CHECK(igraph_i_distances_dijkstra_cutoff(
199
0
            graph, res,
200
0
            from, to,
201
0
            &newweights,
202
0
            mode, -1));
203
204
0
    igraph_vector_destroy(&newweights);
205
0
    IGRAPH_FINALLY_CLEAN(1);
206
207
    /* Reweight the shortest paths */
208
0
    nr = igraph_matrix_nrow(res);
209
0
    nc = igraph_matrix_ncol(res);
210
211
0
    IGRAPH_CHECK(igraph_vit_create(graph, from, &fromvit));
212
0
    IGRAPH_FINALLY(igraph_vit_destroy, &fromvit);
213
214
0
    for (i = 0; i < nr; i++, IGRAPH_VIT_NEXT(fromvit)) {
215
0
        igraph_int_t v1 = IGRAPH_VIT_GET(fromvit);
216
0
        if (igraph_vs_is_all(&to)) {
217
0
            igraph_int_t v2;
218
0
            for (v2 = 0; v2 < nc; v2++) {
219
0
                igraph_real_t sub;
220
0
                if (mode == IGRAPH_OUT) {
221
0
                    sub = MATRIX(bfres, 0, v1) - MATRIX(bfres, 0, v2);
222
0
                    MATRIX(*res, i, v2) -= sub;
223
0
                } else {
224
0
                    sub = MATRIX(bfres, 0, v2) - MATRIX(bfres, 0, v1);
225
0
                    MATRIX(*res, v2, i) -= sub;
226
0
                }
227
0
            }
228
0
        } else {
229
0
            igraph_int_t j;
230
0
            igraph_vit_t tovit;
231
0
            IGRAPH_CHECK(igraph_vit_create(graph, to, &tovit));
232
0
            IGRAPH_FINALLY(igraph_vit_destroy, &tovit);
233
0
            for (j = 0, IGRAPH_VIT_RESET(tovit); j < nc; j++, IGRAPH_VIT_NEXT(tovit)) {
234
0
                igraph_real_t sub;
235
0
                igraph_int_t v2 = IGRAPH_VIT_GET(tovit);
236
0
                if (mode == IGRAPH_OUT) {
237
0
                    sub = MATRIX(bfres, 0, v1) - MATRIX(bfres, 0, v2);
238
0
                    MATRIX(*res, i, j) -= sub;
239
0
                } else {
240
0
                    sub = MATRIX(bfres, 0, v2) - MATRIX(bfres, 0, v1);
241
0
                    MATRIX(*res, j, i) -= sub;
242
0
                }
243
0
            }
244
0
            igraph_vit_destroy(&tovit);
245
0
            IGRAPH_FINALLY_CLEAN(1);
246
0
        }
247
0
    }
248
249
0
    igraph_vit_destroy(&fromvit);
250
0
    igraph_matrix_destroy(&bfres);
251
0
    IGRAPH_FINALLY_CLEAN(2);
252
253
0
    return IGRAPH_SUCCESS;
254
0
}