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}