Coverage Report

Created: 2025-08-29 06:48

/src/igraph/src/operators/simplify.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
   IGraph library.
3
   Copyright (C) 2006-2020 The igraph development team
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, write to the Free Software
17
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18
   02110-1301 USA
19
20
*/
21
22
#include "igraph_operators.h"
23
24
#include "igraph_constructors.h"
25
#include "igraph_interface.h"
26
27
#include "core/fixed_vectorlist.h"
28
#include "graph/attributes.h"
29
30
/**
31
 * \ingroup structural
32
 * \function igraph_simplify
33
 * \brief Removes loop and/or multiple edges from the graph.
34
 *
35
 * This function merges parallel edges and removes self-loops, according
36
 * to the \p multiple and \p loops parameters. Note that this function
37
 * may change the edge order, even if the input was already a simple graph.
38
 *
39
 * \param graph The graph object.
40
 * \param remove_multiple If true, multiple edges will be removed.
41
 * \param remove_loops If true, loops (self edges) will be removed.
42
 * \param edge_comb What to do with the edge attributes. \c NULL means to
43
 *        discard the edge attributes after the operation, even for edges
44
 *        that were unaffected. See the igraph manual section about attributes
45
 *        for details.
46
 * \return Error code:
47
 *    \c IGRAPH_ENOMEM if we are out of memory.
48
 *
49
 * Time complexity: O(|V|+|E|).
50
 *
51
 * \example examples/simple/igraph_simplify.c
52
 */
53
54
igraph_error_t igraph_simplify(igraph_t *graph,
55
                               igraph_bool_t remove_multiple, igraph_bool_t remove_loops,
56
3.24k
                               const igraph_attribute_combination_t *edge_comb) {
57
58
3.24k
    igraph_vector_int_t edges;
59
3.24k
    igraph_integer_t no_of_nodes = igraph_vcount(graph);
60
3.24k
    igraph_integer_t no_of_edges = igraph_ecount(graph);
61
3.24k
    igraph_integer_t edge;
62
3.24k
    igraph_bool_t attr = edge_comb && igraph_has_attribute_table();
63
3.24k
    igraph_integer_t from, to, pfrom = -1, pto = -2;
64
3.24k
    igraph_t res;
65
3.24k
    igraph_es_t es;
66
3.24k
    igraph_eit_t eit;
67
3.24k
    igraph_vector_int_t mergeinto;
68
3.24k
    igraph_integer_t actedge;
69
70
    /* if we already know there are no multi-edges, they don't need to be removed */
71
3.24k
    if (igraph_i_property_cache_has(graph, IGRAPH_PROP_HAS_MULTI) &&
72
3.24k
        !igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_HAS_MULTI)) {
73
0
        remove_multiple = false;
74
0
    }
75
76
    /* if we already know there are no loops, they don't need to be removed */
77
3.24k
    if (igraph_i_property_cache_has(graph, IGRAPH_PROP_HAS_LOOP) &&
78
3.24k
        !igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_HAS_LOOP)) {
79
0
        remove_loops = false;
80
0
    }
81
82
3.24k
    if (!remove_multiple && !remove_loops)
83
        /* nothing to do */
84
0
    {
85
0
        return IGRAPH_SUCCESS;
86
0
    }
87
88
3.24k
    if (!remove_multiple) {
89
0
        igraph_vector_int_t edges_to_delete;
90
91
        /* removing loop edges only, this is simple. No need to combine anything
92
         * and the whole process can be done in-place */
93
0
        IGRAPH_VECTOR_INT_INIT_FINALLY(&edges_to_delete, 0);
94
0
        IGRAPH_CHECK(igraph_es_all(&es, IGRAPH_EDGEORDER_ID));
95
0
        IGRAPH_FINALLY(igraph_es_destroy, &es);
96
0
        IGRAPH_CHECK(igraph_eit_create(graph, es, &eit));
97
0
        IGRAPH_FINALLY(igraph_eit_destroy, &eit);
98
99
0
        while (!IGRAPH_EIT_END(eit)) {
100
0
            edge = IGRAPH_EIT_GET(eit);
101
0
            from = IGRAPH_FROM(graph, edge);
102
0
            to = IGRAPH_TO(graph, edge);
103
0
            if (from == to) {
104
0
                IGRAPH_CHECK(igraph_vector_int_push_back(&edges_to_delete, edge));
105
0
            }
106
0
            IGRAPH_EIT_NEXT(eit);
107
0
        }
108
109
0
        igraph_eit_destroy(&eit);
110
0
        igraph_es_destroy(&es);
111
0
        IGRAPH_FINALLY_CLEAN(2);
112
113
0
        if (igraph_vector_int_size(&edges_to_delete) > 0) {
114
0
            IGRAPH_CHECK(igraph_delete_edges(graph, igraph_ess_vector(&edges_to_delete)));
115
0
        }
116
117
0
        igraph_vector_int_destroy(&edges_to_delete);
118
0
        IGRAPH_FINALLY_CLEAN(1);
119
120
0
        igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_LOOP, false);
