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::{Occupied, Vacant};
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/// DIJKSTRA(G, source, weight)
98/// for each vertex u in V
99/// d[u] := infinity
100/// p[u] := u
101/// end for
102/// d[source] := 0
103/// INSERT(Q, source)
104/// while (Q != Ø)
105/// u := EXTRACT-MIN(Q) discover vertex u
106/// for each vertex v in Adj[u] examine edge (u,v)
107/// if (weight[(u,v)] + d[u] < d[v]) edge (u,v) relaxed
108/// d[v] := weight[(u,v)] + d[u]
109/// p[v] := u
110/// DECREASE-KEY(Q, v)
111/// else edge (u,v) not relaxed
112/// ...
113/// if (d[v] was originally infinity)
114/// INSERT(Q, v)
115/// end for finish vertex u
116/// end while
117/// ```
118///
119/// # Example returning [`Control`](petgraph::visit::Control).
120///
121/// Find the shortest path from vertex 0 to 5, and exit the visit as soon as
122/// we reach the goal vertex.
123///
124/// ```
125/// use rustworkx_core::petgraph::prelude::*;
126/// use rustworkx_core::petgraph::graph::node_index as n;
127/// use rustworkx_core::petgraph::visit::Control;
128///
129/// use rustworkx_core::traversal::{DijkstraEvent, dijkstra_search};
130///
131/// let gr: Graph<(), ()> = Graph::from_edges(&[
132/// (0, 1), (0, 2), (0, 3), (0, 4),
133/// (1, 3),
134/// (2, 3), (2, 4),
135/// (4, 5),
136/// ]);
137///
138/// // record each predecessor, mapping node → node
139/// let mut predecessor = vec![NodeIndex::end(); gr.node_count()];
140/// let start = n(0);
141/// let goal = n(5);
142/// dijkstra_search(
143/// &gr,
144/// Some(start),
145/// |edge| -> Result<usize, ()> {
146/// Ok(1)
147/// },
148/// |event| {
149/// match event {
150/// DijkstraEvent::Discover(v, _) => {
151/// if v == goal {
152/// return Control::Break(v);
153/// }
154/// },
155/// DijkstraEvent::EdgeRelaxed(u, v, _) => {
156/// predecessor[v.index()] = u;
157/// },
158/// _ => {}
159/// };
160///
161/// Control::Continue
162/// },
163/// ).unwrap();
164///
165/// let mut next = goal;
166/// let mut path = vec![next];
167/// while next != start {
168/// let pred = predecessor[next.index()];
169/// path.push(pred);
170/// next = pred;
171/// }
172/// path.reverse();
173/// assert_eq!(&path, &[n(0), n(4), n(5)]);
174/// ```
175pub fn dijkstra_search<G, I, F, K, E, H, C>(
176 graph: G,
177 starts: I,
178 mut edge_cost: F,
179 mut visitor: H,
180) -> Result<C, E>
181where
182 G: IntoEdges + Visitable,
183 G::NodeId: Eq + Hash,
184 I: IntoIterator<Item = G::NodeId>,
185 F: FnMut(G::EdgeRef) -> Result<K, E>,
186 K: Measure + Copy,
187 H: FnMut(DijkstraEvent<G::NodeId, &G::EdgeWeight, K>) -> C,
188 C: ControlFlow,
189{
190 let visited = &mut graph.visit_map();
191
192 for start in starts {
193 // `dijkstra_visitor` returns a "signal" to either continue or exit early
194 // but it never "prunes", so we use `unreachable`.
195 try_control!(
196 dijkstra_visitor(graph, start, &mut edge_cost, &mut visitor, visited),
197 unreachable!()
198 );
199 }
200
201 Ok(C::continuing())
202}
203
204pub fn dijkstra_visitor<G, F, K, E, V, C>(
205 graph: G,
206 start: G::NodeId,
207 mut edge_cost: F,
208 mut visitor: V,
209 visited: &mut G::Map,
210) -> Result<C, E>
211where
212 G: IntoEdges + Visitable,
213 G::NodeId: Eq + Hash,
214 F: FnMut(G::EdgeRef) -> Result<K, E>,
215 K: Measure + Copy,
216 V: FnMut(DijkstraEvent<G::NodeId, &G::EdgeWeight, K>) -> C,
217 C: ControlFlow,
218{
219 if visited.is_visited(&start) {
220 return Ok(C::continuing());
221 }
222
223 let mut scores = HashMap::new();
224 let mut visit_next = BinaryHeap::new();
225 let zero_score = K::default();
226 scores.insert(start, zero_score);
227 visit_next.push(MinScored(zero_score, start));
228
229 while let Some(MinScored(node_score, node)) = visit_next.pop() {
230 if !visited.visit(node) {
231 continue;
232 }
233
234 try_control_with_result!(visitor(DijkstraEvent::Discover(node, node_score)), continue);
235
236 for edge in graph.edges(node) {
237 let next = edge.target();
238 try_control_with_result!(
239 visitor(DijkstraEvent::ExamineEdge(node, next, edge.weight())),
240 continue
241 );
242
243 if visited.is_visited(&next) {
244 continue;
245 }
246
247 let cost = edge_cost(edge)?;
248 let next_score = node_score + cost;
249 match scores.entry(next) {
250 Occupied(ent) => {
251 if next_score < *ent.get() {
252 try_control_with_result!(
253 visitor(DijkstraEvent::EdgeRelaxed(node, next, edge.weight())),
254 continue
255 );
256 *ent.into_mut() = next_score;
257 visit_next.push(MinScored(next_score, next));
258 } else {
259 try_control_with_result!(
260 visitor(DijkstraEvent::EdgeNotRelaxed(node, next, edge.weight())),
261 continue
262 );
263 }
264 }
265 Vacant(ent) => {
266 try_control_with_result!(
267 visitor(DijkstraEvent::EdgeRelaxed(node, next, edge.weight())),
268 continue
269 );
270 ent.insert(next_score);
271 visit_next.push(MinScored(next_score, next));
272 }
273 }
274 }
275
276 try_control_with_result!(
277 visitor(DijkstraEvent::Finish(node)),
278 panic!("Pruning on the `DijkstraEvent::Finish` is not supported!")
279 );
280 }
281
282 Ok(C::continuing())
283}