Coverage Report

Created: 2026-03-12 06:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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(&current) {
191
0
            path.push(previous);
192
0
            current = previous;
193
0
        }
194
195
0
        path.reverse();
196
197
0
        path
198
0
    }
199
}