rustworkx_core/traversal/dijkstra_visit.rs
1// Licensed under the Apache License, Version 2.0 (the "License"); you may
2// not use this file except in compliance with the License. You may obtain
3// a copy of the License at
4//
5// http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10// License for the specific language governing permissions and limitations
11// under the License.
12
13// This module was originally copied and forked from the upstream petgraph
14// repository, specifically:
15// https://github.com/petgraph/petgraph/blob/0.5.1/src/dijkstra.rs
16// this was necessary to modify the error handling to allow python callables
17// to be use for the input functions for edge_cost and return any exceptions
18// raised in Python instead of panicking
19
20use std::collections::BinaryHeap;
21use std::hash::Hash;
22
23use hashbrown::hash_map::Entry;
24use hashbrown::HashMap;
25
26use petgraph::algo::Measure;
27use petgraph::visit::{ControlFlow, EdgeRef, IntoEdges, VisitMap, Visitable};
28
29use crate::min_scored::MinScored;
30
31use super::try_control;
32
33macro_rules! try_control_with_result {
34 ($e:expr, $p:stmt) => {
35 try_control_with_result!($e, $p, ());
36 };
37 ($e:expr, $p:stmt, $q:stmt) => {
38 match $e {
39 x => {
40 if x.should_break() {
41 return Ok(x);
42 } else if x.should_prune() {
43 $p
44 } else {
45 $q
46 }
47 }
48 }
49 };
50}
51
52/// A dijkstra search visitor event.
53#[derive(Copy, Clone, Debug)]
54pub enum DijkstraEvent<N, E, K> {
55 /// This is invoked when a vertex is encountered for the first time and
56 /// it's popped from the queue. Together with the node, we report the optimal
57 /// distance of the node.
58 Discover(N, K),
59 /// This is invoked on every out-edge of each vertex after it is discovered.
60 ExamineEdge(N, N, E),
61 /// Upon examination, if the distance of the target of the edge is reduced, this event is emitted.
62 EdgeRelaxed(N, N, E),
63 /// Upon examination, if the edge is not relaxed, this event is emitted.
64 EdgeNotRelaxed(N, N, E),
65 /// All edges from a node have been reported.
66 Finish(N),
67}
68
69/// Dijkstra traversal of a graph.
70///
71/// Starting points are the nodes in the iterator `starts` (specify just one
72/// start vertex *x* by using `Some(x)`).
73///
74/// The traversal emits discovery and finish events for each reachable vertex,
75/// and edge classification of each reachable edge. `visitor` is called for each
76/// event, see [`DijkstraEvent`] for possible values.
77///
78/// The return value should implement the trait [`ControlFlow`], and can be used to change
79/// the control flow of the search.
80///
81/// [`Control`](petgraph::visit::Control) Implements [`ControlFlow`] such that `Control::Continue` resumes the search.
82/// `Control::Break` will stop the visit early, returning the contained value.
83/// `Control::Prune` will stop traversing any additional edges from the current
84/// node and proceed immediately to the `Finish` event.
85///
86/// There are implementations of [`ControlFlow`] for `()`, and [`Result<C, E>`] where
87/// `C: ControlFlow`. The implementation for `()` will continue until finished.
88/// For [`Result`], upon encountering an `E` it will break, otherwise acting the same as `C`.
89///
90/// ***Panics** if you attempt to prune a node from its `Finish` event.
91///
92/// The pseudo-code for the Dijkstra algorithm is listed below, with the annotated
93/// event points, for which the given visitor object will be called with the
94/// appropriate method.
95///
96/// ```norust
97/// // G - graph, s - single source node, weight - edge cost function
98/// DIJKSTRA(G, s, weight)
99/// let score be empty mapping
100/// let visited be empty set
101/// let Q be a priority queue
102/// score[s] := DEFAULT_COST
103/// PUSH(Q, (score[s], s)) // only score determines the priority
104/// while Q is not empty
105/// cost, u := POP-MIN(Q)
106/// if u in visited
107/// continue
108/// PUT(visited, u) // event: Discover(u, cost)
109/// for each _, v, w in OutEdges(G, u) // v - target vertex, w - edge weight
110/// ... // event: ExamineEdge(u, v, w)
111/// if v in visited
112/// continue
113/// next_cost = cost + weight(w)
114/// if {(v is key in score)
115/// and (score[v] <= next_cost)} // event: EdgeNotRelaxed(u, v, w)
116/// ...
117/// else: // v not scored or scored higher
118/// score[v] = next_cost // event: EdgeRelaxed(u, v, w)
119/// PUSH(Q, (next_cost, v))
120/// end for // event: Finish(u)
121/// end while
122/// ```
123///
124/// # Example returning [`Control`](petgraph::visit::Control).
125///
126/// Find the shortest path from vertex 0 to 5, and exit the visit as soon as
127/// we reach the goal vertex.
128///
129/// ```
130/// use rustworkx_core::petgraph::prelude::*;
131/// use rustworkx_core::petgraph::graph::node_index as n;
132/// use rustworkx_core::petgraph::visit::Control;
133///
134/// use rustworkx_core::traversal::{DijkstraEvent, dijkstra_search};
135///
136/// let gr: Graph<(), ()> = Graph::from_edges(&[
137/// (0, 1), (0, 2), (0, 3), (0, 4),
138/// (1, 3),
139/// (2, 3), (2, 4),
140/// (4, 5),
141/// ]);
142///
143/// // record each predecessor, mapping node → node
144/// let mut predecessor = vec![NodeIndex::end(); gr.node_count()];
145/// let start = n(0);
146/// let goal = n(5);
147/// dijkstra_search(
148/// &gr,
149/// Some(start),
150/// |edge| -> Result<usize, ()> {
151/// Ok(1)
152/// },
153/// |event| {
154/// match event {
155/// DijkstraEvent::Discover(v, _) => {
156/// if v == goal {
157/// return Control::Break(v);
158/// }
159/// },
160/// DijkstraEvent::EdgeRelaxed(u, v, _) => {
161/// predecessor[v.index()] = u;
162/// },
163/// _ => {}
164/// };
165///
166/// Control::Continue
167/// },
168/// ).unwrap();
169///
170/// let mut next = goal;
171/// let mut path = vec![next];
172/// while next != start {
173/// let pred = predecessor[next.index()];
174/// path.push(pred);
175/// next = pred;
176/// }
177/// path.reverse();
178/// assert_eq!(&path, &[n(0), n(4), n(5)]);
179/// ```
180pub fn dijkstra_search<G, I, F, K, E, H, C>(
181 graph: G,
182 starts: I,
183 mut edge_cost: F,
184 mut visitor: H,
185) -> Result<C, E>
186where
187 G: IntoEdges + Visitable,
188 G::NodeId: Eq + Hash,
189 I: IntoIterator<Item = G::NodeId>,
190 F: FnMut(G::EdgeRef) -> Result<K, E>,
191 K: Measure + Copy,
192 H: FnMut(DijkstraEvent<G::NodeId, &G::EdgeWeight, K>) -> C,
193 C: ControlFlow,
194{
195 let visited = &mut graph.visit_map();
196
197 for start in starts {
198 // `dijkstra_visitor` returns a "signal" to either continue or exit early
199 // but it never "prunes", so we use `unreachable`.
200 try_control!(
201 dijkstra_visitor(graph, start, &mut edge_cost, &mut visitor, visited),
202 unreachable!()
203 );
204 }
205
206 Ok(C::continuing())
207}
208
209pub fn dijkstra_visitor<G, F, K, E, V, C>(
210 graph: G,
211 start: G::NodeId,
212 mut edge_cost: F,
213 mut visitor: V,
214 visited: &mut G::Map,
215) -> Result<C, E>
216where
217 G: IntoEdges + Visitable,
218 G::NodeId: Eq + Hash,
219 F: FnMut(G::EdgeRef) -> Result<K, E>,
220 K: Measure + Copy,
221 V: FnMut(DijkstraEvent<G::NodeId, &G::EdgeWeight, K>) -> C,
222 C: ControlFlow,
223{
224 if visited.is_visited(&start) {
225 return Ok(C::continuing());
226 }
227
228 let mut scores = HashMap::new();
229 let mut visit_next = BinaryHeap::new();
230 let zero_score = K::default();
231 scores.insert(start, zero_score);
232 visit_next.push(MinScored(zero_score, start));
233
234 while let Some(MinScored(node_score, node)) = visit_next.pop() {
235 if !visited.visit(node) {
236 continue;
237 }
238
239 try_control_with_result!(visitor(DijkstraEvent::Discover(node, node_score)), continue);
240
241 for edge in graph.edges(node) {
242 let next = edge.target();
243 try_control_with_result!(
244 visitor(DijkstraEvent::ExamineEdge(node, next, edge.weight())),
245 continue
246 );
247
248 if visited.is_visited(&next) {
249 continue;
250 }
251
252 let cost = edge_cost(edge)?;
253 let next_score = node_score + cost;
254 match scores.entry(next) {
255 Entry::Occupied(ent) => {
256 if next_score < *ent.get() {
257 try_control_with_result!(
258 visitor(DijkstraEvent::EdgeRelaxed(node, next, edge.weight())),
259 continue
260 );
261 *ent.into_mut() = next_score;
262 visit_next.push(MinScored(next_score, next));
263 } else {
264 try_control_with_result!(
265 visitor(DijkstraEvent::EdgeNotRelaxed(node, next, edge.weight())),
266 continue
267 );
268 }
269 }
270 Entry::Vacant(ent) => {
271 try_control_with_result!(
272 visitor(DijkstraEvent::EdgeRelaxed(node, next, edge.weight())),
273 continue
274 );
275 ent.insert(next_score);
276 visit_next.push(MinScored(next_score, next));
277 }
278 }
279 }
280
281 try_control_with_result!(
282 visitor(DijkstraEvent::Finish(node)),
283 panic!("Pruning on the `DijkstraEvent::Finish` is not supported!")
284 );
285 }
286
287 Ok(C::continuing())
288}