weavatrix_graph/traversal_cache/
walk.rs1use super::TraversalCache;
2use crate::{Direction, NodeIndex, Vec};
3use alloc::collections::VecDeque;
4
5#[derive(Debug, Clone)]
7pub struct TraversalCacheWorkspace {
8 pub(super) marks: Vec<u32>,
9 predecessor: Vec<Option<NodeIndex>>,
10 epoch: u32,
11 queue: VecDeque<NodeIndex>,
12 stack: Vec<NodeIndex>,
13 scratch: Vec<NodeIndex>,
14 pub(super) visited: Vec<NodeIndex>,
15}
16
17impl TraversalCacheWorkspace {
18 #[must_use]
19 pub const fn new() -> Self {
20 Self {
21 marks: Vec::new(),
22 predecessor: Vec::new(),
23 epoch: 0,
24 queue: VecDeque::new(),
25 stack: Vec::new(),
26 scratch: Vec::new(),
27 visited: Vec::new(),
28 }
29 }
30
31 pub(super) fn begin(&mut self, node_count: usize) {
32 if self.marks.len() < node_count {
33 self.marks.resize(node_count, 0);
34 }
35 self.predecessor.resize(node_count, None);
36 self.epoch = self.epoch.wrapping_add(1);
37 if self.epoch == 0 {
38 self.marks.fill(0);
39 self.epoch = 1;
40 }
41 self.queue.clear();
42 self.stack.clear();
43 self.scratch.clear();
44 self.visited.clear();
45 }
46
47 #[inline]
48 pub(super) fn mark(&mut self, node: NodeIndex) -> bool {
49 let mark = &mut self.marks[node.index()];
50 if *mark == self.epoch {
51 return false;
52 }
53 *mark = self.epoch;
54 true
55 }
56}
57
58impl Default for TraversalCacheWorkspace {
59 fn default() -> Self {
60 Self::new()
61 }
62}
63
64pub struct CacheBfs<'cache, 'workspace> {
66 cache: &'cache TraversalCache,
67 workspace: &'workspace mut TraversalCacheWorkspace,
68 direction: Direction,
69}
70
71impl<'cache, 'workspace> CacheBfs<'cache, 'workspace> {
72 fn new(
73 cache: &'cache TraversalCache,
74 start: NodeIndex,
75 direction: Direction,
76 workspace: &'workspace mut TraversalCacheWorkspace,
77 ) -> Self {
78 workspace.begin(cache.node_count());
79 if cache.contains(start) && workspace.mark(start) {
80 workspace.queue.push_back(start);
81 }
82 Self {
83 cache,
84 workspace,
85 direction,
86 }
87 }
88}
89
90impl Iterator for CacheBfs<'_, '_> {
91 type Item = NodeIndex;
92
93 fn next(&mut self) -> Option<Self::Item> {
94 let node = self.workspace.queue.pop_front()?;
95 for_each_neighbor(self.cache, node, self.direction, |neighbor| {
96 if self.workspace.mark(neighbor) {
97 self.workspace.queue.push_back(neighbor);
98 }
99 });
100 Some(node)
101 }
102}
103
104pub struct CacheDfs<'cache, 'workspace> {
106 cache: &'cache TraversalCache,
107 workspace: &'workspace mut TraversalCacheWorkspace,
108 direction: Direction,
109}
110
111impl<'cache, 'workspace> CacheDfs<'cache, 'workspace> {
112 fn new(
113 cache: &'cache TraversalCache,
114 start: NodeIndex,
115 direction: Direction,
116 workspace: &'workspace mut TraversalCacheWorkspace,
117 ) -> Self {
118 workspace.begin(cache.node_count());
119 if cache.contains(start) && workspace.mark(start) {
120 workspace.stack.push(start);
121 }
122 Self {
123 cache,
124 workspace,
125 direction,
126 }
127 }
128}
129
130impl Iterator for CacheDfs<'_, '_> {
131 type Item = NodeIndex;
132
133 fn next(&mut self) -> Option<Self::Item> {
134 let node = self.workspace.stack.pop()?;
135 self.workspace.scratch.clear();
136 for_each_neighbor(self.cache, node, self.direction, |neighbor| {
137 if self.workspace.mark(neighbor) {
138 self.workspace.scratch.push(neighbor);
139 }
140 });
141 while let Some(neighbor) = self.workspace.scratch.pop() {
142 self.workspace.stack.push(neighbor);
143 }
144 Some(node)
145 }
146}
147
148impl TraversalCache {
149 #[must_use]
150 pub fn bfs_iter<'cache, 'workspace>(
151 &'cache self,
152 start: NodeIndex,
153 direction: Direction,
154 workspace: &'workspace mut TraversalCacheWorkspace,
155 ) -> CacheBfs<'cache, 'workspace> {
156 CacheBfs::new(self, start, direction, workspace)
157 }
158
159 #[must_use]
160 pub fn dfs_iter<'cache, 'workspace>(
161 &'cache self,
162 start: NodeIndex,
163 direction: Direction,
164 workspace: &'workspace mut TraversalCacheWorkspace,
165 ) -> CacheDfs<'cache, 'workspace> {
166 CacheDfs::new(self, start, direction, workspace)
167 }
168
169 #[must_use]
170 pub fn bfs(&self, start: NodeIndex, direction: Direction) -> Vec<NodeIndex> {
171 self.bfs_with_workspace(start, direction, &mut TraversalCacheWorkspace::new())
172 .to_vec()
173 }
174
175 #[must_use]
176 pub fn dfs(&self, start: NodeIndex, direction: Direction) -> Vec<NodeIndex> {
177 self.dfs_iter(start, direction, &mut TraversalCacheWorkspace::new())
178 .collect()
179 }
180
181 #[must_use]
182 pub fn reachable(&self, source: NodeIndex, target: NodeIndex, direction: Direction) -> bool {
183 self.contains(target)
184 && self
185 .bfs_iter(source, direction, &mut TraversalCacheWorkspace::new())
186 .any(|node| node == target)
187 }
188
189 #[must_use]
190 pub fn shortest_path(
191 &self,
192 source: NodeIndex,
193 target: NodeIndex,
194 direction: Direction,
195 workspace: &mut TraversalCacheWorkspace,
196 ) -> Option<Vec<NodeIndex>> {
197 if !self.contains(source) || !self.contains(target) {
198 return None;
199 }
200 workspace.begin(self.node_count());
201 workspace.mark(source);
202 workspace.queue.push_back(source);
203 while let Some(node) = workspace.queue.pop_front() {
204 if node == target {
205 return reconstruct(source, target, &workspace.predecessor);
206 }
207 for_each_neighbor(self, node, direction, |neighbor| {
208 if workspace.mark(neighbor) {
209 workspace.predecessor[neighbor.index()] = Some(node);
210 workspace.queue.push_back(neighbor);
211 }
212 });
213 }
214 None
215 }
216}
217
218pub(super) fn for_each_neighbor(
219 cache: &TraversalCache,
220 node: NodeIndex,
221 direction: Direction,
222 mut visit: impl FnMut(NodeIndex),
223) {
224 if matches!(direction, Direction::Outgoing | Direction::Both) {
225 cache.for_each_outgoing(node, &mut visit);
226 }
227 if matches!(direction, Direction::Incoming | Direction::Both) {
228 cache.for_each_incoming(node, visit);
229 }
230}
231
232fn reconstruct(
233 source: NodeIndex,
234 target: NodeIndex,
235 predecessor: &[Option<NodeIndex>],
236) -> Option<Vec<NodeIndex>> {
237 let mut path = vec![target];
238 let mut cursor = target;
239 while cursor != source {
240 if path.len() > predecessor.len() {
241 return None;
242 }
243 cursor = predecessor.get(cursor.index()).copied().flatten()?;
244 path.push(cursor);
245 }
246 path.reverse();
247 Some(path)
248}