router: make A* a walker, i.e. make it runnable in a while loop

This commit is contained in:
Mikolaj Wielgus 2024-05-27 23:50:12 +02:00
parent 194383d9b3
commit 499564e6cf
4 changed files with 111 additions and 61 deletions

View File

@ -56,7 +56,7 @@ impl Autoroute {
Some(this)
}
pub fn next<R: RulesTrait>(
pub fn step<R: RulesTrait>(
&mut self,
autorouter: &mut Autorouter<R>,
observer: &mut impl RouterObserverTrait<R>,
@ -140,7 +140,7 @@ impl<R: RulesTrait> Autorouter<R> {
pub fn autoroute(&mut self, selection: &Selection, observer: &mut impl RouterObserverTrait<R>) {
if let Some(mut autoroute) = self.autoroute_walk(selection) {
while autoroute.next(self, observer) {
while autoroute.step(self, observer) {
//
}
}

View File

@ -23,7 +23,7 @@ impl Execute {
observer: &mut impl RouterObserverTrait<R>,
) -> bool {
match self {
Execute::Autoroute(autoroute) => autoroute.next(&mut invoker.autorouter, observer),
Execute::Autoroute(autoroute) => autoroute.step(&mut invoker.autorouter, observer),
}
}
}

View File

@ -276,7 +276,7 @@ fn main() -> Result<(), anyhow::Error> {
let mut autorouter = Autorouter::new(layout.clone()).unwrap();
if let Some(mut autoroute) = autorouter.autoroute_walk(&Selection::new()) {
while autoroute.next(
while autoroute.step(
&mut autorouter,
&mut DebugRouterObserver::new(
&mut event_pump,

View File

@ -105,6 +105,110 @@ where
fn estimate_cost(&mut self, node: G::NodeId) -> K;
}
struct Astar<G, K>
where
G: IntoEdges,
G::NodeId: Eq + Hash,
K: Measure + Copy,
{
pub graph: G,
pub visit_next: BinaryHeap<MinScored<K, G::NodeId>>,
pub scores: HashMap<G::NodeId, K>,
pub estimate_scores: HashMap<G::NodeId, K>,
pub path_tracker: PathTracker<G>,
}
enum AstarStatus<G, K, R>
where
G: IntoEdges,
G::NodeId: Eq + Hash,
K: Measure + Copy,
{
Running,
Success(K, Vec<G::NodeId>, R),
Failure,
}
impl<G, K> Astar<G, K>
where
G: IntoEdges,
G::NodeId: Eq + Hash,
K: Measure + Copy,
{
pub fn new<R>(graph: G, start: G::NodeId, strategy: &mut impl AstarStrategy<G, K, R>) -> Self {
let mut this = Self {
graph,
visit_next: BinaryHeap::new(),
scores: HashMap::new(),
estimate_scores: HashMap::new(),
path_tracker: PathTracker::<G>::new(),
};
let zero_score = K::default();
this.scores.insert(start, zero_score);
this.visit_next
.push(MinScored(strategy.estimate_cost(start), start));
this
}
pub fn step<R>(&mut self, strategy: &mut impl AstarStrategy<G, K, R>) -> AstarStatus<G, K, R> {
let Some(MinScored(estimate_score, node)) = self.visit_next.pop() else {
return AstarStatus::Failure;
};
if let Some(result) = strategy.is_goal(node, &self.path_tracker) {
let path = self.path_tracker.reconstruct_path_to(node);
let cost = self.scores[&node];
return AstarStatus::Success(cost, path, result);
}
// This lookup can be unwrapped without fear of panic since the node was
// necessarily scored before adding it to `visit_next`.
let node_score = self.scores[&node];
match self.estimate_scores.entry(node) {
Occupied(mut entry) => {
// If the node has already been visited with an equal or lower score than
// now, then we do not need to re-visit it.
if *entry.get() <= estimate_score {
return AstarStatus::Running;
}
entry.insert(estimate_score);
}
Vacant(entry) => {
entry.insert(estimate_score);
}
}
for edge in self.graph.edges(node) {
if let Some(edge_cost) = strategy.edge_cost(edge) {
let next = edge.target();
let next_score = node_score + edge_cost;
match self.scores.entry(next) {
Occupied(mut entry) => {
// No need to add neighbors that we have already reached through a
// shorter path than now.
if *entry.get() <= next_score {
return AstarStatus::Running;
}
entry.insert(next_score);
}
Vacant(entry) => {
entry.insert(next_score);
}
}
self.path_tracker.set_predecessor(next, node);
let next_estimate_score = next_score + strategy.estimate_cost(next);
self.visit_next.push(MinScored(next_estimate_score, next));
}
}
AstarStatus::Running
}
}
pub fn astar<G, K, R>(
graph: G,
start: G::NodeId,
@ -115,64 +219,10 @@ where
G::NodeId: Eq + Hash,
K: Measure + Copy,
{
let mut visit_next = BinaryHeap::new();
let mut scores = HashMap::new(); // g-values, cost to reach the node
let mut estimate_scores = HashMap::new(); // f-values, cost to reach + estimate cost to goal
let mut path_tracker = PathTracker::<G>::new();
let mut astar = Astar::new(graph, start, strategy);
let zero_score = K::default();
scores.insert(start, zero_score);
visit_next.push(MinScored(strategy.estimate_cost(start), start));
while let Some(MinScored(estimate_score, node)) = visit_next.pop() {
if let Some(result) = strategy.is_goal(node, &path_tracker) {
let path = path_tracker.reconstruct_path_to(node);
let cost = scores[&node];
return Some((cost, path, result));
}
// This lookup can be unwrapped without fear of panic since the node was
// necessarily scored before adding it to `visit_next`.
let node_score = scores[&node];
match estimate_scores.entry(node) {
Occupied(mut entry) => {
// If the node has already been visited with an equal or lower score than
// now, then we do not need to re-visit it.
if *entry.get() <= estimate_score {
continue;
}
entry.insert(estimate_score);
}
Vacant(entry) => {
entry.insert(estimate_score);
}
}
for edge in graph.edges(node) {
if let Some(edge_cost) = strategy.edge_cost(edge) {
let next = edge.target();
let next_score = node_score + edge_cost;
match scores.entry(next) {
Occupied(mut entry) => {
// No need to add neighbors that we have already reached through a
// shorter path than now.
if *entry.get() <= next_score {
continue;
}
entry.insert(next_score);
}
Vacant(entry) => {
entry.insert(next_score);
}
}
path_tracker.set_predecessor(next, node);
let next_estimate_score = next_score + strategy.estimate_cost(next);
visit_next.push(MinScored(next_estimate_score, next));
}
}
while let AstarStatus::Running = astar.step(strategy) {
//
}
None