Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 26 additions & 6 deletions src/directed/astar.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Compute a shortest path (or all shorted paths) using the [A* search
//! algorithm](https://en.wikipedia.org/wiki/A*_search_algorithm).

use crate::add_costs;
use indexmap::map::Entry::{Occupied, Vacant};
use num_traits::Zero;
use std::cmp::Ordering;
Expand Down Expand Up @@ -30,6 +31,13 @@ use crate::FxIndexMap;
///
/// The returned path comprises both the start and end node.
///
/// # Panics
///
/// This function panics if the cost of a path, or the sum of a path cost and a heuristic
/// estimate, does not fit into `C`. Silently returning a wrapped, and therefore wrong, cost
/// would be worse than failing loudly. If your costs can come close to the limits of the
/// type, use a wider type, or a wrapper type whose addition saturates.
///
/// # Example
///
/// We will search the shortest path on a chess board to go from (1, 1) to (4, 6) doing only knight
Expand Down Expand Up @@ -77,7 +85,6 @@ use crate::FxIndexMap;
/// |&p| p == GOAL);
/// assert_eq!(result.expect("no path found").1, 4);
/// ```
#[expect(clippy::missing_panics_doc)]
pub fn astar<N, C, FN, IN, FH, FS>(
start: &N,
mut successors: FN,
Expand Down Expand Up @@ -116,7 +123,7 @@ where
successors(node)
};
for (successor, move_cost) in successors {
let new_cost = cost + move_cost;
let new_cost = add_costs(cost, move_cost);
let h; // heuristic(&successor)
let n; // index for successor
match parents.entry(successor) {
Expand All @@ -137,7 +144,7 @@ where
}

to_see.push(SmallestCostHolder {
estimated_cost: new_cost + h,
estimated_cost: add_costs(new_cost, h),
cost: new_cost,
index: n,
});
Expand Down Expand Up @@ -169,7 +176,13 @@ where
///
/// Each path comprises both the start and an end node. Note that while every path shares the same
/// start node, different paths may have different end nodes.
#[expect(clippy::missing_panics_doc)]
///
/// # Panics
///
/// This function panics if the cost of a path, or the sum of a path cost and a heuristic
/// estimate, does not fit into `C`. Silently returning a wrapped, and therefore wrong, cost
/// would be worse than failing loudly. If your costs can come close to the limits of the
/// type, use a wider type, or a wrapper type whose addition saturates.
pub fn astar_bag<N, C, FN, IN, FH, FS>(
start: &N,
mut successors: FN,
Expand Down Expand Up @@ -223,7 +236,7 @@ where
successors(node)
};
for (successor, move_cost) in successors {
let new_cost = cost + move_cost;
let new_cost = add_costs(cost, move_cost);
let h; // heuristic(&successor)
let n; // index for successor
match parents.entry(successor) {
Expand Down Expand Up @@ -255,7 +268,7 @@ where
}

to_see.push(SmallestCostHolder {
estimated_cost: new_cost + h,
estimated_cost: add_costs(new_cost, h),
cost: new_cost,
index: n,
});
Expand Down Expand Up @@ -289,6 +302,13 @@ where
/// ### Warning
///
/// The number of results with the same value might be very large in some graphs. Use with caution.
///
/// # Panics
///
/// This function panics if the cost of a path, or the sum of a path cost and a heuristic
/// estimate, does not fit into `C`. Silently returning a wrapped, and therefore wrong, cost
/// would be worse than failing loudly. If your costs can come close to the limits of the
/// type, use a wider type, or a wrapper type whose addition saturates.
pub fn astar_bag_collect<N, C, FN, IN, FH, FS>(
start: &N,
successors: FN,
Expand Down
48 changes: 41 additions & 7 deletions src/directed/dijkstra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

use super::reverse_path;
use crate::FxIndexMap;
use crate::add_costs;
use indexmap::map::Entry::{Occupied, Vacant};
use num_traits::Zero;
use std::cmp::Ordering;
Expand All @@ -26,6 +27,13 @@ use std::hash::Hash;
///
/// The returned path comprises both the start and end node.
///
/// # Panics
///
/// This function panics if the cost of a path does not fit into `C`. Silently returning a
/// wrapped, and therefore wrong, cost would be worse than failing loudly. If your costs can
/// come close to the limits of the type, use a wider type, or a wrapper type whose addition
/// saturates.
///
/// # Example
///
/// We will search the shortest path on a chess board to go from (1, 1) to (4, 6) doing only knight
Expand Down Expand Up @@ -129,6 +137,13 @@ where
///
/// The returned path comprises both the start and end node.
///
/// # Panics
///
/// This function panics if the cost of a path does not fit into `C`. Silently returning a
/// wrapped, and therefore wrong, cost would be worse than failing loudly. If your costs can
/// come close to the limits of the type, use a wider type, or a wrapper type whose addition
/// saturates.
///
/// # Example
///
/// We search the shortest path on a chess board to go from (1, 1) to (4, 6) doing only knight
Expand All @@ -147,7 +162,6 @@ where
/// let result = dijkstra_bidirectional(&(1, 1), &(4, 6), neighbours, neighbours);
/// assert_eq!(result.expect("no path found").1, 4);
/// ```
#[expect(clippy::missing_panics_doc)]
pub fn dijkstra_bidirectional<N, C, FS, IS, FP, IP>(
start: &N,
end: &N,
Expand Down Expand Up @@ -200,7 +214,7 @@ where
};
// Any path the two searches have not joined up yet costs at least as much as the sum of
// the two frontier costs, so once that reaches the best known path nothing better is left.
if best.is_some_and(|(cost, ..)| forward_min.cost + backward_min.cost >= cost) {
if best.is_some_and(|(cost, ..)| add_costs(forward_min.cost, backward_min.cost) >= cost) {
break;
}
expand_bidirectional(
Expand Down Expand Up @@ -283,7 +297,7 @@ fn expand_bidirectional<N, C, FN, IN>(
neighbours(node)
};
for (neighbour, move_cost) in neighbours {
let new_cost = cost + move_cost;
let new_cost = add_costs(cost, move_cost);
let n;
match parents.entry(neighbour) {
Vacant(e) => {
Expand All @@ -307,7 +321,7 @@ fn expand_bidirectional<N, C, FN, IN>(
// complete path; keep it if it is the cheapest one seen so far.
let neighbour = parents.get_index(n).unwrap().0;
if let Some((opposite_index, _, &(_, opposite_cost))) = opposite.get_full(neighbour) {
let total = new_cost + opposite_cost;
let total = add_costs(new_cost, opposite_cost);
if best.is_none_or(|(current, ..)| total < current) {
*best = Some(if is_forward {
(total, n, opposite_index)
Expand All @@ -334,6 +348,13 @@ fn expand_bidirectional<N, C, FN, IN>(
/// The [`build_path`] function can be used to build a full path from the starting point to one
/// of the reachable targets.
///
/// # Panics
///
/// This function panics if the cost of a path does not fit into `C`. Silently returning a
/// wrapped, and therefore wrong, cost would be worse than failing loudly. If your costs can
/// come close to the limits of the type, use a wider type, or a wrapper type whose addition
/// saturates.
///
/// # Example
///
/// We use a graph of integer nodes from 1 to 9, each node leading to its double and the value
Expand Down Expand Up @@ -383,7 +404,13 @@ where
///
/// The [`build_path`] function can be used to build a full path from the starting point to one
/// of the reachable targets.
#[expect(clippy::missing_panics_doc)]
///
/// # Panics
///
/// This function panics if the cost of a path does not fit into `C`. Silently returning a
/// wrapped, and therefore wrong, cost would be worse than failing loudly. If your costs can
/// come close to the limits of the type, use a wider type, or a wrapper type whose addition
/// saturates.
pub fn dijkstra_partial<N, C, FN, IN, FS>(
start: &N,
mut successors: FN,
Expand Down Expand Up @@ -443,7 +470,7 @@ where
successors(node)
};
for (successor, move_cost) in successors {
let new_cost = cost + move_cost;
let new_cost = add_costs(cost, move_cost);
let n;
match parents.entry(successor) {
Vacant(e) => {
Expand Down Expand Up @@ -585,7 +612,7 @@ where
(self.successors)(node)
};
for (successor, move_cost) in successors {
let new_cost = cost + move_cost;
let new_cost = add_costs(cost, move_cost);
let n;
match self.parents.entry(successor) {
Vacant(e) => {
Expand Down Expand Up @@ -620,6 +647,13 @@ where
///
/// The `successors` function receives the current node, and returns
/// an iterator of successors associated with their move cost.
///
/// # Panics
///
/// This function panics if the cost of a path does not fit into `C`. Silently returning a
/// wrapped, and therefore wrong, cost would be worse than failing loudly. If your costs can
/// come close to the limits of the type, use a wider type, or a wrapper type whose addition
/// saturates.
pub fn dijkstra_reach<N, C, FN, IN>(start: &N, successors: FN) -> DijkstraReachable<N, C, FN>
where
N: Eq + Hash + Clone,
Expand Down
13 changes: 10 additions & 3 deletions src/directed/fringe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

use super::reverse_path;
use crate::FxIndexMap;
use crate::add_costs;
use indexmap::map::Entry::{Occupied, Vacant};
use num_traits::{Bounded, Zero};
use std::collections::VecDeque;
Expand All @@ -28,6 +29,13 @@ use std::mem;
///
/// The returned path comprises both the start and end node.
///
/// # Panics
///
/// This function panics if the cost of a path, or the sum of a path cost and a heuristic
/// estimate, does not fit into `C`. Silently returning a wrapped, and therefore wrong, cost
/// would be worse than failing loudly. If your costs can come close to the limits of the
/// type, use a wider type, or a wrapper type whose addition saturates.
///
/// # Example
///
/// We will search the shortest path on a chess board to go from (1, 1) to (4, 6) doing only knight
Expand Down Expand Up @@ -77,7 +85,6 @@ use std::mem;
/// |&p| p == GOAL);
/// assert_eq!(result.expect("no path found").1, 4);
/// ```
#[expect(clippy::missing_panics_doc)]
pub fn fringe<N, C, FN, IN, FH, FS>(
start: &N,
mut successors: FN,
Expand Down Expand Up @@ -117,7 +124,7 @@ where
}
let (g, successors) = {
let (node, &(_, g)) = parents.get_index(i).unwrap(); // Cannot fail
let f = g + heuristic(node);
let f = add_costs(g, heuristic(node));
if f > flimit {
if f < fmin {
fmin = f;
Expand All @@ -132,7 +139,7 @@ where
(g, successors(node))
};
for (successor, cost) in successors {
let g_successor = g + cost;
let g_successor = add_costs(g, cost);
let n; // index for successor
match parents.entry(successor) {
Vacant(e) => {
Expand Down
21 changes: 18 additions & 3 deletions src/directed/idastar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//! algorithm](https://en.wikipedia.org/wiki/Iterative_deepening_A*).

use crate::FxIndexSet;
use crate::add_costs;
use num_traits::Zero;
use std::{hash::Hash, ops::ControlFlow};

Expand All @@ -24,6 +25,13 @@ use std::{hash::Hash, ops::ControlFlow};
///
/// The returned path comprises both the start and end node.
///
/// # Panics
///
/// This function panics if the cost of a path, or the sum of a path cost and a heuristic
/// estimate, does not fit into `C`. Silently returning a wrapped, and therefore wrong, cost
/// would be worse than failing loudly. If your costs can come close to the limits of the
/// type, use a wider type, or a wrapper type whose addition saturates.
///
/// # Example
///
/// We will search the shortest path on a chess board to go from (1, 1) to (4, 6) doing only knight
Expand Down Expand Up @@ -124,7 +132,7 @@ where
{
let neighbs = {
let start = &path[path.len() - 1];
let f = cost + heuristic(start);
let f = add_costs(cost, heuristic(start));
if f > bound {
return ControlFlow::Continue(Some(f));
}
Expand All @@ -136,7 +144,7 @@ where
.filter_map(|(n, c)| {
(!path.contains(&n)).then(|| {
let h = heuristic(&n);
(n, c, c + h)
(n, c, add_costs(c, h))
})
})
.collect::<Vec<_>>();
Expand All @@ -146,7 +154,14 @@ where
let mut min = None;
for (node, extra, _) in neighbs {
let (idx, _) = path.insert_full(node);
match search(path, cost + extra, bound, successors, heuristic, success)? {
match search(
path,
add_costs(cost, extra),
bound,
successors,
heuristic,
success,
)? {
Some(m) if min.is_none_or(|n| n >= m) => min = Some(m),
_ => (),
}
Expand Down
10 changes: 9 additions & 1 deletion src/directed/yen.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Compute k-shortest paths using [Yen's search
//! algorithm](https://en.wikipedia.org/wiki/Yen%27s_algorithm).
use crate::add_costs;
use num_traits::Zero;
use rustc_hash::FxHashSet;
use std::cmp::Ordering;
Expand Down Expand Up @@ -61,6 +62,13 @@ where
/// starting with the lowest cost. If there exist less paths than requested, only the existing
/// ones (if any) are returned.
///
/// # Panics
///
/// This function panics if the cost of a path does not fit into `C`. Silently returning a
/// wrapped, and therefore wrong, cost would be worse than failing loudly. If your costs can
/// come close to the limits of the type, use a wider type, or a wrapper type whose addition
/// saturates.
///
/// # Example
/// We will search the 3 shortest paths from node C to node H. See
/// <https://en.wikipedia.org/wiki/Yen's_algorithm#Example> for a visualization.
Expand Down Expand Up @@ -178,7 +186,7 @@ where
// Build a min-heap
k_routes.push(Reverse(Path {
nodes,
cost: root_costs[i] + spur_cost,
cost: add_costs(root_costs[i], spur_cost),
spur_index: i,
}));
}
Expand Down
Loading
Loading