diff --git a/src/freeroute/route/grid_router.py b/src/freeroute/route/grid_router.py index 57d7f9c..4f3c541 100644 --- a/src/freeroute/route/grid_router.py +++ b/src/freeroute/route/grid_router.py @@ -376,8 +376,26 @@ class GridRouter: g_score[s] = self._stub_cost(conn.a, s) heapq.heappush(open_heap, (g_score[s] + self._h(s, goal_cell), s)) + # Via footprints are checked lazily: a layer change goes on the heap as + # a candidate and is checked only if it is popped. Most never are (a + # via costs a lot), and the check is the single most expensive thing + # the search does. A check can only rule a via out, never make it + # cheaper, so deferring it cannot change which route is found. + via_ok: dict[Cell, bool] = {} while open_heap: - _, current = heapq.heappop(open_heap) + entry = heapq.heappop(open_heap) + if len(entry) == 4: # a deferred via: (f, target, g, source) + _, target, g_via, source = entry + if g_via >= g_score.get(target, math.inf): + continue + cell = (target[0], target[1]) + ok = via_ok.get(cell) + if ok is None: + ok = via_ok[cell] = self._via_placeable(cell, net_no, conns) + if ok: + self._relax(source, target, g_via, goal_cell, g_score, came_from, open_heap) + continue + current = entry[1] if current in goals: return _reconstruct(came_from, current) cx, cy, cl = current @@ -397,14 +415,16 @@ class GridRouter: self._relax( current, nxt, base + step + enter, goal_cell, g_score, came_from, open_heap ) - if len(self.layers) > 1 and self._via_placeable((cx, cy), net_no, conns): + if len(self.layers) > 1: + g_via = base + self.via_cost for layer in self.layers: if layer == cl: continue nxt = (cx, cy, layer) - self._relax( - current, nxt, base + self.via_cost, goal_cell, g_score, came_from, open_heap - ) + if g_via < g_score.get(nxt, math.inf): + heapq.heappush( + open_heap, (g_via + self._h(nxt, goal_cell), nxt, g_via, current) + ) return None # --- pin stubs ----------------------------------------------------------