Coverage Report

Created: 2026-07-22 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/igraph/src/cycles/cycle_bases.c
Line
Count
Source
1
/*
2
   igraph library.
3
   Copyright (C) 2021-2025  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
21
#include "igraph_adjlist.h"
22
#include "igraph_components.h"
23
#include "igraph_dqueue.h"
24
#include "igraph_error.h"
25
#include "igraph_interface.h"
26
27
#include "core/interruption.h"
28
#include "cycles/order_cycle.h"
29
30
/**** Fundamental cycles *****/
31
32
/* Computes fundamental cycles for the connected component containing 'start_vid',
33
 * and appends them to 'result'.
34
 *
35
 * 'visited' must be a vector of length igraph_vcount(graph).
36
 * visited[u] will be set to mark+1 or mark+2 for each visited vertex 'u'.
37
 * No elements of 'visited' must have these values when calling this function.
38
 * 'mark' can be specified in order to be able to re-use a 'visited' vector
39
 * multiple times without having to re-set all its elements.
40
 *
41
 * During the operation of the function, mark+1 indicates that a vertex has been
42
 * queued for processing, but not processed yet. mark+2 indicates that it has
43
 * been processed.
44
 */
45
static igraph_error_t
46
igraph_i_fundamental_cycles_bfs(
47
        const igraph_t *graph,
48
        igraph_vector_int_list_t *result,
49
        igraph_int_t start_vid,
50
        igraph_int_t bfs_cutoff,
51
        const igraph_inclist_t *inclist,
52
        igraph_vector_int_t *visited,
53
366k
        igraph_int_t mark /* mark used in 'visited' */) {
54
55
366k
    const igraph_int_t no_of_nodes = igraph_vcount(graph);
56
366k
    igraph_dqueue_int_t q;
57
366k
    igraph_vector_int_t pred_edge;
58
366k
    igraph_vector_int_t u_back, v_back;
59
60
366k
    if (start_vid < 0 || start_vid >= no_of_nodes) {
61
0
        IGRAPH_ERROR("Invalid starting vertex id.", IGRAPH_EINVAL);
62
0
    }
63
64
366k
    if (mark > IGRAPH_INTEGER_MAX - 2) {
65
0
        IGRAPH_ERROR("Graph too large for cycle basis.", IGRAPH_EOVERFLOW);
66
0
    }
67
68
366k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&pred_edge, no_of_nodes);
69
366k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&u_back, 0);
70
366k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&v_back, 0);
71
72
366k
    IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 0);
73
74
366k
    IGRAPH_CHECK(igraph_dqueue_int_push(&q, start_vid)); /* vertex id */
75
366k
    IGRAPH_CHECK(igraph_dqueue_int_push(&q, 0)); /* distance from start_vid*/
76
366k
    VECTOR(*visited)[start_vid] = mark + 1; /* mark as seen */
77
366k
    VECTOR(pred_edge)[start_vid] = -1; /* non-valid predecessor edge id for root vertex */
