/rust/registry/src/index.crates.io-1949cf8c6b5b557f/petgraph-0.8.3/src/algo/astar.rs
Line | Count | Source |
1 | | use alloc::{collections::BinaryHeap, vec, vec::Vec}; |
2 | | use core::hash::Hash; |
3 | | |
4 | | use hashbrown::hash_map::{ |
5 | | Entry::{Occupied, Vacant}, |
6 | | HashMap, |
7 | | }; |
8 | | |
9 | | use crate::algo::Measure; |
10 | | use crate::scored::MinScored; |
11 | | use 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 | | /// ``` |
79 | 0 | pub fn astar<G, F, H, K, IsGoal>( |
80 | 0 | graph: G, |
81 | 0 | start: G::NodeId, |
82 | 0 | mut is_goal: IsGoal, |
83 | 0 | mut edge_cost: F, |
84 | 0 | mut estimate_cost: H, |
85 | 0 | ) -> Option<(K, Vec<G::NodeId>)> |
86 | 0 | where |
87 | 0 | G: IntoEdges + Visitable, |
88 | 0 | IsGoal: FnMut(G::NodeId) -> bool, |
89 | 0 | G::NodeId: Eq + Hash, |
90 | 0 | F: FnMut(G::EdgeRef) -> K, |
91 | 0 | H: FnMut(G::NodeId) -> K, |
92 | 0 | K: Measure + Copy, |
93 | | { |
94 | | // The Open set |
95 | 0 | let mut visit_next = BinaryHeap::new(); |
96 | | // A node -> (f, h, g) mapping |
97 | | // TODO: Derive `g` from `f` and `h`. |
98 | 0 | let mut scores = HashMap::new(); |
99 | | // The search tree |
100 | 0 | let mut path_tracker = PathTracker::<G>::new(); |
101 | | |
102 | 0 | let zero: K = K::default(); |
103 | 0 | let g: K = zero; |
104 | 0 | let h: K = estimate_cost(start); |
105 | 0 | let f: K = g + h; |
106 | 0 | scores.insert(start, (f, h, g)); |
107 | 0 | visit_next.push(MinScored((f, h, g), start)); |
108 | | |
109 | 0 | while let Some(MinScored((f, h, g), node)) = visit_next.pop() { |
110 | 0 | if is_goal(node) { |
111 | 0 | let path = path_tracker.reconstruct_path_to(node); |
112 | 0 | let (goal_f, goal_h, goal_g) = scores[&node]; |
113 | 0 | debug_assert_eq!(goal_h, zero); |
114 | 0 | debug_assert_eq!(goal_f, goal_g); |
115 | 0 | return Some((goal_f, path)); |
116 | 0 | } |
117 | | |
118 | 0 | match scores.entry(node) { |
119 | 0 | Occupied(mut entry) => { |
120 | 0 | let (_, _, old_g) = *entry.get(); |
121 | | // The node has already been expanded with a better cost. |
122 | 0 | if old_g < g { |
123 | 0 | continue; |
124 | 0 | } |
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 | 0 | entry.insert((f, h, g)); |
128 | | } |
129 | 0 | Vacant(entry) => { |
130 | 0 | entry.insert((f, h, g)); |
131 | 0 | } |
132 | | } |
133 | | |
134 | 0 | for edge in graph.edges(node) { |
135 | 0 | let neigh = edge.target(); |
136 | 0 | let neigh_g = g + edge_cost(edge); |
137 | 0 | let neigh_h = estimate_cost(neigh); |
138 | 0 | let neigh_f = neigh_g + neigh_h; |
139 | 0 | let neigh_score = (neigh_f, neigh_h, neigh_g); |
140 | | |
141 | 0 | match scores.entry(neigh) { |
142 | 0 | Occupied(mut entry) => { |
143 | 0 | let (_, _, old_neigh_g) = *entry.get(); |
144 | 0 | if neigh_g >= old_neigh_g { |
145 | | // New cost isn't better |
146 | 0 | continue; |
147 | 0 | } |
148 | 0 | entry.insert(neigh_score); |
149 | | } |
150 | 0 | Vacant(entry) => { |
151 | 0 | entry.insert(neigh_score); |
152 | 0 | } |
153 | | } |
154 | | |
155 | 0 | path_tracker.set_predecessor(neigh, node); |
156 | 0 | visit_next.push(MinScored(neigh_score, neigh)); |
157 | | } |
158 | | } |
159 | | |
160 | 0 | None |
161 | 0 | } |
162 | | |
163 | | struct PathTracker<G> |
164 | | where |
165 | | G: GraphBase, |
166 | | G::NodeId: Eq + Hash, |
167 | | { |
168 | | came_from: HashMap<G::NodeId, G::NodeId>, |
169 | | } |
170 | | |
171 | | impl<G> PathTracker<G> |
172 | | where |
173 | | G: GraphBase, |
174 | | G::NodeId: Eq + Hash, |
175 | | { |
176 | 0 | fn new() -> PathTracker<G> { |
177 | 0 | PathTracker { |
178 | 0 | came_from: HashMap::new(), |
179 | 0 | } |
180 | 0 | } |
181 | | |
182 | 0 | fn set_predecessor(&mut self, node: G::NodeId, previous: G::NodeId) { |
183 | 0 | self.came_from.insert(node, previous); |
184 | 0 | } |
185 | | |
186 | 0 | fn reconstruct_path_to(&self, last: G::NodeId) -> Vec<G::NodeId> { |
187 | 0 | let mut path = vec![last]; |
188 | | |
189 | 0 | let mut current = last; |
190 | 0 | while let Some(&previous) = self.came_from.get(¤t) { |
191 | 0 | path.push(previous); |
192 | 0 | current = previous; |
193 | 0 | } |
194 | | |
195 | 0 | path.reverse(); |
196 | | |
197 | 0 | path |
198 | 0 | } |
199 | | } |