igraph/cycles.rs
1//! Graph cycles: acyclicity, topological orders, feedback sets, cycle
2//! enumeration, cycle bases and Eulerian paths.
3//!
4//! This module binds the C headers `igraph_cycles.h` and `igraph_eulerian.h`,
5//! documented in the [Graph cycles](https://igraph.org/c/html/latest/igraph-Cycles.html)
6//! chapter of the igraph C manual. All functions are methods of [`Graph`].
7//!
8//! | Task | Method | Result |
9//! |------|--------|--------|
10//! | Is the graph a directed acyclic graph? | [`Graph::is_dag`] | `bool` |
11//! | Order the vertices of a DAG | [`Graph::topological_sorting`] | `Vec<VertexId>` |
12//! | Find *one* cycle, if any | [`Graph::find_cycle`] | `Option<`[`Cycle`]`>` |
13//! | List all simple cycles | [`Graph::simple_cycles`] | `Vec<`[`Cycle`]`>` |
14//! | Visit simple cycles lazily, with early stop | [`Graph::simple_cycles_callback`] | `()` |
15//! | Fundamental cycle basis (from a BFS tree) | [`Graph::fundamental_cycles`] | `Vec<Vec<EdgeId>>` |
16//! | Minimum weight cycle basis (Horton), see [`MinimumCycleBasisOptions`] | [`Graph::minimum_cycle_basis`] | `Vec<Vec<EdgeId>>` |
17//! | Edges whose removal breaks all cycles | [`Graph::feedback_arc_set`] | `Vec<EdgeId>` |
18//! | Vertices whose removal breaks all cycles | [`Graph::feedback_vertex_set`] | `Vec<VertexId>` |
19//! | Does an Eulerian path / cycle exist? | [`Graph::is_eulerian`] | [`EulerianStatus`] |
20//! | Find an Eulerian path | [`Graph::eulerian_path`] | [`EulerianWalk`] |
21//! | Find an Eulerian cycle | [`Graph::eulerian_cycle`] | [`EulerianWalk`] |
22//!
23//! Cycles are described both by their vertices and by their edges (see
24//! [`Cycle`]): with multi-edges, the vertex sequence alone would be ambiguous.
25//! Cycle bases are returned as edge-id lists only, as igraph does.
26//!
27//! Some functions ([`Graph::simple_cycles`], [`Graph::simple_cycles_callback`],
28//! [`Graph::fundamental_cycles`] and [`Graph::minimum_cycle_basis`]) are marked
29//! *experimental* in igraph 1.0 (both 1.0.0 and 1.0.1): their behavior may
30//! change in future releases of the C library. None of the C sources bound here
31//! changed between igraph 1.0.0 and 1.0.1.
32//!
33//! # See also
34//!
35//! Related functionality in other modules:
36//!
37//! - [`Graph::is_acyclic`], [`Graph::is_forest`] and [`Graph::is_tree`]
38//! (structural properties): acyclicity tests that, unlike [`Graph::is_dag`],
39//! also apply to undirected graphs.
40//! - [`Graph::girth`] and [`Graph::girth_with_cycle`]: the length of (and a
41//! vertex list for) a *shortest* cycle, where [`Graph::find_cycle`] returns an
42//! arbitrary one.
43//! - [`Graph::list_triangles`] and [`Graph::count_triangles`]: the cycles of
44//! length 3, faster than [`Graph::simple_cycles`] with a length bound.
45//! - [`Graph::get_all_simple_paths`]: simple *paths* instead of cycles.
46//! - [`Graph::minimum_spanning_tree`] and [`Graph::connected_components`]: the
47//! complement of a spanning forest (of a *maximum* weight one, if weighted)
48//! is a minimum feedback arc set of an undirected graph, and the cycle space
49//! has dimension |E| - |V| + #components.
50//! - [`Graph::transitive_closure`] and [`Graph::dfs`]: reachability and the
51//! depth-first search underlying topological orders.
52//! - [`Graph::de_bruijn`] and [`Graph::kautz`]: balanced directed graphs, all
53//! of which have Eulerian cycles.
54//!
55//! # Example
56//!
57//! Scheduling tasks with dependencies: a topological order exists exactly
58//! when the dependency graph has no cycle.
59//!
60//! ```
61//! use igraph::prelude::*;
62//!
63//! // 0: fetch, 1: configure, 2: build, 3: test, 4: package
64//! let mut deps = Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (2, 4), (3, 4)], 5, true)?;
65//! assert!(deps.is_dag()?);
66//! assert_eq!(deps.topological_sorting(NeighborMode::Out)?, vec![0, 1, 2, 3, 4]);
67//!
68//! // Someone adds a circular dependency "package -> configure" ...
69//! deps.add_edge(4, 1)?;
70//! assert!(!deps.is_dag()?);
71//! let cycle = deps.find_cycle(NeighborMode::Out)?.expect("there is a cycle now");
72//! assert!(cycle.vertices.contains(&4) && cycle.vertices.contains(&1));
73//! // ... and removing the edges of a feedback arc set fixes it again.
74//! let fas = deps.feedback_arc_set(None, FasAlgorithm::ExactIp)?;
75//! assert_eq!(fas.len(), 1);
76//! deps.delete_edges(&fas)?;
77//! assert!(deps.is_dag()?);
78//! # Ok::<(), igraph::Error>(())
79//! ```
80
81use crate::{
82 constants::{FasAlgorithm, FvsAlgorithm, NeighborMode},
83 error::{Error, Result, catch_panic},
84 ffi::*,
85 graph::{EdgeId, VertexId},
86 igraph_call,
87 list::VectorIntList,
88 vector::{Vector, VectorInt},
89};
90use std::{ffi::c_void, ops::ControlFlow};
91
92#[cfg(doc)]
93use crate::graph::Graph;
94
95/// A cycle (closed walk) of a graph, given both as vertices and edges.
96///
97/// `edges[i]` connects `vertices[i]` and `vertices[(i + 1) % len]`: the first
98/// vertex is *not* repeated at the end, so `vertices.len() == edges.len()`,
99/// which is the length of the cycle. A self-loop is a cycle of length 1, and
100/// two parallel edges form a cycle of length 2 (when they can be traversed in
101/// opposite directions: undirected edges, mutual directed edges, or any
102/// directed edges with `NeighborMode::All`).
103#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
104pub struct Cycle {
105 /// The vertices of the cycle, in traversal order.
106 pub vertices: Vec<VertexId>,
107 /// The edges of the cycle, in traversal order.
108 pub edges: Vec<EdgeId>,
109}
110
111impl Cycle {
112 /// The length of the cycle, i.e. its number of edges.
113 pub fn len(&self) -> usize {
114 self.edges.len()
115 }
116
117 /// Whether the cycle has no edges (never the case for cycles returned by
118 /// this module).
119 pub fn is_empty(&self) -> bool {
120 self.edges.is_empty()
121 }
122}
123
124/// Options for [`Graph::simple_cycles`] and [`Graph::simple_cycles_callback`].
125///
126/// The default searches for all simple cycles of any length, following edge
127/// directions (`NeighborMode::Out`) in directed graphs. Lengths count edges,
128/// which for simple cycles equals the number of vertices.
129///
130/// ```
131/// use igraph::{cycles::SimpleCyclesOptions, prelude::*};
132/// let opts = SimpleCyclesOptions::default().with_max_length(4).with_max_results(10);
133/// assert_eq!(opts.max_cycle_length, Some(4));
134/// assert_eq!(opts.mode, NeighborMode::Out);
135/// ```
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
137pub struct SimpleCyclesOptions {
138 /// How edge directions are considered in directed graphs: `Out` follows
139 /// them, `In` follows them backwards, `All` ignores them. Ignored for
140 /// undirected graphs. Default: `Out`.
141 pub mode: NeighborMode,
142 /// Only report cycles with at least this many edges (`None`: no limit).
143 pub min_cycle_length: Option<usize>,
144 /// Only report cycles with at most this many edges (`None`: no limit).
145 /// Bounding the length also bounds the search, making it much faster.
146 pub max_cycle_length: Option<usize>,
147 /// Stop after this many cycles have been reported (`None`: no limit).
148 pub max_results: Option<usize>,
149}
150
151impl Default for SimpleCyclesOptions {
152 fn default() -> Self {
153 Self {
154 mode: NeighborMode::Out,
155 min_cycle_length: None,
156 max_cycle_length: None,
157 max_results: None,
158 }
159 }
160}
161
162impl SimpleCyclesOptions {
163 /// Sets [`mode`](Self::mode).
164 pub fn with_mode(mut self, mode: NeighborMode) -> Self {
165 self.mode = mode;
166 self
167 }
168
169 /// Sets [`min_cycle_length`](Self::min_cycle_length).
170 pub fn with_min_length(mut self, len: usize) -> Self {
171 self.min_cycle_length = Some(len);
172 self
173 }
174
175 /// Sets [`max_cycle_length`](Self::max_cycle_length).
176 pub fn with_max_length(mut self, len: usize) -> Self {
177 self.max_cycle_length = Some(len);
178 self
179 }
180
181 /// Sets [`max_results`](Self::max_results).
182 pub fn with_max_results(mut self, n: usize) -> Self {
183 self.max_results = Some(n);
184 self
185 }
186}
187
188/// Options for [`Graph::minimum_cycle_basis`].
189///
190/// The default computes an exact minimum cycle basis with each cycle listed
191/// in cycle order (`bfs_cutoff: None, complete: true, use_cycle_order: true`),
192/// the same defaults as the igraph R and Python interfaces.
193///
194/// ```
195/// use igraph::cycles::MinimumCycleBasisOptions;
196/// let fast = MinimumCycleBasisOptions::default().with_bfs_cutoff(3).with_complete(false);
197/// assert_eq!(fast.bfs_cutoff, Some(3));
198/// assert!(fast.use_cycle_order);
199/// ```
200#[derive(Debug, Clone, Copy, PartialEq, Eq)]
201pub struct MinimumCycleBasisOptions {
202 /// `None` computes an exact minimum basis. `Some(k)` limits the depth of
203 /// the BFS trees used to generate candidate cycles, which can speed up
204 /// the computation substantially: then only the returned cycles of length
205 /// at most `2k + 1` are guaranteed to belong to some minimum basis.
206 /// Default: `None`.
207 pub bfs_cutoff: Option<usize>,
208 /// Only used with a [`bfs_cutoff`](Self::bfs_cutoff). If `true`, a
209 /// complete basis is still returned (its cycles longer than `2k + 1` may
210 /// not be minimal); if `false`, only the cycles of length at most `2k + 1`
211 /// are returned, which is faster but does not span the whole cycle space.
212 /// Default: `true`.
213 pub complete: bool,
214 /// If `true`, the edge ids of each cycle are listed in the order they
215 /// appear along the cycle (at a small cost); if `false`, their order is
216 /// unspecified. Default: `true`.
217 pub use_cycle_order: bool,
218}
219
220impl Default for MinimumCycleBasisOptions {
221 fn default() -> Self {
222 Self {
223 bfs_cutoff: None,
224 complete: true,
225 use_cycle_order: true,
226 }
227 }
228}
229
230impl MinimumCycleBasisOptions {
231 /// Sets [`bfs_cutoff`](Self::bfs_cutoff) to `Some(k)`.
232 pub fn with_bfs_cutoff(mut self, k: usize) -> Self {
233 self.bfs_cutoff = Some(k);
234 self
235 }
236
237 /// Sets [`complete`](Self::complete).
238 pub fn with_complete(mut self, complete: bool) -> Self {
239 self.complete = complete;
240 self
241 }
242
243 /// Sets [`use_cycle_order`](Self::use_cycle_order).
244 pub fn with_cycle_order(mut self, use_cycle_order: bool) -> Self {
245 self.use_cycle_order = use_cycle_order;
246 self
247 }
248}
249
250/// Whether a graph has an Eulerian path and/or cycle, see [`Graph::is_eulerian`].
251#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
252pub struct EulerianStatus {
253 /// `true` if some walk traverses every edge exactly once.
254 pub has_path: bool,
255 /// `true` if some *closed* walk traverses every edge exactly once
256 /// (this implies `has_path`).
257 pub has_cycle: bool,
258}
259
260/// An Eulerian path or cycle, see [`Graph::eulerian_path`] and
261/// [`Graph::eulerian_cycle`].
262///
263/// `edges[i]` connects `vertices[i]` and `vertices[i + 1]`, so for a
264/// non-empty walk `vertices.len() == edges.len() + 1`; for a cycle the first
265/// and last vertices coincide. Every edge of the graph appears in `edges`
266/// exactly once.
267#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
268pub struct EulerianWalk {
269 /// The visited vertices, in order.
270 pub vertices: Vec<VertexId>,
271 /// The traversed edges, in order.
272 pub edges: Vec<EdgeId>,
273}
274
275/// `Option<usize>` limit → igraph's "negative means unlimited" convention.
276/// Values above `igraph_int_t::MAX` saturate (they are unreachable anyway),
277/// instead of wrapping to a negative "unlimited" value.
278fn limit(value: Option<usize>) -> igraph_int_t {
279 value.map_or(-1, |v| {
280 igraph_int_t::try_from(v).unwrap_or(igraph_int_t::MAX)
281 })
282}
283
284/// `Option<usize>` BFS cutoff → igraph's "negative means no cutoff" real.
285fn cutoff(value: Option<usize>) -> igraph_real_t {
286 value.map_or(-1.0, |c| c as igraph_real_t)
287}
288
289/// Pointer to an optional weight view, or NULL.
290fn opt_ptr(v: &Option<crate::vector::View<'_, Vector>>) -> *const igraph_vector_t {
291 v.as_ref().map_or(std::ptr::null(), |v| v.as_ptr())
292}
293
294struct CycleVisitor<'f, F> {
295 f: &'f mut F,
296 remaining: Option<usize>,
297}
298
299unsafe extern "C" fn cycle_trampoline<F>(
300 vertices: *const igraph_vector_int_t,
301 edges: *const igraph_vector_int_t,
302 arg: *mut c_void,
303) -> igraph_error_t
304where
305 F: FnMut(&[VertexId], &[EdgeId]) -> ControlFlow<()>,
306{
307 // The closure runs in a fresh level of igraph's "finally" stack:
308 // otherwise a failing igraph call made by the closure would free the
309 // temporaries of the running cycle search (a use-after-free in C).
310 // SAFETY: bookkeeping on igraph's thread-local finally stack; the
311 // matching EXIT runs below, as `catch_panic` never unwinds.
312 unsafe { IGRAPH_FINALLY_ENTER() };
313 let code = catch_panic(|| {
314 // SAFETY: `arg` is the `CycleVisitor<F>` passed by `simple_cycles_callback`,
315 // alive and exclusively borrowed for the whole C call; the vectors are
316 // valid for the duration of this callback.
317 let visitor = unsafe { &mut *(arg as *mut CycleVisitor<'_, F>) };
318 let (vertices, edges) = unsafe { ((*vertices).as_slice(), (*edges).as_slice()) };
319 // `remaining` is never `Some(0)` here: the wrapper returns early for a
320 // zero cap, and we answer IGRAPH_STOP as soon as it reaches zero.
321 let flow = (visitor.f)(vertices, edges);
322 if let Some(r) = visitor.remaining.as_mut() {
323 *r -= 1;
324 }
325 match flow {
326 ControlFlow::Break(()) => igraph_error_type_t_IGRAPH_STOP,
327 ControlFlow::Continue(()) if visitor.remaining == Some(0) => {
328 igraph_error_type_t_IGRAPH_STOP
329 }
330 ControlFlow::Continue(()) => igraph_error_type_t_IGRAPH_SUCCESS,
331 }
332 });
333 // SAFETY: closes the level opened above; any failed nested call has
334 // already freed its own objects of that level.
335 unsafe { IGRAPH_FINALLY_EXIT() };
336 code
337}
338
339impl igraph_t {
340 /// Checks whether the graph is a directed acyclic graph (DAG).
341 ///
342 /// A DAG is a directed graph without directed cycles (self-loops count as
343 /// cycles here). Undirected graphs are never DAGs: this returns `false` for
344 /// them. The result is cached inside the graph, so repeated calls without
345 /// modifications in between take O(1) time.
346 ///
347 /// Time complexity: O(|V|+|E|).
348 ///
349 /// See also [`is_acyclic`](Self::is_acyclic), which agrees with this
350 /// function on directed graphs but also tests undirected graphs for
351 /// cycles, and [`find_cycle`](Self::find_cycle), which returns a cycle
352 /// proving that a graph is not a DAG.
353 ///
354 /// Binds [`igraph_is_dag`](https://igraph.org/c/html/latest/igraph-Cycles.html#igraph_is_dag).
355 ///
356 /// # Examples
357 ///
358 /// ```
359 /// use igraph::prelude::*;
360 /// let chain = Graph::from_edges(&[(0, 1), (1, 2)], 3, true)?;
361 /// assert!(chain.is_dag()?);
362 /// let triangle = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], 3, true)?;
363 /// assert!(!triangle.is_dag()?);
364 /// // Undirected graphs are not DAGs, even when they are trees.
365 /// let undirected = Graph::from_edges(&[(0, 1)], 2, false)?;
366 /// assert!(!undirected.is_dag()?);
367 /// assert!(undirected.is_acyclic()?); // ... but they can be acyclic
368 /// # Ok::<(), igraph::Error>(())
369 /// ```
370 pub fn is_dag(&self) -> Result<bool> {
371 let mut res = false;
372 igraph_call!(igraph_is_dag(self, &mut res))?;
373 Ok(res)
374 }
375
376 /// Computes a topological sorting of a directed acyclic graph.
377 ///
378 /// A topological sorting is a linear ordering of the vertices in which
379 /// every vertex comes before all the vertices it has edges to. Every DAG
380 /// has at least one, and possibly many; one of them is returned.
381 ///
382 /// With `mode = NeighborMode::Out` each vertex precedes its successors, so
383 /// the sources (no incoming edges) come first; with `NeighborMode::In` each
384 /// vertex precedes its predecessors, so the sinks come first.
385 /// Self-loops are ignored.
386 ///
387 /// Time complexity: O(|V|+|E|).
388 ///
389 /// See also [`is_dag`](Self::is_dag) to test beforehand whether an order
390 /// exists, [`feedback_arc_set`](Self::feedback_arc_set) to make a cyclic
391 /// graph sortable, and [`transitive_closure`](Self::transitive_closure)
392 /// for the full "must come before" relation.
393 ///
394 /// Binds [`igraph_topological_sorting`](https://igraph.org/c/html/latest/igraph-Cycles.html#igraph_topological_sorting).
395 ///
396 /// # Errors
397 ///
398 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if the graph
399 /// contains a cycle (other than self-loops), if the graph is undirected,
400 /// or if `mode` is `NeighborMode::All`.
401 ///
402 /// # Examples
403 ///
404 /// The example graph from the igraph documentation (and Wikipedia):
405 ///
406 /// ```
407 /// use igraph::prelude::*;
408 /// let g = Graph::from_edges(
409 /// &[(0, 3), (0, 4), (1, 3), (2, 4), (2, 7), (3, 5), (3, 6), (3, 7), (4, 6)],
410 /// 8,
411 /// true,
412 /// )?;
413 /// assert_eq!(g.topological_sorting(NeighborMode::Out)?, vec![0, 1, 2, 3, 4, 5, 7, 6]);
414 /// assert_eq!(g.topological_sorting(NeighborMode::In)?, vec![5, 6, 7, 4, 3, 2, 0, 1]);
415 /// # Ok::<(), igraph::Error>(())
416 /// ```
417 pub fn topological_sorting(&self, mode: NeighborMode) -> Result<Vec<VertexId>> {
418 let mut res = VectorInt::new();
419 igraph_call!(igraph_topological_sorting(self, &mut res, mode.into()))?;
420 Ok(res.into())
421 }
422
423 /// Finds a single cycle of the graph, or `None` if the graph is acyclic.
424 ///
425 /// `mode` selects how edge directions are considered in directed graphs:
426 /// `Out` follows them, `In` follows them backwards (the returned cycle is
427 /// then listed against the edge directions) and `All` ignores them. It is
428 /// ignored for undirected graphs. Self-loops and multi-edges count as
429 /// cycles of length 1 and 2.
430 ///
431 /// igraph lists the vertices so that each edge *enters* the vertex at the
432 /// same position; this wrapper rotates them by one so that the result
433 /// follows the [`Cycle`] layout (`edges[i]` goes from `vertices[i]` to the next vertex), like
434 /// [`simple_cycles`](Self::simple_cycles) does.
435 ///
436 /// The cycle is not necessarily a shortest one: use
437 /// [`girth_with_cycle`](Self::girth_with_cycle) for that (undirected,
438 /// cycles of length at least 3), or [`simple_cycles`](Self::simple_cycles)
439 /// to list all cycles. [`is_acyclic`](Self::is_acyclic) only answers
440 /// whether a cycle exists.
441 ///
442 /// Time complexity: O(|V|+|E|).
443 ///
444 /// Binds [`igraph_find_cycle`](https://igraph.org/c/html/latest/igraph-Cycles.html#igraph_find_cycle).
445 ///
446 /// # Examples
447 ///
448 /// ```
449 /// use igraph::prelude::*;
450 /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (2, 0)], 5, true)?;
451 /// let c = g.find_cycle(NeighborMode::Out)?.unwrap();
452 /// assert_eq!(c.vertices, vec![0, 1, 2]);
453 /// assert_eq!(c.edges, vec![0, 1, 4]); // 0 -> 1 -> 2 -> 0
454 ///
455 /// let tree = Graph::from_edges(&[(0, 1), (0, 2)], 3, false)?;
456 /// assert_eq!(tree.find_cycle(NeighborMode::All)?, None);
457 ///
458 /// // Any cycle is at least as long as the girth.
459 /// let petersen = Graph::famous("Petersen")?;
460 /// let c = petersen.find_cycle(NeighborMode::All)?.unwrap();
461 /// assert!(c.len() >= petersen.girth()?.unwrap());
462 /// # Ok::<(), igraph::Error>(())
463 /// ```
464 pub fn find_cycle(&self, mode: NeighborMode) -> Result<Option<Cycle>> {
465 let mut vertices = VectorInt::new();
466 let mut edges = VectorInt::new();
467 igraph_call!(igraph_find_cycle(
468 self,
469 &mut vertices,
470 &mut edges,
471 mode.into()
472 ))?;
473 if edges.is_empty() {
474 Ok(None)
475 } else {
476 let mut vertices: Vec<VertexId> = vertices.into();
477 vertices.rotate_right(1);
478 Ok(Some(Cycle {
479 vertices,
480 edges: edges.into(),
481 }))
482 }
483 }
484
485 /// Lists the simple cycles of the graph (Johnson's algorithm).
486 ///
487 /// A simple cycle is a closed walk without repeated vertices. Each cycle
488 /// is reported once (in undirected graphs, the two traversal directions
489 /// of a cycle count as one). Self-loops are cycles of length 1; two
490 /// parallel edges give a cycle of length 2 when they can be traversed in
491 /// opposite directions (so not two parallel arcs `u -> v` with
492 /// `NeighborMode::Out`). The search can be restricted with
493 /// [`SimpleCyclesOptions`]: direction handling, minimum and maximum cycle
494 /// length and a cap on the number of results. The number of simple cycles
495 /// can grow exponentially with the size of the graph: bound the lengths or
496 /// the result count on large graphs, or use
497 /// [`simple_cycles_callback`](Self::simple_cycles_callback) to avoid
498 /// storing them.
499 ///
500 /// See also [`list_triangles`](Self::list_triangles) for the cycles of
501 /// length 3 only, [`find_cycle`](Self::find_cycle) for a single cycle, and
502 /// [`get_all_simple_paths`](Self::get_all_simple_paths) for simple paths.
503 ///
504 /// This function is *experimental* in igraph 1.0.
505 ///
506 /// Reference: Johnson DB, *Finding all the elementary circuits of a
507 /// directed graph*, SIAM J. Comput. 4(1):77-84 (1975).
508 ///
509 /// Binds [`igraph_simple_cycles`](https://igraph.org/c/html/latest/igraph-Cycles.html#igraph_simple_cycles).
510 ///
511 /// # Examples
512 ///
513 /// A square with one diagonal has three cycles: two triangles and the
514 /// outer square.
515 ///
516 /// ```
517 /// use igraph::{cycles::SimpleCyclesOptions, prelude::*};
518 /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0), (0, 2)], 4, false)?;
519 /// let cycles = g.simple_cycles(&SimpleCyclesOptions::default())?;
520 /// let mut lengths: Vec<usize> = cycles.iter().map(|c| c.len()).collect();
521 /// lengths.sort();
522 /// assert_eq!(lengths, vec![3, 3, 4]);
523 /// // Only the triangles:
524 /// let triangles = g.simple_cycles(&SimpleCyclesOptions::default().with_max_length(3))?;
525 /// assert_eq!(triangles.len(), 2);
526 /// # Ok::<(), igraph::Error>(())
527 /// ```
528 pub fn simple_cycles(&self, options: &SimpleCyclesOptions) -> Result<Vec<Cycle>> {
529 if options.max_results == Some(0) {
530 return Ok(Vec::new());
531 }
532 let mut vertices = VectorIntList::new();
533 let mut edges = VectorIntList::new();
534 igraph_call!(igraph_simple_cycles(
535 self,
536 &mut vertices,
537 &mut edges,
538 options.mode.into(),
539 limit(options.min_cycle_length),
540 limit(options.max_cycle_length),
541 limit(options.max_results)
542 ))?;
543 Ok(vertices
544 .iter()
545 .zip(edges.iter())
546 .map(|(v, e)| Cycle {
547 vertices: v.to_vec(),
548 edges: e.to_vec(),
549 })
550 .collect())
551 }
552
553 /// Visits the simple cycles of the graph with a closure (Johnson's
554 /// algorithm), without storing them.
555 ///
556 /// This is the streaming variant of [`simple_cycles`](Self::simple_cycles),
557 /// with the same semantics and options. For each cycle, `f` is called with
558 /// its vertices and edges (see [`Cycle`] for their layout); it returns
559 /// [`ControlFlow::Continue`] to keep searching or [`ControlFlow::Break`]
560 /// to stop the search early (this is not an error). The search also stops
561 /// after [`max_results`](SimpleCyclesOptions::max_results) cycles.
562 ///
563 /// If `f` panics, the search is aborted and the panic is resumed in the
564 /// caller once igraph has cleaned up.
565 ///
566 /// This function is *experimental* in igraph 1.0.
567 ///
568 /// Binds [`igraph_simple_cycles_callback`](https://igraph.org/c/html/latest/igraph-Cycles.html#igraph_simple_cycles_callback).
569 ///
570 /// # Examples
571 ///
572 /// Is there a cycle through vertex 3 in the complete graph `K5`? Stop at
573 /// the first one.
574 ///
575 /// ```
576 /// use igraph::{cycles::SimpleCyclesOptions, prelude::*};
577 /// use std::ops::ControlFlow;
578 /// let k5 = Graph::full(5, false, false)?;
579 /// let mut total = 0;
580 /// k5.simple_cycles_callback(&SimpleCyclesOptions::default(), |_, _| {
581 /// total += 1;
582 /// ControlFlow::Continue(())
583 /// })?;
584 /// assert_eq!(total, 37); // 10 triangles + 15 squares + 12 pentagons
585 ///
586 /// let mut found = None;
587 /// k5.simple_cycles_callback(&SimpleCyclesOptions::default(), |vs, _| {
588 /// if vs.contains(&3) {
589 /// found = Some(vs.to_vec());
590 /// return ControlFlow::Break(());
591 /// }
592 /// ControlFlow::Continue(())
593 /// })?;
594 /// assert!(found.unwrap().contains(&3));
595 /// # Ok::<(), igraph::Error>(())
596 /// ```
597 pub fn simple_cycles_callback<F>(&self, options: &SimpleCyclesOptions, mut f: F) -> Result<()>
598 where
599 F: FnMut(&[VertexId], &[EdgeId]) -> ControlFlow<()>,
600 {
601 if options.max_results == Some(0) {
602 return Ok(());
603 }
604 let mut visitor = CycleVisitor {
605 f: &mut f,
606 remaining: options.max_results,
607 };
608 igraph_call!(igraph_simple_cycles_callback(
609 self,
610 options.mode.into(),
611 limit(options.min_cycle_length),
612 limit(options.max_cycle_length),
613 Some(cycle_trampoline::<F>),
614 &mut visitor as *mut CycleVisitor<'_, F> as *mut c_void
615 ))
616 }
617
618 /// Computes a fundamental cycle basis, from breadth-first search trees.
619 ///
620 /// Every edge not in a BFS spanning forest closes exactly one cycle with
621 /// the tree edges: these *fundamental cycles* form a basis of the cycle
622 /// space, whose dimension is the cyclomatic number |E| - |V| + c (c being
623 /// the number of connected components). Each cycle is returned as a list
624 /// of edge ids, in cycle order. Edge directions are ignored; multi-edges
625 /// and self-loops are supported.
626 ///
627 /// - `start`: `None` returns a complete basis; `Some(v)` returns only the
628 /// fundamental cycles of the BFS tree rooted at `v`, i.e. of the
629 /// (weakly) connected component of `v`.
630 /// - `bfs_cutoff`: `None` returns a complete basis; `Some(k)` limits the
631 /// BFS depth, so that only cycles of length at most `2k + 1` are found.
632 ///
633 /// Time complexity: O(|V|+|E|). This function is *experimental* in igraph
634 /// 1.0. (The C function also takes a `weights` argument, currently unused
635 /// by igraph, so it is not exposed.)
636 ///
637 /// See also [`minimum_cycle_basis`](Self::minimum_cycle_basis) for a basis
638 /// of shortest total length, and
639 /// [`connected_components`](Self::connected_components) for `c`.
640 ///
641 /// Binds [`igraph_fundamental_cycles`](https://igraph.org/c/html/latest/igraph-Cycles.html#igraph_fundamental_cycles).
642 ///
643 /// # Errors
644 ///
645 /// [`ErrorKind::InvalidVertexId`](crate::ErrorKind::InvalidVertexId) if
646 /// `start` is not a vertex of the graph.
647 ///
648 /// # Examples
649 ///
650 /// ```
651 /// use igraph::prelude::*;
652 /// // Two triangles sharing the edge 0-2: the cycle space has dimension 5 - 4 + 1 = 2.
653 /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (2, 3), (3, 0)], 4, false)?;
654 /// let basis = g.fundamental_cycles(None, None)?;
655 /// assert_eq!(basis.len(), 2);
656 ///
657 /// // In general: one basis cycle per edge outside a spanning forest.
658 /// let karate = Graph::famous("Zachary")?;
659 /// let c = karate.connected_components(Connectedness::Weak)?.count;
660 /// let dim = karate.ecount() - karate.vcount() + c;
661 /// assert_eq!(karate.fundamental_cycles(None, None)?.len(), dim); // 78 - 34 + 1
662 /// # Ok::<(), igraph::Error>(())
663 /// ```
664 pub fn fundamental_cycles(
665 &self,
666 start: Option<VertexId>,
667 bfs_cutoff: Option<usize>,
668 ) -> Result<Vec<Vec<EdgeId>>> {
669 if let Some(v) = start
670 && (v < 0 || v as usize >= self.vcount())
671 {
672 return Err(Error::new(
673 crate::ErrorKind::InvalidVertexId,
674 format!("invalid start vertex {v} for fundamental cycles"),
675 ));
676 }
677 let mut res = VectorIntList::new();
678 igraph_call!(igraph_fundamental_cycles(
679 self,
680 std::ptr::null(),
681 &mut res,
682 start.unwrap_or(-1),
683 cutoff(bfs_cutoff)
684 ))?;
685 Ok(res.to_vecs())
686 }
687
688 /// Computes a minimum weight cycle basis (modified Horton algorithm).
689 ///
690 /// A minimum cycle basis is a basis of the cycle space whose total length
691 /// is as small as possible; its cycles are returned as edge-id lists,
692 /// sorted by increasing length. Edge directions are ignored; multi-edges
693 /// and self-loops are supported.
694 ///
695 /// The search is tuned by [`MinimumCycleBasisOptions`]: an optional BFS
696 /// cutoff trading exactness for speed, whether a cut-off basis should
697 /// still be completed, and whether cycles are listed in cycle order.
698 ///
699 /// This function is *experimental* in igraph 1.0. (The C function also
700 /// takes a `weights` argument, currently unused by igraph, so it is not
701 /// exposed.)
702 ///
703 /// For a simple graph with at least one cycle, the first (shortest) basis
704 /// cycle has the length of the [`girth`](Self::girth). See also
705 /// [`fundamental_cycles`](Self::fundamental_cycles), a faster but usually
706 /// longer basis.
707 ///
708 /// Reference: Horton JD, *A polynomial-time algorithm to find the shortest
709 /// cycle basis of a graph*, SIAM J. Comput. 16(2):358-366 (1987).
710 ///
711 /// Binds [`igraph_minimum_cycle_basis`](https://igraph.org/c/html/latest/igraph-Cycles.html#igraph_minimum_cycle_basis).
712 ///
713 /// # Examples
714 ///
715 /// ```
716 /// use igraph::{cycles::MinimumCycleBasisOptions, prelude::*};
717 /// // A square with a diagonal: the minimum basis is made of the two triangles.
718 /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0), (0, 2)], 4, false)?;
719 /// let basis = g.minimum_cycle_basis(&MinimumCycleBasisOptions::default())?;
720 /// assert_eq!(basis.iter().map(Vec::len).collect::<Vec<_>>(), vec![3, 3]);
721 /// // The outer square is the sum (symmetric difference) of the two
722 /// // triangles: the shared diagonal, edge 4, cancels out.
723 /// let mut parity = [false; 5];
724 /// for &e in basis.concat().iter() {
725 /// parity[e as usize] ^= true;
726 /// }
727 /// assert_eq!(parity, [true, true, true, true, false]);
728 ///
729 /// // The Petersen graph has girth 5: its 15 - 10 + 1 = 6 basis cycles are pentagons.
730 /// let petersen = Graph::famous("Petersen")?;
731 /// let basis = petersen.minimum_cycle_basis(&MinimumCycleBasisOptions::default())?;
732 /// assert_eq!(basis.iter().map(Vec::len).collect::<Vec<_>>(), vec![5; 6]);
733 /// # Ok::<(), igraph::Error>(())
734 /// ```
735 pub fn minimum_cycle_basis(
736 &self,
737 options: &MinimumCycleBasisOptions,
738 ) -> Result<Vec<Vec<EdgeId>>> {
739 let mut res = VectorIntList::new();
740 igraph_call!(igraph_minimum_cycle_basis(
741 self,
742 std::ptr::null(),
743 &mut res,
744 cutoff(options.bfs_cutoff),
745 options.complete,
746 options.use_cycle_order
747 ))?;
748 Ok(res.to_vecs())
749 }
750
751 /// Finds a feedback arc set: edges whose removal makes the graph acyclic.
752 ///
753 /// One is usually interested in a *minimum* feedback arc set, i.e. one of
754 /// smallest total weight (`weights`, one per edge, or `None` for unit
755 /// weights; weights are meant to be non-negative). For undirected graphs
756 /// this is easy: the complement of a maximum weight spanning forest (and
757 /// `algo` is ignored). For directed
758 /// graphs the problem is NP-complete and `algo` selects the method:
759 ///
760 /// - [`FasAlgorithm::ExactIp`]: exact minimum via integer programming,
761 /// picking the best available formulation (currently `ExactIpCg`).
762 /// Exponential in the worst case.
763 /// - [`FasAlgorithm::ExactIpCg`]: exact, set-cover formulation with
764 /// incremental cycle (constraint) generation.
765 /// - [`FasAlgorithm::ExactIpTi`]: exact, topological-order formulation
766 /// with triangle inequalities; usually much slower.
767 /// - [`FasAlgorithm::ApproxEades`]: the linear time O(|E|) heuristic of
768 /// Eades, Lin and Smyth (1993), returning fewer than |E|/2 - |V|/6 edges.
769 ///
770 /// References: Eades P, Lin X, Smyth WF, Inf. Proc. Letters 47(6):319-323
771 /// (1993); Baharev A et al., ACM J. Exp. Algorithmics 26:1-28 (2021).
772 ///
773 /// Time complexity: depends on `algo` (see above).
774 ///
775 /// See also [`feedback_vertex_set`](Self::feedback_vertex_set) for the
776 /// vertex version, and, for undirected graphs,
777 /// [`minimum_spanning_tree`](Self::minimum_spanning_tree): the edges not
778 /// in a *maximum* weight spanning forest form a minimum feedback arc set.
779 /// Delete the returned edges at once with
780 /// [`delete_edges`](Self::delete_edges).
781 ///
782 /// Binds [`igraph_feedback_arc_set`](https://igraph.org/c/html/latest/igraph-Cycles.html#igraph_feedback_arc_set).
783 ///
784 /// # Errors
785 ///
786 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if the
787 /// weight vector has the wrong length or invalid values;
788 /// [`ErrorKind::Unimplemented`](crate::ErrorKind::Unimplemented) for the
789 /// exact methods when igraph was built without GLPK.
790 ///
791 /// # Examples
792 ///
793 /// The graph of igraph's own example program:
794 ///
795 /// ```
796 /// use igraph::prelude::*;
797 /// let edges = [(0, 1), (1, 2), (2, 0), (2, 3), (2, 4), (0, 4), (4, 3), (5, 0), (6, 5)];
798 /// let g = Graph::from_edges(&edges, 7, true)?;
799 /// assert_eq!(g.feedback_arc_set(None, FasAlgorithm::ExactIp)?, vec![0]);
800 /// // Make edge 0 expensive to cut: another edge of the triangle goes.
801 /// let w = [3.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
802 /// let fas = g.feedback_arc_set(Some(&w), FasAlgorithm::ExactIp)?;
803 /// assert!(fas == [1] || fas == [2]);
804 /// # Ok::<(), igraph::Error>(())
805 /// ```
806 pub fn feedback_arc_set(
807 &self,
808 weights: Option<&[f64]>,
809 algo: FasAlgorithm,
810 ) -> Result<Vec<EdgeId>> {
811 if let Some(w) = weights
812 && w.len() != self.ecount()
813 {
814 return Err(Error::invalid(format!(
815 "weight vector length ({}) must match the number of edges ({})",
816 w.len(),
817 self.ecount()
818 )));
819 }
820 let w = weights.map(Vector::view);
821 let mut res = VectorInt::new();
822 igraph_call!(igraph_feedback_arc_set(
823 self,
824 &mut res,
825 opt_ptr(&w),
826 algo.into()
827 ))?;
828 Ok(res.into())
829 }
830
831 /// Finds a minimum feedback vertex set: vertices whose removal makes the
832 /// graph acyclic.
833 ///
834 /// Minimizes the total vertex weight (`vertex_weights`, one per vertex, or
835 /// `None` for unit weights). The problem is NP-complete both for directed
836 /// and undirected graphs; the only method, [`FvsAlgorithm::ExactIp`], uses
837 /// integer programming with incremental cycle generation (like
838 /// [`FasAlgorithm::ExactIpCg`]) and is exponential in the worst case.
839 /// Self-loops count as cycles, so looped vertices are always included.
840 ///
841 /// Time complexity: exponential in the worst case. See also
842 /// [`feedback_arc_set`](Self::feedback_arc_set), and
843 /// [`delete_vertices`](Self::delete_vertices) or
844 /// [`induced_subgraph`](Self::induced_subgraph) to remove the vertices.
845 ///
846 /// Binds [`igraph_feedback_vertex_set`](https://igraph.org/c/html/latest/igraph-Cycles.html#igraph_feedback_vertex_set).
847 ///
848 /// # Errors
849 ///
850 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if the
851 /// weight vector has the wrong length or invalid values;
852 /// [`ErrorKind::Unimplemented`](crate::ErrorKind::Unimplemented) when
853 /// igraph was built without GLPK.
854 ///
855 /// # Examples
856 ///
857 /// ```
858 /// use igraph::prelude::*;
859 /// // Two triangles sharing vertex 0 (a "bowtie"): removing 0 breaks both.
860 /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (0, 3), (3, 4), (4, 0)], 5, false)?;
861 /// assert_eq!(g.feedback_vertex_set(None, FvsAlgorithm::ExactIp)?, vec![0]);
862 ///
863 /// // The Petersen graph needs three vertices removed to become a forest.
864 /// let mut petersen = Graph::famous("Petersen")?;
865 /// let fvs = petersen.feedback_vertex_set(None, FvsAlgorithm::ExactIp)?;
866 /// assert_eq!(fvs.len(), 3);
867 /// petersen.delete_vertices(&fvs)?;
868 /// assert!(petersen.is_forest(NeighborMode::All)?);
869 /// # Ok::<(), igraph::Error>(())
870 /// ```
871 pub fn feedback_vertex_set(
872 &self,
873 vertex_weights: Option<&[f64]>,
874 algo: FvsAlgorithm,
875 ) -> Result<Vec<VertexId>> {
876 if let Some(w) = vertex_weights
877 && w.len() != self.vcount()
878 {
879 return Err(Error::invalid(format!(
880 "vertex weight vector length ({}) must match the number of vertices ({})",
881 w.len(),
882 self.vcount()
883 )));
884 }
885 let w = vertex_weights.map(Vector::view);
886 let mut res = VectorInt::new();
887 igraph_call!(igraph_feedback_vertex_set(
888 self,
889 &mut res,
890 opt_ptr(&w),
891 algo.into()
892 ))?;
893 Ok(res.into())
894 }
895
896 /// Checks whether the graph has an Eulerian path and/or an Eulerian cycle.
897 ///
898 /// An Eulerian path traverses every edge exactly once; an Eulerian cycle
899 /// is a closed one. By Euler's theorem, a connected (ignoring isolated
900 /// vertices) undirected graph has an Eulerian cycle iff all degrees are
901 /// even, and a path iff at most two degrees are odd. A directed graph
902 /// whose edges lie in a single weakly connected component has an Eulerian
903 /// cycle iff every in-degree equals the out-degree, and a path iff this
904 /// holds except for at most one start vertex (out-degree one larger) and
905 /// one end vertex (in-degree one larger). A graph without edges trivially
906 /// has both.
907 ///
908 /// Time complexity: O(|V|+|E|).
909 ///
910 /// See also [`degree`](Self::degree) and
911 /// [`is_connected`](Self::is_connected), the ingredients of Euler's
912 /// theorem, and [`eulerian_path`](Self::eulerian_path) /
913 /// [`eulerian_cycle`](Self::eulerian_cycle) to construct the walks.
914 ///
915 /// Binds [`igraph_is_eulerian`](https://igraph.org/c/html/latest/igraph-Cycles.html#igraph_is_eulerian).
916 ///
917 /// # Examples
918 ///
919 /// The seven bridges of Königsberg have no Eulerian path:
920 ///
921 /// ```
922 /// use igraph::prelude::*;
923 /// // 0: island Kneiphof, 1: north bank, 2: south bank, 3: east island Lomse
924 /// let bridges = [(0, 1), (0, 1), (0, 2), (0, 2), (0, 3), (1, 3), (2, 3)];
925 /// let konigsberg = Graph::from_edges(&bridges, 4, false)?;
926 /// let status = konigsberg.is_eulerian()?;
927 /// assert!(!status.has_path && !status.has_cycle);
928 /// # Ok::<(), igraph::Error>(())
929 /// ```
930 pub fn is_eulerian(&self) -> Result<EulerianStatus> {
931 let mut has_path = false;
932 let mut has_cycle = false;
933 igraph_call!(igraph_is_eulerian(self, &mut has_path, &mut has_cycle))?;
934 Ok(EulerianStatus {
935 has_path,
936 has_cycle,
937 })
938 }
939
940 /// Finds an Eulerian path, traversing every edge exactly once
941 /// (Hierholzer's algorithm).
942 ///
943 /// If the graph has no edges, an empty walk is returned. When the graph
944 /// also has an Eulerian cycle, the returned path is necessarily closed
945 /// (an Eulerian path with distinct ends exists only when exactly two
946 /// vertices have odd degree, or unbalanced in/out-degrees if directed).
947 ///
948 /// Time complexity: O(|V|+|E|).
949 ///
950 /// Binds [`igraph_eulerian_path`](https://igraph.org/c/html/latest/igraph-Cycles.html#igraph_eulerian_path).
951 ///
952 /// # Errors
953 ///
954 /// [`ErrorKind::NoSolution`](crate::ErrorKind::NoSolution) if the graph has
955 /// no Eulerian path (check first with [`is_eulerian`](Self::is_eulerian)).
956 ///
957 /// # Examples
958 ///
959 /// Drawing the "house of Santa Claus" without lifting the pen:
960 ///
961 /// ```
962 /// use igraph::prelude::*;
963 /// // square 0-1-2-3, both diagonals, and the roof 2-4-3
964 /// let house = [(0, 1), (1, 2), (2, 3), (3, 0), (0, 2), (1, 3), (2, 4), (4, 3)];
965 /// let g = Graph::from_edges(&house, 5, false)?;
966 /// let walk = g.eulerian_path()?;
967 /// assert_eq!(walk.edges.len(), 8);
968 /// assert_eq!(walk.vertices.len(), 9);
969 /// // It must start and end at the two odd-degree corners 0 and 1.
970 /// let ends = [walk.vertices[0], walk.vertices[8]];
971 /// assert!(ends == [0, 1] || ends == [1, 0]);
972 /// # Ok::<(), igraph::Error>(())
973 /// ```
974 pub fn eulerian_path(&self) -> Result<EulerianWalk> {
975 let mut edges = VectorInt::new();
976 let mut vertices = VectorInt::new();
977 igraph_call!(igraph_eulerian_path(self, &mut edges, &mut vertices))?;
978 Ok(EulerianWalk {
979 vertices: vertices.into(),
980 edges: edges.into(),
981 })
982 }
983
984 /// Finds an Eulerian cycle, a closed walk traversing every edge exactly
985 /// once (Hierholzer's algorithm).
986 ///
987 /// The first and last returned vertices coincide. If the graph has no
988 /// edges, an empty walk is returned.
989 ///
990 /// Time complexity: O(|V|+|E|).
991 ///
992 /// Binds [`igraph_eulerian_cycle`](https://igraph.org/c/html/latest/igraph-Cycles.html#igraph_eulerian_cycle).
993 ///
994 /// # Errors
995 ///
996 /// [`ErrorKind::NoSolution`](crate::ErrorKind::NoSolution) if the graph has
997 /// no Eulerian cycle.
998 ///
999 /// # Examples
1000 ///
1001 /// ```
1002 /// use igraph::prelude::*;
1003 /// let triangle = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], 3, false)?;
1004 /// let c = triangle.eulerian_cycle()?;
1005 /// assert_eq!(c.edges, vec![0, 1, 2]);
1006 /// assert_eq!(c.vertices, vec![0, 1, 2, 0]);
1007 ///
1008 /// let path = Graph::from_edges(&[(0, 1), (1, 2)], 3, false)?;
1009 /// assert_eq!(path.eulerian_cycle().unwrap_err().kind(), ErrorKind::NoSolution);
1010 ///
1011 /// // An Eulerian cycle of the binary de Bruijn graph B(2, 2) traverses all
1012 /// // 8 three-bit words once: reading one bit per step gives a de Bruijn
1013 /// // sequence, which contains every 3-bit word (cyclically) exactly once.
1014 /// let b = Graph::de_bruijn(2, 2)?;
1015 /// let tour = b.eulerian_cycle()?;
1016 /// let bits: Vec<i64> = tour.vertices[1..].iter().map(|v| v & 1).collect();
1017 /// let mut words: Vec<i64> =
1018 /// (0..8).map(|i| 4 * bits[i] + 2 * bits[(i + 1) % 8] + bits[(i + 2) % 8]).collect();
1019 /// words.sort();
1020 /// assert_eq!(words, (0..8).collect::<Vec<_>>());
1021 /// # Ok::<(), igraph::Error>(())
1022 /// ```
1023 pub fn eulerian_cycle(&self) -> Result<EulerianWalk> {
1024 let mut edges = VectorInt::new();
1025 let mut vertices = VectorInt::new();
1026 igraph_call!(igraph_eulerian_cycle(self, &mut edges, &mut vertices))?;
1027 Ok(EulerianWalk {
1028 vertices: vertices.into(),
1029 edges: edges.into(),
1030 })
1031 }
1032}