78
79
1.49M
    while (! igraph_dqueue_int_empty(&q)) {
80
1.12M
        igraph_int_t v = igraph_dqueue_int_pop(&q);
81
1.12M
        igraph_int_t vdist = igraph_dqueue_int_pop(&q);
82
83
1.12M
        igraph_vector_int_t *incs = igraph_inclist_get(inclist, v);
84
1.12M
        igraph_int_t n = igraph_vector_int_size(incs);
85
1.12M
        igraph_int_t i, j;
86
87
1.12M
        IGRAPH_ALLOW_INTERRUPTION();
88
89
4.02M
        for (i=0; i < n; ++i) {
90
2.89M
            igraph_int_t e = VECTOR(*incs)[i];
91
2.89M
            igraph_int_t u = IGRAPH_OTHER(graph, e, v);
92
93
2.89M
            if (e == VECTOR(pred_edge)[v]) {
94
                /* do not follow the edge through which we came to v */
95
762k
                continue;
96
762k
            }
97
98
2.13M
            if (VECTOR(*visited)[u] == mark + 2) {
99
                /* u has already been processed */
100
632k
                continue;
101
1.50M
            } else if (VECTOR(*visited)[u] == mark + 1) {
102
                /* u has been seen but not yet processed */
103
104
                /* Found cycle edge u-v. Now we walk back up the BFS tree
105
                 * in order to find the common ancestor of u and v. We exploit
106
                 * that the distance of u from the start vertex is either the
107
                 * same as that of v, or one greater. */
108
109
738k
                igraph_int_t up = u, vp = v;
110
738k
                igraph_int_t u_back_len, v_back_len;
111
738k
                igraph_vector_int_t cycle;
112
113
738k
                IGRAPH_CHECK(igraph_vector_int_push_back(&v_back, e));
114
1.75M
                for (;;) {
115
1.75M
                    igraph_int_t upe, vpe;
116
117
1.75M
                    if (up == vp) {
118
262k
                        break;
119
262k
                    }
120
121
1.48M
                    upe = VECTOR(pred_edge)[up];
122
1.48M
                    IGRAPH_CHECK(igraph_vector_int_push_back(&u_back, upe));
123
1.48M
                    up = IGRAPH_OTHER(graph, upe, up);
124
125
1.48M
                    if (up == vp) {
126
475k
                        break;
127
475k
                    }
128
129
1.01M
                    vpe = VECTOR(pred_edge)[vp];
130
1.01M
                    IGRAPH_CHECK(igraph_vector_int_push_back(&v_back, vpe));
131
1.01M
                    vp = IGRAPH_OTHER(graph, vpe, vp);
132
1.01M
                }
133
134
738k
                u_back_len = igraph_vector_int_size(&u_back);
135
738k
                v_back_len = igraph_vector_int_size(&v_back);
136
738k
                IGRAPH_VECTOR_INT_INIT_FINALLY(&cycle, u_back_len + v_back_len);
137
138
2.49M
                for (j=0; j < v_back_len; ++j) {
139
1.75M
                    VECTOR(cycle)[j] = VECTOR(v_back)[j];
140
1.75M
                }
141
2.22M
                for (j=0; j < u_back_len; ++j) {
142
1.48M
                    VECTOR(cycle)[v_back_len + j] = VECTOR(u_back)[u_back_len - j - 1];
143
1.48M
                }
144
145
738k
                igraph_vector_int_clear(&v_back);
146
738k
                igraph_vector_int_clear(&u_back);
147
148
738k
                IGRAPH_CHECK(igraph_vector_int_list_push_back(result, &cycle));
149
738k
                IGRAPH_FINALLY_CLEAN(1); /* pass ownership of 'cycle' to 'result' */
150
762k
            } else {
151
                /* encountering u for the first time, queue it for processing */
152
153
                /* Only queue vertices with distance at most 'bfs_cutoff' from the root. */
154
                /* Negative 'bfs_cutoff' indicates no cutoff. */
155
762k
                if (bfs_cutoff < 0 || vdist < bfs_cutoff) {
156
762k
                    IGRAPH_CHECK(igraph_dqueue_int_push(&q, u));
157
762k
                    IGRAPH_CHECK(igraph_dqueue_int_push(&q, vdist + 1));
158
762k
                    VECTOR(*visited)[u] = mark + 1;
159
762k
                    VECTOR(pred_edge)[u] = e;
160
762k
                }
161
762k
            }
162
2.13M
        }
163
164
1.12M
        VECTOR(*visited)[v] = mark + 2; /* mark v as processed */
165
1.12M
    } /* ! igraph_dqueue_int_empty(&q) */
166
167
366k
    igraph_dqueue_int_destroy(&q);
168
366k
    igraph_vector_int_destroy(&v_back);
169
366k
    igraph_vector_int_destroy(&u_back);
170
366k
    igraph_vector_int_destroy(&pred_edge);
171
366k
    IGRAPH_FINALLY_CLEAN(4);
