weavatrix_graph/algo/walk/
dijkstra.rs1use super::super::measure::Measure;
2use super::super::traversal::{Direction, for_each_adjacent};
3use crate::{GraphError, IndexGraphView, Result, String, Vec};
4use alloc::collections::BinaryHeap;
5use core::cmp::Ordering;
6
7#[derive(Debug, Clone, Copy)]
8struct Scored<Cost> {
9 cost: Cost,
10 slot: usize,
11}
12
13impl<Cost: Measure> PartialEq for Scored<Cost> {
14 fn eq(&self, other: &Self) -> bool {
15 self.slot == other.slot && self.cost.compare(other.cost) == Some(Ordering::Equal)
16 }
17}
18
19impl<Cost: Measure> Eq for Scored<Cost> {}
20
21impl<Cost: Measure> PartialOrd for Scored<Cost> {
22 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
23 Some(self.cmp(other))
24 }
25}
26
27impl<Cost: Measure> Ord for Scored<Cost> {
28 fn cmp(&self, other: &Self) -> Ordering {
29 other
30 .cost
31 .compare(self.cost)
32 .unwrap_or(Ordering::Equal)
33 .then_with(|| other.slot.cmp(&self.slot))
34 }
35}
36
37#[derive(Debug, Clone)]
39pub struct DijkstraWorkspace<Node, Cost> {
40 distances: Vec<Option<Cost>>,
41 predecessors: Vec<Option<Node>>,
42 nodes: Vec<Option<Node>>,
43 queue: BinaryHeap<Scored<Cost>>,
44}
45
46impl<Node, Cost: Measure> DijkstraWorkspace<Node, Cost> {
47 #[must_use]
48 pub const fn new() -> Self {
49 Self {
50 distances: Vec::new(),
51 predecessors: Vec::new(),
52 nodes: Vec::new(),
53 queue: BinaryHeap::new(),
54 }
55 }
56
57 #[must_use]
58 pub fn distance_at(&self, slot: usize) -> Option<Cost>
59 where
60 Cost: Copy,
61 {
62 self.distances.get(slot).copied().flatten()
63 }
64
65 #[must_use]
66 pub fn predecessor_at(&self, slot: usize) -> Option<Node>
67 where
68 Node: Copy,
69 {
70 self.predecessors.get(slot).copied().flatten()
71 }
72
73 #[must_use]
74 pub fn path_to<G>(&self, source: Node, target: Node) -> Option<Vec<Node>>
75 where
76 G: IndexGraphView<Node = Node>,
77 Node: Copy + Eq,
78 Cost: Copy,
79 {
80 self.distance_at(G::node_slot(target))?;
81 let mut path = vec![target];
82 let mut cursor = target;
83 while cursor != source {
84 cursor = self.predecessor_at(G::node_slot(cursor))?;
85 path.push(cursor);
86 }
87 path.reverse();
88 Some(path)
89 }
90
91 fn begin<G>(&mut self, graph: &G, source: Node)
92 where
93 G: IndexGraphView<Node = Node>,
94 Node: Copy,
95 Cost: Measure,
96 {
97 let bound = graph.node_bound();
98 self.distances.resize(bound, None);
99 self.predecessors.resize(bound, None);
100 self.nodes.resize(bound, None);
101 self.distances.fill(None);
102 self.predecessors.fill(None);
103 self.nodes.fill(None);
104 self.queue.clear();
105 for node in graph.node_indices() {
106 self.nodes[G::node_slot(node)] = Some(node);
107 }
108 if graph.contains_node(source) {
109 let slot = G::node_slot(source);
110 self.distances[slot] = Some(Cost::zero());
111 self.queue.push(Scored {
112 cost: Cost::zero(),
113 slot,
114 });
115 }
116 }
117}
118
119impl<Node, Cost: Measure> Default for DijkstraWorkspace<Node, Cost> {
120 fn default() -> Self {
121 Self::new()
122 }
123}
124
125pub struct Dijkstra<'graph, 'workspace, G, Cost, F>
127where
128 G: IndexGraphView,
129{
130 graph: &'graph G,
131 workspace: &'workspace mut DijkstraWorkspace<G::Node, Cost>,
132 direction: Direction,
133 edge_cost: F,
134 failed: bool,
135}
136
137impl<'graph, 'workspace, G, Cost, F> Dijkstra<'graph, 'workspace, G, Cost, F>
138where
139 G: IndexGraphView,
140 Cost: Measure,
141 F: FnMut(G::Edge) -> Option<Cost>,
142{
143 #[must_use]
144 pub fn filtered(
145 graph: &'graph G,
146 source: G::Node,
147 direction: Direction,
148 workspace: &'workspace mut DijkstraWorkspace<G::Node, Cost>,
149 edge_cost: F,
150 ) -> Self {
151 workspace.begin(graph, source);
152 Self {
153 graph,
154 workspace,
155 direction,
156 edge_cost,
157 failed: false,
158 }
159 }
160
161 fn fail(&mut self, error: GraphError) -> Result<(G::Node, Cost)> {
162 self.failed = true;
163 self.workspace.queue.clear();
164 Err(error)
165 }
166}
167
168impl<G, Cost, F> Iterator for Dijkstra<'_, '_, G, Cost, F>
169where
170 G: IndexGraphView,
171 Cost: Measure,
172 F: FnMut(G::Edge) -> Option<Cost>,
173{
174 type Item = Result<(G::Node, Cost)>;
175
176 fn next(&mut self) -> Option<Self::Item> {
177 if self.failed {
178 return None;
179 }
180 while let Some(Scored { cost, slot }) = self.workspace.queue.pop() {
181 let Some(known) = self.workspace.distances.get(slot).copied().flatten() else {
182 continue;
183 };
184 if known.compare(cost) != Some(Ordering::Equal) {
185 continue;
186 }
187 let Some(node) = self.workspace.nodes.get(slot).copied().flatten() else {
188 continue;
189 };
190 let mut error = None;
191 let workspace = &mut *self.workspace;
192 for_each_adjacent(
193 self.graph,
194 node,
195 self.direction,
196 &mut |_| true,
197 |edge, neighbor| {
198 if error.is_some() {
199 return;
200 }
201 let Some(weight) = (self.edge_cost)(edge) else {
202 return;
203 };
204 if !weight.is_valid() || weight.is_negative() {
205 error = Some(invalid_weight());
206 return;
207 }
208 let Some(candidate) = cost.checked_add(weight) else {
209 error = Some(GraphError::ArithmeticOverflow {
210 operation: "generic Dijkstra path cost",
211 });
212 return;
213 };
214 let neighbor_slot = G::node_slot(neighbor);
215 let improves = workspace.distances[neighbor_slot]
216 .is_none_or(|known| candidate.compare(known) == Some(Ordering::Less));
217 if improves {
218 workspace.distances[neighbor_slot] = Some(candidate);
219 workspace.predecessors[neighbor_slot] = Some(node);
220 workspace.queue.push(Scored {
221 cost: candidate,
222 slot: neighbor_slot,
223 });
224 }
225 },
226 );
227 if let Some(error) = error {
228 return Some(self.fail(error));
229 }
230 return Some(Ok((node, cost)));
231 }
232 None
233 }
234}
235
236pub fn dijkstra_iter<'graph, 'workspace, G, Cost, F>(
237 graph: &'graph G,
238 source: G::Node,
239 workspace: &'workspace mut DijkstraWorkspace<G::Node, Cost>,
240 mut edge_cost: F,
241) -> impl Iterator<Item = Result<(G::Node, Cost)>> + 'workspace
242where
243 'graph: 'workspace,
244 G: IndexGraphView + 'graph,
245 Cost: Measure + 'workspace,
246 F: FnMut(G::Edge) -> Cost + 'workspace,
247{
248 Dijkstra::filtered(graph, source, Direction::Outgoing, workspace, move |edge| {
249 Some(edge_cost(edge))
250 })
251}
252
253#[must_use]
254pub fn dijkstra_iter_filtered<'graph, 'workspace, G, Cost, F>(
255 graph: &'graph G,
256 source: G::Node,
257 direction: Direction,
258 workspace: &'workspace mut DijkstraWorkspace<G::Node, Cost>,
259 edge_cost: F,
260) -> Dijkstra<'graph, 'workspace, G, Cost, F>
261where
262 G: IndexGraphView,
263 Cost: Measure,
264 F: FnMut(G::Edge) -> Option<Cost>,
265{
266 Dijkstra::filtered(graph, source, direction, workspace, edge_cost)
267}
268
269fn invalid_weight() -> GraphError {
270 GraphError::InvalidAlgorithmParameter {
271 algorithm: "Dijkstra",
272 parameter: "edge_cost",
273 value: String::from("must be finite and non-negative"),
274 }
275}