Coverage Report

Created: 2025-12-05 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/igraph/src/connectivity/reachability.c
Line
Count
Source
1
/*
2
   igraph library.
3
   Copyright (C) 2024  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_reachability.h"
20
21
#include "igraph_adjlist.h"
22
#include "igraph_bitset_list.h"
23
#include "igraph_components.h"
24
#include "igraph_constructors.h"
25
#include "igraph_interface.h"
26
27
28
/**
29
 * \ingroup structural
30
 * \function igraph_reachability
31
 * \brief Calculates which vertices are reachable from each vertex in the graph.
32
 *
33
 * The resulting list will contain one bitset for each strongly connected component.
34
 * The bitset for component i will have its j-th bit set, if vertex j is reachable
35
 * from some vertex in component i in 0 or more steps.
36
 * In particular, a vertex is always reachable from itself.
37
 *
38
 * \param graph The graph object to analyze.
39
 * \param membership Pointer to an integer vector. For every vertex,
40
 *    the ID of its component is given. The vector will be resized as needed.
41
 *    This parameter must not be \c NULL.
42
 * \param csize Pointer to an integer vector or \c NULL. For every component, it
43
 *    gives its size (vertex count), the order being defined by the component
44
 *    IDs. The vector will be resized as needed.
45
 * \param no_of_components Pointer to an integer or \c NULL. The number of
46
 *    components will be stored here.
47
 * \param reach A list of bitsets representing the result. It will be resized
48
 *    as needed. <code>reach[membership[u]][v]</code> is set to \c true if
49
 *    vertex \c v is reachable from vertex \c u.
50
 * \param mode In directed graphs, controls the treatment of edge directions.
51
 *    Ignored in undirected graphs. With \c IGRAPH_OUT, reachability is computed
52
 *    by traversing edges along their direction. With \c IGRAPH_IN, edges are
53
 *    traversed opposite to their direction. With \c IGRAPH_ALL, edge directions
54
 *    are ignored and the graph is treated as undirected.
55
 * \return Error code:
56
 *         \c IGRAPH_ENOMEM if there is not enough memory
57
 *         to perform the operation.
58
 *
59
 * \sa \ref igraph_connected_components() to find the connnected components
60
 * of a graph; \ref igraph_count_reachable() to count how many vertices
61
 * are reachable from each vertex; \ref igraph_subcomponent() to find
62
 * which vertices are rechable from a single vertex.
63
 *
64
 * Time complexity: O(|C||V|/w + |V| + |E|), where
65
 * |C| is the number of strongly connected components (at most |V|),
66
 * |V| is the number of vertices, and
67
 * |E| is the number of edges respectively,
68
 * and w is the bit width of \type igraph_int_t, typically the
69
 * word size of the machine (32 or 64).
70
 */
71
72
igraph_error_t igraph_reachability(
73
        const igraph_t *graph,
74
        igraph_vector_int_t *membership,
75
        igraph_vector_int_t *csize,
76
        igraph_int_t *no_of_components,
77
        igraph_bitset_list_t *reach,
78
3.49k
        igraph_neimode_t mode) {
79
80
3.49k
    const igraph_int_t no_of_nodes = igraph_vcount(graph);
81
3.49k
    igraph_int_t no_of_comps;
82
3.49k
    igraph_adjlist_t adjlist, dag;
83
84
3.49k
    if (mode != IGRAPH_ALL && mode != IGRAPH_OUT && mode != IGRAPH_IN) {
85
0
        IGRAPH_ERROR("Invalid mode for reachability.", IGRAPH_EINVMODE);
86
0
    }
87
88
3.49k
    if (! igraph_is_directed(graph)) {
89
0
        mode = IGRAPH_ALL;
90
0
    }
91
92
3.49k
    IGRAPH_CHECK(igraph_connected_components(graph,
93
3.49k
                                             membership, csize, &no_of_comps,
94
3.49k
                                             mode == IGRAPH_ALL ? IGRAPH_WEAK : IGRAPH_STRONG));
95
96
3.49k
    if (no_of_components) {
97
0
        *no_of_components = no_of_comps;
98
0
    }
99
100
3.49k
    IGRAPH_CHECK(igraph_bitset_list_resize(reach, no_of_comps));
101
102
680k
    for (igraph_int_t comp = 0; comp < no_of_comps; comp++) {
103
676k
        IGRAPH_CHECK(igraph_bitset_resize(igraph_bitset_list_get_ptr(reach, comp), no_of_nodes));
104
676k
    }
105
706k
    for (igraph_int_t v = 0; v < no_of_nodes; v++) {
106
702k
        IGRAPH_BIT_SET(*igraph_bitset_list_get_ptr(reach, VECTOR(*membership)[v]), v);
107
702k
    }
108
109
3.49k
    if (mode == IGRAPH_ALL) {
110
0
        return IGRAPH_SUCCESS;
111
0
    }
112
113
3.49k
    IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, mode, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE));