172
173
366k
    return IGRAPH_SUCCESS;
174
366k
}
175
176
177
/**
178
 * \function igraph_fundamental_cycles
179
 * \brief Finds a fundamental cycle basis.
180
 *
181
 * \experimental
182
 *
183
 * This function computes a fundamental cycle basis associated with a breadth-first
184
 * search tree of the graph.
185
 *
186
 * </para><para>
187
 * Edge directions are ignored. Multi-edges and self-loops are supported.
188
 *
189
 * \param graph The graph object.
190
 * \param weights Currently unused.
191
 * \param result An initialized integer vector list. The result will be stored here,
192
 *   each vector containing the edge IDs of a basis element.
193
 * \param start_vid If negative, a complete fundamental cycle basis is returned.
194
 *   If a vertex ID, the fundamental cycles associated with the BFS tree rooted
195
 *   in that vertex will be returned, only for the weakly connected component
196
 *   containing that vertex.
197
 * \param bfs_cutoff If negative, a complete cycle basis is returned. Otherwise, only
198
 *   cycles of length <code>2*bfs_cutoff + 1</code> or shorter are included. \p bfs_cutoff
199
 *   is used to limit the depth of the BFS tree when searching for cycle edges.
200
 * \return Error code.
201
 *
202
 * \sa \ref igraph_minimum_cycle_basis()
203
 *
204
 * Time complexity: O(|V| + |E|).
205
 */
206
igraph_error_t igraph_fundamental_cycles(
207
        const igraph_t *graph, const igraph_vector_t *weights,
208
        igraph_vector_int_list_t *result,
209
1.98k
        igraph_int_t start_vid, igraph_real_t bfs_cutoff) {
210
211
1.98k
    const igraph_int_t no_of_nodes = igraph_vcount(graph);
212
1.98k
    const igraph_int_t no_of_edges = igraph_ecount(graph);
213
1.98k
    igraph_int_t estimated_rank;
214
1.98k
    igraph_int_t i;
215
1.98k
    igraph_inclist_t inclist;
216
1.98k
    igraph_vector_int_t visited; /* see comments before igraph_i_fundamental_cycles_bfs() */
217
218
1.98k
    IGRAPH_UNUSED(weights);
219
220
1.98k
    if (start_vid >= no_of_nodes) {
221
0
        IGRAPH_ERROR("Vertex id out of range.", IGRAPH_EINVAL);
222
0
    }
223
224
1.98k
    IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, IGRAPH_ALL, IGRAPH_LOOPS_ONCE));
225
1.98k
    IGRAPH_FINALLY(igraph_inclist_destroy, &inclist);
226
227
1.98k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&visited, no_of_nodes);
228
229
    /* Compute cycle rank assuming that the graph is connected. */
230
1.98k
    estimated_rank = no_of_edges - no_of_nodes + 1;
231
1.98k
    estimated_rank = estimated_rank < 0 ? 0 : estimated_rank;
232
233
1.98k
    igraph_vector_int_list_clear(result);
234
1.98k
    IGRAPH_CHECK(igraph_vector_int_list_reserve(result, estimated_rank));
235
236
1.98k
    if (start_vid < 0) {
237
389k
        for (i=0; i < no_of_nodes; ++i) {
238
387k
            if (! VECTOR(visited)[i]) {
239
351k
                IGRAPH_CHECK(igraph_i_fundamental_cycles_bfs(graph, result, i, bfs_cutoff, &inclist,
240
351k
                                                             &visited, /* mark */ 0));
241
351k
            }
242
387k
        }
243
1.98k
    } else {
244
0
        IGRAPH_CHECK(igraph_i_fundamental_cycles_bfs(graph, result, start_vid, bfs_cutoff, &inclist,
245
0
                                                     &visited, /* mark */ 0));
246
0
    }
247
248
1.98k
    igraph_vector_int_destroy(&visited);
249
1.98k
    igraph_inclist_destroy(&inclist);
