use std::cmp::Reverse;
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
use crate::GcRef;
use crate::context::FaultKind;
use crate::dynamic_key::DynamicKey;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Aborted;
pub trait GraphOracle {
fn neighbours(&mut self, state: GcRef) -> Result<Vec<GcRef>, Aborted>;
fn weight(&mut self, from: GcRef, to: GcRef) -> Result<i64, Aborted>;
fn heuristic(&mut self, state: GcRef) -> Result<i64, Aborted>;
fn is_goal(&mut self, state: GcRef) -> Result<bool, Aborted>;
fn retain(&mut self, state: GcRef);
fn abort(&mut self, kind: FaultKind) -> Aborted;
}
struct Seen {
keys: HashSet<DynamicKey>,
order: Vec<GcRef>,
}
impl Seen {
fn new() -> Seen {
Seen {
keys: HashSet::new(),
order: Vec::new(),
}
}
fn insert(&mut self, state: GcRef) -> bool {
if self.keys.insert(DynamicKey::new(state)) {
self.order.push(state);
true
} else {
false
}
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Route {
pub cost: i64,
pub states: Vec<GcRef>,
}
type Parents = HashMap<DynamicKey, GcRef>;
fn route_to(parents: &Parents, goal: GcRef) -> Vec<GcRef> {
let mut states = vec![goal];
let mut at = goal;
while let Some(parent) = parents.get(&DynamicKey::new(at)) {
states.push(*parent);
at = *parent;
}
states.reverse();
states
}
pub fn bfs_order(oracle: &mut dyn GraphOracle, start: GcRef) -> Result<Vec<GcRef>, Aborted> {
let mut seen = Seen::new();
oracle.retain(start);
seen.insert(start);
let mut queue = VecDeque::new();
queue.push_back(start);
while let Some(state) = queue.pop_front() {
for next in oracle.neighbours(state)? {
oracle.retain(next);
if seen.insert(next) {
queue.push_back(next);
}
}
}
Ok(seen.order)
}
pub fn dfs_order(oracle: &mut dyn GraphOracle, start: GcRef) -> Result<Vec<GcRef>, Aborted> {
let mut seen = Seen::new();
oracle.retain(start);
let mut stack = vec![start];
while let Some(state) = stack.pop() {
if !seen.insert(state) {
continue;
}
let next = oracle.neighbours(state)?;
for n in next.into_iter().rev() {
oracle.retain(n);
stack.push(n);
}
}
Ok(seen.order)
}
pub fn reachable(oracle: &mut dyn GraphOracle, start: GcRef) -> Result<Vec<GcRef>, Aborted> {
bfs_order(oracle, start)
}
pub fn bfs_route(oracle: &mut dyn GraphOracle, start: GcRef) -> Result<Option<Route>, Aborted> {
let mut seen = Seen::new();
let mut parents = Parents::new();
oracle.retain(start);
seen.insert(start);
let mut queue = VecDeque::new();
queue.push_back((start, 0_i64));
while let Some((state, steps)) = queue.pop_front() {
if oracle.is_goal(state)? {
return Ok(Some(Route {
cost: steps,
states: route_to(&parents, state),
}));
}
let Some(next_steps) = steps.checked_add(1) else {
return Err(oracle.abort(FaultKind::IntOverflow));
};
for next in oracle.neighbours(state)? {
oracle.retain(next);
if seen.insert(next) {
parents.insert(DynamicKey::new(next), state);
queue.push_back((next, next_steps));
}
}
}
Ok(None)
}
pub fn dfs_route(oracle: &mut dyn GraphOracle, start: GcRef) -> Result<Option<Route>, Aborted> {
let mut seen = Seen::new();
let mut parents = Parents::new();
oracle.retain(start);
let mut stack: Vec<(GcRef, Option<GcRef>)> = vec![(start, None)];
while let Some((state, parent)) = stack.pop() {
if !seen.insert(state) {
continue;
}
if let Some(parent) = parent {
parents.insert(DynamicKey::new(state), parent);
}
if oracle.is_goal(state)? {
let states = route_to(&parents, state);
let cost = (states.len() - 1) as i64;
return Ok(Some(Route { cost, states }));
}
let next = oracle.neighbours(state)?;
for n in next.into_iter().rev() {
oracle.retain(n);
stack.push((n, Some(state)));
}
}
Ok(None)
}
type PriorityOf = fn(&mut dyn GraphOracle, GcRef, i64) -> Result<i64, Aborted>;
struct Frontier {
heap: BinaryHeap<Reverse<(i64, usize, StateEntry)>>,
best: HashMap<DynamicKey, i64>,
done: HashSet<DynamicKey>,
parents: Parents,
seq: usize,
}
impl Frontier {
fn new() -> Frontier {
Frontier {
heap: BinaryHeap::new(),
best: HashMap::new(),
done: HashSet::new(),
parents: Parents::new(),
seq: 0,
}
}
fn push(&mut self, state: GcRef, parent: Option<GcRef>, cost: i64, priority: i64) {
let key = DynamicKey::new(state);
self.best.insert(key, cost);
if let Some(parent) = parent {
self.parents.insert(key, parent);
}
self.heap
.push(Reverse((priority, self.seq, StateEntry(state))));
self.seq += 1;
}
fn settle(&mut self) -> Option<(GcRef, i64)> {
while let Some(Reverse((_, _, StateEntry(state)))) = self.heap.pop() {
let key = DynamicKey::new(state);
if !self.done.insert(key) {
continue;
}
let cost = *self
.best
.get(&key)
.expect("a popped state has a known cost");
return Some((state, cost));
}
None
}
fn relax(
&mut self,
oracle: &mut dyn GraphOracle,
state: GcRef,
cost: i64,
priority_of: PriorityOf,
) -> Result<(), Aborted> {
for next in oracle.neighbours(state)? {
oracle.retain(next);
let step = oracle.weight(state, next)?;
if step < 0 {
return Err(oracle.abort(FaultKind::NoAnswer));
}
let Some(through) = cost.checked_add(step) else {
return Err(oracle.abort(FaultKind::IntOverflow));
};
let next_key = DynamicKey::new(next);
if self.done.contains(&next_key) {
continue;
}
let improved = match self.best.get(&next_key) {
Some(known) => through < *known,
None => true,
};
if improved {
let priority = priority_of(oracle, next, through)?;
self.push(next, Some(state), through, priority);
}
}
Ok(())
}
}
pub fn dijkstra_costs(
oracle: &mut dyn GraphOracle,
start: GcRef,
) -> Result<Vec<(GcRef, i64)>, Aborted> {
let mut frontier = Frontier::new();
let mut settled: Vec<(GcRef, i64)> = Vec::new();
oracle.retain(start);
frontier.push(start, None, 0, 0);
while let Some((state, cost)) = frontier.settle() {
settled.push((state, cost));
frontier.relax(oracle, state, cost, cost_itself)?;
}
Ok(settled)
}
fn cost_itself(_oracle: &mut dyn GraphOracle, _state: GcRef, cost: i64) -> Result<i64, Aborted> {
Ok(cost)
}
fn best_route(
oracle: &mut dyn GraphOracle,
start: GcRef,
priority_of: PriorityOf,
) -> Result<Option<Route>, Aborted> {
let mut frontier = Frontier::new();
oracle.retain(start);
let start_priority = priority_of(oracle, start, 0)?;
frontier.push(start, None, 0, start_priority);
while let Some((state, cost)) = frontier.settle() {
if oracle.is_goal(state)? {
return Ok(Some(Route {
cost,
states: route_to(&frontier.parents, state),
}));
}
frontier.relax(oracle, state, cost, priority_of)?;
}
Ok(None)
}
pub fn dijkstra_route(
oracle: &mut dyn GraphOracle,
start: GcRef,
) -> Result<Option<Route>, Aborted> {
best_route(oracle, start, cost_itself)
}
pub fn a_star_route(oracle: &mut dyn GraphOracle, start: GcRef) -> Result<Option<Route>, Aborted> {
best_route(oracle, start, estimate)
}
fn estimate(oracle: &mut dyn GraphOracle, state: GcRef, cost: i64) -> Result<i64, Aborted> {
let h = oracle.heuristic(state)?;
if h < 0 {
return Err(oracle.abort(FaultKind::NoAnswer));
}
match cost.checked_add(h) {
Some(f) => Ok(f),
None => Err(oracle.abort(FaultKind::IntOverflow)),
}
}
#[derive(Clone, Copy)]
struct StateEntry(GcRef);
impl PartialEq for StateEntry {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Eq for StateEntry {}
impl PartialOrd for StateEntry {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for StateEntry {
fn cmp(&self, _other: &Self) -> std::cmp::Ordering {
std::cmp::Ordering::Equal
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::abi::{praxis_alloc_int, praxis_int_load};
use crate::context::{Runtime, RuntimeContext};
struct Table {
ctx: *mut RuntimeContext,
edges: Vec<(i64, Vec<i64>)>,
weights: Vec<((i64, i64), i64)>,
heuristics: Vec<(i64, i64)>,
goals: Vec<i64>,
raised: Option<FaultKind>,
retained: Vec<i64>,
}
impl Table {
fn value(&self, state: GcRef) -> i64 {
unsafe { praxis_int_load(self.ctx, state) }
}
fn state(&self, n: i64) -> GcRef {
unsafe { praxis_alloc_int(self.ctx, n) }
}
}
impl GraphOracle for Table {
fn neighbours(&mut self, state: GcRef) -> Result<Vec<GcRef>, Aborted> {
let n = self.value(state);
let out = self
.edges
.iter()
.find(|(from, _)| *from == n)
.map(|(_, to)| to.clone())
.unwrap_or_default();
Ok(out.into_iter().map(|m| self.state(m)).collect())
}
fn weight(&mut self, from: GcRef, to: GcRef) -> Result<i64, Aborted> {
let pair = (self.value(from), self.value(to));
Ok(self
.weights
.iter()
.find(|(p, _)| *p == pair)
.map(|(_, w)| *w)
.unwrap_or(1))
}
fn heuristic(&mut self, state: GcRef) -> Result<i64, Aborted> {
let n = self.value(state);
Ok(self
.heuristics
.iter()
.find(|(s, _)| *s == n)
.map(|(_, h)| *h)
.unwrap_or(0))
}
fn is_goal(&mut self, state: GcRef) -> Result<bool, Aborted> {
Ok(self.goals.contains(&self.value(state)))
}
fn retain(&mut self, state: GcRef) {
let n = self.value(state);
self.retained.push(n);
}
fn abort(&mut self, kind: FaultKind) -> Aborted {
self.raised = Some(kind);
Aborted
}
}
fn table(edges: &[(i64, &[i64])]) -> (Box<Runtime>, Table) {
let mut rt = Box::new(Runtime::new());
let ctx: *mut RuntimeContext = Box::leak(Box::new(rt.context()));
let t = Table {
ctx,
edges: edges
.iter()
.map(|(from, to)| (*from, to.to_vec()))
.collect(),
weights: Vec::new(),
heuristics: Vec::new(),
goals: Vec::new(),
raised: None,
retained: Vec::new(),
};
(rt, t)
}
fn values(t: &Table, states: &[GcRef]) -> Vec<i64> {
states.iter().map(|s| t.value(*s)).collect()
}
#[test]
fn breadth_first_and_depth_first_visit_in_the_orders_they_name() {
let (_rt, mut t) = table(&[(1, &[2, 3]), (2, &[4]), (3, &[4]), (4, &[])]);
let start = t.state(1);
let bfs = bfs_order(&mut t, start).expect("no fault");
assert_eq!(values(&t, &bfs), vec![1, 2, 3, 4]);
let (_rt2, mut t2) = table(&[(1, &[2, 3]), (2, &[4]), (3, &[4]), (4, &[])]);
let start2 = t2.state(1);
let dfs = dfs_order(&mut t2, start2).expect("no fault");
assert_eq!(values(&t2, &dfs), vec![1, 2, 4, 3]);
}
#[test]
fn a_depth_first_walk_takes_the_first_neighbour_first() {
let (_rt, mut t) = table(&[(1, &[2, 3]), (2, &[]), (3, &[])]);
let start = t.state(1);
let order = dfs_order(&mut t, start).expect("no fault");
assert_eq!(values(&t, &order), vec![1, 2, 3]);
}
#[test]
fn a_cycle_is_walked_once_and_terminates() {
let (_rt, mut t) = table(&[(1, &[2]), (2, &[3]), (3, &[1, 2])]);
let start = t.state(1);
let bfs = bfs_order(&mut t, start).expect("no fault");
assert_eq!(values(&t, &bfs), vec![1, 2, 3]);
let (_rt2, mut t2) = table(&[(1, &[2]), (2, &[3]), (3, &[1, 2])]);
let start2 = t2.state(1);
let dfs = dfs_order(&mut t2, start2).expect("no fault");
assert_eq!(values(&t2, &dfs), vec![1, 2, 3]);
}
#[test]
fn a_lone_state_is_its_own_walk() {
let (_rt, mut t) = table(&[(1, &[])]);
let start = t.state(1);
let order = bfs_order(&mut t, start).unwrap();
assert_eq!(values(&t, &order), vec![1]);
let (_rt2, mut t2) = table(&[(1, &[])]);
let start2 = t2.state(1);
let reached = reachable(&mut t2, start2).unwrap();
assert_eq!(values(&t2, &reached), vec![1]);
}
#[test]
fn two_equal_states_are_one_state_however_they_were_allocated() {
let (_rt, mut t) = table(&[(1, &[2]), (2, &[1])]);
let start = t.state(1);
let order = bfs_order(&mut t, start).expect("no fault");
assert_eq!(values(&t, &order), vec![1, 2]);
assert!(t.retained.len() >= 3, "the fresh states were retained");
}
#[test]
fn every_remembered_state_was_retained_first() {
let (_rt, mut t) = table(&[(1, &[2, 3]), (2, &[4]), (3, &[]), (4, &[])]);
let start = t.state(1);
let order = bfs_order(&mut t, start).expect("no fault");
for state in &order {
assert!(
t.retained.contains(&t.value(*state)),
"a visited state was never retained"
);
}
}
fn cost(found: Result<Option<Route>, Aborted>) -> Option<i64> {
found.expect("no fault").map(|r| r.cost)
}
#[test]
fn a_distance_counts_steps_and_absence_is_none() {
let (_rt, mut t) = table(&[(1, &[2]), (2, &[3]), (3, &[]), (9, &[])]);
t.goals = vec![3];
let start = t.state(1);
assert_eq!(cost(bfs_route(&mut t, start)), Some(2));
let (_rt2, mut t2) = table(&[(1, &[2]), (2, &[3]), (3, &[])]);
t2.goals = vec![1];
let start2 = t2.state(1);
assert_eq!(
cost(bfs_route(&mut t2, start2)),
Some(0),
"a start that is already a goal is zero steps, not one"
);
let (_rt3, mut t3) = table(&[(1, &[2]), (2, &[])]);
t3.goals = vec![99];
let start3 = t3.state(1);
assert_eq!(cost(bfs_route(&mut t3, start3)), None);
}
#[test]
fn a_distance_is_the_shortest_path_not_the_first_found() {
let (_rt, mut t) = table(&[(1, &[2, 5]), (2, &[3]), (3, &[4]), (4, &[]), (5, &[4])]);
t.goals = vec![4];
let start = t.state(1);
assert_eq!(cost(bfs_route(&mut t, start)), Some(2));
}
#[test]
fn a_cost_table_prefers_a_cheap_long_path_to_an_expensive_short_one() {
let (_rt, mut t) = table(&[(1, &[2, 4]), (2, &[3]), (3, &[4]), (4, &[]), (7, &[])]);
t.weights = vec![((1, 4), 10), ((1, 2), 1), ((2, 3), 1), ((3, 4), 1)];
let start = t.state(1);
let costs = dijkstra_costs(&mut t, start).expect("no fault");
let mut by_state: Vec<(i64, i64)> = costs.iter().map(|(s, c)| (t.value(*s), *c)).collect();
by_state.sort_unstable();
assert_eq!(by_state, vec![(1, 0), (2, 1), (3, 2), (4, 3)]);
assert!(
!by_state.iter().any(|(s, _)| *s == 7),
"an unreachable state is absent, not present at some cost"
);
}
#[test]
fn each_state_is_settled_once() {
let (_rt, mut t) = table(&[(1, &[2, 3]), (2, &[4]), (3, &[4]), (4, &[])]);
let start = t.state(1);
let costs = dijkstra_costs(&mut t, start).expect("no fault");
assert_eq!(costs.len(), 4, "one entry per reachable state");
}
#[test]
fn a_negative_edge_weight_faults_rather_than_answering() {
let (_rt, mut t) = table(&[(1, &[2]), (2, &[])]);
t.weights = vec![((1, 2), -1)];
let start = t.state(1);
assert_eq!(dijkstra_costs(&mut t, start), Err(Aborted));
assert_eq!(t.raised, Some(FaultKind::NoAnswer));
let (_rt2, mut t2) = table(&[(1, &[2]), (2, &[])]);
t2.weights = vec![((1, 2), -1)];
t2.goals = vec![2];
let start2 = t2.state(1);
assert_eq!(a_star_route(&mut t2, start2), Err(Aborted));
assert_eq!(t2.raised, Some(FaultKind::NoAnswer));
}
#[test]
fn a_cost_with_no_int_faults_rather_than_wrapping() {
let (_rt, mut t) = table(&[(1, &[2]), (2, &[3]), (3, &[])]);
t.weights = vec![((1, 2), i64::MAX), ((2, 3), 1)];
let start = t.state(1);
assert_eq!(dijkstra_costs(&mut t, start), Err(Aborted));
assert_eq!(t.raised, Some(FaultKind::IntOverflow));
}
#[test]
fn a_star_finds_the_cheapest_goal_whatever_the_heuristic_estimates() {
let edges: &[(i64, &[i64])] = &[(1, &[2, 4]), (2, &[3]), (3, &[4]), (4, &[])];
let weights = vec![((1, 4), 10), ((1, 2), 1), ((2, 3), 1), ((3, 4), 1)];
let (_rt, mut t) = table(edges);
t.weights = weights.clone();
t.goals = vec![4];
let start = t.state(1);
assert_eq!(cost(a_star_route(&mut t, start)), Some(3));
let (_rt2, mut t2) = table(edges);
t2.weights = weights;
t2.goals = vec![4];
t2.heuristics = vec![(1, 3), (2, 2), (3, 1), (4, 0)];
let start2 = t2.state(1);
assert_eq!(cost(a_star_route(&mut t2, start2)), Some(3));
}
#[test]
fn a_star_answers_nothing_for_an_unreachable_goal() {
let (_rt, mut t) = table(&[(1, &[2]), (2, &[])]);
t.goals = vec![99];
let start = t.state(1);
assert_eq!(cost(a_star_route(&mut t, start)), None);
let (_rt2, mut t2) = table(&[(1, &[2]), (2, &[])]);
t2.goals = vec![1];
let start2 = t2.state(1);
assert_eq!(cost(a_star_route(&mut t2, start2)), Some(0));
}
#[test]
fn a_negative_heuristic_faults_rather_than_misordering_the_search() {
let (_rt, mut t) = table(&[(1, &[2]), (2, &[])]);
t.goals = vec![2];
t.heuristics = vec![(1, -5)];
let start = t.state(1);
assert_eq!(a_star_route(&mut t, start), Err(Aborted));
assert_eq!(t.raised, Some(FaultKind::NoAnswer));
}
#[test]
fn a_route_is_start_to_goal_inclusive_and_its_cost_is_its_own() {
let edges: &[(i64, &[i64])] = &[(1, &[2]), (2, &[3]), (3, &[])];
let weights = vec![((1, 2), 4), ((2, 3), 6)];
let (_rt, mut t) = table(edges);
t.goals = vec![3];
let start = t.state(1);
let route = bfs_route(&mut t, start).expect("no fault").expect("a goal");
assert_eq!(values(&t, &route.states), vec![1, 2, 3]);
assert_eq!(route.cost, 2, "a breadth-first cost counts edges");
let (_rt2, mut t2) = table(edges);
t2.weights = weights.clone();
t2.goals = vec![3];
let start2 = t2.state(1);
let route = dijkstra_route(&mut t2, start2)
.expect("no fault")
.expect("a goal");
assert_eq!(values(&t2, &route.states), vec![1, 2, 3]);
assert_eq!(route.cost, 10, "a weighted cost sums the weights");
let (_rt3, mut t3) = table(edges);
t3.weights = weights;
t3.goals = vec![3];
let start3 = t3.state(1);
let route = a_star_route(&mut t3, start3)
.expect("no fault")
.expect("a goal");
assert_eq!(values(&t3, &route.states), vec![1, 2, 3]);
assert_eq!(route.cost, 10);
}
#[test]
fn a_start_that_is_the_goal_is_a_route_of_one_state() {
for search in [bfs_route, dfs_route, dijkstra_route, a_star_route] {
let (_rt, mut t) = table(&[(1, &[2]), (2, &[])]);
t.goals = vec![1];
let start = t.state(1);
let route = search(&mut t, start).expect("no fault").expect("a goal");
assert_eq!(values(&t, &route.states), vec![1]);
assert_eq!(route.cost, 0);
}
}
#[test]
fn an_unreachable_goal_is_nothing_from_every_search() {
for search in [bfs_route, dfs_route, dijkstra_route, a_star_route] {
let (_rt, mut t) = table(&[(1, &[2]), (2, &[])]);
t.goals = vec![99];
let start = t.state(1);
assert!(search(&mut t, start).expect("no fault").is_none());
}
}
#[test]
fn a_breadth_first_route_is_a_shortest_one() {
let (_rt, mut t) = table(&[(1, &[2, 5]), (2, &[3]), (3, &[4]), (4, &[]), (5, &[4])]);
t.goals = vec![4];
let start = t.state(1);
let route = bfs_route(&mut t, start).expect("no fault").expect("a goal");
assert_eq!(values(&t, &route.states), vec![1, 5, 4]);
assert_eq!(route.states.len() as i64 - 1, route.cost);
}
#[test]
fn the_cheapest_route_and_the_shortest_route_are_different_routes() {
let edges: &[(i64, &[i64])] = &[(1, &[2, 4]), (2, &[3]), (3, &[4]), (4, &[])];
let weights = vec![((1, 4), 10), ((1, 2), 1), ((2, 3), 1), ((3, 4), 1)];
let (_rt, mut t) = table(edges);
t.weights = weights.clone();
t.goals = vec![4];
let start = t.state(1);
let cheap = dijkstra_route(&mut t, start)
.expect("no fault")
.expect("a goal");
assert_eq!(values(&t, &cheap.states), vec![1, 2, 3, 4]);
assert_eq!(cheap.cost, 3);
let (_rt2, mut t2) = table(edges);
t2.weights = weights;
t2.goals = vec![4];
let start2 = t2.state(1);
let short = bfs_route(&mut t2, start2)
.expect("no fault")
.expect("a goal");
assert_eq!(values(&t2, &short.states), vec![1, 4]);
assert_eq!(short.cost, 1, "one edge, whatever it costs");
}
#[test]
fn a_relaxed_state_keeps_the_cheap_routes_parent() {
let (_rt, mut t) = table(&[(1, &[4, 2]), (2, &[3]), (3, &[4]), (4, &[])]);
t.weights = vec![((1, 4), 10), ((1, 2), 1), ((2, 3), 1), ((3, 4), 1)];
t.goals = vec![4];
let start = t.state(1);
let route = dijkstra_route(&mut t, start)
.expect("no fault")
.expect("a goal");
assert_eq!(values(&t, &route.states), vec![1, 2, 3, 4]);
assert_eq!(route.cost, 3);
}
#[test]
fn a_depth_first_route_need_not_be_a_short_one() {
let edges: &[(i64, &[i64])] = &[(1, &[2, 4]), (2, &[3]), (3, &[4]), (4, &[])];
let (_rt, mut t) = table(edges);
t.goals = vec![4];
let start = t.state(1);
let deep = dfs_route(&mut t, start).expect("no fault").expect("a goal");
assert_eq!(values(&t, &deep.states), vec![1, 2, 3, 4]);
assert_eq!(deep.cost, 3);
let (_rt2, mut t2) = table(edges);
t2.goals = vec![4];
let start2 = t2.state(1);
let wide = bfs_route(&mut t2, start2)
.expect("no fault")
.expect("a goal");
assert_eq!(values(&t2, &wide.states), vec![1, 4]);
assert!(wide.cost < deep.cost);
}
#[test]
fn a_route_refuses_the_graphs_a_cost_refuses() {
let (_rt, mut t) = table(&[(1, &[2]), (2, &[])]);
t.weights = vec![((1, 2), -1)];
t.goals = vec![2];
let start = t.state(1);
assert_eq!(dijkstra_route(&mut t, start), Err(Aborted));
assert_eq!(t.raised, Some(FaultKind::NoAnswer));
let (_rt2, mut t2) = table(&[(1, &[2]), (2, &[3]), (3, &[])]);
t2.weights = vec![((1, 2), i64::MAX), ((2, 3), 1)];
t2.goals = vec![99];
let start2 = t2.state(1);
assert_eq!(dijkstra_route(&mut t2, start2), Err(Aborted));
assert_eq!(t2.raised, Some(FaultKind::IntOverflow));
let (_rt3, mut t3) = table(&[(1, &[2]), (2, &[])]);
t3.goals = vec![2];
t3.heuristics = vec![(1, -5)];
let start3 = t3.state(1);
assert_eq!(a_star_route(&mut t3, start3), Err(Aborted));
assert_eq!(t3.raised, Some(FaultKind::NoAnswer));
}
#[test]
fn every_state_on_a_route_was_retained_first() {
let (_rt, mut t) = table(&[(1, &[2, 3]), (2, &[4]), (3, &[]), (4, &[])]);
t.goals = vec![4];
let start = t.state(1);
let route = bfs_route(&mut t, start).expect("no fault").expect("a goal");
for state in &route.states {
assert!(
t.retained.contains(&t.value(*state)),
"a state on the route was never retained"
);
}
}
}