petgraph/algo/astar.rs
1use alloc::{collections::BinaryHeap, vec, vec::Vec};
2use core::hash::Hash;
3
4use hashbrown::hash_map::{
5 Entry::{Occupied, Vacant},
6 HashMap,
7};
8
9use crate::algo::Measure;
10use crate::scored::MinScored;
11use crate::visit::{EdgeRef, GraphBase, IntoEdges, Visitable};
12
13/// A* shortest path algorithm.
14///
15/// Computes the shortest path from `start` to `finish`, including the total path cost.
16///
17/// `finish` is implicitly given via the `is_goal` callback, which should return `true` if the
18/// given node is the finish node.
19///
20/// The function `edge_cost` should return the cost for a particular edge. Edge costs must be
21/// non-negative.
22///
23/// The function `estimate_cost` should return the estimated cost to the finish for a particular
24/// node. For the algorithm to find the actual shortest path, it should be admissible, meaning that
25/// it should never overestimate the actual cost to get to the nearest goal node. Estimate costs
26/// must also be non-negative.
27///
28/// # Arguments
29/// * `graph`: weighted graph.
30/// * `start`: the start node.
31/// * `is_goal`: the callback defines the goal node.
32/// * `edge_cost`: closure that returns cost of a particular edge.
33/// * `estimate_cost`: closure that returns the estimated cost to the finish for particular node.
34///
35/// # Returns
36/// * `Some(K, Vec<G::NodeId>)` - the total cost and path from start to finish, if one was found.
37/// * `None` - if such a path was not found.
38///
39/// # Complexity
40/// The time complexity largely depends on the heuristic used. Feel free to contribute and provide the exact time complexity :)
41///
42/// With a trivial heuristic, the algorithm will behave like [`fn@crate::algo::dijkstra`].
43///
44/// # Example
45/// ```
46/// use petgraph::Graph;
47/// use petgraph::algo::astar;
48///
49/// let mut g = Graph::new();
50/// let a = g.add_node((0., 0.));
51/// let b = g.add_node((2., 0.));
52/// let c = g.add_node((1., 1.));
53/// let d = g.add_node((0., 2.));
54/// let e = g.add_node((3., 3.));
55/// let f = g.add_node((4., 2.));
56/// g.extend_with_edges(&[
57/// (a, b, 2),
58/// (a, d, 4),
59/// (b, c, 1),
60/// (b, f, 7),
61/// (c, e, 5),
62/// (e, f, 1),
63/// (d, e, 1),
64/// ]);
65///
66/// // Graph represented with the weight of each edge
67/// // Edges with '*' are part of the optimal path.
68/// //
69/// // 2 1
70/// // a ----- b ----- c
71/// // | 4* | 7 |
72/// // d f | 5
73/// // | 1* | 1* |
74/// // \------ e ------/
75///
76/// let path = astar(&g, a, |finish| finish == f, |e| *e.weight(), |_| 0);
77/// assert_eq!(path, Some((6, vec![a, d, e, f])));
78/// ```
79pub fn astar<G, F, H, K, IsGoal>(
80 graph: G,
81 start: G::NodeId,
82 mut is_goal: IsGoal,
83 mut edge_cost: F,
84 mut estimate_cost: H,
85) -> Option<(K, Vec<G::NodeId>)>
86where
87 G: IntoEdges + Visitable,
88 IsGoal: FnMut(G::NodeId) -> bool,
89 G::NodeId: Eq + Hash,
90 F: FnMut(G::EdgeRef) -> K,
91 H: FnMut(G::NodeId) -> K,
92 K: Measure + Copy,
93{
94 // The Open set
95 let mut visit_next = BinaryHeap::new();
96 // A node -> (f, h, g) mapping
97 // TODO: Derive `g` from `f` and `h`.
98 let mut scores = HashMap::new();
99 // The search tree
100 let mut path_tracker = PathTracker::<G>::new();
101
102 let zero: K = K::default();
103 let g: K = zero;
104 let h: K = estimate_cost(start);
105 let f: K = g + h;
106 scores.insert(start, (f, h, g));
107 visit_next.push(MinScored((f, h, g), start));
108
109 while let Some(MinScored((f, h, g), node)) = visit_next.pop() {
110 if is_goal(node) {
111 let path = path_tracker.reconstruct_path_to(node);
112 let (goal_f, goal_h, goal_g) = scores[&node];
113 debug_assert_eq!(goal_h, zero);
114 debug_assert_eq!(goal_f, goal_g);
115 return Some((goal_f, path));
116 }
117
118 match scores.entry(node) {
119 Occupied(mut entry) => {
120 let (_, _, old_g) = *entry.get();
121 // The node has already been expanded with a better cost.
122 if old_g < g {
123 continue;
124 }
125 // NOTE: Because there's no closed set, we don't know if we expanded this node.
126 // if old_g = g we may be re-expanding this node, but won't insert new neigbours.
127 entry.insert((f, h, g));
128 }
129 Vacant(entry) => {
130 entry.insert((f, h, g));
131 }
132 }
133
134 for edge in graph.edges(node) {
135 let neigh = edge.target();
136 let neigh_g = g + edge_cost(edge);
137 let neigh_h = estimate_cost(neigh);
138 let neigh_f = neigh_g + neigh_h;
139 let neigh_score = (neigh_f, neigh_h, neigh_g);
140
141 match scores.entry(neigh) {
142 Occupied(mut entry) => {
143 let (_, _, old_neigh_g) = *entry.get();
144 if neigh_g >= old_neigh_g {
145 // New cost isn't better
146 continue;
147 }
148 entry.insert(neigh_score);
149 }
150 Vacant(entry) => {
151 entry.insert(neigh_score);
152 }
153 }
154
155 path_tracker.set_predecessor(neigh, node);
156 visit_next.push(MinScored(neigh_score, neigh));
157 }
158 }
159
160 None
161}
162
163struct PathTracker<G>
164where
165 G: GraphBase,
166 G::NodeId: Eq + Hash,
167{
168 came_from: HashMap<G::NodeId, G::NodeId>,
169}
170
171impl<G> PathTracker<G>
172where
173 G: GraphBase,
174 G::NodeId: Eq + Hash,
175{
176 fn new() -> PathTracker<G> {
177 PathTracker {
178 came_from: HashMap::new(),
179 }
180 }
181
182 fn set_predecessor(&mut self, node: G::NodeId, previous: G::NodeId) {
183 self.came_from.insert(node, previous);
184 }
185
186 fn reconstruct_path_to(&self, last: G::NodeId) -> Vec<G::NodeId> {
187 let mut path = vec![last];
188
189 let mut current = last;
190 while let Some(&previous) = self.came_from.get(¤t) {
191 path.push(previous);
192 current = previous;
193 }
194
195 path.reverse();
196
197 path
198 }
199}