250
1.98k
    IGRAPH_FINALLY_CLEAN(2);
251
252
1.98k
    return IGRAPH_SUCCESS;
253
1.98k
}
254
255
256
/***** Minimum weight cycle basis *****/
257
258
/* In this implementation, the cycle vectors (basis elements) are stored as a sparse representation:
259
 * a sorted list of edge indices. */
260
261
262
/* qsort-compatible comparison for sparse cycle vectors: shorter ones come first, use lexicographic
263
 * order for equal length ones. Lexicographic order helps keep row insertion into the reduced matrix
264
 * efficient during Gaussian elimination, by ensuring that insertions usually happen near the end. */
265
5.13M
static int cycle_cmp(const igraph_vector_int_t *v1, const igraph_vector_int_t *v2) {
266
5.13M
    igraph_int_t n1 = igraph_vector_int_size(v1), n2 = igraph_vector_int_size(v2);
267
268
5.13M
    if (n1 < n2) {
269
673k
        return -1;
270
4.46M
    } else if (n1 > n2) {
271
753k
        return 1;
272
3.71M
    } else {
273
3.71M
        return igraph_vector_int_lex_cmp(v1, v2);
274
3.71M
    }
275
5.13M
}
276
277
/* Adding cycle vectors produces the symmetric difference of the corresponding edge sets. */
278
242k
static igraph_error_t cycle_add(const igraph_vector_int_t *a, const igraph_vector_int_t *b, igraph_vector_int_t *res) {
279
242k
    igraph_int_t na = igraph_vector_int_size(a), nb = igraph_vector_int_size(b);
280
242k
    const igraph_int_t *pa = VECTOR(*a), *pb = VECTOR(*b);
281
242k
    const igraph_int_t *pa_end = pa + na, *pb_end = pb + nb;
282
283
242k
    igraph_vector_int_clear(res);
284
637k
    for (;;) {
285
1.00M
        while (pa != pa_end && pb != pb_end && *pa < *pb) {
286
370k
            IGRAPH_CHECK(igraph_vector_int_push_back(res, *pa));
287
370k
            pa++;
288
370k
        }
289
1.30M
        while (pa != pa_end && pb != pb_end && *pa == *pb) {
290
665k
            pa++; pb++;
291
665k
        }
292
1.28M
        while (pa != pa_end && pb != pb_end && *pb < *pa) {
293
650k
            IGRAPH_CHECK(igraph_vector_int_push_back(res, *pb));
294
650k
            pb++;
295
650k
        }
296
637k
        if (pa == pa_end) {
297
575k
            while (pb != pb_end) {
298
366k
                IGRAPH_CHECK(igraph_vector_int_push_back(res, *pb));
299
366k
                pb++;
300
366k
            }
301
208k
            break;
302
208k
        }
303
428k
        if (pb == pb_end) {
304
78.5k
            while (pa != pa_end) {
305
44.3k
                IGRAPH_CHECK(igraph_vector_int_push_back(res, *pa));
306
44.3k
                pa++;
307
44.3k
            }
308
34.2k
            break;
309
34.2k
        }
310
428k
    }
311
312
242k
    return IGRAPH_SUCCESS;
313
242k
}
314
315
316
3.56M
#define MATROW(m, i) (&VECTOR(m)[i])
317
#define MATEL(m, i, j) VECTOR(*MATROW(m, i))[j]
318
319
/* Gaussian elimination for sparse cycle vectors. 'reduced_matrix' is always maintained
320
 * in row-echelon form. This function decides if 'cycle' is linearly independent of this
321
 * matrix, and if not, it adds it to the matrix. */
