use std::collections::{BinaryHeap, HashMap, HashSet};
use super::node::{heuristic, layer_indices, snap, GridNode, SearchState};
use super::path::{reconstruct_path, PathFrame};
use super::{AStarConfig, RoutePath, Terminal};
use crate::router::spatial::{Probe, SpatialRoutingIndex};
const MAX_EXPANSIONS: usize = 400_000;
const DIRS: [(i32, i32); 8] = [
(1, 0),
(-1, 0),
(0, 1),
(0, -1),
(1, 1),
(1, -1),
(-1, 1),
(-1, -1),
];
pub(super) struct Search<'a> {
spatial: &'a SpatialRoutingIndex,
config: &'a AStarConfig,
start: &'a Terminal,
target: &'a Terminal,
wire: Probe<'a>,
via: Probe<'a>,
goal: GridNode,
goal_layers: Vec<usize>,
open_set: BinaryHeap<SearchState>,
g_score: HashMap<GridNode, f64>,
came_from: HashMap<GridNode, GridNode>,
closed: HashSet<GridNode>,
}
impl<'a> Search<'a> {
pub(super) fn new(
start: &'a Terminal,
target: &'a Terminal,
net_id: &'a str,
spatial: &'a SpatialRoutingIndex,
config: &'a AStarConfig,
) -> Option<Self> {
let goal_layers = layer_indices(&config.layers, target);
let (gx, gy) = snap(target.point, config.grid_step);
let goal = GridNode {
gx,
gy,
layer_idx: goal_layers.first().copied()?,
};
let wire = Probe {
width: config.trace_width,
clearance: config.clearance,
net_id,
};
Some(Self {
spatial,
config,
start,
target,
wire,
via: Probe {
width: config.via_diameter,
..wire
},
goal,
goal_layers,
open_set: BinaryHeap::new(),
g_score: HashMap::new(),
came_from: HashMap::new(),
closed: HashSet::new(),
})
}
fn at(&self, n: GridNode) -> [f64; 2] {
let step = self.config.grid_step;
[n.gx as f64 * step, n.gy as f64 * step]
}
fn wire_clear(&self, layer: usize, a: [f64; 2], b: [f64; 2]) -> bool {
self.spatial
.is_segment_clear(&self.config.layers[layer], a, b, self.wire)
}
pub(super) fn seed(&mut self) {
let (sgx, sgy) = snap(self.start.point, self.config.grid_step);
for layer_idx in layer_indices(&self.config.layers, self.start) {
let node = GridNode {
gx: sgx,
gy: sgy,
layer_idx,
};
if self.wire_clear(layer_idx, self.start.point, self.at(node)) {
self.g_score.insert(node, 0.0);
self.open_set.push(SearchState {
cost: heuristic(node, self.goal, self.config.grid_step),
node,
});
}
}
}
pub(super) fn run(mut self) -> Option<RoutePath> {
while let Some(SearchState { node, .. }) = self.open_set.pop() {
if !self.closed.insert(node) {
continue;
}
if self.closed.len() > MAX_EXPANSIONS {
break;
}
let current_p = self.at(node);
if node.gx == self.goal.gx
&& node.gy == self.goal.gy
&& self.goal_layers.contains(&node.layer_idx)
&& self.wire_clear(node.layer_idx, current_p, self.target.point)
{
let frame = PathFrame {
step: self.config.grid_step,
layers: &self.config.layers,
start: self.start,
target: self.target,
};
return Some(reconstruct_path(node, &self.came_from, &frame));
}
let current_g = self.g_score[&node];
self.expand_wires(node, current_p, current_g);
self.expand_vias(node, current_p, current_g);
}
None
}
fn expand_wires(&mut self, node: GridNode, current_p: [f64; 2], current_g: f64) {
let step = self.config.grid_step;
for &(dx, dy) in &DIRS {
let next = GridNode {
gx: node.gx + dx,
gy: node.gy + dy,
layer_idx: node.layer_idx,
};
if self.closed.contains(&next)
|| !self.wire_clear(node.layer_idx, current_p, self.at(next))
{
continue;
}
let dist = if dx != 0 && dy != 0 {
step * std::f64::consts::SQRT_2
} else {
step
};
self.relax(node, next, current_g + dist);
}
}
fn expand_vias(&mut self, node: GridNode, current_p: [f64; 2], current_g: f64) {
let layer_count = self.config.layers.len();
if layer_count < 2 {
return;
}
let via_clear = self.config.layers.iter().all(|layer| {
self.spatial
.is_segment_clear(layer, current_p, current_p, self.via)
});
if !via_clear {
return;
}
for layer_idx in (0..layer_count).filter(|&i| i != node.layer_idx) {
self.relax(
node,
GridNode { layer_idx, ..node },
current_g + self.config.via_cost,
);
}
}
fn relax(&mut self, from: GridNode, next: GridNode, tentative_g: f64) {
if tentative_g < *self.g_score.get(&next).unwrap_or(&f64::INFINITY) {
self.g_score.insert(next, tentative_g);
self.came_from.insert(next, from);
self.open_set.push(SearchState {
cost: tentative_g + heuristic(next, self.goal, self.config.grid_step),
node: next,
});
}
}
}