Coverage Report

Created: 2025-11-11 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/igraph/src/operators/simplify.c
Line
Count
Source
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
37.1k
                               const igraph_attribute_combination_t *edge_comb) {
57
58
37.1k
    igraph_vector_int_t edges;
59
37.1k
    igraph_int_t no_of_nodes = igraph_vcount(graph);
60
37.1k
    igraph_int_t no_of_edges = igraph_ecount(graph);
61
37.1k
    igraph_int_t edge;
62
37.1k
    igraph_bool_t attr = edge_comb && igraph_has_attribute_table();
63
37.1k
    igraph_int_t from, to, pfrom = -1, pto = -2;
64
37.1k
    igraph_t res;
65
37.1k
    igraph_es_t es;
66
37.1k
    igraph_eit_t eit;
67
37.1k
    igraph_vector_int_t mergeinto;
68
37.1k
    igraph_int_t actedge;
69
70
    /* if we already know there are no multi-edges, they don't need to be removed */
71
37.1k
    if (igraph_i_property_cache_has(graph, IGRAPH_PROP_HAS_MULTI) &&
72
4.24k
        !igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_HAS_MULTI)) {
73
2.87k
        remove_multiple = false;
74
2.87k
    }
75
76
    /* if we already know there are no loops, they don't need to be removed */
77
37.1k
    if (igraph_i_property_cache_has(graph, IGRAPH_PROP_HAS_LOOP) &&
78
5.29k
        !igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_HAS_LOOP)) {
79
2.86k
        remove_loops = false;
80
2.86k
    }
81
82
37.1k
    if (!remove_multiple && !remove_loops)
83
        /* nothing to do */
84
2.42k
    {
85
2.42k
        return IGRAPH_SUCCESS;
86
2.42k
    }
87
88
34.7k
    if (!remove_multiple) {
89
448
        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
448
        IGRAPH_VECTOR_INT_INIT_FINALLY(&edges_to_delete, 0);
94
448
        IGRAPH_CHECK(igraph_es_all(&es, IGRAPH_EDGEORDER_ID));
95
448
        IGRAPH_FINALLY(igraph_es_destroy, &es);
96
448
        IGRAPH_CHECK(igraph_eit_create(graph, es, &eit));
97
448
        IGRAPH_FINALLY(igraph_eit_destroy, &eit);
98
99
13.2k
        while (!IGRAPH_EIT_END(eit)) {
100
12.7k
            edge = IGRAPH_EIT_GET(eit);
101
12.7k
            from = IGRAPH_FROM(graph, edge);
102
12.7k
            to = IGRAPH_TO(graph, edge);
103
12.7k
            if (from == to) {
104
832
                IGRAPH_CHECK(igraph_vector_int_push_back(&edges_to_delete, edge));
105
832
            }
106
12.7k
            IGRAPH_EIT_NEXT(eit);
107
12.7k
        }
108
109
448
        igraph_eit_destroy(&eit);
110
448
        igraph_es_destroy(&es);
111
448
        IGRAPH_FINALLY_CLEAN(2);
112
113
448
        if (igraph_vector_int_size(&edges_to_delete) > 0) {
114
448
            IGRAPH_CHECK(igraph_delete_edges(graph, igraph_ess_vector(&edges_to_delete)));
115
448
        }
116
117
448
        igraph_vector_int_destroy(&edges_to_delete);
118
448
        IGRAPH_FINALLY_CLEAN(1);
119
120
448
        igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_LOOP, false);
121
122
448
        return IGRAPH_SUCCESS;
123
448
    }
124
125
34.2k
    if (attr) {
126
4.06k
        IGRAPH_VECTOR_INT_INIT_FINALLY(&mergeinto, no_of_edges);
127
4.06k
    }
128
34.2k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0);
129
34.2k
    IGRAPH_CHECK(igraph_vector_int_reserve(&edges, no_of_edges * 2));
130
131
34.2k
    IGRAPH_CHECK(igraph_es_all(&es, IGRAPH_EDGEORDER_FROM));
132
34.2k
    IGRAPH_FINALLY(igraph_es_destroy, &es);
133
34.2k
    IGRAPH_CHECK(igraph_eit_create(graph, es, &eit));
134
34.2k
    IGRAPH_FINALLY(igraph_eit_destroy, &eit);
135
136
1.61M
    for (actedge = -1; !IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit)) {
137
1.57M
        edge = IGRAPH_EIT_GET(eit);
138
1.57M
        from = IGRAPH_FROM(graph, edge);
139
1.57M
        to = IGRAPH_TO(graph, edge);
140
141
1.57M
        if (remove_loops && from == to) {
142
            /* Loop edge to be removed */
143
878k
            if (attr) {
144
24.8k
                VECTOR(mergeinto)[edge] = -1;
145
24.8k
            }
146
878k
        } else if (remove_multiple && from == pfrom && to == pto) {
147
            /* Multiple edge to be contracted */
148
315k
            if (attr) {
149
12.2k
                VECTOR(mergeinto)[edge] = actedge;
150
12.2k
            }
151
383k
        } else {
152
            /* Edge to be kept */
153
383k
            igraph_vector_int_push_back(&edges, from);  /* reserved */
154
383k
            igraph_vector_int_push_back(&edges, to);  /* reserved */
155
383k
            if (attr) {
156
65.6k
                actedge++;
157
65.6k
                VECTOR(mergeinto)[edge] = actedge;
158
65.6k
            }
159
383k
        }
160
1.57M
        pfrom = from; pto = to;
161
1.57M
    }
162
163
34.2k
    igraph_eit_destroy(&eit);
164
34.2k
    igraph_es_destroy(&es);
165
34.2k
    IGRAPH_FINALLY_CLEAN(2);
166
167
34.2k
    IGRAPH_CHECK(igraph_create(&res, &edges, no_of_nodes, igraph_is_directed(graph)));
168
169
34.2k
    igraph_vector_int_destroy(&edges);
170
34.2k
    IGRAPH_FINALLY_CLEAN(1);
171
172
34.2k
    IGRAPH_FINALLY(igraph_destroy, &res);
173
174
34.2k
    IGRAPH_CHECK(igraph_i_attribute_copy(&res, graph, true, true, /* edges= */ false));
175
176
34.2k
    if (attr) {
177
4.06k
        igraph_fixed_vectorlist_t vl;
178
4.06k
        IGRAPH_CHECK(igraph_fixed_vectorlist_convert(&vl, &mergeinto, actedge + 1));
179
4.06k
        IGRAPH_FINALLY(igraph_fixed_vectorlist_destroy, &vl);
180
181
4.06k
        IGRAPH_CHECK(igraph_i_attribute_combine_edges(graph, &res, &vl.vecs, edge_comb));
182
183
4.06k
        igraph_fixed_vectorlist_destroy(&vl);
184
4.06k
        igraph_vector_int_destroy(&mergeinto);
185
4.06k
        IGRAPH_FINALLY_CLEAN(2);
186
4.06k
    }
187
188
34.2k
    IGRAPH_FINALLY_CLEAN(1);
189
34.2k
    igraph_destroy(graph);
190
34.2k
    *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
34.2k
    if (remove_loops) {
196
        /* Loop edges were removed so we know for sure that there aren't any
197
         * loop edges now */
198
32.5k
        igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_LOOP, false);
199
32.5k
    }
200
201
34.2k
    if (remove_multiple) {
202
        /* Multi-edges were removed so we know for sure that there aren't any
203
         * multi-edges now */
204
34.2k
        igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_MULTI, false);
205
34.2k
    }
206
207
34.2k
    return IGRAPH_SUCCESS;
208
34.2k
}