322
static igraph_error_t gaussian_elimination(igraph_vector_int_list_t *reduced_matrix,
323
                                           const igraph_vector_int_t *cycle,
324
68.3k
                                           igraph_bool_t *independent) {
325
326
68.3k
    const igraph_int_t nrow = igraph_vector_int_list_size(reduced_matrix);
327
68.3k
    igraph_int_t i;
328
329
68.3k
    igraph_vector_int_t work, tmp;
330
331
68.3k
    IGRAPH_CHECK(igraph_vector_int_init_copy(&work, cycle));
332
68.3k
    IGRAPH_FINALLY(igraph_vector_int_destroy, &work);
333
334
68.3k
    IGRAPH_VECTOR_INT_INIT_FINALLY(&tmp, 0);
335
336
3.58M
    for (i=0; i < nrow; ++i) {
337
3.56M
        igraph_vector_int_t *row = MATROW(*reduced_matrix, i);
338
339
3.56M
        if ( VECTOR(*row)[0] < VECTOR(work)[0] ) {
340
3.30M
            continue;
341
3.30M
        } else if ( VECTOR(*row)[0] == VECTOR(work)[0] ) {
342
242k
            IGRAPH_CHECK(cycle_add(row, &work, &tmp));
343
242k
            if (igraph_vector_int_empty(&tmp)) {
344
36.5k
                *independent = false;
345
36.5k
                igraph_vector_int_destroy(&work);
346
36.5k
                igraph_vector_int_destroy(&tmp);
347
36.5k
                IGRAPH_FINALLY_CLEAN(2);
348
36.5k
                return IGRAPH_SUCCESS;
349
36.5k
            }
350
206k
            igraph_vector_int_swap(&work, &tmp);
351
206k
        } else { /* VECTOR(*row)[0] > VECTOR(work)[0] */
352
14.3k
            break;
353
14.3k
        }
354
3.56M
    }
355
356
    /* 'cycle' was linearly independent, insert new row into matrix */
357
31.8k
    *independent = true;
358
31.8k
    IGRAPH_CHECK(igraph_vector_int_list_insert(reduced_matrix, i, &work)); /* transfers ownership */
359
360
31.8k
    igraph_vector_int_destroy(&tmp);
361
31.8k
    IGRAPH_FINALLY_CLEAN(2); /* +1, transferring ownership of 'work' to 'reduced_matrix' */
362
363
31.8k
    return IGRAPH_SUCCESS;
364
31.8k
}
365
366
#undef MATEL
367
#undef MATROW
368
369
370
/**
371
 * \function igraph_minimum_cycle_basis
372
 * \brief Computes a minimum weight cycle basis.
373
 *
374
 * \experimental
375
 *
376
 * This function computes a minimum weight cycle basis of a graph. Currently,
377
 * a modified version of Horton's algorithm is used that allows for cutoffs.
378
 *
379
 * </para><para>
380
 * Edge directions are ignored. Multi-edges and self-loops are supported.
381
 *
382
 * </para><para>
383
 * References:
384
 *
385
 * </para><para>
386
 * Horton, J. D. (1987)
387
 * A polynomial-time algorithm to find the shortest cycle basis of a graph,
388
 * SIAM Journal on Computing, 16 (2): 358–366.
389
 * https://doi.org/10.1137%2F0216026
390
 *
391
 * \param graph The graph object.
392
 * \param weights Currently unused.
393
 * \param result An initialized integer vector list, the elements of the cycle
394
 *   basis will be stored here as vectors of edge IDs.
395
 * \param bfs_cutoff If negative, an exact minimum cycle basis is returned. Otherwise
396
 *   only those cycles in the result will be part of some minimum cycle basis which
397
 *   are of size <code>2*bfs_cutoff + 1</code> or smaller. Cycles longer than this limit
398
 *   may not be of the smallest possible size.
399
 *   \p bfs_cutoff is used to limit the depth of the BFS tree when computing candidate
400
 *   cycles. Specifying a bfs_cutoff can speed up the computation substantially.
401
 * \param complete Boolean value. Used only when \p bfs_cutoff was given.
402
 *   If true, a complete basis is returned. If false, only cycles not greater
403
 *   than <code>2*bfs_cutoff + 1</code> are returned. This may save computation
404
 *   time, however, the result will not span the entire cycle space.
405
 * \param use_cycle_order If true, each cycle is returned in natural order:
406
 *   the edge IDs will appear ordered along the cycle. This comes at a small
407
 *   performance cost. If false, no guarantees are given about the ordering
408
 *   of edge IDs within cycles. This parameter exists solely to control
409
 *   performance tradeoffs.
410
 * \return Error code.
411
 *
412
 * \sa \ref igraph_fundamental_cycles()
413
 *
414
 * Time complexity: TODO.
415
 */
