igraph/visitor.rs
1//! Breadth-first and depth-first traversals, with Rust closures as visitors
2//! (`igraph_visitor.h`).
3//!
4//! This module binds the three graph traversal functions of igraph's
5//! [Visitors](https://igraph.org/c/html/latest/igraph-Visitors.html) chapter:
6//!
7//! | Rust method | C function | What you get |
8//! |-------------|------------|--------------|
9//! | [`Graph::bfs`] | [`igraph_bfs`](https://igraph.org/c/html/latest/igraph-Visitors.html#igraph_bfs) | [`BfsResult`]: order, rank, parents, pred, succ, dist |
10//! | [`Graph::bfs_with`] | `igraph_bfs` + `igraph_bfshandler_t` | same, calling a closure on every visited vertex ([`BfsVisit`]) |
11//! | [`Graph::bfs_simple`] | [`igraph_bfs_simple`](https://igraph.org/c/html/latest/igraph-Visitors.html#igraph_bfs_simple) | [`BfsSimpleResult`]: order, distance layers, parents |
12//! | [`Graph::dfs`] | [`igraph_dfs`](https://igraph.org/c/html/latest/igraph-Visitors.html#igraph_dfs) | [`DfsResult`]: discovery and finishing orders, parents, dist |
13//! | [`Graph::dfs_with`] | `igraph_dfs` + `igraph_dfshandler_t` (in and out) | same, calling a closure on every [`DfsEvent`] |
14//!
15//! Traversal parameters are grouped in [`BfsOptions`] and [`DfsOptions`]
16//! (edge direction to follow, whether to restart from unreachable vertices,
17//! and, for BFS, an optional restricted vertex set).
18//!
19//! # Visitors are closures
20//!
21//! The callback variants accept any `FnMut` closure returning
22//! [`ControlFlow<()>`](std::ops::ControlFlow): return
23//! [`ControlFlow::Continue(())`](std::ops::ControlFlow::Continue) to go on,
24//! or [`ControlFlow::Break(())`](std::ops::ControlFlow::Break) to stop the
25//! traversal early. Stopping is *not* an error (it maps to igraph's
26//! `IGRAPH_STOP`): the wrapper returns `Ok` with the partial results computed
27//! so far and sets the `stopped` flag of the result. If the closure panics,
28//! the traversal is aborted inside igraph (no unwinding crosses the FFI
29//! boundary) and the panic is then resumed in the calling Rust code.
30//!
31//! # Rusty results
32//!
33//! Where igraph stores negative sentinels (`-1` for "root", `-2` for "not
34//! visited"), the result structs use [`Option`]s instead, and the visiting
35//! orders only contain the vertices that were actually reached (igraph pads
36//! them with `-1`).
37//!
38//! # Differences from the raw C functions
39//!
40//! The bindings smooth over a few rough edges of `src/graph/visitors.c`,
41//! which are present in igraph 1.0.0 and 1.0.1 (the file is unchanged
42//! between the two releases):
43//!
44//! - `igraph_dfs` reports a wrong depth to its out-callback (one less than
45//! the depth given at discovery) and, after a restart with `unreachable`,
46//! its depth counter is not reset: the roots of later trees still get
47//! depth 0, but the other vertices of the second tree get one less than
48//! their true depth, those of the third tree two less, and so on (the raw
49//! `dist` output can even become `-1`, the "not visited" sentinel).
50//! [`DfsEvent`] and [`DfsResult::dist`] carry the true depths, tracked on
51//! the Rust side.
52//! - `igraph_bfs` with `unreachable = true` reads out of bounds on the null
53//! graph: the bindings answer that case without calling igraph.
54//! - `igraph_bfs_simple` does not validate its root, and `igraph_dfs`
55//! reports an invalid root as `IGRAPH_EINVAL`: both methods check the
56//! root first and report [`ErrorKind::InvalidVertexId`], like
57//! [`Graph::bfs`].
58//! - After an early stop, `igraph_bfs` has already assigned a parent to the
59//! vertices waiting in its queue; [`BfsResult::parents`] only describes
60//! the vertices that were actually visited.
61//!
62//! # See also
63//!
64//! Many questions that can be answered with a hand-written traversal have a
65//! dedicated (and usually faster) function elsewhere in the crate:
66//!
67//! - distances and shortest paths: [`Graph::distances`],
68//! [`Graph::get_shortest_path`], [`Graph::get_shortest_paths`],
69//! [`Graph::eccentricity`] (all in [`crate::paths`]);
70//! - reachability and components: [`Graph::subcomponent`] (the vertices a
71//! BFS from one root reaches), [`Graph::connected_components`],
72//! [`Graph::neighborhood`] (vertices within a given number of hops);
73//! - DAGs and cycles: [`Graph::topological_sorting`], [`Graph::is_dag`],
74//! [`Graph::find_cycle`] (in [`crate::cycles`]);
75//! - trees: [`Graph::unfold_tree`] (unrolls a graph into a BFS tree),
76//! [`Graph::kary_tree`] and [`Graph::famous`] to build test inputs;
77//! - low-level neighbor access for your own traversals: [`crate::adjlist`].
78//!
79//! # Example
80//!
81//! ```
82//! use igraph::prelude::*;
83//! use igraph::visitor::{BfsOptions, DfsOptions};
84//! use std::ops::ControlFlow;
85//!
86//! // A small binary tree: 0
87//! // / \
88//! // 1 2
89//! // / \ /
90//! // 3 4 5
91//! let tree = Graph::kary_tree(6, 2, TreeMode::Undirected)?;
92//!
93//! let bfs = tree.bfs(&[0], &BfsOptions::default())?;
94//! assert_eq!(bfs.order, [0, 1, 2, 3, 4, 5]);
95//! assert_eq!(bfs.dist, [Some(0), Some(1), Some(1), Some(2), Some(2), Some(2)]);
96//! assert_eq!(bfs.path_to(5), Some(vec![0, 2, 5]));
97//!
98//! let dfs = tree.dfs(0, &DfsOptions::default())?;
99//! assert_eq!(dfs.order, [0, 1, 3, 4, 2, 5]); // pre-order
100//! assert_eq!(dfs.order_out, [3, 4, 1, 5, 2, 0]); // post-order
101//!
102//! // Stop as soon as a vertex at distance 2 is found.
103//! let mut first_deep = None;
104//! let partial = tree.bfs_with(&[0], &BfsOptions::default(), |visit| {
105//! if visit.dist == 2 {
106//! first_deep = Some(visit.vid);
107//! return ControlFlow::Break(());
108//! }
109//! ControlFlow::Continue(())
110//! })?;
111//! assert_eq!(first_deep, Some(3));
112//! assert!(partial.stopped);
113//!
114//! // In an unweighted graph, BFS distances are shortest-path lengths.
115//! let d = tree.distances(0, .., None, NeighborMode::All)?;
116//! assert_eq!(d.to_rows()[0], [0.0, 1.0, 1.0, 2.0, 2.0, 2.0]);
117//! # Ok::<(), igraph::Error>(())
118//! ```
119
120use crate::{
121 constants::NeighborMode,
122 error::{Error, ErrorKind, Result, catch_panic},
123 ffi::*,
124 graph::{Graph, VertexId},
125 igraph_call,
126 vector::VectorInt,
127};
128use std::{ffi::c_void, ops::ControlFlow, ptr};
129
130// ---------------------------------------------------------------------------
131// Options
132// ---------------------------------------------------------------------------
133
134/// Parameters of a breadth-first search ([`Graph::bfs`], [`Graph::bfs_with`]).
135///
136/// The defaults are: follow out-edges ([`NeighborMode::Out`]), do **not**
137/// visit vertices unreachable from the roots, no restriction.
138///
139/// ```
140/// use igraph::prelude::*;
141/// use igraph::visitor::BfsOptions;
142///
143/// let restricted = [0, 1, 2];
144/// let opts = BfsOptions::default()
145/// .with_mode(NeighborMode::All)
146/// .with_unreachable(true)
147/// .with_restricted(&restricted);
148/// assert_eq!(opts.mode, NeighborMode::All);
149/// assert!(opts.unreachable);
150/// assert_eq!(opts.restricted, Some(&restricted[..]));
151/// ```
152#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153pub struct BfsOptions<'a> {
154 /// Which edges to follow in directed graphs: [`NeighborMode::Out`] follows
155 /// the edge directions, [`NeighborMode::In`] goes against them and
156 /// [`NeighborMode::All`] ignores them. Ignored for undirected graphs.
157 pub mode: NeighborMode,
158 /// If `true`, once the roots are exhausted, further searches are started
159 /// from the not yet visited vertices, in increasing id order, until every
160 /// (allowed) vertex has been visited.
161 pub unreachable: bool,
162 /// If set, the search only walks on these vertices: every other vertex
163 /// is treated as already visited (even when it is given as a root, in
164 /// which case it is silently skipped).
165 pub restricted: Option<&'a [VertexId]>,
166}
167
168impl Default for BfsOptions<'_> {
169 fn default() -> Self {
170 Self {
171 mode: NeighborMode::Out,
172 unreachable: false,
173 restricted: None,
174 }
175 }
176}
177
178impl<'a> BfsOptions<'a> {
179 /// Sets [`mode`](Self::mode).
180 pub fn with_mode(mut self, mode: NeighborMode) -> Self {
181 self.mode = mode;
182 self
183 }
184
185 /// Sets [`unreachable`](Self::unreachable).
186 pub fn with_unreachable(mut self, unreachable: bool) -> Self {
187 self.unreachable = unreachable;
188 self
189 }
190
191 /// Sets [`restricted`](Self::restricted) to `Some(vertices)`.
192 pub fn with_restricted(mut self, vertices: &'a [VertexId]) -> Self {
193 self.restricted = Some(vertices);
194 self
195 }
196}
197
198/// Parameters of a depth-first search ([`Graph::dfs`], [`Graph::dfs_with`]).
199///
200/// The defaults are: follow out-edges ([`NeighborMode::Out`]) and do **not**
201/// visit vertices unreachable from the root.
202///
203/// ```
204/// use igraph::prelude::*;
205/// use igraph::visitor::DfsOptions;
206///
207/// // 0 -> 1, 2 -> 1: walking against the edges from 1 finds 0 and 2.
208/// let g = Graph::from_edges(&[(0, 1), (2, 1)], 3, true)?;
209/// let opts = DfsOptions::default().with_mode(NeighborMode::In);
210/// assert_eq!(g.dfs(1, &opts)?.order, [1, 0, 2]);
211/// assert_eq!(g.dfs(1, &DfsOptions::default())?.order, [1]);
212/// # Ok::<(), igraph::Error>(())
213/// ```
214#[derive(Debug, Clone, Copy, PartialEq, Eq)]
215pub struct DfsOptions {
216 /// Which edges to follow in directed graphs (see [`BfsOptions::mode`]).
217 /// Ignored for undirected graphs.
218 pub mode: NeighborMode,
219 /// If `true`, once the tree of the root is complete, further searches are
220 /// started from the not yet visited vertices, in increasing id order.
221 pub unreachable: bool,
222}
223
224impl Default for DfsOptions {
225 fn default() -> Self {
226 Self {
227 mode: NeighborMode::Out,
228 unreachable: false,
229 }
230 }
231}
232
233impl DfsOptions {
234 /// Sets [`mode`](Self::mode).
235 pub fn with_mode(mut self, mode: NeighborMode) -> Self {
236 self.mode = mode;
237 self
238 }
239
240 /// Sets [`unreachable`](Self::unreachable).
241 pub fn with_unreachable(mut self, unreachable: bool) -> Self {
242 self.unreachable = unreachable;
243 self
244 }
245}
246
247// ---------------------------------------------------------------------------
248// Results and events
249// ---------------------------------------------------------------------------
250
251/// What a breadth-first search visitor sees each time a vertex is visited
252/// (the arguments of igraph's `igraph_bfshandler_t`).
253#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
254pub struct BfsVisit {
255 /// The vertex being visited.
256 pub vid: VertexId,
257 /// The vertex visited just before, or `None` if `vid` is the root of a
258 /// search tree.
259 pub pred: Option<VertexId>,
260 /// The vertex that will be visited next, or `None` if `vid` is the last
261 /// vertex of its search tree.
262 pub succ: Option<VertexId>,
263 /// The rank of `vid`, i.e. its position in the visiting order (from 0).
264 pub rank: usize,
265 /// The distance (number of hops) of `vid` from the root of its search
266 /// tree.
267 pub dist: usize,
268}
269
270/// Results of a breadth-first search ([`Graph::bfs`], [`Graph::bfs_with`]).
271///
272/// The per-vertex vectors (`rank`, `parents`, `pred`, `succ`, `dist`) have
273/// one entry per vertex of the graph, `None` for vertices that were not
274/// visited (e.g. unreachable, outside the restricted set, or not reached
275/// because the visitor stopped the search).
276#[derive(Debug, Clone, PartialEq, Eq)]
277pub struct BfsResult {
278 /// The visited vertices, in visiting order.
279 pub order: Vec<VertexId>,
280 /// The rank (position in [`order`](Self::order)) of each vertex.
281 pub rank: Vec<Option<usize>>,
282 /// The parent of each vertex in the BFS forest: `None` for the roots of
283 /// the search trees and for unvisited vertices (igraph itself already
284 /// assigns a parent to vertices that were queued but not yet visited when
285 /// the visitor stopped the search; the bindings report them as `None`
286 /// too, so that `parents` always describes the visited forest).
287 pub parents: Vec<Option<VertexId>>,
288 /// The vertex visited just before each vertex: `None` for the roots of
289 /// the search trees and for unvisited vertices.
290 pub pred: Vec<Option<VertexId>>,
291 /// The vertex visited just after each vertex: `None` for the last vertex
292 /// of each search tree and for unvisited vertices. When the visitor stops
293 /// the search, the successor of the vertex it stopped at is `None` too.
294 pub succ: Vec<Option<VertexId>>,
295 /// The distance of each vertex from the root of its search tree.
296 pub dist: Vec<Option<usize>>,
297 /// `true` if the visitor closure returned
298 /// [`ControlFlow::Break`], so that the search ended early.
299 pub stopped: bool,
300}
301
302impl BfsResult {
303 /// Whether vertex `v` was visited by the search.
304 pub fn is_visited(&self, v: VertexId) -> bool {
305 usize::try_from(v)
306 .ok()
307 .and_then(|i| self.rank.get(i))
308 .is_some_and(Option::is_some)
309 }
310
311 /// The roots of the search trees, in the order they were used.
312 ///
313 /// In an undirected graph searched with [`BfsOptions::unreachable`] set
314 /// (and no restriction), there is exactly one root per connected
315 /// component, see [`Graph::connected_components`].
316 pub fn roots(&self) -> Vec<VertexId> {
317 self.order
318 .iter()
319 .copied()
320 .filter(|&v| self.parents[v as usize].is_none())
321 .collect()
322 }
323
324 /// The path of the BFS forest from the root of `v`'s search tree down to
325 /// `v` (a shortest path in unweighted graphs), or `None` if `v` was not
326 /// visited. [`Graph::get_shortest_path`] computes a single such path
327 /// directly (also with weights).
328 pub fn path_to(&self, v: VertexId) -> Option<Vec<VertexId>> {
329 tree_path(&self.parents, &self.rank, v)
330 }
331}
332
333/// Results of [`Graph::bfs_simple`].
334#[derive(Debug, Clone, PartialEq, Eq)]
335pub struct BfsSimpleResult {
336 /// The visited vertices, in visiting order (only the ones reachable from
337 /// the root).
338 pub order: Vec<VertexId>,
339 /// Layer boundaries: the vertices at distance `i` from the root are
340 /// `order[layers[i]..layers[i + 1]]`. It has one more element than the
341 /// number of layers; the last one is `order.len()`.
342 pub layers: Vec<usize>,
343 /// The parent of each vertex in the BFS tree: `None` for the root and for
344 /// vertices that were not reached.
345 pub parents: Vec<Option<VertexId>>,
346}
347
348impl BfsSimpleResult {
349 /// Number of distance layers (the eccentricity of the root plus one).
350 pub fn num_layers(&self) -> usize {
351 self.layers.len().saturating_sub(1)
352 }
353
354 /// The vertices at distance `i` from the root (empty if `i` is too large).
355 pub fn layer(&self, i: usize) -> &[VertexId] {
356 match (self.layers.get(i), self.layers.get(i + 1)) {
357 (Some(&a), Some(&b)) => &self.order[a..b],
358 _ => &[],
359 }
360 }
361
362 /// Iterates over the distance layers, from the root outwards.
363 pub fn iter_layers(&self) -> impl Iterator<Item = &[VertexId]> + '_ {
364 self.layers.windows(2).map(|w| &self.order[w[0]..w[1]])
365 }
366}
367
368/// An event reported to a depth-first search visitor ([`Graph::dfs_with`]).
369///
370/// igraph has two DFS callbacks: one when a vertex is *discovered*
371/// (`in_callback`) and one when its subtree is *finished* (`out_callback`).
372/// A single Rust closure receives both, as the two variants of this enum.
373#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
374pub enum DfsEvent {
375 /// A vertex has just been discovered (pre-order).
376 Discover {
377 /// The discovered vertex.
378 vid: VertexId,
379 /// Its distance (depth) from the root of its search tree.
380 dist: usize,
381 },
382 /// The whole subtree of a vertex has been explored (post-order).
383 Finish {
384 /// The finished vertex.
385 vid: VertexId,
386 /// Its distance (depth) from the root of its search tree: the same
387 /// value as in the matching [`Discover`](DfsEvent::Discover) event
388 /// (the raw C callback receives one less).
389 dist: usize,
390 },
391}
392
393impl DfsEvent {
394 /// The vertex the event is about.
395 pub fn vid(&self) -> VertexId {
396 match *self {
397 Self::Discover { vid, .. } | Self::Finish { vid, .. } => vid,
398 }
399 }
400
401 /// The depth of the vertex in its search tree.
402 pub fn dist(&self) -> usize {
403 match *self {
404 Self::Discover { dist, .. } | Self::Finish { dist, .. } => dist,
405 }
406 }
407}
408
409/// Results of a depth-first search ([`Graph::dfs`], [`Graph::dfs_with`]).
410#[derive(Debug, Clone, PartialEq, Eq)]
411pub struct DfsResult {
412 /// The vertices in the order they were discovered (pre-order).
413 pub order: Vec<VertexId>,
414 /// The vertices in the order their subtrees were completed (post-order).
415 /// If the search was stopped early, it may be shorter than
416 /// [`order`](Self::order).
417 pub order_out: Vec<VertexId>,
418 /// The parent of each vertex in the DFS forest: `None` for the roots of
419 /// the search trees and for unvisited vertices.
420 pub parents: Vec<Option<VertexId>>,
421 /// The depth of each vertex in its DFS tree (the number of tree edges
422 /// between it and the root of its tree), `None` if not visited. Unlike
423 /// the raw output of `igraph_dfs`, it stays exact after a restart with
424 /// [`DfsOptions::unreachable`] (see the [module docs](self)).
425 pub dist: Vec<Option<usize>>,
426 /// `true` if the visitor closure returned [`ControlFlow::Break`].
427 pub stopped: bool,
428}
429
430impl DfsResult {
431 /// Whether vertex `v` was discovered by the search.
432 pub fn is_visited(&self, v: VertexId) -> bool {
433 usize::try_from(v)
434 .ok()
435 .and_then(|i| self.dist.get(i))
436 .is_some_and(Option::is_some)
437 }
438
439 /// The path of the DFS forest from the root of `v`'s tree down to `v`,
440 /// or `None` if `v` was not visited.
441 pub fn path_to(&self, v: VertexId) -> Option<Vec<VertexId>> {
442 tree_path(&self.parents, &self.dist, v)
443 }
444}
445
446// ---------------------------------------------------------------------------
447// Helpers
448// ---------------------------------------------------------------------------
449
450/// Follows `parents` from `v` up to a root; `visited[v]` tells whether `v`
451/// belongs to the forest at all.
452fn tree_path<T>(
453 parents: &[Option<VertexId>],
454 visited: &[Option<T>],
455 v: VertexId,
456) -> Option<Vec<VertexId>> {
457 let i = usize::try_from(v).ok()?;
458 visited.get(i)?.as_ref()?;
459 let mut path = vec![v];
460 let mut cur = v;
461 while let Some(p) = parents[cur as usize] {
462 path.push(p);
463 cur = p;
464 if path.len() > parents.len() {
465 return None; // defensive: never loop on a malformed forest
466 }
467 }
468 path.reverse();
469 Some(path)
470}
471
472/// Converts an igraph id vector with negative sentinels into `Option`s.
473fn opt_ids(v: VectorInt) -> Vec<Option<VertexId>> {
474 v.iter().map(|&x| (x >= 0).then_some(x)).collect()
475}
476
477/// Converts an igraph count vector with negative sentinels into `Option`s.
478fn opt_counts(v: VectorInt) -> Vec<Option<usize>> {
479 v.iter().map(|&x| usize::try_from(x).ok()).collect()
480}
481
482/// Keeps the visited prefix of an order vector padded with `-1`.
483fn visited_prefix(v: VectorInt) -> Vec<VertexId> {
484 v.iter().copied().take_while(|&x| x >= 0).collect()
485}
486
487fn check_vertex(graph: &Graph, v: VertexId, what: &str) -> Result<()> {
488 if v < 0 || v as usize >= graph.vcount() {
489 return Err(Error::new(
490 ErrorKind::InvalidVertexId,
491 format!(
492 "invalid {what} vertex id {v} (the graph has {} vertices)",
493 graph.vcount()
494 ),
495 ));
496 }
497 Ok(())
498}
499
500fn flow_to_code(flow: ControlFlow<()>, stopped: &mut bool) -> igraph_error_t {
501 match flow {
502 ControlFlow::Continue(()) => igraph_error_type_t_IGRAPH_SUCCESS,
503 ControlFlow::Break(()) => {
504 *stopped = true;
505 igraph_error_type_t_IGRAPH_STOP
506 }
507 }
508}
509
510/// Runs the Rust side of a BFS/DFS callback in a new level of igraph's
511/// "finally" stack (`IGRAPH_FINALLY_ENTER` / `IGRAPH_FINALLY_EXIT`), catching
512/// panics.
513///
514/// The running search keeps its temporaries (queue, stack, bitsets) on that
515/// stack, and igraph's error handler frees the objects of the current level
516/// when a call fails. Without a new level, an igraph call made by the user
517/// closure that fails (returning an `Err` the closure may well ignore) would
518/// free the temporaries of the search that is still running, a
519/// use-after-free in C.
520fn in_finally_level(f: impl FnOnce() -> igraph_error_t) -> igraph_error_t {
521 // SAFETY: plain bookkeeping on igraph's thread-local finally stack; the
522 // matching EXIT runs below, as `catch_panic` never unwinds.
523 unsafe { IGRAPH_FINALLY_ENTER() };
524 let code = catch_panic(f);
525 // SAFETY: closes the level opened above. Every igraph call made by the
526 // closure has returned, and a failed one has already freed its objects.
527 unsafe { IGRAPH_FINALLY_EXIT() };
528 code
529}
530
531struct BfsState<F> {
532 f: F,
533 stopped: bool,
534}
535
536unsafe extern "C" fn bfs_trampoline<F>(
537 _graph: *const igraph_t,
538 vid: igraph_int_t,
539 pred: igraph_int_t,
540 succ: igraph_int_t,
541 rank: igraph_int_t,
542 dist: igraph_int_t,
543 extra: *mut c_void,
544) -> igraph_error_t
545where
546 F: FnMut(BfsVisit) -> ControlFlow<()>,
547{
548 in_finally_level(|| {
549 // SAFETY: `extra` is the `&mut BfsState<F>` passed by `bfs_impl`,
550 // alive and exclusively borrowed for the whole C call.
551 let state = unsafe { &mut *(extra as *mut BfsState<F>) };
552 let visit = BfsVisit {
553 vid,
554 pred: (pred >= 0).then_some(pred),
555 succ: (succ >= 0).then_some(succ),
556 rank: rank as usize,
557 dist: dist as usize,
558 };
559 let flow = (state.f)(visit);
560 flow_to_code(flow, &mut state.stopped)
561 })
562}
563
564/// State shared by the two DFS trampolines. igraph's own `dist` argument to
565/// the out-callback is off by one, and both callbacks' `dist` drift after a
566/// restart with
567/// `unreachable` (igraph 1.0.0 and 1.0.1: `act_dist` is never reset for a new
568/// root), so the depth is tracked here: `depth` is the size of the DFS
569/// stack, incremented on discovery and decremented on completion.
570struct DfsState<F> {
571 f: F,
572 depth: usize,
573 stopped: bool,
574}
575
576unsafe extern "C" fn dfs_in_trampoline<F>(
577 _graph: *const igraph_t,
578 vid: igraph_int_t,
579 _dist: igraph_int_t,
580 extra: *mut c_void,
581) -> igraph_error_t
582where
583 F: FnMut(DfsEvent) -> ControlFlow<()>,
584{
585 in_finally_level(|| {
586 // SAFETY: see `bfs_trampoline`.
587 let state = unsafe { &mut *(extra as *mut DfsState<F>) };
588 let dist = state.depth;
589 state.depth += 1;
590 let flow = (state.f)(DfsEvent::Discover { vid, dist });
591 flow_to_code(flow, &mut state.stopped)
592 })
593}
594
595unsafe extern "C" fn dfs_out_trampoline<F>(
596 _graph: *const igraph_t,
597 vid: igraph_int_t,
598 _dist: igraph_int_t,
599 extra: *mut c_void,
600) -> igraph_error_t
601where
602 F: FnMut(DfsEvent) -> ControlFlow<()>,
603{
604 in_finally_level(|| {
605 // SAFETY: see `bfs_trampoline`.
606 let state = unsafe { &mut *(extra as *mut DfsState<F>) };
607 state.depth = state.depth.saturating_sub(1);
608 let dist = state.depth;
609 let flow = (state.f)(DfsEvent::Finish { vid, dist });
610 flow_to_code(flow, &mut state.stopped)
611 })
612}
613
614// ---------------------------------------------------------------------------
615// Graph methods
616// ---------------------------------------------------------------------------
617
618impl igraph_t {
619 fn bfs_impl(
620 &self,
621 roots: &[VertexId],
622 options: &BfsOptions<'_>,
623 callback: igraph_bfshandler_t,
624 extra: *mut c_void,
625 ) -> Result<BfsResult> {
626 if self.vcount() == 0 {
627 // igraph 1.0.0 and 1.0.1 read out of bounds when `unreachable` is
628 // set on the null graph (`IGRAPH_BIT_TEST(added, 0)` on an empty
629 // bitset): handle it here (any given id is invalid).
630 if let Some(&v) = roots.iter().chain(options.restricted.unwrap_or(&[])).next() {
631 check_vertex(self, v, "root or restricted")?;
632 }
633 return Ok(BfsResult {
634 order: vec![],
635 rank: vec![],
636 parents: vec![],
637 pred: vec![],
638 succ: vec![],
639 dist: vec![],
640 stopped: false,
641 });
642 }
643 let roots_v = VectorInt::view(roots);
644 let restricted_v = options.restricted.map(VectorInt::view);
645 let restricted_p = restricted_v.as_ref().map_or(ptr::null(), |v| v.as_ptr());
646 let mut order = VectorInt::new();
647 let mut rank = VectorInt::new();
648 let mut parents = VectorInt::new();
649 let mut pred = VectorInt::new();
650 let mut succ = VectorInt::new();
651 let mut dist = VectorInt::new();
652 igraph_call!(igraph_bfs(
653 self,
654 0,
655 roots_v.as_ptr(),
656 options.mode.into(),
657 options.unreachable,
658 restricted_p,
659 &mut order,
660 &mut rank,
661 &mut parents,
662 &mut pred,
663 &mut succ,
664 &mut dist,
665 callback,
666 extra,
667 ))?;
668 let rank = opt_counts(rank);
669 // igraph assigns `parents` when a vertex is *enqueued*, not when it is
670 // visited: after an early stop, vertices still waiting in the queue
671 // would have a parent but no rank/dist. Keep the documented invariant
672 // "parent is `Some` only for visited, non-root vertices".
673 let parents = opt_ids(parents)
674 .into_iter()
675 .zip(&rank)
676 .map(|(p, r)| r.and(p))
677 .collect();
678 Ok(BfsResult {
679 order: visited_prefix(order),
680 rank,
681 parents,
682 pred: opt_ids(pred),
683 succ: opt_ids(succ),
684 dist: opt_counts(dist),
685 stopped: false,
686 })
687 }
688
689 /// Breadth-first search from one or more root vertices.
690 ///
691 /// The search starts from `roots[0]`; when its tree is exhausted, it
692 /// continues from the next root in `roots` that was not visited yet, and
693 /// so on (roots already reached from a previous one are skipped). If
694 /// [`options.unreachable`](BfsOptions::unreachable) is `true`, the
695 /// remaining vertices are then used as roots too, in increasing id order,
696 /// so that the whole graph is traversed. Neighbors are enqueued in the
697 /// order of the graph's adjacency lists (increasing neighbor id). An
698 /// empty `roots` slice is allowed: nothing is visited unless
699 /// `unreachable` is set.
700 ///
701 /// The result gathers every output of the C function: the visiting
702 /// order, and, for each vertex, its rank, BFS-tree parent, predecessor
703 /// and successor in the visiting order, and distance from its root. See
704 /// [`BfsResult`]. Use [`Graph::bfs_with`] to run a visitor closure during
705 /// the search, and [`Graph::bfs_simple`] for a lighter single-root
706 /// variant that also reports distance layers.
707 ///
708 /// Time complexity: O(|V| + |E|).
709 ///
710 /// Binds [`igraph_bfs`](https://igraph.org/c/html/latest/igraph-Visitors.html#igraph_bfs).
711 ///
712 /// See also [`Graph::subcomponent`] (just the set of reachable
713 /// vertices), [`Graph::distances`] and [`Graph::get_shortest_paths`]
714 /// (weighted distances and paths), [`Graph::connected_components`], and
715 /// [`Graph::unfold_tree`] (turns the graph into its BFS tree).
716 ///
717 /// # Errors
718 ///
719 /// [`ErrorKind::InvalidVertexId`] if a root or a restricted vertex does
720 /// not exist.
721 ///
722 /// # Examples
723 ///
724 /// Two disjoint 4-cycles `0-1-2-3` and `4-5-6-7`:
725 ///
726 /// ```
727 /// use igraph::prelude::*;
728 /// use igraph::visitor::BfsOptions;
729 ///
730 /// let square = Graph::ring(4, false, false, true)?;
731 /// let g = square.disjoint_union(&square)?;
732 /// let r = g.bfs(&[0], &BfsOptions::default())?;
733 /// assert_eq!(r.order, [0, 1, 3, 2]);
734 /// assert_eq!(r.dist[2], Some(2));
735 /// assert!(!r.is_visited(5));
736 ///
737 /// let all = g.bfs(&[0], &BfsOptions::default().with_unreachable(true))?;
738 /// assert_eq!(all.order, [0, 1, 3, 2, 4, 5, 7, 6]);
739 /// // One search tree per connected component.
740 /// assert_eq!(all.roots(), [0, 4]);
741 /// assert_eq!(g.connected_components(Connectedness::Weak)?.count, 2);
742 /// # Ok::<(), igraph::Error>(())
743 /// ```
744 pub fn bfs(&self, roots: &[VertexId], options: &BfsOptions<'_>) -> Result<BfsResult> {
745 self.bfs_impl(roots, options, None, ptr::null_mut())
746 }
747
748 /// Breadth-first search calling a visitor closure on every visited vertex.
749 ///
750 /// Same traversal as [`Graph::bfs`]; in addition, `visitor` is called
751 /// each time a vertex is *visited* (dequeued), after all its unvisited
752 /// neighbors have been enqueued, with a [`BfsVisit`] describing the
753 /// vertex, its predecessor and successor in the visiting order, its rank
754 /// and its distance from the root.
755 ///
756 /// Return [`ControlFlow::Continue`] to go on or [`ControlFlow::Break`] to
757 /// stop: the search then ends normally, the returned [`BfsResult`] has
758 /// [`stopped`](BfsResult::stopped) set, and contains the values computed
759 /// so far (the vertices not yet visited are `None`). Note that, when
760 /// stopping, the vertex the visitor stopped at has already been visited
761 /// (it is in `order`), but its `succ` entry is `None`. If `visitor`
762 /// panics, the search is aborted and the panic resumes in the caller.
763 ///
764 /// Binds [`igraph_bfs`](https://igraph.org/c/html/latest/igraph-Visitors.html#igraph_bfs)
765 /// with an [`igraph_bfshandler_t`](https://igraph.org/c/html/latest/igraph-Visitors.html#igraph_bfshandler_t)
766 /// callback.
767 ///
768 /// See also [`Graph::dfs_with`] for the depth-first counterpart.
769 ///
770 /// # Errors
771 ///
772 /// Same as [`Graph::bfs`].
773 ///
774 /// # Examples
775 ///
776 /// Find the first vertex of a path that is at least three hops away,
777 /// without visiting the rest of the graph:
778 ///
779 /// ```
780 /// use igraph::prelude::*;
781 /// use igraph::visitor::BfsOptions;
782 /// use std::ops::ControlFlow;
783 ///
784 /// let path = Graph::ring(6, false, false, false)?; // 0 - 1 - 2 - 3 - 4 - 5
785 /// let mut seen = vec![];
786 /// let r = path.bfs_with(&[0], &BfsOptions::default(), |v| {
787 /// seen.push(v.vid);
788 /// if v.dist >= 3 { ControlFlow::Break(()) } else { ControlFlow::Continue(()) }
789 /// })?;
790 /// assert_eq!(seen, [0, 1, 2, 3]);
791 /// assert!(r.stopped);
792 /// assert_eq!(r.order, [0, 1, 2, 3]);
793 /// assert_eq!(r.rank[5], None);
794 /// # Ok::<(), igraph::Error>(())
795 /// ```
796 pub fn bfs_with<F>(
797 &self,
798 roots: &[VertexId],
799 options: &BfsOptions<'_>,
800 visitor: F,
801 ) -> Result<BfsResult>
802 where
803 F: FnMut(BfsVisit) -> ControlFlow<()>,
804 {
805 let mut state = BfsState {
806 f: visitor,
807 stopped: false,
808 };
809 let extra = &mut state as *mut BfsState<F> as *mut c_void;
810 let mut result = self.bfs_impl(roots, options, Some(bfs_trampoline::<F>), extra)?;
811 result.stopped = state.stopped;
812 Ok(result)
813 }
814
815 /// Simple single-source breadth-first search, reporting distance layers.
816 ///
817 /// Visits the vertices reachable from `root` (following edges according
818 /// to `mode` in directed graphs; `mode` is ignored for undirected graphs)
819 /// and returns the visiting order, the *layers* (vertices grouped by
820 /// their distance from `root`) and the BFS-tree parents. This is the
821 /// lighter alternative to [`Graph::bfs`] when only these outputs matter.
822 ///
823 /// Time complexity: O(|V| + |E|).
824 ///
825 /// Binds [`igraph_bfs_simple`](https://igraph.org/c/html/latest/igraph-Visitors.html#igraph_bfs_simple).
826 ///
827 /// See also [`Graph::neighborhood`] (the vertices within a number of
828 /// hops, possibly from several roots) and [`Graph::eccentricity`] (which,
829 /// computed with the same `mode`, is `num_layers() - 1`: igraph ignores
830 /// unreachable vertices there too).
831 ///
832 /// # Errors
833 ///
834 /// [`ErrorKind::InvalidVertexId`] if `root` does not exist (checked on
835 /// the Rust side: igraph 1.0.0 and 1.0.1 do not validate it).
836 ///
837 /// # Examples
838 ///
839 /// The layers of a complete binary tree with 7 vertices:
840 ///
841 /// ```
842 /// use igraph::prelude::*;
843 ///
844 /// let t = Graph::kary_tree(7, 2, TreeMode::Undirected)?;
845 /// let r = t.bfs_simple(0, NeighborMode::All)?;
846 /// assert_eq!(r.num_layers(), 3);
847 /// assert_eq!(r.layer(0), [0]);
848 /// assert_eq!(r.layer(1), [1, 2]);
849 /// assert_eq!(r.layer(2), [3, 4, 5, 6]);
850 /// assert_eq!(r.parents[4], Some(1));
851 /// // The eccentricity of the root is the index of the last layer.
852 /// assert_eq!(t.eccentricity(0, None, NeighborMode::All)?, [2.0]);
853 /// # Ok::<(), igraph::Error>(())
854 /// ```
855 pub fn bfs_simple(&self, root: VertexId, mode: NeighborMode) -> Result<BfsSimpleResult> {
856 check_vertex(self, root, "root")?;
857 let mut order = VectorInt::new();
858 let mut layers = VectorInt::new();
859 let mut parents = VectorInt::new();
860 igraph_call!(igraph_bfs_simple(
861 self,
862 root,
863 mode.into(),
864 &mut order,
865 &mut layers,
866 &mut parents,
867 ))?;
868 Ok(BfsSimpleResult {
869 order: order.into(),
870 layers: layers.iter().map(|&x| x as usize).collect(),
871 parents: opt_ids(parents),
872 })
873 }
874
875 fn dfs_impl(
876 &self,
877 root: VertexId,
878 options: &DfsOptions,
879 callbacks: (igraph_dfshandler_t, igraph_dfshandler_t),
880 extra: *mut c_void,
881 ) -> Result<DfsResult> {
882 check_vertex(self, root, "root")?;
883 let mut order = VectorInt::new();
884 let mut order_out = VectorInt::new();
885 let mut parents = VectorInt::new();
886 igraph_call!(igraph_dfs(
887 self,
888 root,
889 options.mode.into(),
890 options.unreachable,
891 &mut order,
892 &mut order_out,
893 &mut parents,
894 ptr::null_mut(),
895 callbacks.0,
896 callbacks.1,
897 extra,
898 ))?;
899 let order = visited_prefix(order);
900 let parents = opt_ids(parents);
901 // igraph's `dist` output drifts after a restart with `unreachable`
902 // (igraph 1.0.0 and 1.0.1); recompute it from the parents, which are
903 // always exact. Parents are
904 // discovered before their children, so one pass in `order` suffices.
905 let mut dist: Vec<Option<usize>> = vec![None; self.vcount()];
906 for &v in &order {
907 dist[v as usize] = Some(match parents[v as usize] {
908 Some(p) => dist[p as usize].map_or(0, |d| d + 1),
909 None => 0,
910 });
911 }
912 Ok(DfsResult {
913 order,
914 order_out: visited_prefix(order_out),
915 parents,
916 dist,
917 stopped: false,
918 })
919 }
920
921 /// Depth-first search from a root vertex.
922 ///
923 /// Explores the graph depth first from `root`, following edges according
924 /// to [`options.mode`](DfsOptions::mode) in directed graphs; neighbors
925 /// are tried in adjacency list order (increasing neighbor id). If
926 /// [`options.unreachable`](DfsOptions::unreachable) is set, further
927 /// searches are started from the unvisited vertices, in
928 /// increasing id order, until all vertices are visited.
929 ///
930 /// Returns the discovery order (pre-order), the completion order
931 /// (post-order), the DFS-forest parents and the depth of each vertex,
932 /// see [`DfsResult`]. Use [`Graph::dfs_with`] to react to discovery and
933 /// completion events while the search runs.
934 ///
935 /// Time complexity: O(|V| + |E|).
936 ///
937 /// Binds [`igraph_dfs`](https://igraph.org/c/html/latest/igraph-Visitors.html#igraph_dfs).
938 ///
939 /// See also [`Graph::topological_sorting`], [`Graph::is_dag`] and
940 /// [`Graph::find_cycle`] (in [`crate::cycles`]), which answer the most
941 /// common DFS questions directly, and [`Graph::biconnected_components`].
942 ///
943 /// # Errors
944 ///
945 /// [`ErrorKind::InvalidVertexId`] if `root` does not exist (checked on
946 /// the Rust side; igraph 1.0.0 and 1.0.1 would report
947 /// [`ErrorKind::InvalidValue`]). In particular, the graph must have at
948 /// least one vertex.
949 ///
950 /// # Examples
951 ///
952 /// A reverse post-order of a DAG is a topological order:
953 ///
954 /// ```
955 /// use igraph::prelude::*;
956 /// use igraph::visitor::DfsOptions;
957 ///
958 /// // 0 → 1 → 3, 0 → 2 → 3
959 /// let dag = Graph::from_edges(&[(0, 1), (0, 2), (1, 3), (2, 3)], 4, true)?;
960 /// let r = dag.dfs(0, &DfsOptions::default())?;
961 /// assert_eq!(r.order, [0, 1, 3, 2]);
962 /// assert_eq!(r.order_out, [3, 1, 2, 0]);
963 /// let topo: Vec<_> = r.order_out.iter().rev().copied().collect();
964 /// assert_eq!(topo, [0, 2, 1, 3]);
965 /// assert_eq!(r.dist[3], Some(2));
966 /// // `topological_sorting` finds another valid order (Kahn's algorithm).
967 /// assert_eq!(dag.topological_sorting(NeighborMode::Out)?, [0, 1, 2, 3]);
968 /// # Ok::<(), igraph::Error>(())
969 /// ```
970 pub fn dfs(&self, root: VertexId, options: &DfsOptions) -> Result<DfsResult> {
971 self.dfs_impl(root, options, (None, None), ptr::null_mut())
972 }
973
974 /// Depth-first search calling a visitor closure on discovery and
975 /// completion of every vertex.
976 ///
977 /// Same traversal as [`Graph::dfs`]; in addition `visitor` receives a
978 /// [`DfsEvent::Discover`] when a vertex is first reached and a
979 /// [`DfsEvent::Finish`] when its whole subtree has been explored (the
980 /// two C callbacks `in_callback` and `out_callback`, merged in a single
981 /// closure so that they can share mutable state). Both events carry the
982 /// depth of the vertex in its DFS tree.
983 ///
984 /// Return [`ControlFlow::Break`] to stop the search: the call still
985 /// returns `Ok` with the partial results and
986 /// [`stopped`](DfsResult::stopped) set. If `visitor` panics, the search
987 /// is aborted and the panic resumes in the caller.
988 ///
989 /// Binds [`igraph_dfs`](https://igraph.org/c/html/latest/igraph-Visitors.html#igraph_dfs)
990 /// with two [`igraph_dfshandler_t`](https://igraph.org/c/html/latest/igraph-Visitors.html#igraph_dfshandler_t)
991 /// callbacks.
992 ///
993 /// See also [`Graph::bfs_with`] for the breadth-first counterpart.
994 ///
995 /// # Errors
996 ///
997 /// Same as [`Graph::dfs`].
998 ///
999 /// # Examples
1000 ///
1001 /// Print a tree as an indented outline, using the discovery events:
1002 ///
1003 /// ```
1004 /// use igraph::prelude::*;
1005 /// use igraph::visitor::{DfsEvent, DfsOptions};
1006 /// use std::ops::ControlFlow;
1007 ///
1008 /// let t = Graph::from_edges(&[(0, 1), (0, 2), (1, 3)], 4, false)?;
1009 /// let mut outline = String::new();
1010 /// t.dfs_with(0, &DfsOptions::default(), |e| {
1011 /// if let DfsEvent::Discover { vid, dist } = e {
1012 /// outline += &format!("{}{vid}\n", " ".repeat(dist));
1013 /// }
1014 /// ControlFlow::Continue(())
1015 /// })?;
1016 /// assert_eq!(outline, "0\n 1\n 3\n 2\n");
1017 /// # Ok::<(), igraph::Error>(())
1018 /// ```
1019 pub fn dfs_with<F>(&self, root: VertexId, options: &DfsOptions, visitor: F) -> Result<DfsResult>
1020 where
1021 F: FnMut(DfsEvent) -> ControlFlow<()>,
1022 {
1023 let mut state = DfsState {
1024 f: visitor,
1025 depth: 0,
1026 stopped: false,
1027 };
1028 let extra = &mut state as *mut DfsState<F> as *mut c_void;
1029 let callbacks = (
1030 Some(dfs_in_trampoline::<F> as _),
1031 Some(dfs_out_trampoline::<F> as _),
1032 );
1033 let mut result = self.dfs_impl(root, options, callbacks, extra)?;
1034 result.stopped = state.stopped;
1035 Ok(result)
1036 }
1037}