webgraph_algo/visits/
mod.rs

1/*
2 * SPDX-FileCopyrightText: 2024 Matteo Dell'Acqua
3 * SPDX-FileCopyrightText: 2025 Sebastiano Vigna
4 *
5 * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
6 */
7
8//! Visits on graphs.
9//!
10//! Implementation of [sequential](Sequential) and [parallel][Parallel] visits
11//! depend on a type parameter `A` implementing the trait [`Event`]; they
12//! provide visit methods accepting a callback function with argument `A` and
13//! returning a `ControlFlow<E, ()>`, where `E` is a type parameter of the visit
14//! method: for example, `E` might be [`StoppedWhenDone`] when completing early,
15//! [`Interrupted`] when interrupted or [`Infallible`](std::convert::Infallible)
16//! if the visit cannot be interrupted.
17//!
18//! If a callback returns [`Break`](ControlFlow::Break), the visit will be
19//! interrupted, and the interrupt propagated to the caller of the visit method;
20//! for uninterruptible visits we suggest to use the
21//! [`no-break`](https://crates.io/crates/no-break) crate and its
22//! [`continue_value_no_break`](no_break::NoBreak::continue_value_no_break)
23//! method on the result to let type inference run smoothly.
24//!
25//! Note that an interruption does not necessarily denote an error condition
26//! (see, e.g., [`StoppedWhenDone`]).
27//!
28//! [Sequential visits](Sequential) are visits that are executed in a single
29//! thread, whereas [parallel visits](Parallel) use multiple threads. The
30//! signature of callbacks reflects this difference ([`FnMut`] for the sequential
31//! case vs. [`Fn`] + [`Sync`] for the parallel case).
32//!
33//! In case of interruption sequential visits usually return immediately to the
34//! caller, whereas in general parallel visits might need to complete part of
35//! their subtasks before returning to the caller.
36//!
37//! Additionally, implementations might accepts a filter function accepting a
38//! [`Event::FilterArgs`] that will be called when a new node is discovered. If
39//! the filter returns false, the node will be ignored, that is, not even marked
40//! as known. Note that in case of parallel visits the filter might be called
41//! multiple times on the same node (and with a different predecessor, if
42//! available) due to race conditions.
43//!
44//! All visits have also methods accepting an `init` item similarly to the
45//! [Rayon](rayon) [`map_with`](rayon::iter::ParallelIterator::map_with) method. For
46//! parallel visits, the item will be cloned.
47//!
48//! There is a blanket implementation of the [`Parallel`] trait for all types
49//! implementing the [`Sequential`] trait. This approach makes it possible to
50//! have structures that can use both sequential and parallel visits.
51//!
52//! Visit must provide a `reset` method that makes it possible to reuse the
53//! visit.
54//!
55//! # Examples
56//!
57//! There are examples of visits in
58//! [`SeqIter`](crate::visits::depth_first::SeqIter),
59//! [`ParFair`](crate::visits::breadth_first::ParFair) and
60//! [`ParLowMem`](crate::visits::breadth_first::ParLowMem).
61
62pub mod breadth_first;
63pub mod depth_first;
64
65use rayon::ThreadPool;
66use std::ops::ControlFlow;
67use thiserror::Error;
68
69#[derive(Error, Debug)]
70/// The visit was interrupted.
71#[error("The visit was interrupted")]
72pub struct Interrupted;
73
74#[derive(Error, Debug)]
75/// The result of the visit was computed without completing the visit; for
76/// example, during an acyclicity test a single arc pointing at the visit path
77/// is sufficient to compute the result.
78#[error("Stopped when done")]
79pub struct StoppedWhenDone;
80
81/// Types usable as arguments for the callbacks in visits.
82///
83/// Arguments are usually enums in which variants represent visit events
84/// (previsits, postvisits, etc.). Each variant then contains additional data
85/// related to the specific event.
86///
87/// The associated type [`Event::FilterArgs`] is the type of the arguments
88/// passed to the filter associated with the visit. It can be set to `()` if
89/// filtering is not supported
90pub trait Event {
91    /// The type passed as input to the filter.
92    type FilterArgs;
93}
94
95/// A convenience type alias for the filter arguments of an event.
96///
97/// It is useful to write match patterns using destructuring syntax.
98pub type FilterArgs<A> = <A as Event>::FilterArgs;
99
100/// A sequential visit.
101///
102/// Implementation of this trait must provide the
103/// [`visit_filtered_with`](Sequential::visit_filtered_with) method, which
104/// should perform a visit of a graph starting from a given set of nodes. Note
105/// that different visits types might interpret the set of nodes differently:
106/// for example, a [breadth-first visit](breadth_first) will interpret the set
107/// of nodes as the initial queue, whereas a [depth-first visit](depth_first)
108/// will interpret the set of nodes as a list of nodes from which to start
109/// visits.
110pub trait Sequential<A: Event> {
111    /// Visits the graph from the specified nodes with an initialization value
112    /// and a filter function.
113    ///
114    /// # Arguments
115    ///
116    /// * `roots`: The nodes to start the visit from.
117    ///
118    /// * `init`: a value the will be passed to the callback function.
119    ///
120    /// * `callback`: The callback function.
121    ///
122    /// * `filter`: The filter function.
123    fn visit_filtered_with<
124        R: IntoIterator<Item = usize>,
125        T,
126        E,
127        C: FnMut(&mut T, A) -> ControlFlow<E, ()>,
128        F: FnMut(&mut T, A::FilterArgs) -> bool,
129    >(
130        &mut self,
131        roots: R,
132        init: T,
133        callback: C,
134        filter: F,
135    ) -> ControlFlow<E, ()>;
136
137    /// Visits the graph from the specified nodes with a filter function.
138    ///
139    /// # Arguments
140    ///
141    /// * `roots`: The nodes to start the visit from.
142    ///
143    /// * `callback`: The callback function.
144    ///
145    /// * `filter`: The filter function.
146    fn visit_filtered<
147        R: IntoIterator<Item = usize>,
148        E,
149        C: FnMut(A) -> ControlFlow<E, ()>,
150        F: FnMut(A::FilterArgs) -> bool,
151    >(
152        &mut self,
153        roots: R,
154        mut callback: C,
155        mut filter: F,
156    ) -> ControlFlow<E, ()> {
157        self.visit_filtered_with(roots, (), |(), a| callback(a), |(), a| filter(a))
158    }
159
160    /// Visits the graph from the specified nodes with an initialization value.
161    ///
162    /// # Arguments
163    ///
164    /// * `roots`: The nodes to start the visit from.
165    ///
166    /// * `init`: a value the will be passed to the callback function.
167    ///
168    /// * `callback`: The callback function.
169    fn visit_with<
170        R: IntoIterator<Item = usize>,
171        T,
172        E,
173        C: FnMut(&mut T, A) -> ControlFlow<E, ()>,
174    >(
175        &mut self,
176        roots: R,
177        init: T,
178        callback: C,
179    ) -> ControlFlow<E, ()> {
180        self.visit_filtered_with(roots, init, callback, |_, _| true)
181    }
182
183    /// Visits the graph from the specified nodes.
184    ///
185    /// # Arguments
186    ///
187    /// * `roots`: The nodes to start the visit from.
188    ///
189    /// * `callback`: The callback function.
190    fn visit<R: IntoIterator<Item = usize>, E, C: FnMut(A) -> ControlFlow<E, ()>>(
191        &mut self,
192        roots: R,
193        callback: C,
194    ) -> ControlFlow<E, ()> {
195        self.visit_filtered(roots, callback, |_| true)
196    }
197
198    /// Resets the visit status, making it possible to reuse it.
199    fn reset(&mut self);
200}
201
202/// A parallel visit.
203///
204/// Implementation of this trait must provide the
205/// [`par_visit_filtered_with`](Parallel::par_visit_filtered_with) method, which
206/// should perform a parallel visit of a graph starting from a given set of
207/// nodes. Note that different visits types might interpret the set of nodes
208/// differently: for example, a [breadth-first visit](breadth_first) will
209/// interpret the set of nodes as the initial queue, whereas a [depth-first
210/// visit](depth_first) will interpret the set of nodes as a list of nodes from
211/// which to start visits.
212pub trait Parallel<A: Event> {
213    /// Visits the graph from the specified nodes with an initialization value
214    /// and a filter function.
215    ///
216    /// # Arguments
217    ///
218    /// * `roots`: The nodes to start the visit from.
219    ///
220    /// * `init`: a value the will be cloned and passed to the callback
221    ///   function.
222    ///
223    /// * `callback`: The callback function.
224    ///
225    /// * `filter`: The filter function.
226    ///
227    /// * `thread_pool`: The thread pool to use for parallel computation.
228    fn par_visit_filtered_with<
229        R: IntoIterator<Item = usize>,
230        T: Clone + Send + Sync + Sync,
231        E: Send,
232        C: Fn(&mut T, A) -> ControlFlow<E, ()> + Sync,
233        F: Fn(&mut T, A::FilterArgs) -> bool + Sync,
234    >(
235        &mut self,
236        roots: R,
237        init: T,
238        callback: C,
239        filter: F,
240        thread_pool: &ThreadPool,
241    ) -> ControlFlow<E, ()>;
242
243    /// Visits the graph from the specified nodes with a filter function.
244    ///
245    /// # Arguments
246    ///
247    /// * `roots`: The nodes to start the visit from.
248    ///
249    /// * `callback`: The callback function.
250    ///
251    /// * `filter`: The filter function.
252    ///
253    /// * `thread_pool`: The thread pool to use for parallel computation.
254    fn par_visit_filtered<
255        R: IntoIterator<Item = usize>,
256        E: Send,
257        C: Fn(A) -> ControlFlow<E, ()> + Sync,
258        F: Fn(A::FilterArgs) -> bool + Sync,
259    >(
260        &mut self,
261        roots: R,
262        callback: C,
263        filter: F,
264        thread_pool: &ThreadPool,
265    ) -> ControlFlow<E, ()> {
266        self.par_visit_filtered_with(
267            roots,
268            (),
269            |(), a| callback(a),
270            |(), a| filter(a),
271            thread_pool,
272        )
273    }
274
275    /// Visits the graph from the specified nodes with an initialization value.
276    ///
277    /// # Arguments
278    ///
279    /// * `roots`: The nodes to start the visit from.
280    ///
281    /// * `init`: a value the will be cloned and passed to the callback
282    ///   function.
283    ///
284    /// * `callback`: The callback function.
285    ///
286    /// * `thread_pool`: The thread pool to use for parallel computation.
287    fn par_visit_with<
288        R: IntoIterator<Item = usize>,
289        T: Clone + Send + Sync + Sync,
290        E: Send,
291        C: Fn(&mut T, A) -> ControlFlow<E, ()> + Sync,
292    >(
293        &mut self,
294        roots: R,
295        init: T,
296        callback: C,
297        thread_pool: &ThreadPool,
298    ) -> ControlFlow<E, ()> {
299        self.par_visit_filtered_with(roots, init, callback, |_, _| true, thread_pool)
300    }
301
302    /// Visits the graph from the specified nodes.
303    ///
304    /// # Arguments
305    ///
306    /// * `roots`: The nodes to start the visit from.
307    ///
308    /// * `callback`: The callback function.
309    ///
310    /// * `thread_pool`: The thread pool to use for parallel computation.
311    fn par_visit<R: IntoIterator<Item = usize>, E: Send, C: Fn(A) -> ControlFlow<E, ()> + Sync>(
312        &mut self,
313        roots: R,
314        callback: C,
315        thread_pool: &ThreadPool,
316    ) -> ControlFlow<E, ()> {
317        self.par_visit_filtered(roots, callback, |_| true, thread_pool)
318    }
319
320    /// Resets the visit status, making it possible to reuse it.
321    fn reset(&mut self);
322}