121
122
0
        return IGRAPH_SUCCESS;
123
0
    }
124
125
3.24k
    if (attr) {
126
0
        IGRAPH_VECTOR_INT_INIT_FINALLY(&mergeinto, no_of_edges);
127
0
    }
128
3.24k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0);
129
3.24k
    IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges * 2));
130
131
3.24k
    IGRAPH_CHECK(igraph_es_all(&es, IGRAPH_EDGEORDER_FROM));
132
3.24k
    IGRAPH_FINALLY(igraph_es_destroy, &es);
133
3.24k
    IGRAPH_CHECK(igraph_eit_create(graph, es, &eit));
134
3.24k
    IGRAPH_FINALLY(igraph_eit_destroy, &eit);
135
136
109k
    for (actedge = -1; !IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit)) {
137
106k
        edge = IGRAPH_EIT_GET(eit);
138
106k
        from = IGRAPH_FROM(graph, edge);
139
106k
        to = IGRAPH_TO(graph, edge);
140
141
106k
        if (remove_loops && from == to) {
142
            /* Loop edge to be removed */
143
25.0k
            if (attr) {
144
0
                VECTOR(mergeinto)[edge] = -1;
145
0
            }
146
81.5k
        } else if (remove_multiple && from == pfrom && to == pto) {
147
            /* Multiple edge to be contracted */
148
14.9k
            if (attr) {
149
0
                VECTOR(mergeinto)[edge] = actedge;
150
0
            }
151
66.6k
        } else {
152
            /* Edge to be kept */
153
66.6k
            igraph_vector_int_push_back(&edges, from);  /* reserved */
154
66.6k
            igraph_vector_int_push_back(&edges, to);  /* reserved */
155
66.6k
            if (attr) {
156
0
                actedge++;
157
0
                VECTOR(mergeinto)[edge] = actedge;
158
0
            }
159
66.6k
        }
160
106k
        pfrom = from; pto = to;
161
106k
    }
162
163
3.24k
    igraph_eit_destroy(&eit);
164
3.24k
    igraph_es_destroy(&es);
165
3.24k
    IGRAPH_FINALLY_CLEAN(2);
166
167
3.24k
    IGRAPH_CHECK(igraph_create(&res, &edges, no_of_nodes, igraph_is_directed(graph)));
168
169
3.24k
    igraph_vector_int_destroy(&edges);
170
3.24k
    IGRAPH_FINALLY_CLEAN(1);
171
172
3.24k
    IGRAPH_FINALLY(igraph_destroy, &res);
173
174
3.24k
    IGRAPH_CHECK(igraph_i_attribute_copy(&res, graph, true, true, /* edges= */ false));
175
176
3.24k
    if (attr) {
177
0
        igraph_fixed_vectorlist_t vl;
178
0
        IGRAPH_CHECK(igraph_fixed_vectorlist_convert(&vl, &mergeinto, actedge + 1));
179
0
        IGRAPH_FINALLY(igraph_fixed_vectorlist_destroy, &vl);
180
181
0
        IGRAPH_CHECK(igraph_i_attribute_combine_edges(graph, &res, &vl.vecs, edge_comb));
182
183
0
        igraph_fixed_vectorlist_destroy(&vl);
184
0
        igraph_vector_int_destroy(&mergeinto);
185
0
        IGRAPH_FINALLY_CLEAN(2);
186
0
    }
187
188
3.24k
    IGRAPH_FINALLY_CLEAN(1);
189
3.24k
    igraph_destroy(graph);
190
3.24k
    *graph = res;
191
192
    /* The cache must be set as the very last step, only after all functions that can
193
     * potentially return with an error have finished. */
194
195
3.24k
    if (remove_loops) {
196
        /* Loop edges were removed so we know for sure that there aren't any
197
         * loop edges now */
198
3.24k
        igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_LOOP, false);
199
3.24k
    }
200
201
3.24k
    if (remove_multiple) {
202
        /* Multi-edges were removed so we know for sure that there aren't any
203
         * multi-edges now */
204
3.24k
        igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_MULTI, false);
205
3.24k
    }
206
207
3.24k
    return IGRAPH_SUCCESS;
208
3.24k
}