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:
@@ -118,6 +118,14 @@ def build_parser() -> argparse.ArgumentParser:
|
|||||||
help="grid and exact engines: cells K times finer than trace + clearance "
|
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",
|
"(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(
|
parser.add_argument(
|
||||||
"--edge-clearance",
|
"--edge-clearance",
|
||||||
type=float,
|
type=float,
|
||||||
@@ -175,6 +183,7 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
max_passes=max_passes,
|
max_passes=max_passes,
|
||||||
edge_clearance_mm=args.edge_clearance,
|
edge_clearance_mm=args.edge_clearance,
|
||||||
resolution=args.grid_resolution,
|
resolution=args.grid_resolution,
|
||||||
|
heuristic_weight=args.heuristic_weight,
|
||||||
)
|
)
|
||||||
elif args.engine == "room":
|
elif args.engine == "room":
|
||||||
result = build_rooms_routing_result(
|
result = build_rooms_routing_result(
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ def route_board_exact(
|
|||||||
via_half: int | None = None,
|
via_half: int | None = None,
|
||||||
edge_clearance: int | None = None,
|
edge_clearance: int | None = None,
|
||||||
resolution: int = 1,
|
resolution: int = 1,
|
||||||
|
heuristic_weight: float = 1.0,
|
||||||
) -> ExactRouteResult:
|
) -> ExactRouteResult:
|
||||||
"""Route ``board`` orthogonally and verify the result with exact geometry.
|
"""Route ``board`` orthogonally and verify the result with exact geometry.
|
||||||
|
|
||||||
@@ -120,6 +121,7 @@ def route_board_exact(
|
|||||||
via_half=via_half,
|
via_half=via_half,
|
||||||
edge_clearance=edge_clearance,
|
edge_clearance=edge_clearance,
|
||||||
resolution=resolution,
|
resolution=resolution,
|
||||||
|
heuristic_weight=heuristic_weight,
|
||||||
)
|
)
|
||||||
edge = 0 if edge_clearance is None else edge_clearance
|
edge = 0 if edge_clearance is None else edge_clearance
|
||||||
outline = board.outline
|
outline = board.outline
|
||||||
|
|||||||
@@ -138,8 +138,10 @@ class GridRouter:
|
|||||||
via_half: int | None = None,
|
via_half: int | None = None,
|
||||||
edge_clearance: int | None = None,
|
edge_clearance: int | None = None,
|
||||||
resolution: int = 1,
|
resolution: int = 1,
|
||||||
|
heuristic_weight: float = 1.0,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.board = board
|
self.board = board
|
||||||
|
self.heuristic_weight = max(float(heuristic_weight), 1.0)
|
||||||
self.trace_width = max(trace_width, 1)
|
self.trace_width = max(trace_width, 1)
|
||||||
self.clearance = max(clearance, 0)
|
self.clearance = max(clearance, 0)
|
||||||
self.half_width = self.trace_width // 2
|
self.half_width = self.trace_width // 2
|
||||||
@@ -471,7 +473,13 @@ class GridRouter:
|
|||||||
heapq.heappush(open_heap, (tentative + self._h(nxt, goal_cell), nxt))
|
heapq.heappush(open_heap, (tentative + self._h(nxt, goal_cell), nxt))
|
||||||
|
|
||||||
def _h(self, node: Node, goal_cell: Cell) -> float:
|
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 -------------------------------------------------------
|
# --- commit / rip -------------------------------------------------------
|
||||||
|
|
||||||
@@ -687,6 +695,7 @@ def route_board(
|
|||||||
via_half: int | None = None,
|
via_half: int | None = None,
|
||||||
edge_clearance: int | None = None,
|
edge_clearance: int | None = None,
|
||||||
resolution: int = 1,
|
resolution: int = 1,
|
||||||
|
heuristic_weight: float = 1.0,
|
||||||
) -> RouteResult:
|
) -> RouteResult:
|
||||||
"""Convenience wrapper: build a :class:`GridRouter` and route the board."""
|
"""Convenience wrapper: build a :class:`GridRouter` and route the board."""
|
||||||
router = GridRouter(
|
router = GridRouter(
|
||||||
@@ -701,5 +710,6 @@ def route_board(
|
|||||||
via_half=via_half,
|
via_half=via_half,
|
||||||
edge_clearance=edge_clearance,
|
edge_clearance=edge_clearance,
|
||||||
resolution=resolution,
|
resolution=resolution,
|
||||||
|
heuristic_weight=heuristic_weight,
|
||||||
)
|
)
|
||||||
return router.route()
|
return router.route()
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ def route_dsn_board_exact(
|
|||||||
dsn: DsnBoard,
|
dsn: DsnBoard,
|
||||||
*,
|
*,
|
||||||
resolution: int = 1,
|
resolution: int = 1,
|
||||||
|
heuristic_weight: float = 1.0,
|
||||||
edge_clearance_mm: float | None = None,
|
edge_clearance_mm: float | None = None,
|
||||||
layers: list[int] | None = None,
|
layers: list[int] | None = None,
|
||||||
max_passes: int = 10,
|
max_passes: int = 10,
|
||||||
@@ -170,6 +171,7 @@ def route_dsn_board_exact(
|
|||||||
exact = route_board_exact(
|
exact = route_board_exact(
|
||||||
board,
|
board,
|
||||||
resolution=resolution,
|
resolution=resolution,
|
||||||
|
heuristic_weight=heuristic_weight,
|
||||||
via_half=_via_half_board(dsn, scale),
|
via_half=_via_half_board(dsn, scale),
|
||||||
edge_clearance=edge_clearance_board,
|
edge_clearance=edge_clearance_board,
|
||||||
trace_width=width_board,
|
trace_width=width_board,
|
||||||
@@ -187,6 +189,7 @@ def build_exact_routing_result(
|
|||||||
dsn: DsnBoard,
|
dsn: DsnBoard,
|
||||||
*,
|
*,
|
||||||
resolution: int = 1,
|
resolution: int = 1,
|
||||||
|
heuristic_weight: float = 1.0,
|
||||||
layers: list[int] | None = None,
|
layers: list[int] | None = None,
|
||||||
shove: bool = False,
|
shove: bool = False,
|
||||||
diagonal: bool = False,
|
diagonal: bool = False,
|
||||||
@@ -204,6 +207,7 @@ def build_exact_routing_result(
|
|||||||
max_passes=max_passes,
|
max_passes=max_passes,
|
||||||
edge_clearance_mm=edge_clearance_mm,
|
edge_clearance_mm=edge_clearance_mm,
|
||||||
resolution=resolution,
|
resolution=resolution,
|
||||||
|
heuristic_weight=heuristic_weight,
|
||||||
)
|
)
|
||||||
return _to_routing_result(exact.result, dsn, scale, layer_names)
|
return _to_routing_result(exact.result, dsn, scale, layer_names)
|
||||||
|
|
||||||
|
|||||||
@@ -73,3 +73,24 @@ def test_resolution_one_has_no_halo_so_the_grid_is_unchanged():
|
|||||||
assert GridRouter(board, trace_width=2500, clearance=1500)._halo_offsets == []
|
assert GridRouter(board, trace_width=2500, clearance=1500)._halo_offsets == []
|
||||||
fine = GridRouter(board, trace_width=2500, clearance=1500, resolution=2)
|
fine = GridRouter(board, trace_width=2500, clearance=1500, resolution=2)
|
||||||
assert fine.step == 2000 and (1, 0) in fine._halo_offsets and (2, 0) not in fine._halo_offsets
|
assert fine.step == 2000 and (1, 0) in fine._halo_offsets and (2, 0) not in fine._halo_offsets
|
||||||
|
|
||||||
|
|
||||||
|
def test_heuristic_weight_bounds_route_length():
|
||||||
|
# weighted A* may take a longer route, but never more than W times the shortest
|
||||||
|
places = "(place A 3000 -3000 front 0) (place B 17000 -17000 front 0)"
|
||||||
|
|
||||||
|
def length(weight):
|
||||||
|
result, _, _ = route_dsn_board_exact(
|
||||||
|
parse_dsn(_dsn(f"(component P {places})", "(net N (pins A-1 B-1))")),
|
||||||
|
heuristic_weight=weight,
|
||||||
|
)
|
||||||
|
return sum(
|
||||||
|
math.hypot(q.x - p.x, q.y - p.y)
|
||||||
|
for segs in result.result.wires.values()
|
||||||
|
for _layer, pts in segs
|
||||||
|
for p, q in zip(pts, pts[1:], strict=False)
|
||||||
|
)
|
||||||
|
|
||||||
|
shortest = length(1.0)
|
||||||
|
assert shortest > 0
|
||||||
|
assert length(2.0) <= 2.0 * shortest
|
||||||
|
|||||||
Reference in New Issue
Block a user