zrx_graph/graph/
traversal.rs

1// Copyright (c) Zensical LLC <https://zensical.org>
2
3// SPDX-License-Identifier: MIT
4// Third-party contributions licensed under CLA
5
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to
8// deal in the Software without restriction, including without limitation the
9// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10// sell copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12
13// The above copyright notice and this permission notice shall be included in
14// all copies or substantial portions of the Software.
15
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22// IN THE SOFTWARE.
23
24// ----------------------------------------------------------------------------
25
26//! Topological traversal.
27
28use std::collections::VecDeque;
29
30use super::topology::Topology;
31use super::{Error, Result};
32
33// ----------------------------------------------------------------------------
34// Structs
35// ----------------------------------------------------------------------------
36
37/// Topological traversal.
38///
39/// This data type manages a topological traversal of a directed acyclic graph
40/// (DAG). It allows visiting nodes in a way that respects their dependencies,
41/// meaning that a node can only be visited after all of its dependencies have
42/// been visited. Visitable nodes can be obtained with [`Traversal::take`].
43///
44/// Note that the traversal itself doesn't know whether it's complete or not,
45/// as it only tracks visitable nodes depending on what has been reported back
46/// to [`Traversal::complete`]. This is because we also need to support partial
47/// traversals that can be resumed, which must be managed by the caller. In case
48/// a traversal starts at an intermediate node, only the nodes and dependencies
49/// reachable from this node are considered, which is necessary for implementing
50/// subgraph traversals that are self-contained, allowing for the creation of
51/// frontiers at any point in the graph.
52#[derive(Clone, Debug)]
53pub struct Traversal {
54    /// Graph topology.
55    topology: Topology,
56    /// Dependency counts.
57    dependencies: Vec<u8>,
58    /// Visitable nodes.
59    visitable: VecDeque<usize>,
60}
61
62// ----------------------------------------------------------------------------
63// Implementations
64// ----------------------------------------------------------------------------
65
66impl Traversal {
67    /// Creates a topological traversal.
68    ///
69    /// The given initial nodes are immediately marked as visitable, and thus
70    /// returned by [`Traversal::take`], so the caller must make sure they can
71    /// be processed. Note that the canonical way to create a [`Traversal`] is
72    /// to invoke the [`Graph::traverse`][] method.
73    ///
74    /// [`Graph::traverse`]: crate::graph::Graph::traverse
75    ///
76    /// # Examples
77    ///
78    /// ```
79    /// # use std::error::Error;
80    /// # fn main() -> Result<(), Box<dyn Error>> {
81    /// use zrx_graph::{Graph, Traversal};
82    ///
83    /// // Create graph builder and add nodes
84    /// let mut builder = Graph::builder();
85    /// let a = builder.add_node("a");
86    /// let b = builder.add_node("b");
87    /// let c = builder.add_node("c");
88    ///
89    /// // Create edges between nodes
90    /// builder.add_edge(a, b, 0)?;
91    /// builder.add_edge(b, c, 0)?;
92    ///
93    /// // Create graph from builder
94    /// let graph = builder.build();
95    ///
96    /// // Create topological traversal
97    /// let traversal = Traversal::new(graph.topology(), [a]);
98    /// # Ok(())
99    /// # }
100    /// ```
101    #[must_use]
102    pub fn new<I>(topology: &Topology, initial: I) -> Self
103    where
104        I: IntoIterator<Item = usize>,
105    {
106        let visitable = initial.into_iter().collect::<VecDeque<_>>();
107
108        // Obtain incoming edges and distance matrix
109        let incoming = topology.incoming();
110        let distance = topology.distance();
111
112        // When doing a topological traversal, we only visit a node once all of
113        // its dependencies have been visited. This means that we need to track
114        // the number of dependencies for each node, which is the number of
115        // incoming edges for that node.
116        let mut dependencies = incoming.degrees().to_vec();
117        for node in incoming {
118            // We must adjust the dependency count of each node's dependents
119            // if it's not reachable from any of the initial nodes
120            if !visitable.iter().any(|&n| distance[n][node] != u8::MAX) {
121                // Obtain adjacency list of outgoing edges, and decrement the
122                // number of unresolved dependencies for each dependent by one
123                let outgoing = topology.outgoing();
124                for &dependent in &outgoing[node] {
125                    dependencies[dependent] -= 1;
126                }
127            }
128        }
129
130        // Return traversal
131        Self {
132            topology: topology.clone(),
133            dependencies,
134            visitable,
135        }
136    }
137
138    /// Returns the next visitable node.
139    ///
140    /// # Examples
141    ///
142    /// ```
143    /// # use std::error::Error;
144    /// # fn main() -> Result<(), Box<dyn Error>> {
145    /// use zrx_graph::Graph;
146    ///
147    /// // Create graph builder and add nodes
148    /// let mut builder = Graph::builder();
149    /// let a = builder.add_node("a");
150    /// let b = builder.add_node("b");
151    /// let c = builder.add_node("c");
152    ///
153    /// // Create edges between nodes
154    /// builder.add_edge(a, b, 0)?;
155    /// builder.add_edge(b, c, 0)?;
156    ///
157    /// // Create graph from builder
158    /// let graph = builder.build();
159    ///
160    /// // Create topological traversal
161    /// let mut traversal = graph.traverse([a]);
162    /// while let Some(node) = traversal.take() {
163    ///     println!("{node:?}");
164    ///     traversal.complete(node)?;
165    /// }
166    /// # Ok(())
167    /// # }
168    /// ```
169    #[inline]
170    #[must_use]
171    pub fn take(&mut self) -> Option<usize> {
172        self.visitable.pop_front()
173    }
174
175    /// Marks the given node as visited.
176    ///
177    /// This method marks a node as visited as part of a traversal, which might
178    /// allow visiting dependent nodes when all of their dependencies have been
179    /// satisfied. After marking a node as visited, the next nodes that can be
180    /// visited can be obtained using the [`Traversal::take`] method.
181    ///
182    /// # Errors
183    ///
184    /// In case the node has already been marked as visited, [`Error::Found`]
185    /// is returned. This is likely an error in the traversal business logic.
186    ///
187    /// # Panics
188    ///
189    /// Panics if a node does not exist, as this indicates that there's a bug
190    /// in the code that creates or uses the traversal. While the [`Builder`][]
191    /// is designed to be fallible to ensure the structure is valid, methods
192    /// that operate on [`Graph`][] panic on violated invariants.
193    ///
194    /// [`Builder`]: crate::graph::Builder
195    /// [`Graph`]: crate::graph::Graph
196    ///
197    /// # Examples
198    ///
199    /// ```
200    /// # use std::error::Error;
201    /// # fn main() -> Result<(), Box<dyn Error>> {
202    /// use zrx_graph::Graph;
203    ///
204    /// // Create graph builder and add nodes
205    /// let mut builder = Graph::builder();
206    /// let a = builder.add_node("a");
207    /// let b = builder.add_node("b");
208    /// let c = builder.add_node("c");
209    ///
210    /// // Create edges between nodes
211    /// builder.add_edge(a, b, 0)?;
212    /// builder.add_edge(b, c, 0)?;
213    ///
214    /// // Create graph from builder
215    /// let graph = builder.build();
216    ///
217    /// // Create topological traversal
218    /// let mut traversal = graph.traverse([a]);
219    /// while let Some(node) = traversal.take() {
220    ///     println!("{node:?}");
221    ///     traversal.complete(node)?;
222    /// }
223    /// # Ok(())
224    /// # }
225    /// ```
226    pub fn complete(&mut self, node: usize) -> Result {
227        if self.dependencies[node] == u8::MAX {
228            return Err(Error::Found(node));
229        }
230
231        // Mark node as visited - we can just use the maximum value of `u8` as
232        // a marker, as we don't expect more than 255 dependencies for any node
233        self.dependencies[node] = u8::MAX;
234
235        // Obtain adjacency list of outgoing edges, and decrement the number
236        // of unresolved dependencies for each dependent by one. When the number
237        // of dependencies for a dependent reaches zero, it can be visited, so
238        // we add it to the queue of visitable nodes.
239        let outgoing = self.topology.outgoing();
240        for &dependent in &outgoing[node] {
241            self.dependencies[dependent] -= 1;
242
243            // We satisfied all dependencies, so the dependent can be visited
244            if self.dependencies[dependent] == 0 {
245                self.visitable.push_back(dependent);
246            }
247        }
248
249        // No errors occurred.
250        Ok(())
251    }
252}
253
254#[allow(clippy::must_use_candidate)]
255impl Traversal {
256    /// Returns the graph topology.
257    #[inline]
258    pub fn topology(&self) -> &Topology {
259        &self.topology
260    }
261
262    /// Returns the number of visitable nodes.
263    #[inline]
264    pub fn len(&self) -> usize {
265        self.visitable.len()
266    }
267
268    /// Returns whether there are any visitable nodes.
269    #[inline]
270    pub fn is_empty(&self) -> bool {
271        self.visitable.is_empty()
272    }
273}