416
igraph_error_t igraph_minimum_cycle_basis(
417
        const igraph_t *graph, const igraph_vector_t *weights,
418
        igraph_vector_int_list_t *result,
419
        igraph_real_t bfs_cutoff,
420
1.98k
        igraph_bool_t complete, igraph_bool_t use_cycle_order) {
421
422
1.98k
    const igraph_int_t no_of_nodes = igraph_vcount(graph);
423
1.98k
    const igraph_int_t no_of_edges = igraph_ecount(graph);
424
1.98k
    igraph_int_t rank;
425
1.98k
    igraph_vector_int_list_t candidates;
426
427
1.98k
    IGRAPH_UNUSED(weights);
428
429
    /* Compute candidate elements for the minimum weight basis. */
430
1.98k
    {
431
1.98k
        igraph_inclist_t inclist;
432
1.98k
        igraph_vector_int_t visited; /* visited[v] % 3 is zero for unvisited vertices, see igraph_i_fundamental_cycles_bfs() */
433
1.98k
        igraph_vector_int_t degrees;
434
1.98k
        igraph_int_t no_of_comps;
435
1.98k
        igraph_int_t mark;
436
437
        /* We use the degrees to avoid doing a BFS from vertices with d < 3, except in special cases.
438
         * Degrees cannot be computed from the inclist because there we use IGRAPH_LOOPS_ONCE. */
439
1.98k
        IGRAPH_VECTOR_INT_INIT_FINALLY(&degrees, no_of_nodes);
440
1.98k
        IGRAPH_CHECK(igraph_degree(graph, &degrees, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS));
441
442
1.98k
        IGRAPH_CHECK(igraph_connected_components(graph, NULL, NULL, &no_of_comps, IGRAPH_WEAK));
443
1.98k
        rank = no_of_edges - no_of_nodes + no_of_comps;
444
445
1.98k
        IGRAPH_VECTOR_INT_INIT_FINALLY(&visited, no_of_nodes);
446
447
1.98k
        IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, IGRAPH_ALL, IGRAPH_LOOPS_ONCE));
448
1.98k
        IGRAPH_FINALLY(igraph_inclist_destroy, &inclist);
449
450
         /* TODO: estimate space to reserve. 'rank' is a lower bound only. */
451
1.98k
        IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&candidates, 0);
452
1.98k
        IGRAPH_CHECK(igraph_vector_int_list_reserve(&candidates, rank));
453
454
1.98k
        mark = 0;
455
389k
        for (igraph_int_t i=0; i < no_of_nodes; ++i) {
456
387k
            igraph_int_t degree = VECTOR(degrees)[i];
457
387k
            igraph_bool_t vis = VECTOR(visited)[i] % 3 != 0; /* was vertex i visited already? */
458
459
            /* Generally, we only need to run a BFS from vertices of degree 3 or greater.
460
             * The exception is a connected component which is itself a cycle, and therefore
461
             * only contains vertices of degree 2. Thus from unvisited vertices we always run
462
             * a full BFS while from already visited ones only if their degree is at least 3. */
463
464
            /* TODO: mark entire component as visited, not just vertex. */
465
387k
            if (degree <= 1 || (vis && degree < 3)) {
466
372k
                continue;
467
372k
            }
468
469
            /* TODO: BFS is only necessary from a feedback vertex set, find fast FVS approximation algorithm. */
470
471
15.2k
            IGRAPH_CHECK(igraph_i_fundamental_cycles_bfs(
472
15.2k
                    graph, &candidates, i, (vis || !complete) ? bfs_cutoff : -1, &inclist, &visited, mark));
473
15.2k
            mark += 3;
474
15.2k
        }
