Coverage Report

Created: 2026-06-09 06:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/igraph/src/properties/dag.c
Line
Count
Source
1
/*
2
  igraph library.
3
  Copyright (C) 2005-2024 The igraph development team <igraph@igraph.org>
4
5
  This program is free software; you can redistribute it and/or modify it under
6
  the terms of the GNU General Public License as published by the Free Software
7
  Foundation; either version 2 of the License, or (at your option) any later
8
  version.
9
10
  This program is distributed in the hope that it will be useful, but WITHOUT
11
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12
  FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
13
14
  You should have received a copy of the GNU General Public License along with
15
  this program. If not, see <https://www.gnu.org/licenses/>.
16
*/
17
18
#include "igraph_cycles.h"
19
20
#include "igraph_dqueue.h"
21
#include "igraph_interface.h"
22
23
/**
24
 * \function igraph_topological_sorting
25
 * \brief Calculate a possible topological sorting of the graph.
26
 *
27
 * </para><para>
28
 * A topological sorting of a directed acyclic graph (DAG) is a linear ordering
29
 * of its vertices where each vertex comes before all nodes to which it has
30
 * edges. Every DAG has at least one topological sort, and may have many.
31
 * This function returns one possible topological sort among them. If the
32
 * graph contains any cycles that are not self-loops, an error is raised.
33
 *
34
 * \param graph The input graph.
35
 * \param res Pointer to a vector, the result will be stored here.
36
 *   It will be resized if needed.
37
 * \param mode Specifies how to use the direction of the edges.
38
 *   For \c IGRAPH_OUT, the sorting order ensures that each vertex comes
39
 *   before all vertices to which it has edges, so vertices with no incoming
40
 *   edges go first. For \c IGRAPH_IN, it is quite the opposite: each
41
 *   vertex comes before all vertices from which it receives edges. Vertices
42
 *   with no outgoing edges go first.
43
 * \return Error code.
44
 *
45
 * Time complexity: O(|V|+|E|), where |V| and |E| are the number of
46
 * vertices and edges in the original input graph.
47
 *
48
 * \sa \ref igraph_is_dag() if you are only interested in whether a given
49
 *     graph is a DAG or not, or \ref igraph_feedback_arc_set() to find a
50
 *     set of edges whose removal makes the graph acyclic.
51
 *
52
 * \example examples/simple/igraph_topological_sorting.c
53
 */
54
igraph_error_t igraph_topological_sorting(
55
0
        const igraph_t* graph, igraph_vector_int_t *res, igraph_neimode_t mode) {
56
57
    /* Note: This function ignores self-loops, there it cannot
58
     * use the IGRAPH_PROP_IS_DAG property cache entry. */
59
60
0
    igraph_int_t no_of_nodes = igraph_vcount(graph);
61
0
    igraph_vector_int_t degrees;
62
0
    igraph_vector_int_t neis;
63
0
    igraph_dqueue_int_t sources;
64
0
    igraph_neimode_t deg_mode;
65
0
    igraph_int_t node, i, j;
66
67
0
    if (mode == IGRAPH_ALL || !igraph_is_directed(graph)) {
68
0
        IGRAPH_ERROR("Topological sorting does not make sense for undirected graphs.",
69
0
                     IGRAPH_EINVAL);
70
0
    } else if (mode == IGRAPH_OUT) {
71
0
        deg_mode = IGRAPH_IN;
72
0
    } else if (mode == IGRAPH_IN) {
73
0
        deg_mode = IGRAPH_OUT;
74
0
    } else {
75
0
        IGRAPH_ERROR("Invalid mode for topological sorting.", IGRAPH_EINVMODE);
76
0
    }
77
78
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&degrees, no_of_nodes);
79
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0);
80
0
    IGRAPH_CHECK(igraph_dqueue_int_init(&sources, 0));
81
0
    IGRAPH_FINALLY(igraph_dqueue_int_destroy, &sources);
82
0
    IGRAPH_CHECK(igraph_degree(graph, &degrees, igraph_vss_all(), deg_mode, IGRAPH_NO_LOOPS));
83
84
0
    igraph_vector_int_clear(res);
85
86
    /* Do we have nodes with no incoming vertices? */
87
0
    for (i = 0; i < no_of_nodes; i++) {
88
0
        if (VECTOR(degrees)[i] == 0) {
89
0
            IGRAPH_CHECK(igraph_dqueue_int_push(&sources, i));
90
0
        }
91
0
    }
92
93
    /* Take all nodes with no incoming vertices and remove them */
94
0
    while (!igraph_dqueue_int_empty(&sources)) {
95
0
        node = igraph_dqueue_int_pop(&sources);
96
        /* Add the node to the result vector */
97
0
        IGRAPH_CHECK(igraph_vector_int_push_back(res, node));
98
        /* Exclude the node from further source searches */
99
0
        VECTOR(degrees)[node] = -1;
100
        /* Get the neighbors and decrease their degrees by one */
101
0
        IGRAPH_CHECK(igraph_neighbors(graph, &neis, node, mode, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE));
102
0
        j = igraph_vector_int_size(&neis);
103
0
        for (i = 0; i < j; i++) {
104
0
            VECTOR(degrees)[ VECTOR(neis)[i] ]--;
105
0
            if (VECTOR(degrees)[ VECTOR(neis)[i] ] == 0) {
106
0
                IGRAPH_CHECK(igraph_dqueue_int_push(&sources, VECTOR(neis)[i]));
107
0
            }
108
0
        }
109
0
    }
