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}