From f2b2bf22f9f96e061ef5d5a66d64b387c5b9e2e8 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 29 Mar 2026 15:46:37 -0700 Subject: [PATCH] Add refinement by default --- examples/03_locked_paths.png | Bin 73527 -> 65072 bytes inire/router/pathfinder.py | 231 ++++++++++++++++++++++++++++----- inire/tests/test_pathfinder.py | 95 ++++++++++++++ 3 files changed, 294 insertions(+), 32 deletions(-) diff --git a/examples/03_locked_paths.png b/examples/03_locked_paths.png index d767df93276e33e5ece68343026ef212e8cf323e..687dad6aad6d507bf1353f267c4535de1b2de61d 100644 GIT binary patch delta 31562 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≶DP$a&IOhxChuZOuje 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 delta 40084 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 None: self.context = context self.metrics = metrics if metrics is not None else AStarMetrics() @@ -158,6 +158,11 @@ class PathFinder: all_dilated.extend([p.buffer(dilation) for p in res.geometry]) return all_geoms, all_dilated + def _path_ports(self, start: Port, path: list[ComponentResult]) -> list[Port]: + ports = [start] + ports.extend(comp.end_port for comp in path) + return ports + def _to_local(self, start: Port, point: Port) -> tuple[int, int]: dx = point.x - start.x dy = point.y - start.y @@ -169,6 +174,112 @@ class PathFinder: return -dx, -dy return -dy, dx + def _to_local_xy(self, start: Port, x: float, y: float) -> tuple[float, float]: + dx = float(x) - start.x + dy = float(y) - start.y + if start.r == 0: + return dx, dy + if start.r == 90: + return dy, -dx + if start.r == 180: + return -dx, -dy + return -dy, dx + + def _window_query_bounds(self, start: Port, target: Port, path: list[ComponentResult], pad: float) -> tuple[float, float, float, float]: + min_x = float(min(start.x, target.x)) + min_y = float(min(start.y, target.y)) + max_x = float(max(start.x, target.x)) + max_y = float(max(start.y, target.y)) + for comp in path: + bounds = comp.total_bounds + min_x = min(min_x, bounds[0]) + min_y = min(min_y, bounds[1]) + max_x = max(max_x, bounds[2]) + max_y = max(max_y, bounds[3]) + return (min_x - pad, min_y - pad, max_x + pad, max_y + pad) + + def _candidate_side_extents( + self, + start: Port, + target: Port, + window_path: list[ComponentResult], + net_width: float, + radius: float, + ) -> list[float]: + local_dx, local_dy = self._to_local(start, target) + if local_dx < 4.0 * radius - 0.01: + return [] + + local_points = [self._to_local(start, start)] + local_points.extend(self._to_local(start, comp.end_port) for comp in window_path) + min_side = float(min(point[1] for point in local_points)) + max_side = float(max(point[1] for point in local_points)) + + positive_anchors: set[float] = set() + negative_anchors: set[float] = set() + direct_extents: set[float] = set() + + if max_side > 0.01: + positive_anchors.add(max_side) + direct_extents.add(max_side) + if min_side < -0.01: + negative_anchors.add(min_side) + direct_extents.add(min_side) + if local_dy > 0: + positive_anchors.add(float(local_dy)) + elif local_dy < 0: + negative_anchors.add(float(local_dy)) + + collision_engine = self.cost_evaluator.collision_engine + pad = 2.0 * radius + collision_engine.clearance + net_width + query_bounds = self._window_query_bounds(start, target, window_path, pad) + x_min = min(0.0, float(local_dx)) - 0.01 + x_max = max(0.0, float(local_dx)) + 0.01 + + for obj_id in collision_engine.static_index.intersection(query_bounds): + bounds = collision_engine.static_geometries[obj_id].bounds + local_corners = ( + self._to_local_xy(start, bounds[0], bounds[1]), + self._to_local_xy(start, bounds[0], bounds[3]), + self._to_local_xy(start, bounds[2], bounds[1]), + self._to_local_xy(start, bounds[2], bounds[3]), + ) + obs_min_x = min(pt[0] for pt in local_corners) + obs_max_x = max(pt[0] for pt in local_corners) + if obs_max_x < x_min or obs_min_x > x_max: + continue + obs_min_y = min(pt[1] for pt in local_corners) + obs_max_y = max(pt[1] for pt in local_corners) + positive_anchors.add(obs_max_y) + negative_anchors.add(obs_min_y) + + for obj_id in collision_engine.dynamic_index.intersection(query_bounds): + _, poly = collision_engine.dynamic_geometries[obj_id] + bounds = poly.bounds + local_corners = ( + self._to_local_xy(start, bounds[0], bounds[1]), + self._to_local_xy(start, bounds[0], bounds[3]), + self._to_local_xy(start, bounds[2], bounds[1]), + self._to_local_xy(start, bounds[2], bounds[3]), + ) + obs_min_x = min(pt[0] for pt in local_corners) + obs_max_x = max(pt[0] for pt in local_corners) + if obs_max_x < x_min or obs_min_x > x_max: + continue + obs_min_y = min(pt[1] for pt in local_corners) + obs_max_y = max(pt[1] for pt in local_corners) + positive_anchors.add(obs_max_y) + negative_anchors.add(obs_min_y) + + for anchor in tuple(positive_anchors): + if anchor > max(0.0, float(local_dy)) - 0.01: + direct_extents.add(anchor + pad) + for anchor in tuple(negative_anchors): + if anchor < min(0.0, float(local_dy)) + 0.01: + direct_extents.add(anchor - pad) + + return sorted(direct_extents, key=lambda value: (abs(value), value)) + def _build_same_orientation_dogleg( self, start: Port, @@ -178,17 +289,25 @@ class PathFinder: side_extent: float, ) -> list[ComponentResult] | None: local_dx, local_dy = self._to_local(start, target) - if abs(local_dy) > 0 or local_dx < 4.0 * radius - 0.01: + if local_dx < 4.0 * radius - 0.01 or abs(side_extent) < 0.01: return None side_abs = abs(side_extent) - side_length = side_abs - 2.0 * radius - if side_length < self.context.config.min_straight_length - 0.01: + first_straight = side_abs - 2.0 * radius + second_straight = side_abs - 2.0 * radius - math.copysign(float(local_dy), side_extent) + if first_straight < -0.01 or second_straight < -0.01: + return None + min_straight = self.context.config.min_straight_length + if 0.01 < first_straight < min_straight - 0.01: + return None + if 0.01 < second_straight < min_straight - 0.01: return None forward_length = local_dx - 4.0 * radius if forward_length < -0.01: return None + if 0.01 < forward_length < min_straight - 0.01: + return None first_dir = "CCW" if side_extent > 0 else "CW" second_dir = "CW" if side_extent > 0 else "CCW" @@ -198,9 +317,9 @@ class PathFinder: curr = start for direction, straight_len in ( - (first_dir, side_length), + (first_dir, first_straight), (second_dir, forward_length), - (second_dir, side_length), + (second_dir, second_straight), (first_dir, None), ): bend = Bend90.generate(curr, radius, net_width, direction, dilation=dilation) @@ -217,6 +336,68 @@ class PathFinder: return None return path + def _iter_refinement_windows(self, start: Port, path: list[ComponentResult]) -> list[tuple[int, int]]: + ports = self._path_ports(start, path) + windows: list[tuple[int, int]] = [] + min_radius = min(self.context.config.bend_radii, default=0.0) + + for window_size in range(len(path), 0, -1): + for start_idx in range(0, len(path) - window_size + 1): + end_idx = start_idx + window_size + window = path[start_idx:end_idx] + bend_count = sum(1 for comp in window if comp.move_type == "Bend90") + if bend_count < 4: + continue + window_start = ports[start_idx] + window_end = ports[end_idx] + if window_start.r != window_end.r: + continue + local_dx, _ = self._to_local(window_start, window_end) + if local_dx < 4.0 * min_radius - 0.01: + continue + windows.append((start_idx, end_idx)) + return windows + + def _try_refine_window( + self, + net_id: str, + start: Port, + net_width: float, + path: list[ComponentResult], + start_idx: int, + end_idx: int, + best_cost: float, + ) -> tuple[list[ComponentResult], float] | None: + ports = self._path_ports(start, path) + window_start = ports[start_idx] + window_end = ports[end_idx] + window_path = path[start_idx:end_idx] + collision_engine = self.cost_evaluator.collision_engine + + best_path: list[ComponentResult] | None = None + best_candidate_cost = best_cost + + for radius in self.context.config.bend_radii: + side_extents = self._candidate_side_extents(window_start, window_end, window_path, net_width, radius) + for side_extent in side_extents: + replacement = self._build_same_orientation_dogleg(window_start, window_end, net_width, radius, side_extent) + if replacement is None: + continue + candidate_path = path[:start_idx] + replacement + path[end_idx:] + if self._has_self_collision(candidate_path): + continue + is_valid, collisions = collision_engine.verify_path(net_id, candidate_path) + if not is_valid or collisions != 0: + continue + candidate_cost = self._path_cost(candidate_path) + if candidate_cost + 1e-6 < best_candidate_cost: + best_candidate_cost = candidate_cost + best_path = candidate_path + + if best_path is None: + return None + return best_path, best_candidate_cost + def _refine_path( self, net_id: str, @@ -225,41 +406,27 @@ class PathFinder: net_width: float, path: list[ComponentResult], ) -> list[ComponentResult]: - if not path or start.r != target.r: + if not path: return path bend_count = sum(1 for comp in path if comp.move_type == "Bend90") - if bend_count < 5: - return path - - side_extents = [] - local_points = [self._to_local(start, start)] - local_points.extend(self._to_local(start, comp.end_port) for comp in path) - min_side = min(point[1] for point in local_points) - max_side = max(point[1] for point in local_points) - if min_side < -0.01: - side_extents.append(float(min_side)) - if max_side > 0.01: - side_extents.append(float(max_side)) - if not side_extents: + if bend_count < 4: return path best_path = path best_cost = self._path_cost(path) - collision_engine = self.cost_evaluator.collision_engine - for radius in self.context.config.bend_radii: - for side_extent in side_extents: - candidate = self._build_same_orientation_dogleg(start, target, net_width, radius, side_extent) - if candidate is None: + for _ in range(3): + improved = False + for start_idx, end_idx in self._iter_refinement_windows(start, best_path): + refined = self._try_refine_window(net_id, start, net_width, best_path, start_idx, end_idx, best_cost) + if refined is None: continue - is_valid, collisions = collision_engine.verify_path(net_id, candidate) - if not is_valid or collisions != 0: - continue - candidate_cost = self._path_cost(candidate) - if candidate_cost + 1e-6 < best_cost: - best_cost = candidate_cost - best_path = candidate + best_path, best_cost = refined + improved = True + break + if not improved: + break return best_path diff --git a/inire/tests/test_pathfinder.py b/inire/tests/test_pathfinder.py index 252a96e..f6923c4 100644 --- a/inire/tests/test_pathfinder.py +++ b/inire/tests/test_pathfinder.py @@ -1,6 +1,7 @@ import pytest from inire.geometry.collision import CollisionEngine +from inire.geometry.components import Bend90, Straight from inire.geometry.primitives import Port from inire.router.astar import AStarContext from inire.router.cost import CostEvaluator @@ -16,6 +17,20 @@ def basic_evaluator() -> CostEvaluator: return CostEvaluator(engine, danger_map) +def _build_manual_path(start: Port, width: float, clearance: float, steps: list[tuple[str, float | str]]) -> list: + path = [] + curr = start + dilation = clearance / 2.0 + for kind, value in steps: + if kind == "B": + comp = Bend90.generate(curr, 5.0, width, value, dilation=dilation) + else: + comp = Straight.generate(curr, value, width, dilation=dilation) + path.append(comp) + curr = comp.end_port + return path + + def test_pathfinder_parallel(basic_evaluator: CostEvaluator) -> None: context = AStarContext(basic_evaluator) pf = PathFinder(context) @@ -116,3 +131,83 @@ def test_pathfinder_refine_paths_simplifies_triple_crossing_detours() -> None: assert base_result.is_valid assert refined_result.is_valid assert refined_bends < base_bends + + +def test_refine_path_handles_same_orientation_lateral_offset() -> None: + engine = CollisionEngine(clearance=2.0) + danger_map = DangerMap(bounds=(-20, -20, 120, 120)) + danger_map.precompute([]) + evaluator = CostEvaluator(engine, danger_map, bend_penalty=250.0, sbend_penalty=500.0) + context = AStarContext(evaluator, bend_radii=[5.0, 10.0]) + pf = PathFinder(context, refine_paths=True) + + start = Port(0, 0, 0) + width = 2.0 + path = _build_manual_path( + start, + width, + engine.clearance, + [ + ("B", "CCW"), + ("S", 10.0), + ("B", "CW"), + ("S", 20.0), + ("B", "CW"), + ("S", 10.0), + ("B", "CCW"), + ("S", 10.0), + ("B", "CCW"), + ("S", 5.0), + ("B", "CW"), + ], + ) + target = path[-1].end_port + + refined = pf._refine_path("net", start, target, width, path) + + assert target == Port(60, 15, 0) + assert sum(1 for comp in path if comp.move_type == "Bend90") == 6 + assert sum(1 for comp in refined if comp.move_type == "Bend90") == 4 + assert refined[-1].end_port == target + assert pf._path_cost(refined) < pf._path_cost(path) + + +def test_refine_path_can_simplify_subpath_with_different_global_orientation() -> None: + engine = CollisionEngine(clearance=2.0) + danger_map = DangerMap(bounds=(-20, -20, 120, 120)) + danger_map.precompute([]) + evaluator = CostEvaluator(engine, danger_map, bend_penalty=250.0, sbend_penalty=500.0) + context = AStarContext(evaluator, bend_radii=[5.0, 10.0]) + pf = PathFinder(context, refine_paths=True) + + start = Port(0, 0, 0) + width = 2.0 + path = _build_manual_path( + start, + width, + engine.clearance, + [ + ("B", "CCW"), + ("S", 10.0), + ("B", "CW"), + ("S", 20.0), + ("B", "CW"), + ("S", 10.0), + ("B", "CCW"), + ("S", 10.0), + ("B", "CCW"), + ("S", 5.0), + ("B", "CW"), + ("B", "CCW"), + ("S", 10.0), + ], + ) + target = path[-1].end_port + + refined = pf._refine_path("net", start, target, width, path) + + assert target == Port(65, 30, 90) + assert sum(1 for comp in path if comp.move_type == "Bend90") == 7 + assert sum(1 for comp in refined if comp.move_type == "Bend90") == 5 + assert refined[-1].end_port == target + assert pf._path_cost(refined) < pf._path_cost(path)