110
111
0
    igraph_vector_int_destroy(&degrees);
112
0
    igraph_vector_int_destroy(&neis);
113
0
    igraph_dqueue_int_destroy(&sources);
114
0
    IGRAPH_FINALLY_CLEAN(3);
115
116
0
    if (igraph_vector_int_size(res) < no_of_nodes) {
117
0
        IGRAPH_ERROR("The graph has cycles; "
118
0
                     "topological sorting is only possible in acyclic graphs.",
119
0
                     IGRAPH_EINVAL);
120
0
    }
121
122
0
    return IGRAPH_SUCCESS;
123
0
}
124
125
/**
126
 * \function igraph_is_dag
127
 * \brief Checks whether a graph is a directed acyclic graph (DAG).
128
 *
129
 * </para><para>
130
 * A directed acyclic graph (DAG) is a directed graph with no cycles.
131
 *
132
 * </para><para>
133
 * This function returns \c false for undirected graphs.
134
 *
135
 * </para><para>
136
 * The return value of this function is cached in the graph itself; calling
137
 * the function multiple times with no modifications to the graph in between
138
 * will return a cached value in O(1) time.
139
 *
140
 * \param graph The input graph.
141
 * \param res Pointer to a boolean constant, the result
142
 *     is stored here.
143
 * \return Error code.
144
 *
145
 * Time complexity: O(|V|+|E|), where |V| and |E| are the number of
146
 * vertices and edges in the original input graph.
147
 *
148
 * \sa \ref igraph_topological_sorting() to get a possible topological
149
 *     sorting of a DAG.
150
 */
151
0
igraph_error_t igraph_is_dag(const igraph_t* graph, igraph_bool_t *res) {
152
0
    igraph_int_t no_of_nodes = igraph_vcount(graph);
153
0
    igraph_vector_int_t degrees;
154
0
    igraph_vector_int_t neis;
155
0
    igraph_dqueue_int_t sources;
156
157
0
    if (!igraph_is_directed(graph)) {
158
0
        *res = false;
159
0
        return IGRAPH_SUCCESS;
160
0
    }
161
162
0
    IGRAPH_RETURN_IF_CACHED_BOOL(graph, IGRAPH_PROP_IS_DAG, res);
163
164
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&degrees, no_of_nodes);
165
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0);
166
0
    IGRAPH_DQUEUE_INT_INIT_FINALLY(&sources, 0);
167
168
0
    IGRAPH_CHECK(igraph_degree(graph, &degrees, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS));
169
170
0
    igraph_int_t vertices_left = no_of_nodes;
171
172
    /* Do we have nodes with no incoming edges? */
173
0
    for (igraph_int_t i = 0; i < no_of_nodes; i++) {
174
0
        if (VECTOR(degrees)[i] == 0) {
175
0
            IGRAPH_CHECK(igraph_dqueue_int_push(&sources, i));
176
0
        }
177
0
    }
178
179
    /* Take all nodes with no incoming edges and remove them */
180
0
    while (!igraph_dqueue_int_empty(&sources)) {
181
0
        igraph_int_t node = igraph_dqueue_int_pop(&sources);
182
        /* Exclude the node from further source searches */
183
0
        VECTOR(degrees)[node] = -1;
184
0
        vertices_left--;
185
        /* Get the neighbors and decrease their degrees by one */
186
0
        IGRAPH_CHECK(igraph_neighbors(graph, &neis, node, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE));
187
0
        igraph_int_t n = igraph_vector_int_size(&neis);
188
0
        for (igraph_int_t i = 0; i < n; i++) {
189
0
            igraph_int_t nei = VECTOR(neis)[i];
190
0
            if (nei == node) {
191
                /* Found a self-loop, graph is not a DAG */
192
0
                *res = false;
193
0
                goto finalize;
194
0
            }
195
0
            VECTOR(degrees)[nei]--;
196
0
            if (VECTOR(degrees)[nei] == 0) {
197
0
                IGRAPH_CHECK(igraph_dqueue_int_push(&sources, nei));
198
0
            }
199
0
        }
200
0
    }
201
202
0
    IGRAPH_ASSERT(vertices_left >= 0);
203
0
    *res = (vertices_left == 0);
204
205
0
finalize:
206
0
    igraph_vector_int_destroy(&degrees);
207
0
    igraph_vector_int_destroy(&neis);
208
0
    igraph_dqueue_int_destroy(&sources);
209
0
    IGRAPH_FINALLY_CLEAN(3);
210
211
0
    igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_IS_DAG, *res);
212
213
0
    return IGRAPH_SUCCESS;
214
0
}