/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 | } |