114
3.49k
    IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist);
115
116
3.49k
    IGRAPH_CHECK(igraph_adjlist_init_empty(&dag, no_of_comps));
117
3.49k
    IGRAPH_FINALLY(igraph_adjlist_destroy, &dag);
118
119
706k
    for (igraph_int_t v = 0; v < no_of_nodes; v++) {
120
702k
        const igraph_vector_int_t *neighbours = igraph_adjlist_get(&adjlist, v);
121
702k
        igraph_vector_int_t *dag_neighbours = igraph_adjlist_get(&dag, VECTOR(*membership)[v]);
122
702k
        const igraph_int_t n = igraph_vector_int_size(neighbours);
123
827k
        for (igraph_int_t i = 0; i < n; i++) {
124
124k
            igraph_int_t w = VECTOR(*neighbours)[i];
125
124k
            if (VECTOR(*membership)[v] != VECTOR(*membership)[w]) {
126
56.8k
                IGRAPH_CHECK(igraph_vector_int_push_back(dag_neighbours, VECTOR(*membership)[w]));
127
56.8k
            }
128
124k
        }
129
702k
    }
130
131
    /* Iterate through strongly connected components in reverser topological order,
132
     * exploiting the fact that they are indexed in topological order. */
133
680k
    for (igraph_int_t i = 0; i < no_of_comps; i++) {
134
676k
        const igraph_int_t comp = mode == IGRAPH_IN ? i : no_of_comps - i - 1;
135
676k
        const igraph_vector_int_t *dag_neighbours = igraph_adjlist_get(&dag, comp);
136
676k
        igraph_bitset_t *from_bitset = igraph_bitset_list_get_ptr(reach, comp);
137
676k
        const igraph_int_t n = igraph_vector_int_size(dag_neighbours);
138
733k
        for (igraph_int_t j = 0; j < n; j++) {
139
56.8k
            const igraph_bitset_t *to_bitset = igraph_bitset_list_get_ptr(reach, VECTOR(*dag_neighbours)[j]);
140
56.8k
            igraph_bitset_or(from_bitset, from_bitset, to_bitset);
141
56.8k
        }
142
676k
    }
143
144
3.49k
    igraph_adjlist_destroy(&adjlist);
145
3.49k
    igraph_adjlist_destroy(&dag);
146
3.49k
    IGRAPH_FINALLY_CLEAN(2);
147
148
3.49k
    return IGRAPH_SUCCESS;
149
3.49k
}
150
151
152
/**
153
 * \ingroup structural
154
 * \function igraph_count_reachable
155
 * \brief The number of vertices reachable from each vertex in the graph.
156
 *
157
 * \param graph The graph object to analyze.
158
 * \param counts Integer vector. <code>counts[v]</code> will store the number
159
 *    of vertices reachable from vertex \c v, including \c v itself.
160
 * \param mode In directed graphs, controls the treatment of edge directions.
161
 *    Ignored in undirected graphs. With \c IGRAPH_OUT, reachability is computed
162
 *    by traversing edges along their direction. With \c IGRAPH_IN, edges are
163
 *    traversed opposite to their direction. With \c IGRAPH_ALL, edge directions
164
 *    are ignored and the graph is treated as undirected.
165
 * \return Error code:
166
 *         \c IGRAPH_ENOMEM if there is not enough memory
167
 *         to perform the operation.
168
 *
169
 * \sa \ref igraph_connected_components(), \ref igraph_transitive_closure()
170
 *
171
 * Time complexity: O(|C||V|/w + |V| + |E|), where
172
 * |C| is the number of strongly connected components (at most |V|),
173
 * |V| is the number of vertices, and
174
 * |E| is the number of edges respectively,
175
 * and w is the bit width of \type igraph_int_t, typically the
176
 * word size of the machine (32 or 64).
177
 */
