/src/igraph/src/cycles/feedback_sets.c
Line | Count | Source |
1 | | /* |
2 | | igraph library. |
3 | | Copyright (C) 2011-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_cycles.h" |
20 | | #include "cycles/feedback_sets.h" |
21 | | |
22 | | #include "igraph_bitset.h" |
23 | | #include "igraph_components.h" |
24 | | #include "igraph_dqueue.h" |
25 | | #include "igraph_interface.h" |
26 | | #include "igraph_memory.h" |
27 | | #include "igraph_stack.h" |
28 | | #include "igraph_structural.h" |
29 | | #include "igraph_vector.h" |
30 | | #include "igraph_vector_list.h" |
31 | | #include "igraph_visitor.h" |
32 | | |
33 | | #include "internal/glpk_support.h" |
34 | | #include "math/safe_intop.h" |
35 | | |
36 | | #include <limits.h> |
37 | | |
38 | | /** |
39 | | * \param found If not NULL, will indicate if any cycles were found. |
40 | | * \param A bitset the same length as the edge count, indicating which edges |
41 | | * to consider non-existent. |
42 | | * |
43 | | * See igraph_find_cycle() for the other parameters. |
44 | | * |
45 | | * \return Error code. |
46 | | */ |
47 | | static igraph_error_t igraph_i_find_cycle(const igraph_t *graph, |
48 | | igraph_vector_int_t *vertices, |
49 | | igraph_vector_int_t *edges, |
50 | | igraph_bool_t *found, |
51 | | igraph_neimode_t mode, |
52 | 4.73k | const igraph_bitset_t *removed) { |
53 | | |
54 | 4.73k | const igraph_int_t vcount = igraph_vcount(graph); |
55 | 4.73k | const igraph_int_t ecount = igraph_ecount(graph); |
56 | 4.73k | igraph_stack_int_t stack; |
57 | 4.73k | igraph_vector_int_t inc; |
58 | 4.73k | igraph_vector_int_t vpath, epath; |
59 | 4.73k | igraph_vector_char_t seen; /* 0 = unseen, 1 = acestor of current, 2 = seen, non-ancestor */ |
60 | 4.73k | igraph_int_t ea, va; |
61 | 4.73k | igraph_int_t depth; |
62 | | |
63 | 4.73k | if (vertices) { |
64 | 4.73k | igraph_vector_int_clear(vertices); |
65 | 4.73k | } |
66 | 4.73k | if (edges) { |
67 | 4.73k | igraph_vector_int_clear(edges); |
68 | 4.73k | } |
69 | 4.73k | if (found) { |
70 | 4.73k | *found = false; |
71 | 4.73k | } |
72 | | |
73 | 4.73k | if (ecount == 0) { |
74 | 230 | return IGRAPH_SUCCESS; |
75 | 230 | } |
76 | | |
77 | 4.50k | #define PATH_PUSH(v, e) \ |
78 | 376k | do { \ |
79 | 376k | IGRAPH_CHECK(igraph_vector_int_push_back(&epath, e)); \ |
80 | 376k | IGRAPH_CHECK(igraph_vector_int_push_back(&vpath, v)); \ |
81 | 376k | VECTOR(seen)[v] = 1; \ |
82 | 376k | } while (0) |
83 | | |
84 | 4.50k | #define PATH_POP() \ |
85 | 357k | do { \ |
86 | 357k | igraph_vector_int_pop_back(&epath); \ |
87 | 357k | VECTOR(seen)[igraph_vector_int_pop_back(&vpath)] = 2; \ |
88 | 357k | } while (0) |
89 | | |
90 | 4.50k | IGRAPH_VECTOR_INT_INIT_FINALLY(&vpath, 100); |
91 | 4.50k | igraph_vector_int_clear(&vpath); |
92 | | |
93 | 4.50k | IGRAPH_VECTOR_INT_INIT_FINALLY(&epath, 100); |
94 | 4.50k | igraph_vector_int_clear(&epath); |
95 | | |
96 | 4.50k | IGRAPH_VECTOR_INT_INIT_FINALLY(&inc, 10); |
97 | 4.50k | IGRAPH_VECTOR_CHAR_INIT_FINALLY(&seen, vcount); |
98 | 4.50k | IGRAPH_STACK_INT_INIT_FINALLY(&stack, 200); |
99 | | |
100 | 351k | for (igraph_int_t v=0; v < vcount; v++) { |
101 | 350k | if (VECTOR(seen)[v]) { |
102 | 29.1k | continue; |
103 | 29.1k | } |
104 | | |
105 | 321k | IGRAPH_CHECK(igraph_stack_int_push(&stack, -1)); |
106 | 321k | IGRAPH_CHECK(igraph_stack_int_push(&stack, v)); |
107 | | |
108 | 1.05M | while (! igraph_stack_int_empty(&stack)) { |
109 | 739k | igraph_int_t x = igraph_stack_int_pop(&stack); |
110 | 739k | if (x == -1) { |
111 | 357k | PATH_POP(); |
112 | 357k | continue; |
113 | 381k | } else { |
114 | 381k | va = x; |
115 | 381k | ea = igraph_stack_int_pop(&stack); |
116 | | |
117 | 381k | if (VECTOR(seen)[va] == 1) { |
118 | 2.90k | goto finish; |
119 | 378k | } else if (VECTOR(seen)[va] == 2) { |
120 | 2.13k | continue; |
121 | 2.13k | } |
122 | 381k | } |
123 | | |
124 | 376k | PATH_PUSH(va, ea); |
125 | | |
126 | 376k | IGRAPH_CHECK(igraph_stack_int_push(&stack, -1)); |
127 | 376k | IGRAPH_CHECK(igraph_incident(graph, &inc, va, mode, IGRAPH_LOOPS)); |
128 | 376k | igraph_int_t n = igraph_vector_int_size(&inc); |
129 | 563k | for (igraph_int_t i=0; i < n; i++) { |
130 | 187k | igraph_int_t eb = VECTOR(inc)[i]; |
131 | 187k | igraph_int_t vb = IGRAPH_OTHER(graph, eb, va); |
132 | 187k | if (eb == ea) continue; |
133 | 149k | if (VECTOR(seen)[vb] == 2) continue; |
134 | 140k | if (removed && IGRAPH_BIT_TEST(*removed, eb)) continue; |
135 | 140k | IGRAPH_CHECK(igraph_stack_int_push(&stack, eb)); |
136 | 140k | IGRAPH_CHECK(igraph_stack_int_push(&stack, vb)); |
137 | 140k | } |
138 | 376k | } |
139 | 321k | } |
140 | | |
141 | | |
142 | 4.50k | finish: |
143 | | |
144 | 4.50k | igraph_stack_int_destroy(&stack); |
145 | 4.50k | igraph_vector_char_destroy(&seen); |
146 | 4.50k | igraph_vector_int_destroy(&inc); |
147 | 4.50k | IGRAPH_FINALLY_CLEAN(3); |
148 | | |
149 | 4.50k | depth = igraph_vector_int_size(&vpath); |
150 | | |
151 | 4.50k | if (depth > 0) { |
152 | 2.90k | igraph_int_t i = depth; |
153 | 14.4k | while (VECTOR(vpath)[i-1] != va) i--; |
154 | 14.4k | for (; i < depth; i++) { |
155 | 11.5k | if (vertices) { |
156 | 11.5k | IGRAPH_CHECK(igraph_vector_int_push_back(vertices, VECTOR(vpath)[i])); |
157 | 11.5k | } |
158 | 11.5k | if (edges) { |
159 | 11.5k | IGRAPH_CHECK(igraph_vector_int_push_back(edges, VECTOR(epath)[i])); |
160 | 11.5k | } |
161 | 11.5k | } |
162 | 2.90k | if (vertices) { |
163 | 2.90k | IGRAPH_CHECK(igraph_vector_int_push_back(vertices, va)); |
164 | 2.90k | } |
165 | 2.90k | if (edges) { |
166 | 2.90k | IGRAPH_CHECK(igraph_vector_int_push_back(edges, ea)); |
167 | 2.90k | } |
168 | 2.90k | if (found) { |
169 | 2.90k | *found = true; |
170 | 2.90k | } |
171 | 2.90k | } |
172 | | |
173 | 4.50k | igraph_vector_int_destroy(&epath); |
174 | 4.50k | igraph_vector_int_destroy(&vpath); |
175 | 4.50k | IGRAPH_FINALLY_CLEAN(2); |
176 | | |
177 | 4.50k | return IGRAPH_SUCCESS; |
178 | | |
179 | 4.50k | #undef PATH_PUSH |
180 | 4.50k | #undef PATH_POP |
181 | 4.50k | } |
182 | | |
183 | | |
184 | | /** |
185 | | * \function igraph_find_cycle |
186 | | * \brief Finds a single cycle in the graph. |
187 | | * |
188 | | * This function returns a cycle of the graph, if there is one. If the graph |
189 | | * is acyclic, it returns empty vectors. |
190 | | * |
191 | | * \param graph The input graph. |
192 | | * \param vertices Pointer to an integer vector. If a cycle is found, its |
193 | | * vertices will be stored here. Otherwise the vector will be empty. |
194 | | * \param edges Pointer to an integer vector. If a cycle is found, its |
195 | | * edges will be stored here. Otherwise the vector will be empty. |
196 | | * \param mode A constant specifying how edge directions are |
197 | | * considered in directed graphs. Valid modes are: |
198 | | * \c IGRAPH_OUT, follows edge directions; |
199 | | * \c IGRAPH_IN, follows the opposite directions; and |
200 | | * \c IGRAPH_ALL, ignores edge directions. This argument is |
201 | | * ignored for undirected graphs. |
202 | | * \return Error code. |
203 | | * |
204 | | * Time complexity: O(|V|+|E|), where |V| and |E| are the number of |
205 | | * vertices and edges in the original input graph. |
206 | | * |
207 | | * \sa \ref igraph_is_acyclic() to determine if a graph is acyclic, |
208 | | * without returning a specific cycle; \ref igraph_simple_cycles() |
209 | | * to list all cycles in a graph. |
210 | | */ |
211 | | igraph_error_t igraph_find_cycle(const igraph_t *graph, |
212 | | igraph_vector_int_t *vertices, |
213 | | igraph_vector_int_t *edges, |
214 | 4.73k | igraph_neimode_t mode) { |
215 | | |
216 | | /* If the graph is cached to be acyclic, we don't need to run the algorithm. */ |
217 | | |
218 | 4.73k | igraph_bool_t known_acyclic = false; |
219 | 4.73k | igraph_bool_t found; |
220 | | |
221 | 4.73k | if (mode != IGRAPH_OUT && mode != IGRAPH_IN && mode != IGRAPH_ALL) { |
222 | 0 | IGRAPH_ERROR("Invalid mode for finding cycles.", IGRAPH_EINVAL); |
223 | 0 | } |
224 | | |
225 | 4.73k | if (! igraph_is_directed(graph)) { |
226 | 2.43k | mode = IGRAPH_ALL; |
227 | 2.43k | } |
228 | | |
229 | 4.73k | if (mode == IGRAPH_ALL) /* undirected */ { |
230 | 2.43k | if (igraph_i_property_cache_has(graph, IGRAPH_PROP_IS_FOREST) && |
231 | 0 | igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_IS_FOREST)) { |
232 | 0 | known_acyclic = true; |
233 | 0 | } |
234 | 2.43k | } else /* directed */ { |
235 | 2.30k | if (igraph_i_property_cache_has(graph, IGRAPH_PROP_IS_DAG) && |
236 | 0 | igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_IS_DAG)) { |
237 | 0 | known_acyclic = true; |
238 | 0 | } |
239 | 2.30k | } |
240 | | |
241 | 4.73k | if (known_acyclic) { |
242 | 0 | if (vertices) { |
243 | 0 | igraph_vector_int_clear(vertices); |
244 | 0 | } |
245 | 0 | if (edges) { |
246 | 0 | igraph_vector_int_clear(edges); |
247 | 0 | } |
248 | 0 | return IGRAPH_SUCCESS; |
249 | 0 | } |
250 | | |
251 | 4.73k | IGRAPH_CHECK(igraph_i_find_cycle(graph, vertices, edges, &found, mode, NULL)); |
252 | | |
253 | 4.73k | if (! found) { |
254 | 1.82k | if (mode == IGRAPH_ALL) /* undirected */ { |
255 | 950 | igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_IS_FOREST, true); |
256 | 950 | } else /* directed */ { |
257 | 879 | igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_IS_DAG, true); |
258 | 879 | } |
259 | 1.82k | } |
260 | | |
261 | 4.73k | return IGRAPH_SUCCESS; |
262 | 4.73k | } |
263 | | |
264 | | |
265 | | /** |
266 | | * \ingroup structural |
267 | | * \function igraph_feedback_arc_set |
268 | | * \brief Feedback arc set of a graph using exact or heuristic methods. |
269 | | * |
270 | | * A feedback arc set is a set of edges whose removal makes the graph acyclic. |
271 | | * We are usually interested in \em minimum feedback arc sets, i.e. sets of edges |
272 | | * whose total weight is the smallest among all the feedback arc sets. |
273 | | * |
274 | | * </para><para> |
275 | | * For undirected graphs, the solution is simple: one has to find a maximum weight |
276 | | * spanning tree and then remove all the edges not in the spanning tree. For directed |
277 | | * graphs, this is an NP-complete problem, and various heuristics are usually used to |
278 | | * find an approximate solution to the problem. This function implements both exact |
279 | | * methods and heuristics, selectable with the \p algo parameter. |
280 | | * |
281 | | * </para><para> |
282 | | * References: |
283 | | * |
284 | | * </para><para> |
285 | | * Eades P, Lin X and Smyth WF: |
286 | | * A fast and effective heuristic for the feedback arc set problem. |
287 | | * Information Processing Letters 47(6), pp 319-323 (1993). |
288 | | * https://doi.org/10.1016/0020-0190(93)90079-O |
289 | | * |
290 | | * </para><para> |
291 | | * Baharev A, Hermann S, Arnold N and Tobias A: |
292 | | * An Exact Method for the Minimum Feedback Arc Set Problem. |
293 | | * ACM Journal of Experimental Algorithmics 26, 1–28 (2021). |
294 | | * https://doi.org/10.1145/3446429. |
295 | | * |
296 | | * \param graph The graph object. |
297 | | * \param result An initialized vector, the result will be written here. |
298 | | * \param weights Weight vector or \c NULL if no weights are specified. |
299 | | * \param algo The algorithm to use to solve the problem if the graph is directed. |
300 | | * Possible values: |
301 | | * \clist |
302 | | * \cli IGRAPH_FAS_EXACT_IP |
303 | | * Finds a \em minimum feedback arc set using integer programming (IP), |
304 | | * automatically selecting the best method of this type (currently |
305 | | * always \c IGRAPH_FAS_EXACT_IP_CG). The complexity is of course |
306 | | * at least exponential. |
307 | | * \cli IGRAPH_FAS_EXACT_IP_CG |
308 | | * This is an integer programming approach based on a minimum set cover |
309 | | * formulation and using incremental constraint generation (CG), added |
310 | | * in igraph 0.10.14. We minimize <code>sum_e w_e b_e</code> subject to |
311 | | * the constraints <code>sum_e c_e b_e >= 1</code> for all cycles \c c. |
312 | | * Here \c w_e is the weight of edge \c e, \c b_e is a binary variable |
313 | | * (0 or 1) indicating whether edge \c e is in the feedback set, |
314 | | * and \c c_e is a binary coefficient indicating whether edge \c e |
315 | | * is in cycle \c c. The constraint expresses the requirement that all |
316 | | * cycles must intersect with (be broken by) the edge set represented |
317 | | * by \c b. Since there are a very large number of cycles in the graph, |
318 | | * constraints are generated incrementally, iteratively adding some cycles |
319 | | * that do not intersect with the current edge set \c b, then solving for |
320 | | * \c b again, until finally no unbroken cycles remain. This approach is |
321 | | * similar to that described by Baharev et al (though with a simpler |
322 | | * cycle generation scheme), and to what is implemented by SageMath's. |
323 | | * \c feedback_edge_set function. |
324 | | * \cli IGRAPH_FAS_EXACT_IP_TI |
325 | | * This is another integer programming approach based on finding a |
326 | | * maximum (largest weight) edge set that adheres to a topological |
327 | | * order. It uses the common formulation through triangle inequalities |
328 | | * (TI), see Section 3.1 of Baharev et al (2021) for an overview. This |
329 | | * method was used before igraph 0.10.14, and is typically much slower |
330 | | * than \c IGRAPH_FAS_EXACT_IP_CG. |
331 | | * \cli IGRAPH_FAS_APPROX_EADES |
332 | | * Finds a feedback arc set using the heuristic of Eades, Lin and |
333 | | * Smyth (1993). This is guaranteed to be smaller than |E|/2 - |V|/6, |
334 | | * and it is linear in the number of edges (i.e. O(|E|)) to compute. |
335 | | * \endclist |
336 | | * |
337 | | * \return Error code: |
338 | | * \c IGRAPH_EINVAL if an unknown method was specified or the weight vector |
339 | | * is invalid. |
340 | | * |
341 | | * \example examples/simple/igraph_feedback_arc_set.c |
342 | | * \example examples/simple/igraph_feedback_arc_set_ip.c |
343 | | * |
344 | | * Time complexity: depends on \p algo, see the time complexities there. |
345 | | */ |
346 | | igraph_error_t igraph_feedback_arc_set( |
347 | | const igraph_t *graph, |
348 | | igraph_vector_int_t *result, |
349 | | const igraph_vector_t *weights, |
350 | 4.25k | igraph_fas_algorithm_t algo) { |
351 | | |
352 | 4.25k | if (weights) { |
353 | 1.95k | if (igraph_vector_size(weights) != igraph_ecount(graph)) { |
354 | 0 | IGRAPH_ERROR("Weight vector length must match the number of edges.", IGRAPH_EINVAL); |
355 | 0 | } |
356 | 1.95k | if (! igraph_vector_is_all_finite(weights)) { |
357 | 0 | IGRAPH_ERROR("Weights must not be infinite or NaN.", IGRAPH_EINVAL); |
358 | 0 | } |
359 | 1.95k | } |
360 | | |
361 | 4.25k | if (!igraph_is_directed(graph)) { |
362 | 0 | return igraph_i_feedback_arc_set_undirected(graph, result, weights, NULL); |
363 | 0 | } |
364 | | |
365 | 4.25k | switch (algo) { |
366 | 0 | case IGRAPH_FAS_EXACT_IP: |
367 | 0 | case IGRAPH_FAS_EXACT_IP_CG: |
368 | 0 | return igraph_i_feedback_arc_set_ip_cg(graph, result, weights); |
369 | | |
370 | 0 | case IGRAPH_FAS_EXACT_IP_TI: |
371 | 0 | return igraph_i_feedback_arc_set_ip_ti(graph, result, weights); |
372 | | |
373 | 4.25k | case IGRAPH_FAS_APPROX_EADES: |
374 | 4.25k | return igraph_i_feedback_arc_set_eades(graph, result, weights, NULL); |
375 | | |
376 | 0 | default: |
377 | 0 | IGRAPH_ERROR("Invalid feedback arc set algorithm.", IGRAPH_EINVAL); |
378 | 4.25k | } |
379 | 4.25k | } |
380 | | |
381 | | |
382 | | /** |
383 | | * \function igraph_feedback_vertex_set |
384 | | * \brief Feedback vertex set of a graph. |
385 | | * |
386 | | * A feedback vertex set is a set of vertices whose removal makes the graph |
387 | | * acyclic. Finding a \em minimum feedback vertex set is an NP-complete |
388 | | * problem, both on directed and undirected graphs. |
389 | | * |
390 | | * \param graph The graph. |
391 | | * \param result An initialized vector, the result will be written here. |
392 | | * \param vertex_weights Vertex weight vector or \c NULL if no weights are specified. |
393 | | * \param algo Algorithm to use. Possible values: |
394 | | * \clist |
395 | | * \cli IGRAPH_FVS_EXACT_IP |
396 | | * Finds a \em miniumum feedback vertex set using integer programming |
397 | | * (IP). The complexity is of course at least exponential. Currently |
398 | | * this method uses an approach analogous to that of the |
399 | | * \c IGRAPH_FAS_EXACT_IP_CG algorithm of \ref igraph_feedback_arc_set(). |
400 | | * \endclist |
401 | | * |
402 | | * \return Error code. |
403 | | * |
404 | | * Time complexity: depends on \p algo, see the time complexities there. |
405 | | */ |
406 | | igraph_error_t igraph_feedback_vertex_set( |
407 | | const igraph_t *graph, igraph_vector_int_t *result, |
408 | 0 | const igraph_vector_t *vertex_weights, igraph_fvs_algorithm_t algo) { |
409 | |
|
410 | 0 | if (vertex_weights) { |
411 | 0 | if (igraph_vector_size(vertex_weights) != igraph_vcount(graph)) { |
412 | 0 | IGRAPH_ERROR("Vertex weight vector length must match the number of vertices.", IGRAPH_EINVAL); |
413 | 0 | } |
414 | 0 | if (! igraph_vector_is_all_finite(vertex_weights)) { |
415 | 0 | IGRAPH_ERROR("Vertex weights must not be infinite or NaN.", IGRAPH_EINVAL); |
416 | 0 | } |
417 | 0 | } |
418 | | |
419 | 0 | switch (algo) { |
420 | 0 | case IGRAPH_FVS_EXACT_IP: |
421 | 0 | return igraph_i_feedback_vertex_set_ip_cg(graph, result, vertex_weights); |
422 | | |
423 | 0 | default: |
424 | 0 | IGRAPH_ERROR("Invalid feedback vertex set algorithm.", IGRAPH_EINVAL); |
425 | 0 | } |
426 | 0 | } |
427 | | |
428 | | |
429 | | /** |
430 | | * Solves the feedback arc set problem for undirected graphs. |
431 | | */ |
432 | | igraph_error_t igraph_i_feedback_arc_set_undirected(const igraph_t *graph, igraph_vector_int_t *result, |
433 | 0 | const igraph_vector_t *weights, igraph_vector_int_t *layering) { |
434 | |
|
435 | 0 | const igraph_int_t no_of_nodes = igraph_vcount(graph); |
436 | 0 | const igraph_int_t no_of_edges = igraph_ecount(graph); |
437 | 0 | igraph_vector_int_t edges; |
438 | |
|
439 | 0 | IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, no_of_nodes > 0 ? no_of_nodes - 1 : 0); |
440 | 0 | if (weights) { |
441 | | /* Find a maximum weight spanning tree. igraph has a routine for minimum |
442 | | * spanning trees, so we negate the weights */ |
443 | 0 | igraph_vector_t vcopy; |
444 | 0 | IGRAPH_CHECK(igraph_vector_init_copy(&vcopy, weights)); |
445 | 0 | IGRAPH_FINALLY(igraph_vector_destroy, &vcopy); |
446 | 0 | igraph_vector_scale(&vcopy, -1); |
447 | 0 | IGRAPH_CHECK(igraph_minimum_spanning_tree(graph, &edges, &vcopy, IGRAPH_MST_AUTOMATIC)); |
448 | 0 | igraph_vector_destroy(&vcopy); |
449 | 0 | IGRAPH_FINALLY_CLEAN(1); |
450 | 0 | } else { |
451 | | /* Any spanning tree will do */ |
452 | 0 | IGRAPH_CHECK(igraph_minimum_spanning_tree(graph, &edges, NULL, IGRAPH_MST_AUTOMATIC)); |
453 | 0 | } |
454 | | |
455 | | /* Now we have a bunch of edges that constitute a spanning forest. We have |
456 | | * to come up with a layering, and return those edges that are not in the |
457 | | * spanning forest */ |
458 | 0 | igraph_vector_int_sort(&edges); |
459 | 0 | IGRAPH_CHECK(igraph_vector_int_push_back(&edges, -1)); /* guard element */ |
460 | | |
461 | 0 | if (result) { |
462 | 0 | igraph_vector_int_clear(result); |
463 | 0 | for (igraph_int_t i = 0, j = 0; i < no_of_edges; i++) { |
464 | 0 | if (i == VECTOR(edges)[j]) { |
465 | 0 | j++; |
466 | 0 | continue; |
467 | 0 | } |
468 | 0 | IGRAPH_CHECK(igraph_vector_int_push_back(result, i)); |
469 | 0 | } |
470 | 0 | } |
471 | | |
472 | 0 | if (layering) { |
473 | 0 | igraph_vector_t degrees; |
474 | 0 | igraph_vector_int_t roots; |
475 | |
|
476 | 0 | IGRAPH_VECTOR_INIT_FINALLY(°rees, no_of_nodes); |
477 | 0 | IGRAPH_VECTOR_INT_INIT_FINALLY(&roots, no_of_nodes); |
478 | 0 | IGRAPH_CHECK(igraph_strength(graph, °rees, igraph_vss_all(), |
479 | 0 | IGRAPH_ALL, IGRAPH_NO_LOOPS, weights)); |
480 | 0 | IGRAPH_CHECK(igraph_vector_sort_ind(°rees, &roots, IGRAPH_DESCENDING)); |
481 | | |
482 | 0 | IGRAPH_CHECK(igraph_bfs(graph, |
483 | 0 | /* root = */ 0, |
484 | 0 | /* roots = */ &roots, |
485 | 0 | /* mode = */ IGRAPH_OUT, |
486 | 0 | /* unreachable = */ false, |
487 | 0 | /* restricted = */ NULL, |
488 | 0 | /* order = */ NULL, |
489 | 0 | /* rank = */ NULL, |
490 | 0 | /* parents = */ NULL, |
491 | 0 | /* pred = */ NULL, |
492 | 0 | /* succ = */ NULL, |
493 | 0 | /* dist = */ layering, |
494 | 0 | /* callback = */ NULL, |
495 | 0 | /* extra = */ NULL)); |
496 | | |
497 | 0 | igraph_vector_destroy(°rees); |
498 | 0 | igraph_vector_int_destroy(&roots); |
499 | 0 | IGRAPH_FINALLY_CLEAN(2); |
500 | 0 | } |
501 | | |
502 | 0 | igraph_vector_int_destroy(&edges); |
503 | 0 | IGRAPH_FINALLY_CLEAN(1); |
504 | |
|
505 | 0 | return IGRAPH_SUCCESS; |
506 | 0 | } |
507 | | |
508 | | |
509 | | /** |
510 | | * Solves the feedback arc set problem using the heuristics of Eades et al. |
511 | | */ |
512 | | igraph_error_t igraph_i_feedback_arc_set_eades(const igraph_t *graph, igraph_vector_int_t *result, |
513 | 4.25k | const igraph_vector_t *weights, igraph_vector_int_t *layers) { |
514 | 4.25k | const igraph_int_t no_of_nodes = igraph_vcount(graph); |
515 | 4.25k | const igraph_int_t no_of_edges = igraph_ecount(graph); |
516 | 4.25k | igraph_int_t nodes_left; |
517 | 4.25k | igraph_int_t neis_size; |
518 | 4.25k | igraph_dqueue_int_t sources, sinks; |
519 | 4.25k | igraph_vector_int_t neis; |
520 | 4.25k | igraph_vector_int_t indegrees, outdegrees; |
521 | 4.25k | igraph_vector_t instrengths, outstrengths; |
522 | 4.25k | igraph_vector_int_t ordering; |
523 | 4.25k | igraph_int_t order_next_pos = 0, order_next_neg = -1; |
524 | | |
525 | 4.25k | IGRAPH_VECTOR_INT_INIT_FINALLY(&ordering, no_of_nodes); |
526 | 4.25k | IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0); |
527 | | |
528 | 4.25k | IGRAPH_DQUEUE_INT_INIT_FINALLY(&sources, 0); |
529 | 4.25k | IGRAPH_DQUEUE_INT_INIT_FINALLY(&sinks, 0); |
530 | 4.25k | IGRAPH_VECTOR_INT_INIT_FINALLY(&indegrees, no_of_nodes); |
531 | 4.25k | IGRAPH_VECTOR_INT_INIT_FINALLY(&outdegrees, no_of_nodes); |
532 | 4.25k | IGRAPH_VECTOR_INIT_FINALLY(&instrengths, no_of_nodes); |
533 | 4.25k | IGRAPH_VECTOR_INIT_FINALLY(&outstrengths, no_of_nodes); |
534 | | |
535 | 4.25k | IGRAPH_CHECK(igraph_degree(graph, &indegrees, igraph_vss_all(), IGRAPH_IN, IGRAPH_NO_LOOPS)); |
536 | 4.25k | IGRAPH_CHECK(igraph_degree(graph, &outdegrees, igraph_vss_all(), IGRAPH_OUT, IGRAPH_NO_LOOPS)); |
537 | | |
538 | 4.25k | if (weights) { |
539 | 1.95k | IGRAPH_CHECK(igraph_strength(graph, &instrengths, igraph_vss_all(), IGRAPH_IN, IGRAPH_NO_LOOPS, weights)); |
540 | 1.95k | IGRAPH_CHECK(igraph_strength(graph, &outstrengths, igraph_vss_all(), IGRAPH_OUT, IGRAPH_NO_LOOPS, weights)); |
541 | 2.30k | } else { |
542 | 392k | for (igraph_int_t u = 0; u < no_of_nodes; u++) { |
543 | 390k | VECTOR(instrengths)[u] = VECTOR(indegrees)[u]; |
544 | 390k | VECTOR(outstrengths)[u] = VECTOR(outdegrees)[u]; |
545 | 390k | } |
546 | 2.30k | } |
547 | | |
548 | | /* Find initial sources and sinks */ |
549 | 4.25k | nodes_left = no_of_nodes; |
550 | 757k | for (igraph_int_t u = 0; u < no_of_nodes; u++) { |
551 | 752k | if (VECTOR(indegrees)[u] == 0) { |
552 | 677k | if (VECTOR(outdegrees)[u] == 0) { |
553 | | /* Isolated vertex, we simply ignore it */ |
554 | 646k | nodes_left--; |
555 | 646k | VECTOR(ordering)[u] = order_next_pos++; |
556 | 646k | VECTOR(indegrees)[u] = VECTOR(outdegrees)[u] = -1; |
557 | 646k | } else { |
558 | | /* This is a source */ |
559 | 30.0k | IGRAPH_CHECK(igraph_dqueue_int_push(&sources, u)); |
560 | 30.0k | } |
561 | 677k | } else if (VECTOR(outdegrees)[u] == 0) { |
562 | | /* This is a sink */ |
563 | 29.0k | IGRAPH_CHECK(igraph_dqueue_int_push(&sinks, u)); |
564 | 29.0k | } |
565 | 752k | } |
566 | | |
567 | | /* While we have any nodes left... */ |
568 | 12.4k | while (nodes_left > 0) { |
569 | | |
570 | | /* (1) Remove the sources one by one */ |
571 | 98.8k | while (!igraph_dqueue_int_empty(&sources)) { |
572 | 90.6k | const igraph_int_t u = igraph_dqueue_int_pop(&sources); |
573 | | /* Add the node to the ordering */ |
574 | 90.6k | VECTOR(ordering)[u] = order_next_pos++; |
575 | | /* Exclude the node from further searches */ |
576 | 90.6k | VECTOR(indegrees)[u] = VECTOR(outdegrees)[u] = -1; |
577 | | /* Get the neighbors and decrease their degrees */ |
578 | 90.6k | IGRAPH_CHECK(igraph_incident(graph, &neis, u, IGRAPH_OUT, IGRAPH_LOOPS)); |
579 | 90.6k | neis_size = igraph_vector_int_size(&neis); |
580 | 210k | for (igraph_int_t i = 0; i < neis_size; i++) { |
581 | 120k | const igraph_int_t eid = VECTOR(neis)[i]; |
582 | 120k | const igraph_int_t w = IGRAPH_TO(graph, eid); |
583 | 120k | if (VECTOR(indegrees)[w] <= 0) { |
584 | | /* Already removed, continue */ |
585 | 28.1k | continue; |
586 | 28.1k | } |
587 | 92.0k | VECTOR(indegrees)[w]--; |
588 | 92.0k | VECTOR(instrengths)[w] -= (weights ? VECTOR(*weights)[eid] : 1.0); |
589 | 92.0k | if (VECTOR(indegrees)[w] == 0) { |
590 | 54.6k | IGRAPH_CHECK(igraph_dqueue_int_push(&sources, w)); |
591 | 54.6k | } |
592 | 92.0k | } |
593 | 90.6k | nodes_left--; |
594 | 90.6k | } |
595 | | |
596 | | /* (2) Remove the sinks one by one */ |
597 | 44.4k | while (!igraph_dqueue_int_empty(&sinks)) { |
598 | 36.2k | const igraph_int_t u = igraph_dqueue_int_pop(&sinks); |
599 | | /* Maybe the vertex became sink and source at the same time, hence it |
600 | | * was already removed in the previous iteration. Check it. */ |
601 | 36.2k | if (VECTOR(indegrees)[u] < 0) { |
602 | 24.9k | continue; |
603 | 24.9k | } |
604 | | /* Add the node to the ordering */ |
605 | 11.3k | VECTOR(ordering)[u] = order_next_neg--; |
606 | | /* Exclude the node from further searches */ |
607 | 11.3k | VECTOR(indegrees)[u] = VECTOR(outdegrees)[u] = -1; |
608 | | /* Get the neighbors and decrease their degrees */ |
609 | 11.3k | IGRAPH_CHECK(igraph_incident(graph, &neis, u, IGRAPH_IN, IGRAPH_LOOPS)); |
610 | 11.3k | neis_size = igraph_vector_int_size(&neis); |
611 | 28.4k | for (igraph_int_t i = 0; i < neis_size; i++) { |
612 | 17.0k | const igraph_int_t eid = VECTOR(neis)[i]; |
613 | 17.0k | const igraph_int_t w = IGRAPH_FROM(graph, eid); |
614 | 17.0k | if (VECTOR(outdegrees)[w] <= 0) { |
615 | | /* Already removed, continue */ |
616 | 3.07k | continue; |
617 | 3.07k | } |
618 | 14.0k | VECTOR(outdegrees)[w]--; |
619 | 14.0k | VECTOR(outstrengths)[w] -= (weights ? VECTOR(*weights)[eid] : 1.0); |
620 | 14.0k | if (VECTOR(outdegrees)[w] == 0) { |
621 | 5.01k | IGRAPH_CHECK(igraph_dqueue_int_push(&sinks, w)); |
622 | 5.01k | } |
623 | 14.0k | } |
624 | 11.3k | nodes_left--; |
625 | 11.3k | } |
626 | | |
627 | | /* (3) No more sources or sinks. Find the node with the largest |
628 | | * difference between its out-strength and in-strength */ |
629 | 8.24k | igraph_int_t v = -1; |
630 | 8.24k | igraph_real_t maxdiff = -IGRAPH_INFINITY; |
631 | 1.66M | for (igraph_int_t u = 0; u < no_of_nodes; u++) { |
632 | 1.65M | if (VECTOR(outdegrees)[u] < 0) { |
633 | 1.53M | continue; |
634 | 1.53M | } |
635 | 116k | igraph_real_t diff = VECTOR(outstrengths)[u] - VECTOR(instrengths)[u]; |
636 | 116k | if (diff > maxdiff) { |
637 | 8.73k | maxdiff = diff; |
638 | 8.73k | v = u; |
639 | 8.73k | } |
640 | 116k | } |
641 | 8.24k | if (v >= 0) { |
642 | | /* Remove vertex v */ |
643 | 4.38k | VECTOR(ordering)[v] = order_next_pos++; |
644 | | /* Remove outgoing edges */ |
645 | 4.38k | IGRAPH_CHECK(igraph_incident(graph, &neis, v, IGRAPH_OUT, IGRAPH_LOOPS)); |
646 | 4.38k | neis_size = igraph_vector_int_size(&neis); |
647 | 27.6k | for (igraph_int_t i = 0; i < neis_size; i++) { |
648 | 23.2k | const igraph_int_t eid = VECTOR(neis)[i]; |
649 | 23.2k | const igraph_int_t w = IGRAPH_TO(graph, eid); |
650 | 23.2k | if (VECTOR(indegrees)[w] <= 0) { |
651 | | /* Already removed, continue */ |
652 | 6.75k | continue; |
653 | 6.75k | } |
654 | 16.5k | VECTOR(indegrees)[w]--; |
655 | 16.5k | VECTOR(instrengths)[w] -= (weights ? VECTOR(*weights)[eid] : 1.0); |
656 | 16.5k | if (VECTOR(indegrees)[w] == 0) { |
657 | 5.95k | IGRAPH_CHECK(igraph_dqueue_int_push(&sources, w)); |
658 | 5.95k | } |
659 | 16.5k | } |
660 | | /* Remove incoming edges */ |
661 | 4.38k | IGRAPH_CHECK(igraph_incident(graph, &neis, v, IGRAPH_IN, IGRAPH_LOOPS)); |
662 | 4.38k | neis_size = igraph_vector_int_size(&neis); |
663 | 21.8k | for (igraph_int_t i = 0; i < neis_size; i++) { |
664 | 17.4k | const igraph_int_t eid = VECTOR(neis)[i]; |
665 | 17.4k | const igraph_int_t w = IGRAPH_FROM(graph, eid); |
666 | 17.4k | if (VECTOR(outdegrees)[w] <= 0) { |
667 | | /* Already removed, continue */ |
668 | 7.45k | continue; |
669 | 7.45k | } |
670 | 10.0k | VECTOR(outdegrees)[w]--; |
671 | 10.0k | VECTOR(outstrengths)[w] -= (weights ? VECTOR(*weights)[eid] : 1.0); |
672 | 10.0k | if (VECTOR(outdegrees)[w] == 0 && VECTOR(indegrees)[w] > 0) { |
673 | 2.17k | IGRAPH_CHECK(igraph_dqueue_int_push(&sinks, w)); |
674 | 2.17k | } |
675 | 10.0k | } |
676 | | |
677 | 4.38k | VECTOR(outdegrees)[v] = -1; |
678 | 4.38k | VECTOR(indegrees)[v] = -1; |
679 | 4.38k | nodes_left--; |
680 | 4.38k | } |
681 | 8.24k | } |
682 | | |
683 | 4.25k | igraph_vector_destroy(&outstrengths); |
684 | 4.25k | igraph_vector_destroy(&instrengths); |
685 | 4.25k | igraph_vector_int_destroy(&outdegrees); |
686 | 4.25k | igraph_vector_int_destroy(&indegrees); |
687 | 4.25k | igraph_dqueue_int_destroy(&sinks); |
688 | 4.25k | igraph_dqueue_int_destroy(&sources); |
689 | 4.25k | IGRAPH_FINALLY_CLEAN(6); |
690 | | |
691 | | /* Tidy up the ordering */ |
692 | 757k | for (igraph_int_t i = 0; i < no_of_nodes; i++) { |
693 | 752k | if (VECTOR(ordering)[i] < 0) { |
694 | 11.3k | VECTOR(ordering)[i] += no_of_nodes; |
695 | 11.3k | } |
696 | 752k | } |
697 | | |
698 | | /* Find the feedback edges based on the ordering */ |
699 | 4.25k | if (result) { |
700 | 4.25k | igraph_vector_int_clear(result); |
701 | 157k | for (igraph_int_t eid = 0; eid < no_of_edges; eid++) { |
702 | 153k | igraph_int_t from = IGRAPH_FROM(graph, eid); |
703 | 153k | igraph_int_t to = IGRAPH_TO(graph, eid); |
704 | 153k | if (from == to || VECTOR(ordering)[from] > VECTOR(ordering)[to]) { |
705 | 32.3k | IGRAPH_CHECK(igraph_vector_int_push_back(result, eid)); |
706 | 32.3k | } |
707 | 153k | } |
708 | 4.25k | } |
709 | | |
710 | | /* If we have also requested a layering, return that as well */ |
711 | 4.25k | if (layers) { |
712 | 0 | igraph_vector_int_t ranks; |
713 | |
|
714 | 0 | IGRAPH_CHECK(igraph_vector_int_resize(layers, no_of_nodes)); |
715 | 0 | igraph_vector_int_null(layers); |
716 | |
|
717 | 0 | IGRAPH_VECTOR_INT_INIT_FINALLY(&ranks, 0); |
718 | | |
719 | 0 | IGRAPH_CHECK(igraph_vector_int_sort_ind(&ordering, &ranks, IGRAPH_ASCENDING)); |
720 | | |
721 | 0 | for (igraph_int_t i = 0; i < no_of_nodes; i++) { |
722 | 0 | igraph_int_t from = VECTOR(ranks)[i]; |
723 | 0 | IGRAPH_CHECK(igraph_neighbors( |
724 | 0 | graph, &neis, from, IGRAPH_OUT, IGRAPH_LOOPS, IGRAPH_MULTIPLE |
725 | 0 | )); |
726 | 0 | neis_size = igraph_vector_int_size(&neis); |
727 | 0 | for (igraph_int_t j = 0; j < neis_size; j++) { |
728 | 0 | igraph_int_t to = VECTOR(neis)[j]; |
729 | 0 | if (from == to) { |
730 | 0 | continue; |
731 | 0 | } |
732 | 0 | if (VECTOR(ordering)[from] > VECTOR(ordering)[to]) { |
733 | 0 | continue; |
734 | 0 | } |
735 | 0 | if (VECTOR(*layers)[to] < VECTOR(*layers)[from] + 1) { |
736 | 0 | VECTOR(*layers)[to] = VECTOR(*layers)[from] + 1; |
737 | 0 | } |
738 | 0 | } |
739 | 0 | } |
740 | | |
741 | 0 | igraph_vector_int_destroy(&ranks); |
742 | 0 | IGRAPH_FINALLY_CLEAN(1); |
743 | 0 | } |
744 | | |
745 | | /* Free the ordering vector */ |
746 | 4.25k | igraph_vector_int_destroy(&neis); |
747 | 4.25k | igraph_vector_int_destroy(&ordering); |
748 | 4.25k | IGRAPH_FINALLY_CLEAN(2); |
749 | | |
750 | 4.25k | return IGRAPH_SUCCESS; |
751 | 4.25k | } |
752 | | |
753 | | |
754 | | /** |
755 | | * Solves the feedback arc set problem with integer programming, |
756 | | * using the triangle inequalities formulation. |
757 | | */ |
758 | | igraph_error_t igraph_i_feedback_arc_set_ip_ti( |
759 | | const igraph_t *graph, igraph_vector_int_t *result, |
760 | 0 | const igraph_vector_t *weights) { |
761 | | #ifndef HAVE_GLPK |
762 | | IGRAPH_ERROR("GLPK is not available.", IGRAPH_UNIMPLEMENTED); |
763 | | #else |
764 | |
|
765 | 0 | const igraph_int_t no_of_vertices = igraph_vcount(graph); |
766 | 0 | const igraph_int_t no_of_edges = igraph_ecount(graph); |
767 | 0 | igraph_int_t no_of_components; |
768 | 0 | igraph_vector_int_t membership, *vec; |
769 | 0 | igraph_vector_int_t ordering, vertex_remapping; |
770 | 0 | igraph_vector_int_list_t vertices_by_components, edges_by_components; |
771 | 0 | igraph_int_t i, j, k, l, m, n, from, to, no_of_rows, n_choose_2; |
772 | 0 | igraph_real_t weight; |
773 | 0 | glp_prob *ip; |
774 | 0 | glp_iocp parm; |
775 | |
|
776 | 0 | IGRAPH_VECTOR_INT_INIT_FINALLY(&membership, 0); |
777 | 0 | IGRAPH_VECTOR_INT_INIT_FINALLY(&ordering, 0); |
778 | 0 | IGRAPH_VECTOR_INT_INIT_FINALLY(&vertex_remapping, no_of_vertices); |
779 | | |
780 | 0 | igraph_vector_int_clear(result); |
781 | | |
782 | | /* Decompose the graph into connected components */ |
783 | 0 | IGRAPH_CHECK(igraph_connected_components(graph, &membership, NULL, &no_of_components, IGRAPH_WEAK)); |
784 | | |
785 | | /* Construct vertex and edge lists for each of the components */ |
786 | 0 | IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&vertices_by_components, no_of_components); |
787 | 0 | IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&edges_by_components, no_of_components); |
788 | 0 | for (i = 0; i < no_of_vertices; i++) { |
789 | 0 | j = VECTOR(membership)[i]; |
790 | 0 | vec = igraph_vector_int_list_get_ptr(&vertices_by_components, j); |
791 | 0 | IGRAPH_CHECK(igraph_vector_int_push_back(vec, i)); |
792 | 0 | } |
793 | 0 | for (i = 0; i < no_of_edges; i++) { |
794 | 0 | j = VECTOR(membership)[IGRAPH_FROM(graph, i)]; |
795 | 0 | vec = igraph_vector_int_list_get_ptr(&edges_by_components, j); |
796 | 0 | IGRAPH_CHECK(igraph_vector_int_push_back(vec, i)); |
797 | 0 | } |
798 | | |
799 | 0 | #define VAR2IDX(i, j) (i*(n-1)+j-(i+1)*i/2) |
800 | | |
801 | | /* Configure GLPK */ |
802 | 0 | IGRAPH_GLPK_SETUP(); |
803 | 0 | glp_init_iocp(&parm); |
804 | 0 | parm.br_tech = GLP_BR_DTH; |
805 | 0 | parm.bt_tech = GLP_BT_BLB; |
806 | 0 | parm.pp_tech = GLP_PP_ALL; |
807 | 0 | parm.presolve = GLP_ON; |
808 | 0 | parm.binarize = GLP_OFF; |
809 | 0 | parm.cb_func = igraph_i_glpk_interruption_hook; |
810 | | |
811 | | /* Solve an IP for feedback arc sets in each of the components */ |
812 | 0 | for (i = 0; i < no_of_components; i++) { |
813 | 0 | igraph_vector_int_t *vertices_in_comp = igraph_vector_int_list_get_ptr(&vertices_by_components, i); |
814 | 0 | igraph_vector_int_t *edges_in_comp = igraph_vector_int_list_get_ptr(&edges_by_components, i); |
815 | | |
816 | | /* |
817 | | * Let x_ij denote whether layer(i) < layer(j). |
818 | | * |
819 | | * The standard formulation of the problem is as follows: |
820 | | * |
821 | | * max sum_{i,j} w_ij x_ij |
822 | | * |
823 | | * subject to |
824 | | * |
825 | | * (1) x_ij + x_ji = 1 (i.e. either layer(i) < layer(j) or layer(i) > layer(j)) |
826 | | * for all i < j |
827 | | * (2) x_ij + x_jk + x_ki <= 2 for all i < j, i < k, j != k |
828 | | * |
829 | | * Note that x_ij = 1 implies that x_ji = 0 and vice versa; in other words, |
830 | | * x_ij = 1 - x_ji. Thus, we can get rid of the (1) constraints and half of the |
831 | | * x_ij variables (where j < i) if we rewrite constraints of type (2) as follows: |
832 | | * |
833 | | * (2a) x_ij + x_jk - x_ik <= 1 for all i < j, i < k, j < k |
834 | | * (2b) x_ij - x_kj - x_ik <= 0 for all i < j, i < k, j > k |
835 | | * |
836 | | * The goal function then becomes: |
837 | | * |
838 | | * max sum_{i<j} (w_ij-w_ji) x_ij |
839 | | */ |
840 | 0 | n = igraph_vector_int_size(vertices_in_comp); |
841 | 0 | ip = glp_create_prob(); |
842 | 0 | IGRAPH_FINALLY(igraph_i_glp_delete_prob, ip); |
843 | 0 | glp_set_obj_dir(ip, GLP_MAX); |
844 | | |
845 | | /* Construct a mapping from vertex IDs to the [0; n-1] range */ |
846 | 0 | for (j = 0; j < n; j++) { |
847 | 0 | VECTOR(vertex_remapping)[VECTOR(*vertices_in_comp)[j]] = j; |
848 | 0 | } |
849 | | |
850 | | /* Set up variables */ |
851 | 0 | IGRAPH_SAFE_N_CHOOSE_2(n, &n_choose_2); |
852 | 0 | if (n_choose_2 > INT_MAX) { |
853 | 0 | IGRAPH_ERROR("Feedback arc set problem too large for GLPK.", IGRAPH_EOVERFLOW); |
854 | 0 | } |
855 | | |
856 | 0 | if (n_choose_2 > 0) { |
857 | 0 | glp_add_cols(ip, (int) n_choose_2); |
858 | 0 | for (j = 1; j <= n_choose_2; j++) { |
859 | 0 | glp_set_col_kind(ip, (int) j, GLP_BV); |
860 | 0 | } |
861 | 0 | } |
862 | | |
863 | | /* Set up coefficients in the goal function */ |
864 | 0 | k = igraph_vector_int_size(edges_in_comp); |
865 | 0 | for (j = 0; j < k; j++) { |
866 | 0 | l = VECTOR(*edges_in_comp)[j]; |
867 | 0 | from = VECTOR(vertex_remapping)[IGRAPH_FROM(graph, l)]; |
868 | 0 | to = VECTOR(vertex_remapping)[IGRAPH_TO(graph, l)]; |
869 | 0 | if (from == to) { |
870 | 0 | continue; |
871 | 0 | } |
872 | | |
873 | 0 | weight = weights ? VECTOR(*weights)[l] : 1; |
874 | |
|
875 | 0 | if (from < to) { |
876 | 0 | l = VAR2IDX(from, to); |
877 | 0 | glp_set_obj_coef(ip, (int) l, glp_get_obj_coef(ip, (int) l) + weight); |
878 | 0 | } else { |
879 | 0 | l = VAR2IDX(to, from); |
880 | 0 | glp_set_obj_coef(ip, (int) l, glp_get_obj_coef(ip, (int) l) - weight); |
881 | 0 | } |
882 | 0 | } |
883 | | |
884 | | /* Add constraints */ |
885 | 0 | if (n > 1) { |
886 | 0 | { |
887 | | /* Overflow-safe block for: |
888 | | * no_of_rows = n * (n - 1) / 2 + n * (n - 1) * (n - 2) / 3 |
889 | | */ |
890 | | |
891 | | /* res = n * (n - 1) * (n - 2) / 3 */ |
892 | 0 | igraph_int_t mod = n % 3; |
893 | 0 | igraph_int_t res = n / 3; /* same as (n - mod) / 3 */ |
894 | |
|
895 | 0 | mod = (mod + 1) % 3; |
896 | 0 | IGRAPH_SAFE_MULT(res, n - mod, &res); |
897 | 0 | mod = (mod + 1) % 3; |
898 | 0 | IGRAPH_SAFE_MULT(res, n - mod, &res); |
899 | | |
900 | | /* no_of_rows = n * (n - 1) / 2 + res */ |
901 | 0 | IGRAPH_SAFE_ADD(n_choose_2, res, &no_of_rows); |
902 | 0 | } |
903 | 0 | if (no_of_rows > INT_MAX) { |
904 | 0 | IGRAPH_ERROR("Feedback arc set problem too large for GLPK.", IGRAPH_EOVERFLOW); |
905 | 0 | } |
906 | 0 | glp_add_rows(ip, (int) no_of_rows); |
907 | 0 | m = 1; |
908 | 0 | for (j = 0; j < n; j++) { |
909 | 0 | int ind[4]; |
910 | 0 | double val[4] = {0, 1, 1, -1}; |
911 | 0 | for (k = j + 1; k < n; k++) { |
912 | 0 | ind[1] = (int) VAR2IDX(j, k); |
913 | | /* Type (2a) */ |
914 | 0 | val[2] = 1; |
915 | 0 | for (l = k + 1; l < n; l++, m++) { |
916 | 0 | ind[2] = (int) VAR2IDX(k, l); |
917 | 0 | ind[3] = (int) VAR2IDX(j, l); |
918 | 0 | glp_set_row_bnds(ip, (int) m, GLP_UP, 1, 1); |
919 | 0 | glp_set_mat_row(ip, (int) m, 3, ind, val); |
920 | 0 | } |
921 | | /* Type (2b) */ |
922 | 0 | val[2] = -1; |
923 | 0 | for (l = j + 1; l < k; l++, m++) { |
924 | 0 | ind[2] = (int) VAR2IDX(l, k); |
925 | 0 | ind[3] = (int) VAR2IDX(j, l); |
926 | 0 | glp_set_row_bnds(ip, (int) m, GLP_UP, 0, 0); |
927 | 0 | glp_set_mat_row(ip, (int) m, 3, ind, val); |
928 | 0 | } |
929 | 0 | } |
930 | 0 | } |
931 | 0 | } |
932 | | |
933 | | /* Solve the problem */ |
934 | 0 | IGRAPH_GLPK_CHECK(glp_intopt(ip, &parm), |
935 | 0 | "Feedback arc set using IP with triangle inequalities failed"); |
936 | | |
937 | | /* Find the ordering of the vertices */ |
938 | 0 | IGRAPH_CHECK(igraph_vector_int_resize(&ordering, n)); |
939 | 0 | igraph_vector_int_null(&ordering); |
940 | 0 | j = 0; k = 1; |
941 | 0 | for (l = 1; l <= n_choose_2; l++) { |
942 | | /* variable l always corresponds to the (j, k) vertex pair */ |
943 | | /* printf("(%ld, %ld) = %g\n", i, j, glp_mip_col_val(ip, l)); */ |
944 | 0 | if (glp_mip_col_val(ip, (int) l) > 0) { |
945 | | /* j comes earlier in the ordering than k */ |
946 | 0 | VECTOR(ordering)[j]++; |
947 | 0 | } else { |
948 | | /* k comes earlier in the ordering than j */ |
949 | 0 | VECTOR(ordering)[k]++; |
950 | 0 | } |
951 | 0 | k++; |
952 | 0 | if (k == n) { |
953 | 0 | j++; k = j + 1; |
954 | 0 | } |
955 | 0 | } |
956 | | |
957 | | /* Find the feedback edges */ |
958 | 0 | k = igraph_vector_int_size(edges_in_comp); |
959 | 0 | for (j = 0; j < k; j++) { |
960 | 0 | l = VECTOR(*edges_in_comp)[j]; |
961 | 0 | from = VECTOR(vertex_remapping)[IGRAPH_FROM(graph, l)]; |
962 | 0 | to = VECTOR(vertex_remapping)[IGRAPH_TO(graph, l)]; |
963 | 0 | if (from == to || VECTOR(ordering)[from] < VECTOR(ordering)[to]) { |
964 | 0 | IGRAPH_CHECK(igraph_vector_int_push_back(result, l)); |
965 | 0 | } |
966 | 0 | } |
967 | | |
968 | | /* Clean up */ |
969 | 0 | glp_delete_prob(ip); |
970 | 0 | IGRAPH_FINALLY_CLEAN(1); |
971 | 0 | } |
972 | | |
973 | 0 | igraph_vector_int_list_destroy(&vertices_by_components); |
974 | 0 | igraph_vector_int_list_destroy(&edges_by_components); |
975 | 0 | igraph_vector_int_destroy(&vertex_remapping); |
976 | 0 | igraph_vector_int_destroy(&ordering); |
977 | 0 | igraph_vector_int_destroy(&membership); |
978 | 0 | IGRAPH_FINALLY_CLEAN(5); |
979 | |
|
980 | 0 | return IGRAPH_SUCCESS; |
981 | 0 | #endif |
982 | 0 | } |
983 | | |
984 | | |
985 | | /** |
986 | | * Incremental constraint generation based integer programming implementation |
987 | | * for feedback arc set (FAS) and feedback vertex set (FVS). |
988 | | * |
989 | | * b_i are binary variables indicating the presence of edge/vertex i in the |
990 | | * FAS/FVS. w_i is the weight of edge/vertex i. |
991 | | * |
992 | | * We minimize |
993 | | * |
994 | | * sum_i w_i b_i |
995 | | * |
996 | | * subject to the constraints |
997 | | * |
998 | | * sum_i c^k_i b_i >= 1 |
999 | | * |
1000 | | * where c^k_i is a binary coefficient indicating if edge/vertex i is present |
1001 | | * in cycle k. |
1002 | | * |
1003 | | * While this must hold for all cycles (all cycles must be broken), |
1004 | | * we generate cycles incrementally, re-solving the problem after |
1005 | | * each step. New cycles are generated in such a way as to avoid |
1006 | | * the feedback set from the previous solution step. |
1007 | | */ |
1008 | | |
1009 | 0 | #define VAR_TO_ID(j) ((j) - 1) |
1010 | | |
1011 | | /* Helper data structure for adding rows to GLPK problems. |
1012 | | * ind[] and val[] use one-based indexing, in line with GLPK's convention. |
1013 | | * Storing the zero-based ind0/val0 and the offset ind/val is necessary |
1014 | | * to avoid GCC warnings. */ |
1015 | | typedef struct { |
1016 | | int alloc_size; |
1017 | | int *ind0, *ind; |
1018 | | double *val0, *val; |
1019 | | } rowdata_t; |
1020 | | |
1021 | 0 | static igraph_error_t rowdata_init(rowdata_t *rd, int size) { |
1022 | 0 | int *ind0 = IGRAPH_CALLOC(size, int); |
1023 | 0 | IGRAPH_CHECK_OOM(ind0, "Insufficient memory for feedback arc set."); |
1024 | 0 | IGRAPH_FINALLY(igraph_free, ind0); |
1025 | 0 | double *val0 = IGRAPH_CALLOC(size, double); |
1026 | 0 | IGRAPH_CHECK_OOM(val0, "Insufficient memory for feedback arc set."); |
1027 | 0 | for (int i=0; i < size; i++) { |
1028 | 0 | val0[i] = 1.0; |
1029 | 0 | } |
1030 | 0 | rd->alloc_size = size; |
1031 | 0 | rd->ind0 = ind0; |
1032 | 0 | rd->ind = ind0 - 1; |
1033 | 0 | rd->val0 = val0; |
1034 | 0 | rd->val = val0 - 1; |
1035 | 0 | IGRAPH_FINALLY_CLEAN(1); |
1036 | 0 | return IGRAPH_SUCCESS; |
1037 | 0 | } |
1038 | | |
1039 | 0 | static igraph_error_t rowdata_set(rowdata_t *rd, const igraph_vector_int_t *idx) { |
1040 | 0 | int size = igraph_vector_int_size(idx); |
1041 | | |
1042 | | /* Expand size if needed */ |
1043 | 0 | if (size > rd->alloc_size) { |
1044 | 0 | int new_alloc_size = 2 * rd->alloc_size; |
1045 | 0 | if (size > new_alloc_size) { |
1046 | 0 | new_alloc_size = size; |
1047 | 0 | } |
1048 | |
|
1049 | 0 | int *ind0 = rd->ind0; |
1050 | 0 | double *val0 = rd->val0; |
1051 | |
|
1052 | 0 | ind0 = IGRAPH_REALLOC(ind0, new_alloc_size, int); |
1053 | 0 | IGRAPH_CHECK_OOM(ind0, "Insufficient memory for feedback arc set."); |
1054 | 0 | rd->ind0 = ind0; |
1055 | 0 | rd->ind = ind0 - 1; |
1056 | |
|
1057 | 0 | val0 = IGRAPH_REALLOC(val0, new_alloc_size, double); |
1058 | 0 | IGRAPH_CHECK_OOM(val0, "Insufficient memory for feedback arc set."); |
1059 | 0 | for (int i = rd->alloc_size; i < new_alloc_size; i++) { |
1060 | 0 | val0[i] = 1.0; |
1061 | 0 | } |
1062 | 0 | rd->val0 = val0; |
1063 | 0 | rd->val = val0 - 1; |
1064 | |
|
1065 | 0 | rd->alloc_size = new_alloc_size; |
1066 | 0 | } |
1067 | | |
1068 | 0 | for (int i = 0; i < size; i++) { |
1069 | 0 | rd->ind0[i] = VECTOR(*idx)[i] + 1; |
1070 | 0 | } |
1071 | |
|
1072 | 0 | return IGRAPH_SUCCESS; |
1073 | 0 | } |
1074 | | |
1075 | 0 | static void rowdata_destroy(rowdata_t *rd) { |
1076 | 0 | igraph_free(rd->ind0); |
1077 | 0 | igraph_free(rd->val0); |
1078 | 0 | } |
1079 | | |
1080 | | igraph_error_t igraph_i_feedback_arc_set_ip_cg( |
1081 | | const igraph_t *graph, igraph_vector_int_t *result, |
1082 | 0 | const igraph_vector_t *weights) { |
1083 | |
|
1084 | | #ifndef HAVE_GLPK |
1085 | | IGRAPH_ERROR("GLPK is not available.", IGRAPH_UNIMPLEMENTED); |
1086 | | #else |
1087 | 0 | const igraph_int_t ecount = igraph_ecount(graph); |
1088 | 0 | igraph_bool_t is_dag; |
1089 | 0 | igraph_bitset_t removed; |
1090 | 0 | igraph_vector_int_t cycle; |
1091 | 0 | glp_prob *ip; |
1092 | 0 | glp_iocp parm; |
1093 | 0 | rowdata_t rd; |
1094 | 0 | int var_count; |
1095 | | |
1096 | | /* Avoid starting up the IP machinery for DAGs. */ |
1097 | 0 | IGRAPH_CHECK(igraph_is_dag(graph, &is_dag)); |
1098 | 0 | if (is_dag) { |
1099 | 0 | igraph_vector_int_clear(result); |
1100 | 0 | return IGRAPH_SUCCESS; |
1101 | 0 | } |
1102 | | |
1103 | 0 | if (ecount > INT_MAX) { |
1104 | 0 | IGRAPH_ERROR("Feedback arc set problem too large for GLPK.", IGRAPH_EOVERFLOW); |
1105 | 0 | } |
1106 | | |
1107 | 0 | var_count = (int) ecount; |
1108 | | |
1109 | | /* TODO: In-depth investigation of whether decomposing to SCCs helps performance. |
1110 | | * Basic benchmarking on sparse random graphs with mean degrees between 1-2 |
1111 | | * indicate no benefit from avoiding creating GLPK variables for non-cycle edges. */ |
1112 | |
|
1113 | 0 | IGRAPH_BITSET_INIT_FINALLY(&removed, ecount); |
1114 | 0 | IGRAPH_VECTOR_INT_INIT_FINALLY(&cycle, 0); |
1115 | | |
1116 | 0 | IGRAPH_CHECK(rowdata_init(&rd, 20)); |
1117 | 0 | IGRAPH_FINALLY(rowdata_destroy, &rd); |
1118 | | |
1119 | | /* Configure GLPK */ |
1120 | 0 | IGRAPH_GLPK_SETUP(); |
1121 | 0 | glp_init_iocp(&parm); |
1122 | 0 | parm.br_tech = GLP_BR_MFV; |
1123 | 0 | parm.bt_tech = GLP_BT_BLB; |
1124 | 0 | parm.pp_tech = GLP_PP_ALL; |
1125 | 0 | parm.presolve = GLP_ON; |
1126 | 0 | parm.cb_func = igraph_i_glpk_interruption_hook; |
1127 | |
|
1128 | 0 | ip = glp_create_prob(); |
1129 | 0 | IGRAPH_FINALLY(igraph_i_glp_delete_prob, ip); |
1130 | |
|
1131 | 0 | glp_set_obj_dir(ip, GLP_MIN); |
1132 | |
|
1133 | 0 | glp_add_cols(ip, var_count); |
1134 | 0 | for (int j = 1; j <= var_count; j++) { |
1135 | 0 | glp_set_obj_coef(ip, j, weights ? VECTOR(*weights)[ VAR_TO_ID(j) ] : 1); |
1136 | 0 | glp_set_col_kind(ip, j, GLP_BV); |
1137 | 0 | } |
1138 | |
|
1139 | 0 | while (true) { |
1140 | 0 | int cycle_size, row; |
1141 | |
|
1142 | 0 | IGRAPH_CHECK(igraph_i_find_cycle(graph, NULL, &cycle, NULL, IGRAPH_OUT, &removed)); |
1143 | | |
1144 | 0 | cycle_size = (int) igraph_vector_int_size(&cycle); |
1145 | |
|
1146 | 0 | if (cycle_size == 0) break; /* no more cycles, we're done */ |
1147 | | |
1148 | 0 | IGRAPH_CHECK(rowdata_set(&rd, &cycle)); |
1149 | | |
1150 | 0 | row = glp_add_rows(ip, 1); |
1151 | 0 | glp_set_row_bnds(ip, row, GLP_LO, 1, 0); |
1152 | 0 | glp_set_mat_row(ip, row, cycle_size, rd.ind, rd.val); |
1153 | | |
1154 | | /* Add as many edge-disjoint cycles at once as possible. */ |
1155 | 0 | while (true) { |
1156 | 0 | for (int i=0; i < cycle_size; i++) { |
1157 | 0 | IGRAPH_BIT_SET(removed, VECTOR(cycle)[i]); |
1158 | 0 | } |
1159 | 0 | IGRAPH_CHECK(igraph_i_find_cycle(graph, NULL, &cycle, NULL, IGRAPH_OUT, &removed)); |
1160 | | |
1161 | 0 | cycle_size = (int) igraph_vector_int_size(&cycle); |
1162 | 0 | if (cycle_size == 0) break; /* no more edge disjoint cycles */ |
1163 | | |
1164 | 0 | IGRAPH_CHECK(rowdata_set(&rd, &cycle)); |
1165 | | |
1166 | 0 | row = glp_add_rows(ip, 1); |
1167 | 0 | glp_set_row_bnds(ip, row, GLP_LO, 1, 0); |
1168 | 0 | glp_set_mat_row(ip, row, cycle_size, rd.ind, rd.val); |
1169 | 0 | } |
1170 | | |
1171 | 0 | IGRAPH_GLPK_CHECK(glp_intopt(ip, &parm), |
1172 | 0 | "Feedback arc set using IP with incremental cycle generation failed"); |
1173 | | |
1174 | 0 | igraph_vector_int_clear(result); |
1175 | 0 | igraph_bitset_null(&removed); |
1176 | 0 | for (int j=1; j <= var_count; j++) { |
1177 | 0 | if (glp_mip_col_val(ip, j) > 0) { |
1178 | 0 | igraph_int_t i = VAR_TO_ID(j); |
1179 | 0 | IGRAPH_CHECK(igraph_vector_int_push_back(result, i)); |
1180 | 0 | IGRAPH_BIT_SET(removed, i); |
1181 | 0 | } |
1182 | 0 | } |
1183 | 0 | } |
1184 | | |
1185 | | /* Clean up */ |
1186 | 0 | glp_delete_prob(ip); |
1187 | 0 | rowdata_destroy(&rd); |
1188 | 0 | igraph_vector_int_destroy(&cycle); |
1189 | 0 | igraph_bitset_destroy(&removed); |
1190 | 0 | IGRAPH_FINALLY_CLEAN(4); |
1191 | |
|
1192 | 0 | return IGRAPH_SUCCESS; |
1193 | 0 | #endif |
1194 | 0 | } |
1195 | | |
1196 | | |
1197 | | igraph_error_t igraph_i_feedback_vertex_set_ip_cg( |
1198 | | const igraph_t *graph, igraph_vector_int_t *result, |
1199 | 0 | const igraph_vector_t *vertex_weights) { |
1200 | | #ifndef HAVE_GLPK |
1201 | | IGRAPH_ERROR("GLPK is not available.", IGRAPH_UNIMPLEMENTED); |
1202 | | #else |
1203 | |
|
1204 | 0 | const igraph_int_t vcount = igraph_vcount(graph); |
1205 | 0 | const igraph_int_t ecount = igraph_ecount(graph); |
1206 | 0 | igraph_bool_t is_acyclic; |
1207 | 0 | igraph_bitset_t removed; |
1208 | 0 | igraph_vector_int_t cycle; |
1209 | 0 | igraph_vector_int_t incident; |
1210 | 0 | glp_prob *ip; |
1211 | 0 | glp_iocp parm; |
1212 | 0 | rowdata_t rd; |
1213 | 0 | int var_count; |
1214 | | |
1215 | | /* Avoid starting up the IP machinery for acyclic graphs. */ |
1216 | 0 | IGRAPH_CHECK(igraph_is_acyclic(graph, &is_acyclic)); |
1217 | | |
1218 | 0 | if (is_acyclic) { |
1219 | 0 | igraph_vector_int_clear(result); |
1220 | 0 | return IGRAPH_SUCCESS; |
1221 | 0 | } |
1222 | | |
1223 | 0 | if (vcount > INT_MAX) { |
1224 | 0 | IGRAPH_ERROR("Feedback vertex set problem too large for GLPK.", IGRAPH_EOVERFLOW); |
1225 | 0 | } |
1226 | | |
1227 | 0 | var_count = (int) vcount; |
1228 | |
|
1229 | 0 | IGRAPH_BITSET_INIT_FINALLY(&removed, ecount); |
1230 | 0 | IGRAPH_VECTOR_INT_INIT_FINALLY(&cycle, 0); |
1231 | 0 | IGRAPH_VECTOR_INT_INIT_FINALLY(&incident, 0); |
1232 | 0 | IGRAPH_CHECK(rowdata_init(&rd, 20)); |
1233 | 0 | IGRAPH_FINALLY(rowdata_destroy, &rd); |
1234 | | |
1235 | | /* Configure GLPK */ |
1236 | 0 | IGRAPH_GLPK_SETUP(); |
1237 | 0 | glp_init_iocp(&parm); |
1238 | 0 | parm.br_tech = GLP_BR_MFV; |
1239 | 0 | parm.bt_tech = GLP_BT_BLB; |
1240 | 0 | parm.pp_tech = GLP_PP_ALL; |
1241 | 0 | parm.presolve = GLP_ON; |
1242 | 0 | parm.cb_func = igraph_i_glpk_interruption_hook; |
1243 | |
|
1244 | 0 | ip = glp_create_prob(); |
1245 | 0 | IGRAPH_FINALLY(igraph_i_glp_delete_prob, ip); |
1246 | |
|
1247 | 0 | glp_set_obj_dir(ip, GLP_MIN); |
1248 | |
|
1249 | 0 | glp_add_cols(ip, var_count); |
1250 | 0 | for (int j = 1; j <= var_count; j++) { |
1251 | 0 | glp_set_obj_coef(ip, j, vertex_weights ? VECTOR(*vertex_weights)[ VAR_TO_ID(j) ] : 1); |
1252 | 0 | glp_set_col_kind(ip, j, GLP_BV); |
1253 | 0 | } |
1254 | |
|
1255 | 0 | while (true) { |
1256 | 0 | int cycle_size, row; |
1257 | |
|
1258 | 0 | IGRAPH_CHECK(igraph_i_find_cycle(graph, &cycle, NULL, NULL, IGRAPH_OUT, &removed)); |
1259 | | |
1260 | 0 | cycle_size = (int) igraph_vector_int_size(&cycle); |
1261 | |
|
1262 | 0 | if (cycle_size == 0) break; /* no more cycles, we're done */ |
1263 | | |
1264 | 0 | IGRAPH_CHECK(rowdata_set(&rd, &cycle)); |
1265 | | |
1266 | 0 | row = glp_add_rows(ip, 1); |
1267 | 0 | glp_set_row_bnds(ip, row, GLP_LO, 1, 0); |
1268 | 0 | glp_set_mat_row(ip, row, cycle_size, rd.ind, rd.val); |
1269 | | |
1270 | | /* Add as many vertex-disjoint cycles at once as possible. */ |
1271 | 0 | while (true) { |
1272 | 0 | for (int i=0; i < cycle_size; i++) { |
1273 | 0 | IGRAPH_CHECK(igraph_incident(graph, &incident, VECTOR(cycle)[i], IGRAPH_ALL, IGRAPH_LOOPS)); |
1274 | 0 | const igraph_int_t incident_size = igraph_vector_int_size(&incident); |
1275 | 0 | for (igraph_int_t j = 0; j < incident_size; j++) { |
1276 | 0 | igraph_int_t eid = VECTOR(incident)[j]; |
1277 | 0 | IGRAPH_BIT_SET(removed, eid); |
1278 | 0 | } |
1279 | 0 | } |
1280 | 0 | IGRAPH_CHECK(igraph_i_find_cycle(graph, &cycle, NULL, NULL, IGRAPH_OUT, &removed)); |
1281 | | |
1282 | 0 | cycle_size = (int) igraph_vector_int_size(&cycle); |
1283 | 0 | if (cycle_size == 0) break; /* no more vertex disjoint cycles */ |
1284 | | |
1285 | 0 | IGRAPH_CHECK(rowdata_set(&rd, &cycle)); |
1286 | | |
1287 | 0 | row = glp_add_rows(ip, 1); |
1288 | 0 | glp_set_row_bnds(ip, row, GLP_LO, 1, 0); |
1289 | 0 | glp_set_mat_row(ip, row, cycle_size, rd.ind, rd.val); |
1290 | 0 | } |
1291 | | |
1292 | 0 | IGRAPH_GLPK_CHECK(glp_intopt(ip, &parm), |
1293 | 0 | "Feedback vertex set using IP with incremental cycle generation failed"); |
1294 | | |
1295 | 0 | igraph_vector_int_clear(result); |
1296 | 0 | igraph_bitset_null(&removed); |
1297 | |
|
1298 | 0 | for (int j=1; j <= var_count; j++) { |
1299 | 0 | if (glp_mip_col_val(ip, j) > 0) { |
1300 | 0 | igraph_int_t i = VAR_TO_ID(j); |
1301 | 0 | IGRAPH_CHECK(igraph_vector_int_push_back(result, i)); |
1302 | | |
1303 | 0 | IGRAPH_CHECK(igraph_incident(graph, &incident, i, IGRAPH_ALL, IGRAPH_LOOPS)); |
1304 | | |
1305 | 0 | const igraph_int_t incident_size = igraph_vector_int_size(&incident); |
1306 | 0 | for (igraph_int_t k = 0; k < incident_size; k++) { |
1307 | 0 | igraph_int_t eid = VECTOR(incident)[k]; |
1308 | 0 | IGRAPH_BIT_SET(removed, eid); |
1309 | 0 | } |
1310 | 0 | } |
1311 | 0 | } |
1312 | 0 | } |
1313 | | |
1314 | | /* Clean up */ |
1315 | 0 | glp_delete_prob(ip); |
1316 | 0 | rowdata_destroy(&rd); |
1317 | 0 | igraph_vector_int_destroy(&cycle); |
1318 | 0 | igraph_vector_int_destroy(&incident); |
1319 | 0 | igraph_bitset_destroy(&removed); |
1320 | 0 | IGRAPH_FINALLY_CLEAN(5); |
1321 | |
|
1322 | 0 | return IGRAPH_SUCCESS; |
1323 | 0 | #endif |
1324 | 0 | } |
1325 | | |
1326 | | #undef VAR_TO_ID |