Tighter A* estimate on the orthogonal track, and --heuristic-weight

The grid search estimated the remaining distance as a straight line even
when only orthogonal steps exist; Manhattan is the tighter estimate those
moves admit and still never overestimates. --heuristic-weight W (default
1) makes it weighted A*: routes at most W times the shortest (tested).
This commit is contained in:
2026-09-27 10:55:15 -06:00
parent 8929fd367e
commit 67c6c205f1
5 changed files with 47 additions and 1 deletions
+9
View File
@@ -118,6 +118,14 @@ def build_parser() -> argparse.ArgumentParser:
help="grid and exact engines: cells K times finer than trace + clearance "
"(default 1); 2 or more reaches fine-pitch pins, at some cost in time",
)
parser.add_argument(
"--heuristic-weight",
type=float,
default=1.0,
metavar="W",
help="exact engine: weighted A* (W >= 1); larger is faster, routes at most "
"W times the shortest length (default 1)",
)
parser.add_argument(
"--edge-clearance",
type=float,
@@ -175,6 +183,7 @@ def main(argv: list[str] | None = None) -> int:
max_passes=max_passes,
edge_clearance_mm=args.edge_clearance,
resolution=args.grid_resolution,
heuristic_weight=args.heuristic_weight,
)
elif args.engine == "room":
result = build_rooms_routing_result(
+2
View File
@@ -93,6 +93,7 @@ def route_board_exact(
via_half: int | None = None,
edge_clearance: int | None = None,
resolution: int = 1,
heuristic_weight: float = 1.0,
) -> ExactRouteResult:
"""Route ``board`` orthogonally and verify the result with exact geometry.
@@ -120,6 +121,7 @@ def route_board_exact(
via_half=via_half,
edge_clearance=edge_clearance,
resolution=resolution,
heuristic_weight=heuristic_weight,
)
edge = 0 if edge_clearance is None else edge_clearance
outline = board.outline
+11 -1
View File
@@ -138,8 +138,10 @@ class GridRouter:
via_half: int | None = None,
edge_clearance: int | None = None,
resolution: int = 1,
heuristic_weight: float = 1.0,
) -> None:
self.board = board
self.heuristic_weight = max(float(heuristic_weight), 1.0)
self.trace_width = max(trace_width, 1)
self.clearance = max(clearance, 0)
self.half_width = self.trace_width // 2
@@ -471,7 +473,13 @@ class GridRouter:
heapq.heappush(open_heap, (tentative + self._h(nxt, goal_cell), nxt))
def _h(self, node: Node, goal_cell: Cell) -> float:
return math.hypot(node[0] - goal_cell[0], node[1] - goal_cell[1])
dx, dy = abs(node[0] - goal_cell[0]), abs(node[1] - goal_cell[1])
# the tightest estimate the allowed moves admit (Manhattan when only
# orthogonal steps exist), scaled by heuristic_weight: above 1 the
# search heads for the goal instead of flooding open board, for a
# route at most that factor longer than the shortest
base = dx + dy if self._orthogonal else math.hypot(dx, dy)
return self.heuristic_weight * base
# --- commit / rip -------------------------------------------------------
@@ -687,6 +695,7 @@ def route_board(
via_half: int | None = None,
edge_clearance: int | None = None,
resolution: int = 1,
heuristic_weight: float = 1.0,
) -> RouteResult:
"""Convenience wrapper: build a :class:`GridRouter` and route the board."""
router = GridRouter(
@@ -701,5 +710,6 @@ def route_board(
via_half=via_half,
edge_clearance=edge_clearance,
resolution=resolution,
heuristic_weight=heuristic_weight,
)
return router.route()
+4
View File
@@ -150,6 +150,7 @@ def route_dsn_board_exact(
dsn: DsnBoard,
*,
resolution: int = 1,
heuristic_weight: float = 1.0,
edge_clearance_mm: float | None = None,
layers: list[int] | None = None,
max_passes: int = 10,
@@ -170,6 +171,7 @@ def route_dsn_board_exact(
exact = route_board_exact(
board,
resolution=resolution,
heuristic_weight=heuristic_weight,
via_half=_via_half_board(dsn, scale),
edge_clearance=edge_clearance_board,
trace_width=width_board,
@@ -187,6 +189,7 @@ def build_exact_routing_result(
dsn: DsnBoard,
*,
resolution: int = 1,
heuristic_weight: float = 1.0,
layers: list[int] | None = None,
shove: bool = False,
diagonal: bool = False,
@@ -204,6 +207,7 @@ def build_exact_routing_result(
max_passes=max_passes,
edge_clearance_mm=edge_clearance_mm,
resolution=resolution,
heuristic_weight=heuristic_weight,
)
return _to_routing_result(exact.result, dsn, scale, layer_names)