178
179
igraph_error_t igraph_count_reachable(const igraph_t *graph,
180
                                      igraph_vector_int_t *counts,
181
1.74k
                                      igraph_neimode_t mode) {
182
183
1.74k
    igraph_vector_int_t membership;
184
1.74k
    igraph_int_t no_of_nodes = igraph_vcount(graph);
185
1.74k
    igraph_bitset_list_t reach;
186
187
1.74k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&membership, 0);
188
1.74k
    IGRAPH_BITSET_LIST_INIT_FINALLY(&reach, 0);
189
190
1.74k
    IGRAPH_CHECK(igraph_reachability(graph, &membership, NULL, NULL, &reach, mode));
191
192
1.74k
    IGRAPH_CHECK(igraph_vector_int_resize(counts, igraph_vcount(graph)));
193
353k
    for (igraph_int_t i = 0; i < no_of_nodes; i++) {
194
351k
        VECTOR(*counts)[i] = igraph_bitset_popcount(igraph_bitset_list_get_ptr(&reach, VECTOR(membership)[i]));
195
351k
    }
196
197
1.74k
    igraph_bitset_list_destroy(&reach);
198
1.74k
    igraph_vector_int_destroy(&membership);
199
1.74k
    IGRAPH_FINALLY_CLEAN(2);
200
201
1.74k
    return IGRAPH_SUCCESS;
202
1.74k
}
203
204
205
/**
206
 * \ingroup structural
207
 * \function igraph_transitive_closure
208
 * \brief Computes the transitive closure of a graph.
209
 *
210
 * The resulting graph will have an edge from vertex \c i to vertex \c j
211
 * if \c j is reachable from \c i.
212
 *
213
 * \param graph The graph object to analyze.
214
 * \param closure The resulting graph representing the transitive closure.
215
 * \return Error code:
216
 *         \c IGRAPH_ENOMEM if there is not enough memory
217
 *         to perform the operation.
218
 *
219
 * \sa \ref igraph_connected_components(), \ref igraph_count_reachable()
220
 *
221
 * Time complexity: O(|V|^2 + |E|), where
222
 * |V| is the number of vertices, and
223
 * |E| is the number of edges, respectively.
224
 */
225
1.74k
igraph_error_t igraph_transitive_closure(const igraph_t *graph, igraph_t *closure) {
226
1.74k
    const igraph_int_t no_of_nodes = igraph_vcount(graph);
227
1.74k
    const igraph_bool_t directed = igraph_is_directed(graph);
228
1.74k
    igraph_vector_int_t membership, edges;
229
1.74k
    igraph_bitset_list_t reach;
230
231
1.74k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&membership, 0);
232
1.74k
    IGRAPH_BITSET_LIST_INIT_FINALLY(&reach, 0);
233
234
1.74k
    IGRAPH_CHECK(igraph_reachability(graph, &membership, NULL, NULL, &reach, IGRAPH_OUT));
235
236
1.74k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0);
237
353k
    for (igraph_int_t u = 0; u < no_of_nodes; u++) {
238
351k
        const igraph_bitset_t *row = igraph_bitset_list_get_ptr(&reach, VECTOR(membership)[u]);
239
84.1M
        for (igraph_int_t v = directed ? 0 : u + 1; v < no_of_nodes; v++) {
240
83.7M
            if (u != v && IGRAPH_BIT_TEST(*row, v)) {
241
1.07M
                IGRAPH_CHECK(igraph_vector_int_push_back(&edges, u));
242
1.07M
                IGRAPH_CHECK(igraph_vector_int_push_back(&edges, v));
243
1.07M
            }
244
83.7M
        }
245
351k
    }
246
247
1.74k
    igraph_bitset_list_destroy(&reach);
248
1.74k
    igraph_vector_int_destroy(&membership);
249
1.74k
    IGRAPH_FINALLY_CLEAN(2);
250
251
1.74k
    IGRAPH_CHECK(igraph_create(closure, &edges, no_of_nodes, directed));
252
253
1.74k
    igraph_vector_int_destroy(&edges);
254
1.74k
    IGRAPH_FINALLY_CLEAN(1);
255
256
1.74k
    return IGRAPH_SUCCESS;
257
1.74k
}