use super::ContainerResultExt;
use crate::containers::maps::MapTrait;
use crate::graph::{GraphWithEdgeValues, NodeIndex};
use super::AlgorithmError;
pub struct AStarConfig<OS, CF, GS> {
pub open_set: OS,
pub came_from: CF,
pub g_score: GS,
}
#[allow(clippy::too_many_arguments)]
pub fn astar<'a, G, NI, V, OS, CF, GS, H>(
graph: &G,
start: NI,
goal: NI,
heuristic: H,
mut open_set: OS,
mut came_from: CF,
mut g_score: GS,
path_buffer: &'a mut [NI],
) -> Result<Option<&'a [NI]>, AlgorithmError<NI>>
where
G: GraphWithEdgeValues<NI, V>,
NI: NodeIndex,
OS: MapTrait<NI, V>, CF: MapTrait<NI, NI>, GS: MapTrait<NI, Option<V>>, H: Fn(NI, NI) -> V, V: PartialOrd + Copy + core::ops::Add<Output = V> + Default,
AlgorithmError<NI>: From<G::Error>,
{
for node in graph.iter_nodes()? {
g_score.insert(node, None).capacity_error()?;
}
let start_g_score = V::default(); let start_f_score = start_g_score + heuristic(start, goal);
g_score
.insert(start, Some(start_g_score))
.capacity_error()?;
open_set.insert(start, start_f_score).capacity_error()?;
while !open_set.is_empty() {
let mut current = None;
let mut lowest_f_score = None;
for (&node, &f_score) in open_set.iter() {
if let Some(current_lowest) = lowest_f_score {
if f_score < current_lowest {
lowest_f_score = Some(f_score);
current = Some(node);
}
} else {
lowest_f_score = Some(f_score);
current = Some(node);
}
}
let current = match current {
Some(node) => node,
None => return Err(AlgorithmError::InvalidState),
};
if current == goal {
return Ok(Some(reconstruct_path(&came_from, current, path_buffer)?));
}
open_set.remove(¤t);
for (neighbor, edge_weight_opt) in graph.outgoing_edge_values(current)? {
if let Some(edge_weight) = edge_weight_opt {
let current_g_score = match g_score.get(¤t).and_then(|opt| *opt) {
Some(score) => score,
None => return Err(AlgorithmError::InvalidState), };
let tentative_g_score = current_g_score + *edge_weight;
let neighbor_g_score = g_score.get(&neighbor).and_then(|opt| *opt);
if neighbor_g_score.is_none_or(|existing| tentative_g_score < existing) {
came_from.insert(neighbor, current).capacity_error()?;
g_score
.insert(neighbor, Some(tentative_g_score))
.capacity_error()?;
let f_score = tentative_g_score + heuristic(neighbor, goal);
if let Some(&existing_f) = open_set.get(&neighbor) {
if f_score < existing_f {
open_set.insert(neighbor, f_score).capacity_error()?;
}
} else {
open_set.insert(neighbor, f_score).capacity_error()?;
}
}
}
}
}
Ok(None)
}
fn reconstruct_path<'a, NI, CF>(
came_from: &CF,
mut current: NI,
path_buffer: &'a mut [NI],
) -> Result<&'a [NI], AlgorithmError<NI>>
where
NI: NodeIndex,
CF: MapTrait<NI, NI>,
{
let mut path_len = 1; let mut temp_current = current;
while let Some(&parent) = came_from.get(&temp_current) {
path_len += 1;
temp_current = parent;
}
if path_len > path_buffer.len() {
return Err(AlgorithmError::ResultCapacityExceeded);
}
let mut index = path_len - 1;
path_buffer[index] = current;
while let Some(&parent) = came_from.get(¤t) {
index -= 1;
path_buffer[index] = parent;
current = parent;
}
Ok(&path_buffer[..path_len])
}
#[cfg(test)]
mod tests {
use super::*;
use crate::containers::maps::staticdict::Dictionary;
use crate::edgelist::edge_list::EdgeList;
use crate::edges::EdgeValueStruct;
#[test]
fn test_astar_simple_path() {
let edge_data = EdgeValueStruct([(0usize, 1usize, 2i32), (1, 2, 3)]);
let graph = EdgeList::<8, _, _>::new(edge_data);
let heuristic = |_node: usize, _goal: usize| 0i32;
let open_set = Dictionary::<usize, i32, 16>::new();
let came_from = Dictionary::<usize, usize, 16>::new();
let g_score = Dictionary::<usize, Option<i32>, 16>::new();
let mut path_buffer = [0usize; 16];
let result = astar(
&graph,
0,
2,
heuristic,
open_set,
came_from,
g_score,
&mut path_buffer,
)
.unwrap();
assert!(result.is_some());
let path = result.unwrap();
assert_eq!(path, &[0, 1, 2]);
}
#[test]
fn test_astar_with_heuristic() {
let edge_data = EdgeValueStruct([
(0usize, 1usize, 1i32), (0, 2, 4), (1, 3, 1), (2, 3, 1), ]);
let graph = EdgeList::<8, _, _>::new(edge_data);
let heuristic = |node: usize, goal: usize| {
if node == goal {
0
} else {
1
}
};
let open_set = Dictionary::<usize, i32, 16>::new();
let came_from = Dictionary::<usize, usize, 16>::new();
let g_score = Dictionary::<usize, Option<i32>, 16>::new();
let mut path_buffer = [0usize; 16];
let result = astar(
&graph,
0,
3,
heuristic,
open_set,
came_from,
g_score,
&mut path_buffer,
)
.unwrap();
assert!(result.is_some());
let path = result.unwrap();
assert_eq!(path, &[0, 1, 3]);
}
#[test]
fn test_astar_no_path() {
let edge_data = EdgeValueStruct([(0usize, 1usize, 1i32)]);
let graph = EdgeList::<8, _, _>::new(edge_data);
let heuristic = |_node: usize, _goal: usize| 0i32;
let open_set = Dictionary::<usize, i32, 16>::new();
let came_from = Dictionary::<usize, usize, 16>::new();
let g_score = Dictionary::<usize, Option<i32>, 16>::new();
let mut path_buffer = [0usize; 16];
let result = astar(
&graph,
0,
2,
heuristic,
open_set,
came_from,
g_score,
&mut path_buffer,
)
.unwrap();
assert!(result.is_none()); }
#[test]
fn test_astar_same_start_goal() {
let edge_data = EdgeValueStruct([(0usize, 1usize, 1i32)]);
let graph = EdgeList::<8, _, _>::new(edge_data);
let heuristic = |_node: usize, _goal: usize| 0i32;
let open_set = Dictionary::<usize, i32, 16>::new();
let came_from = Dictionary::<usize, usize, 16>::new();
let g_score = Dictionary::<usize, Option<i32>, 16>::new();
let mut path_buffer = [0usize; 16];
let result = astar(
&graph,
0,
0,
heuristic,
open_set,
came_from,
g_score,
&mut path_buffer,
)
.unwrap();
assert!(result.is_some());
let path = result.unwrap();
assert_eq!(path, &[0]); }
#[test]
fn test_astar_complex_graph() {
let edge_data = EdgeValueStruct([
(0usize, 1usize, 1i32),
(0, 2, 1),
(1, 2, 1),
(1, 4, 3),
(2, 3, 5),
(4, 3, 1),
(4, 6, 1),
(3, 6, 1),
]);
let graph = EdgeList::<16, _, _>::new(edge_data);
let heuristic = |node: usize, goal: usize| {
match (node, goal) {
(6, 6) => 0,
(3, 6) | (4, 6) => 1,
(1, 6) | (2, 6) => 2,
(0, 6) => 3,
_ => 10, }
};
let open_set = Dictionary::<usize, i32, 32>::new();
let came_from = Dictionary::<usize, usize, 32>::new();
let g_score = Dictionary::<usize, Option<i32>, 32>::new();
let mut path_buffer = [0usize; 16];
let result = astar(
&graph,
0,
6,
heuristic,
open_set,
came_from,
g_score,
&mut path_buffer,
)
.unwrap();
assert!(result.is_some());
let path = result.unwrap();
assert_eq!(path[0], 0);
assert_eq!(path[path.len() - 1], 6);
assert!(path.len() >= 3 && path.len() <= 5);
}
}