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}