From 499564e6cfbb95cefb73fc120c03c115c99b5500 Mon Sep 17 00:00:00 2001 From: Mikolaj Wielgus Date: Mon, 27 May 2024 23:50:12 +0200 Subject: [PATCH] router: make A* a walker, i.e. make it runnable in a while loop --- src/autorouter/autorouter.rs | 4 +- src/autorouter/invoker.rs | 2 +- src/bin/topola-sdl2-demo/main.rs | 2 +- src/router/astar.rs | 164 ++++++++++++++++++++----------- 4 files changed, 111 insertions(+), 61 deletions(-) diff --git a/src/autorouter/autorouter.rs b/src/autorouter/autorouter.rs index 346c034..f9c1583 100644 --- a/src/autorouter/autorouter.rs +++ b/src/autorouter/autorouter.rs @@ -56,7 +56,7 @@ impl Autoroute { Some(this) } - pub fn next( + pub fn step( &mut self, autorouter: &mut Autorouter, observer: &mut impl RouterObserverTrait, @@ -140,7 +140,7 @@ impl Autorouter { pub fn autoroute(&mut self, selection: &Selection, observer: &mut impl RouterObserverTrait) { if let Some(mut autoroute) = self.autoroute_walk(selection) { - while autoroute.next(self, observer) { + while autoroute.step(self, observer) { // } } diff --git a/src/autorouter/invoker.rs b/src/autorouter/invoker.rs index 410396b..35f360b 100644 --- a/src/autorouter/invoker.rs +++ b/src/autorouter/invoker.rs @@ -23,7 +23,7 @@ impl Execute { observer: &mut impl RouterObserverTrait, ) -> bool { match self { - Execute::Autoroute(autoroute) => autoroute.next(&mut invoker.autorouter, observer), + Execute::Autoroute(autoroute) => autoroute.step(&mut invoker.autorouter, observer), } } } diff --git a/src/bin/topola-sdl2-demo/main.rs b/src/bin/topola-sdl2-demo/main.rs index 71dd6d9..1b050a5 100644 --- a/src/bin/topola-sdl2-demo/main.rs +++ b/src/bin/topola-sdl2-demo/main.rs @@ -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, diff --git a/src/router/astar.rs b/src/router/astar.rs index e5222bd..8e5f62f 100644 --- a/src/router/astar.rs +++ b/src/router/astar.rs @@ -105,6 +105,110 @@ where fn estimate_cost(&mut self, node: G::NodeId) -> K; } +struct Astar +where + G: IntoEdges, + G::NodeId: Eq + Hash, + K: Measure + Copy, +{ + pub graph: G, + pub visit_next: BinaryHeap>, + pub scores: HashMap, + pub estimate_scores: HashMap, + pub path_tracker: PathTracker, +} + +enum AstarStatus +where + G: IntoEdges, + G::NodeId: Eq + Hash, + K: Measure + Copy, +{ + Running, + Success(K, Vec, R), + Failure, +} + +impl Astar +where + G: IntoEdges, + G::NodeId: Eq + Hash, + K: Measure + Copy, +{ + pub fn new(graph: G, start: G::NodeId, strategy: &mut impl AstarStrategy) -> Self { + let mut this = Self { + graph, + visit_next: BinaryHeap::new(), + scores: HashMap::new(), + estimate_scores: HashMap::new(), + path_tracker: PathTracker::::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(&mut self, strategy: &mut impl AstarStrategy) -> AstarStatus { + 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( 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::::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