/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(°rees, no_of_nodes); |
440 | 1.98k | IGRAPH_CHECK(igraph_degree(graph, °rees, 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(°rees); |
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 | } |