Coverage Report

Created: 2026-06-09 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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 &gt;= 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(&degrees, no_of_nodes);
477
0
        IGRAPH_VECTOR_INT_INIT_FINALLY(&roots, no_of_nodes);
478
0
        IGRAPH_CHECK(igraph_strength(graph, &degrees, igraph_vss_all(),
479
0
                                     IGRAPH_ALL, IGRAPH_NO_LOOPS, weights));
480
0
        IGRAPH_CHECK(igraph_vector_sort_ind(&degrees, &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(&degrees);
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