475
476
1.98k
        igraph_inclist_destroy(&inclist);
477
1.98k
        igraph_vector_int_destroy(&visited);
478
1.98k
        igraph_vector_int_destroy(&degrees);
479
1.98k
        IGRAPH_FINALLY_CLEAN(3);
480
1.98k
    }
481
482
    /* Sort candidates by size (= weight) and remove duplicates. */
483
0
    {
484
1.98k
        igraph_int_t cand_count = igraph_vector_int_list_size(&candidates);
485
486
708k
        for (igraph_int_t i=0; i < cand_count; ++i) {
487
706k
            igraph_vector_int_sort(igraph_vector_int_list_get_ptr(&candidates, i));
488
706k
        }
489
1.98k
        igraph_vector_int_list_sort(&candidates, &cycle_cmp);
490
1.98k
        igraph_vector_int_list_remove_consecutive_duplicates(&candidates, igraph_vector_int_all_e);
491
1.98k
    }
492
493
1.98k
    igraph_vector_int_list_clear(result);
494
1.98k
    IGRAPH_CHECK(igraph_vector_int_list_reserve(result, rank));
495
496
    /* Find a complete basis, starting with smallest elements. */
497
    /* This is typically the slowest part of the algorithm. */
498
1.98k
    {
499
1.98k
        igraph_int_t cand_len = igraph_vector_int_list_size(&candidates);
500
1.98k
        igraph_vector_int_list_t reduced_matrix;
501
1.98k
        igraph_bool_t independent;
502
503
1.98k
        IGRAPH_VECTOR_INT_LIST_INIT_FINALLY(&reduced_matrix, 0);
504
505
68.7k
        for (igraph_int_t i=0; i < cand_len; ++i) {
506
68.3k
            const igraph_vector_int_t *cycle = igraph_vector_int_list_get_ptr(&candidates, i);
507
508
68.3k
            IGRAPH_ALLOW_INTERRUPTION();
509
510
68.3k
            IGRAPH_CHECK(gaussian_elimination(&reduced_matrix, cycle, &independent));
511
68.3k
            if (independent) {
512
31.8k
                IGRAPH_CHECK(igraph_vector_int_list_push_back_copy(result, cycle));
513
31.8k
            }
514
515
68.3k
            if (igraph_vector_int_list_size(&reduced_matrix) == rank) {
516
                /* We have a complete basis. */
517
1.54k
                break;
518
1.54k
            }
519
68.3k
        }
520
521
1.98k
        igraph_vector_int_list_destroy(&reduced_matrix);
522
1.98k
        IGRAPH_FINALLY_CLEAN(1);
523
1.98k
    }
524
525
0
    igraph_vector_int_list_destroy(&candidates);
526
1.98k
    IGRAPH_FINALLY_CLEAN(1);
527
528
1.98k
    if (use_cycle_order) {
529
1.98k
        igraph_int_t result_size = igraph_vector_int_list_size(result);
530
1.98k
        igraph_vector_int_t tmp;
531
1.98k
        IGRAPH_VECTOR_INT_INIT_FINALLY(&tmp, 0);
532
33.7k
        for (igraph_int_t i=0; i < result_size; ++i) {
533
31.8k
            igraph_vector_int_t *cycle = igraph_vector_int_list_get_ptr(result, i);
534
31.8k
            IGRAPH_CHECK(igraph_vector_int_update(&tmp, cycle));
535
31.8k
            IGRAPH_CHECK(igraph_i_order_cycle(graph, &tmp, cycle));
536
31.8k
        }
537
1.98k
        igraph_vector_int_destroy(&tmp);
538
1.98k
        IGRAPH_FINALLY_CLEAN(1);
539
1.98k
    }
540
541
1.98k
    return IGRAPH_SUCCESS;
542
1.98k
}