linter fixes
This commit is contained in:
parent
e2c91076f7
commit
1849075b11
26 changed files with 152 additions and 104 deletions
|
|
@ -37,7 +37,12 @@ def main() -> None:
|
|||
else:
|
||||
print("The route unexpectedly reached the target. Increase difficulty or reduce the node budget further.")
|
||||
|
||||
fig, _ax = plot_routing_results(run.results_by_net, list(obstacles), bounds, netlist={"budget_limited_net": (Port(10, 50, 0), Port(85, 60, 180))})
|
||||
fig, _ax = plot_routing_results(
|
||||
run.results_by_net,
|
||||
list(obstacles),
|
||||
bounds,
|
||||
netlist={"budget_limited_net": (Port(10, 50, 0), Port(85, 60, 180))},
|
||||
)
|
||||
fig.savefig("examples/09_unroutable_best_effort.png")
|
||||
print("Saved plot to examples/09_unroutable_best_effort.png")
|
||||
|
||||
|
|
|
|||
|
|
@ -105,9 +105,16 @@ class RoutingWorld:
|
|||
return reach < length - 0.001
|
||||
|
||||
def _is_in_safety_zone_fast(self, idx: int, start_port: Port | None, end_port: Port | None) -> bool:
|
||||
bounds = self._static_obstacles.bounds_array[idx]
|
||||
bounds_array = self._static_obstacles.bounds_array
|
||||
if bounds_array is None:
|
||||
return False
|
||||
bounds = bounds_array[idx]
|
||||
safety_zone = self.safety_zone_radius
|
||||
if start_port and bounds[0] - safety_zone <= start_port.x <= bounds[2] + safety_zone and bounds[1] - safety_zone <= start_port.y <= bounds[3] + safety_zone:
|
||||
if (
|
||||
start_port
|
||||
and bounds[0] - safety_zone <= start_port.x <= bounds[2] + safety_zone
|
||||
and bounds[1] - safety_zone <= start_port.y <= bounds[3] + safety_zone
|
||||
):
|
||||
return True
|
||||
return bool(
|
||||
end_port
|
||||
|
|
@ -176,15 +183,18 @@ class RoutingWorld:
|
|||
return False
|
||||
|
||||
self._ensure_static_tree()
|
||||
tree = static_obstacles.tree
|
||||
bounds_array = static_obstacles.bounds_array
|
||||
if tree is None or bounds_array is None:
|
||||
return False
|
||||
|
||||
hits = static_obstacles.tree.query(box(*result.total_dilated_bounds))
|
||||
hits = tree.query(box(*result.total_dilated_bounds))
|
||||
if hits.size == 0:
|
||||
return False
|
||||
|
||||
static_bounds = static_obstacles.bounds_array
|
||||
move_poly_bounds = result.dilated_bounds
|
||||
for hit_idx in hits:
|
||||
obstacle_bounds = static_bounds[hit_idx]
|
||||
obstacle_bounds = bounds_array[hit_idx]
|
||||
poly_hits_obstacle_aabb = False
|
||||
for poly_bounds in move_poly_bounds:
|
||||
if (
|
||||
|
|
@ -319,7 +329,7 @@ class RoutingWorld:
|
|||
raw_geometries = static_obstacles.raw_tree.geometries
|
||||
for component in components:
|
||||
for polygon in component.physical_geometry:
|
||||
buffered = polygon.buffer(self.clearance, join_style=2)
|
||||
buffered = polygon.buffer(self.clearance, join_style="mitre")
|
||||
hits = static_obstacles.raw_tree.query(buffered, predicate="intersects")
|
||||
for hit_idx in hits:
|
||||
obstacle = raw_geometries[hit_idx]
|
||||
|
|
@ -373,6 +383,9 @@ class RoutingWorld:
|
|||
net_width: float | None = None,
|
||||
) -> float:
|
||||
static_obstacles = self._static_obstacles
|
||||
tree: STRtree | None
|
||||
is_rect_array: numpy.ndarray | None
|
||||
bounds_array: numpy.ndarray | None
|
||||
|
||||
radians = numpy.radians(angle_deg)
|
||||
cos_v, sin_v = numpy.cos(radians), numpy.sin(radians)
|
||||
|
|
@ -391,7 +404,7 @@ class RoutingWorld:
|
|||
is_rect_array = static_obstacles.is_rect_array
|
||||
bounds_array = static_obstacles.bounds_array
|
||||
|
||||
if tree is None:
|
||||
if tree is None or is_rect_array is None or bounds_array is None:
|
||||
return max_dist
|
||||
|
||||
candidates = tree.query(box(min_x, min_y, max_x, max_y))
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
from shapely.geometry import Polygon
|
||||
|
||||
from inire.geometry.components import ComponentResult
|
||||
|
|
@ -13,8 +14,8 @@ def components_overlap(
|
|||
component_b: ComponentResult,
|
||||
prefer_actual: bool = False,
|
||||
) -> bool:
|
||||
polygons_a: tuple[Polygon, ...]
|
||||
polygons_b: tuple[Polygon, ...]
|
||||
polygons_a: Sequence[Polygon]
|
||||
polygons_b: Sequence[Polygon]
|
||||
if prefer_actual:
|
||||
polygons_a = component_a.physical_geometry
|
||||
polygons_b = component_b.physical_geometry
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Literal
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
|
||||
import numpy
|
||||
from shapely.affinity import rotate as shapely_rotate
|
||||
|
|
@ -13,6 +13,9 @@ from inire.constants import TOLERANCE_ANGULAR
|
|||
from inire.seeds import Bend90Seed, PathSegmentSeed, SBendSeed, StraightSeed
|
||||
from .primitives import Port, rotation_matrix2
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
|
||||
|
||||
MoveKind = Literal["straight", "bend90", "sbend"]
|
||||
BendCollisionModelName = Literal["arc", "bbox", "clipped_bbox"]
|
||||
|
|
@ -27,14 +30,14 @@ def _normalize_length(value: float) -> float:
|
|||
@dataclass(frozen=True, slots=True)
|
||||
class ComponentResult:
|
||||
start_port: Port
|
||||
collision_geometry: tuple[Polygon, ...]
|
||||
collision_geometry: Sequence[Polygon]
|
||||
end_port: Port
|
||||
length: float
|
||||
move_type: MoveKind
|
||||
move_spec: PathSegmentSeed
|
||||
physical_geometry: tuple[Polygon, ...]
|
||||
dilated_collision_geometry: tuple[Polygon, ...]
|
||||
dilated_physical_geometry: tuple[Polygon, ...]
|
||||
physical_geometry: Sequence[Polygon]
|
||||
dilated_collision_geometry: Sequence[Polygon]
|
||||
dilated_physical_geometry: Sequence[Polygon]
|
||||
_bounds: tuple[tuple[float, float, float, float], ...] = field(init=False, repr=False)
|
||||
_total_bounds: tuple[float, float, float, float] = field(init=False, repr=False)
|
||||
_dilated_bounds: tuple[tuple[float, float, float, float], ...] = field(init=False, repr=False)
|
||||
|
|
@ -146,7 +149,7 @@ def _clip_bbox_legacy(
|
|||
minx, miny, maxx, maxy = arc_poly.bounds
|
||||
bbox_poly = box(minx, miny, maxx, maxy)
|
||||
shrink = min(clip_margin, max(radius, width))
|
||||
return bbox_poly.buffer(-shrink, join_style=2) if shrink > 0 else bbox_poly
|
||||
return bbox_poly.buffer(-shrink, join_style="mitre") if shrink > 0 else bbox_poly
|
||||
|
||||
|
||||
def _clip_bbox_polygonal(cxy: tuple[float, float], radius: float, width: float, ts: tuple[float, float]) -> Polygon:
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from collections.abc import Iterator, Mapping
|
||||
from typing import TypeVar
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import numpy
|
||||
|
||||
GeometryT = TypeVar("GeometryT")
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator, Mapping
|
||||
from shapely.geometry.base import BaseGeometry
|
||||
|
||||
|
||||
def build_index_payload(
|
||||
geometries: Mapping[int, GeometryT],
|
||||
) -> tuple[list[int], list[GeometryT], numpy.ndarray]:
|
||||
geometries: Mapping[int, BaseGeometry],
|
||||
) -> tuple[list[int], list[BaseGeometry], numpy.ndarray]:
|
||||
obj_ids = sorted(geometries)
|
||||
ordered_geometries = [geometries[obj_id] for obj_id in obj_ids]
|
||||
bounds_array = numpy.array([geometry.bounds for geometry in ordered_geometries], dtype=numpy.float64)
|
||||
|
|
@ -42,7 +43,7 @@ def iter_grid_cells(
|
|||
yield (gx, gy)
|
||||
|
||||
|
||||
def is_axis_aligned_rect(geometry, *, tolerance: float = 1e-4) -> bool:
|
||||
def is_axis_aligned_rect(geometry: BaseGeometry, *, tolerance: float = 1e-4) -> bool:
|
||||
bounds = geometry.bounds
|
||||
area = (bounds[2] - bounds[0]) * (bounds[3] - bounds[1])
|
||||
return abs(geometry.area - area) < tolerance
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Self
|
||||
from typing import TYPE_CHECKING, Self
|
||||
|
||||
import numpy
|
||||
from numpy.typing import NDArray
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from numpy.typing import NDArray
|
||||
|
||||
|
||||
def _normalize_angle(angle_deg: int | float) -> int:
|
||||
|
|
@ -58,6 +60,6 @@ ROT2_180 = numpy.array(((-1, 0), (0, -1)), dtype=numpy.int32)
|
|||
ROT2_270 = numpy.array(((0, 1), (-1, 0)), dtype=numpy.int32)
|
||||
|
||||
|
||||
def rotation_matrix2(rotation_deg: int) -> NDArray[numpy.int32]:
|
||||
def rotation_matrix2(rotation_deg: int | float) -> NDArray[numpy.int32]:
|
||||
quadrant = (_normalize_angle(rotation_deg) // 90) % 4
|
||||
return (ROT2_0, ROT2_90, ROT2_180, ROT2_270)[quadrant]
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class StaticObstacleIndex:
|
|||
if dilated_geometry is not None:
|
||||
dilated = dilated_geometry
|
||||
else:
|
||||
dilated = polygon.buffer(self.engine.clearance / 2.0, join_style=2)
|
||||
dilated = polygon.buffer(self.engine.clearance / 2.0, join_style="mitre")
|
||||
|
||||
self.geometries[obj_id] = polygon
|
||||
self.dilated[obj_id] = dilated
|
||||
|
|
@ -109,7 +109,7 @@ class StaticObstacleIndex:
|
|||
|
||||
for obj_id in sorted(self.geometries.keys()):
|
||||
polygon = self.geometries[obj_id]
|
||||
dilated = polygon.buffer(total_dilation, join_style=2)
|
||||
dilated = polygon.buffer(total_dilation, join_style="mitre")
|
||||
geometries.append(dilated)
|
||||
bounds_list.append(dilated.bounds)
|
||||
is_rect_list.append(is_axis_aligned_rect(dilated))
|
||||
|
|
|
|||
|
|
@ -5,11 +5,10 @@ from dataclasses import dataclass, field
|
|||
from typing import TYPE_CHECKING, Literal
|
||||
|
||||
from shapely.geometry import Polygon
|
||||
|
||||
from inire.geometry.components import BendCollisionModel, BendPhysicalGeometry
|
||||
from inire.seeds import PathSeed
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from inire.geometry.components import BendCollisionModel, BendPhysicalGeometry
|
||||
from inire.geometry.primitives import Port
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class RoutingResult:
|
|||
|
||||
@property
|
||||
def locked_geometry(self) -> tuple[Polygon, ...]:
|
||||
polygons = []
|
||||
polygons: list[Polygon] = []
|
||||
for component in self.path:
|
||||
polygons.extend(component.physical_geometry)
|
||||
return tuple(polygons)
|
||||
|
|
|
|||
|
|
@ -94,7 +94,6 @@ def process_move(
|
|||
res = res_rel.translate(cp.x, cp.y)
|
||||
context.move_cache_abs[abs_key] = res
|
||||
|
||||
move_radius = params[0] if move_class == "bend90" else (params[1] if move_class == "sbend" else None)
|
||||
add_node(
|
||||
parent,
|
||||
res,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from inire.constants import TOLERANCE_LINEAR
|
||||
from inire.geometry.components import MoveKind
|
||||
from inire.geometry.primitives import Port
|
||||
|
||||
from ._astar_admission import process_move
|
||||
from ._astar_types import AStarContext, AStarMetrics, AStarNode, SearchRunConfig
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from inire.geometry.components import MoveKind
|
||||
from inire.geometry.primitives import Port
|
||||
from ._astar_types import AStarContext, AStarMetrics, AStarNode, SearchRunConfig
|
||||
|
||||
|
||||
def _quantized_lengths(values: list[float], max_reach: float) -> list[int]:
|
||||
|
|
@ -96,7 +99,7 @@ def _visible_straight_candidates(
|
|||
return []
|
||||
|
||||
collision_engine = context.cost_evaluator.collision_engine
|
||||
candidates: set[int] = set()
|
||||
tangent_candidates: set[int] = set()
|
||||
for _, dist, length, dx, dy in sorted(scored)[:4]:
|
||||
angle = math.degrees(math.atan2(dy, dx))
|
||||
corner_reach = collision_engine.ray_cast(current, angle, max_dist=dist + 0.05, net_width=net_width)
|
||||
|
|
@ -104,9 +107,9 @@ def _visible_straight_candidates(
|
|||
continue
|
||||
qlen = int(round(length))
|
||||
if qlen > 0:
|
||||
candidates.add(qlen)
|
||||
tangent_candidates.add(qlen)
|
||||
|
||||
return sorted(candidates, reverse=True)
|
||||
return sorted(tangent_candidates, reverse=True)
|
||||
|
||||
|
||||
def _previous_move_metadata(node: AStarNode) -> tuple[MoveKind | None, float | None]:
|
||||
|
|
|
|||
|
|
@ -3,13 +3,14 @@ from __future__ import annotations
|
|||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from inire.geometry.components import BendCollisionModel, BendPhysicalGeometry
|
||||
from inire.model import RoutingOptions, RoutingProblem, resolve_bend_geometry
|
||||
from inire.model import resolve_bend_geometry
|
||||
from inire.results import RouteMetrics
|
||||
from inire.router.visibility import VisibilityManager
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from inire.geometry.components import ComponentResult
|
||||
from inire.geometry.components import BendCollisionModel, BendPhysicalGeometry, ComponentResult
|
||||
from inire.geometry.primitives import Port
|
||||
from inire.model import RoutingOptions, RoutingProblem
|
||||
from inire.router.cost import CostEvaluator
|
||||
|
||||
|
||||
|
|
@ -61,7 +62,7 @@ class AStarNode:
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
port,
|
||||
port: Port,
|
||||
g_cost: float,
|
||||
h_cost: float,
|
||||
parent: AStarNode | None = None,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ from inire.router.refiner import PathRefiner
|
|||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable, Sequence
|
||||
|
||||
from shapely.geometry import Polygon
|
||||
|
||||
from inire.geometry.components import ComponentResult
|
||||
|
||||
|
||||
|
|
@ -48,8 +50,8 @@ class PathFinder:
|
|||
self.accumulated_expanded_nodes: list[tuple[int, int, int]] = []
|
||||
|
||||
def _install_path(self, net_id: str, path: Sequence[ComponentResult]) -> None:
|
||||
all_geoms = []
|
||||
all_dilated = []
|
||||
all_geoms: list[Polygon] = []
|
||||
all_dilated: list[Polygon] = []
|
||||
for result in path:
|
||||
all_geoms.extend(result.collision_geometry)
|
||||
all_dilated.extend(result.dilated_collision_geometry)
|
||||
|
|
@ -215,7 +217,7 @@ class PathFinder:
|
|||
|
||||
return RoutingResult(
|
||||
net_id=net_id,
|
||||
path=path,
|
||||
path=tuple(path),
|
||||
reached_target=reached_target,
|
||||
report=RoutingReport() if report is None else report,
|
||||
)
|
||||
|
|
@ -276,7 +278,7 @@ class PathFinder:
|
|||
report = self.context.cost_evaluator.collision_engine.verify_path_report(net_id, refined_path)
|
||||
state.results[net_id] = RoutingResult(
|
||||
net_id=net_id,
|
||||
path=refined_path,
|
||||
path=tuple(refined_path),
|
||||
reached_target=result.reached_target,
|
||||
report=report,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ import heapq
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from inire.constants import TOLERANCE_LINEAR
|
||||
from inire.geometry.primitives import Port
|
||||
|
||||
from ._astar_moves import expand_moves as _expand_moves
|
||||
from ._astar_types import AStarContext, AStarMetrics, AStarNode as _AStarNode, SearchRunConfig
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from inire.geometry.components import ComponentResult
|
||||
from inire.geometry.primitives import Port
|
||||
|
||||
|
||||
def _reconstruct_path(end_node: _AStarNode) -> list[ComponentResult]:
|
||||
|
|
|
|||
|
|
@ -1,17 +1,24 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from inire.model import RoutingOptions, RoutingProblem
|
||||
if TYPE_CHECKING:
|
||||
from inire.geometry.collision import RoutingWorld
|
||||
from inire.model import RoutingOptions, RoutingProblem
|
||||
from inire.router._astar_types import AStarContext
|
||||
from inire.router._router import PathFinder
|
||||
from inire.router.cost import CostEvaluator
|
||||
from inire.router.danger_map import DangerMap
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class RoutingStack:
|
||||
world: object
|
||||
danger_map: object
|
||||
evaluator: object
|
||||
context: object
|
||||
finder: object
|
||||
world: RoutingWorld
|
||||
danger_map: DangerMap
|
||||
evaluator: CostEvaluator
|
||||
context: AStarContext
|
||||
finder: PathFinder
|
||||
|
||||
|
||||
def build_routing_stack(problem: RoutingProblem, options: RoutingOptions) -> RoutingStack:
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from inire.constants import TOLERANCE_LINEAR
|
|||
from inire.model import ObjectiveWeights
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
from inire.geometry.collision import RoutingWorld
|
||||
from inire.geometry.components import ComponentResult, MoveKind
|
||||
from inire.geometry.primitives import Port
|
||||
|
|
@ -71,7 +72,7 @@ class CostEvaluator:
|
|||
def set_target(self, target: Port) -> None:
|
||||
self._target_x = target.x
|
||||
self._target_y = target.y
|
||||
self._target_r = target.r
|
||||
self._target_r = int(target.r)
|
||||
rad = np.radians(target.r)
|
||||
self._target_cos = np.cos(rad)
|
||||
self._target_sin = np.sin(rad)
|
||||
|
|
@ -176,7 +177,7 @@ class CostEvaluator:
|
|||
def path_cost(
|
||||
self,
|
||||
start_port: Port,
|
||||
path: list[ComponentResult],
|
||||
path: Sequence[ComponentResult],
|
||||
*,
|
||||
weights: ObjectiveWeights | None = None,
|
||||
) -> float:
|
||||
|
|
|
|||
|
|
@ -51,14 +51,14 @@ class DangerMap:
|
|||
for poly in obstacles:
|
||||
# Sample exterior
|
||||
exterior = poly.exterior
|
||||
dist = 0
|
||||
dist = 0.0
|
||||
while dist < exterior.length:
|
||||
pt = exterior.interpolate(dist)
|
||||
all_points.append((pt.x, pt.y))
|
||||
dist += self.resolution
|
||||
# Sample interiors (holes)
|
||||
for interior in poly.interiors:
|
||||
dist = 0
|
||||
dist = 0.0
|
||||
while dist < interior.length:
|
||||
pt = interior.interpolate(dist)
|
||||
all_points.append((pt.x, pt.y))
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import TYPE_CHECKING, Any, Literal
|
||||
|
||||
from inire.geometry.component_overlap import components_overlap
|
||||
from inire.geometry.components import Bend90, Straight
|
||||
|
|
@ -55,7 +55,7 @@ class PathRefiner:
|
|||
ports.extend(comp.end_port for comp in path)
|
||||
return ports
|
||||
|
||||
def _to_local(self, start: Port, point: Port) -> tuple[int, int]:
|
||||
def _to_local(self, start: Port, point: Port) -> tuple[float, float]:
|
||||
dx = point.x - start.x
|
||||
dy = point.y - start.y
|
||||
if start.r == 0:
|
||||
|
|
@ -197,8 +197,8 @@ class PathRefiner:
|
|||
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"
|
||||
first_dir: Literal["CW", "CCW"] = "CCW" if side_extent > 0 else "CW"
|
||||
second_dir: Literal["CW", "CCW"] = "CW" if side_extent > 0 else "CCW"
|
||||
dilation = self.collision_engine.clearance / 2.0
|
||||
|
||||
path: list[ComponentResult] = []
|
||||
|
|
@ -288,10 +288,10 @@ class PathRefiner:
|
|||
net_id: str,
|
||||
start: Port,
|
||||
net_width: float,
|
||||
path: list[ComponentResult],
|
||||
path: Sequence[ComponentResult],
|
||||
) -> list[ComponentResult]:
|
||||
if not path:
|
||||
return path
|
||||
return list(path)
|
||||
|
||||
path = list(path)
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class VisibilityManager:
|
|||
self.corners: list[tuple[float, float]] = []
|
||||
self.corner_index = rtree.index.Index()
|
||||
self._corner_graph: dict[int, list[tuple[float, float, float]]] = {}
|
||||
self._point_visibility_cache: dict[tuple[int, int], list[tuple[float, float, float]]] = {}
|
||||
self._point_visibility_cache: dict[tuple[int, int, int], list[tuple[float, float, float]]] = {}
|
||||
self._built_static_version = -1
|
||||
self._build()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from time import perf_counter
|
||||
from typing import Callable
|
||||
from collections.abc import Callable
|
||||
|
||||
from shapely.geometry import Polygon, box
|
||||
|
||||
|
|
@ -154,7 +154,7 @@ def run_example_02() -> ScenarioOutcome:
|
|||
"vertical_up": (Port(45, 10, 90), Port(45, 90, 90)),
|
||||
"vertical_down": (Port(55, 90, 270), Port(55, 10, 270)),
|
||||
}
|
||||
widths = {net_id: 2.0 for net_id in netlist}
|
||||
widths = dict.fromkeys(netlist, 2.0)
|
||||
_, _, _, pathfinder = _build_routing_stack(
|
||||
bounds=(0, 0, 100, 100),
|
||||
netlist=netlist,
|
||||
|
|
@ -232,7 +232,7 @@ def run_example_05() -> ScenarioOutcome:
|
|||
"loop": (Port(100, 100, 90), Port(100, 80, 270)),
|
||||
"zig_zag": (Port(20, 150, 0), Port(180, 150, 0)),
|
||||
}
|
||||
widths = {net_id: 2.0 for net_id in netlist}
|
||||
widths = dict.fromkeys(netlist, 2.0)
|
||||
_, _, _, pathfinder = _build_routing_stack(
|
||||
bounds=(0, 0, 200, 200),
|
||||
netlist=netlist,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
import pytest
|
||||
import numpy
|
||||
from shapely.geometry import Polygon
|
||||
from inire import CongestionOptions, RoutingOptions, RoutingProblem, SearchOptions
|
||||
from inire.geometry.collision import RoutingWorld
|
||||
from inire.geometry.primitives import Port
|
||||
|
|
@ -10,7 +7,6 @@ from inire.router._astar_types import AStarContext
|
|||
from inire.router._router import PathFinder
|
||||
from inire.router.cost import CostEvaluator
|
||||
from inire.router.danger_map import DangerMap
|
||||
from inire import RoutingResult
|
||||
|
||||
|
||||
def _build_pathfinder(
|
||||
|
|
@ -45,19 +41,19 @@ def test_clearance_thresholds():
|
|||
# Clearance = 2.0, Width = 2.0
|
||||
# Required Centerline-to-Centerline = (2+2)/2 + 2.0 = 4.0
|
||||
ce = RoutingWorld(clearance=2.0)
|
||||
|
||||
|
||||
# Net 1: Centerline at y=0
|
||||
p1 = Port(0, 0, 0)
|
||||
res1 = Straight.generate(p1, 50.0, width=2.0, dilation=1.0)
|
||||
ce.add_path("net1", res1.collision_geometry, dilated_geometry=res1.dilated_collision_geometry)
|
||||
|
||||
|
||||
# Net 2: Parallel to Net 1
|
||||
# 1. Beyond minimum spacing: y=5. Gap = 5 - 2 = 3 > 2. OK.
|
||||
p2_ok = Port(0, 5, 0)
|
||||
res2_ok = Straight.generate(p2_ok, 50.0, width=2.0, dilation=1.0)
|
||||
report_ok = ce.verify_path_report("net2", [res2_ok])
|
||||
assert report_ok.is_valid, f"Gap 3 should be valid, but got {report_ok.collision_count} collisions"
|
||||
|
||||
|
||||
# 2. Exactly at: y=4.0. Gap = 4.0 - 2.0 = 2.0. OK.
|
||||
p2_exact = Port(0, 4, 0)
|
||||
res2_exact = Straight.generate(p2_exact, 50.0, width=2.0, dilation=1.0)
|
||||
|
|
@ -105,7 +101,7 @@ def test_verify_all_nets_cases():
|
|||
# Reset engine
|
||||
engine.remove_path("net1")
|
||||
engine.remove_path("net2")
|
||||
|
||||
|
||||
results_p = _build_pathfinder(
|
||||
evaluator,
|
||||
bounds=(0, 0, 100, 100),
|
||||
|
|
@ -124,7 +120,7 @@ def test_verify_all_nets_cases():
|
|||
}
|
||||
engine.remove_path("net3")
|
||||
engine.remove_path("net4")
|
||||
|
||||
|
||||
results_c = _build_pathfinder(
|
||||
evaluator,
|
||||
bounds=(0, 0, 100, 100),
|
||||
|
|
|
|||
|
|
@ -43,15 +43,15 @@ def test_ray_cast_width_clearance() -> None:
|
|||
# Clearance = 2.0um, Width = 2.0um.
|
||||
# Centerline to obstacle edge must be >= W/2 + C = 1.0 + 2.0 = 3.0um.
|
||||
engine = RoutingWorld(clearance=2.0)
|
||||
|
||||
|
||||
# Obstacle at x=10 to 20
|
||||
_install_static_straight(engine, Port(10, 50, 0), 10.0, width=100.0)
|
||||
|
||||
|
||||
# 1. Parallel move at x=6. Gap = 10 - 6 = 4.0. Clearly OK.
|
||||
start_ok = Port(6, 50, 90)
|
||||
reach_ok = engine.ray_cast(start_ok, 90, max_dist=10.0, net_width=2.0)
|
||||
assert reach_ok >= 10.0
|
||||
|
||||
|
||||
# 2. Parallel move at x=8. Gap = 10 - 8 = 2.0. COLLISION.
|
||||
start_fail = Port(8, 50, 90)
|
||||
reach_fail = engine.ray_cast(start_fail, 90, max_dist=10.0, net_width=2.0)
|
||||
|
|
@ -61,19 +61,19 @@ def test_ray_cast_width_clearance() -> None:
|
|||
def test_check_move_static_clearance() -> None:
|
||||
engine = RoutingWorld(clearance=2.0)
|
||||
_install_static_straight(engine, Port(10, 50, 0), 10.0, width=100.0, dilation=1.0)
|
||||
|
||||
|
||||
# Straight move of length 10 at x=8 (Width 2.0)
|
||||
# Gap = 10 - 8 = 2.0 < 3.0. COLLISION.
|
||||
start = Port(8, 0, 90)
|
||||
res = Straight.generate(start, 10.0, width=2.0, dilation=1.0) # dilation = C/2
|
||||
|
||||
|
||||
assert engine.check_move_static(res, start_port=start)
|
||||
|
||||
|
||||
# Move at x=7. Gap = 3.0 == minimum. OK.
|
||||
start_ok = Port(7, 0, 90)
|
||||
res_ok = Straight.generate(start_ok, 10.0, width=2.0, dilation=1.0)
|
||||
assert not engine.check_move_static(res_ok, start_port=start_ok)
|
||||
|
||||
|
||||
# 3. Same exact-boundary case.
|
||||
start_exact = Port(7, 0, 90)
|
||||
res_exact = Straight.generate(start_exact, 10.0, width=2.0, dilation=1.0)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ def test_cost_calculation() -> None:
|
|||
p2 = Port(10, 10, 0)
|
||||
|
||||
h = evaluator.h_manhattan(p1, p2)
|
||||
# Manhattan distance = 20.
|
||||
# Manhattan distance = 20.
|
||||
# Jog alignment penalty = 2*bp = 20.
|
||||
# Side check penalty = 2*bp = 20.
|
||||
# Total = 1.1 * (20 + 40) = 66.0
|
||||
|
|
@ -56,25 +56,25 @@ def test_danger_map_kd_tree_and_cache() -> None:
|
|||
# Test that KD-Tree based danger map works and uses cache
|
||||
bounds = (0, 0, 1000, 1000)
|
||||
dm = DangerMap(bounds, resolution=1.0, safety_threshold=10.0)
|
||||
|
||||
|
||||
# Square obstacle at (100, 100) to (110, 110)
|
||||
obstacle = Polygon([(100, 100), (110, 100), (110, 110), (100, 110)])
|
||||
dm.precompute([obstacle])
|
||||
|
||||
|
||||
# 1. High cost near boundary
|
||||
cost_near = dm.get_cost(100.5, 100.5)
|
||||
assert cost_near > 1.0
|
||||
|
||||
|
||||
# 2. Zero cost far away
|
||||
cost_far = dm.get_cost(500, 500)
|
||||
assert cost_far == 0.0
|
||||
|
||||
|
||||
# 3. Check cache usage (internal detail check)
|
||||
# We can check if calling it again is fast or just verify it returns same result
|
||||
cost_near_2 = dm.get_cost(100.5, 100.5)
|
||||
assert cost_near_2 == cost_near
|
||||
assert len(dm._cost_cache) == 2
|
||||
|
||||
|
||||
# 4. Out of bounds
|
||||
assert dm.get_cost(-1, -1) >= 1e12
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,15 @@ from __future__ import annotations
|
|||
|
||||
import os
|
||||
import statistics
|
||||
from collections.abc import Callable
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
|
||||
from inire.tests.example_scenarios import SCENARIOS, ScenarioOutcome
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
|
||||
RUN_PERFORMANCE = os.environ.get("INIRE_RUN_PERFORMANCE") == "1"
|
||||
PERFORMANCE_REPEATS = 3
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, cast
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy
|
||||
from shapely.geometry import MultiPolygon, Polygon
|
||||
|
|
@ -129,7 +129,7 @@ def plot_danger_map(
|
|||
if ax is None:
|
||||
fig, ax = plt.subplots(figsize=(10, 10))
|
||||
else:
|
||||
fig = ax.get_figure()
|
||||
fig = cast("Figure", ax.get_figure())
|
||||
|
||||
# Generate a temporary grid for visualization
|
||||
res = resolution if resolution is not None else max(1.0, (danger_map.maxx - danger_map.minx) / 200.0)
|
||||
|
|
@ -155,12 +155,12 @@ def plot_danger_map(
|
|||
# Need to transpose because grid is [x, y] and imshow expects [row, col] (y, x)
|
||||
im = ax.imshow(
|
||||
grid.T,
|
||||
origin='lower',
|
||||
extent=[danger_map.minx, danger_map.maxx, danger_map.miny, danger_map.maxy],
|
||||
cmap='YlOrRd',
|
||||
alpha=0.6
|
||||
origin="lower",
|
||||
extent=(danger_map.minx, danger_map.maxx, danger_map.miny, danger_map.maxy),
|
||||
cmap="YlOrRd",
|
||||
alpha=0.6,
|
||||
)
|
||||
plt.colorbar(im, ax=ax, label='Danger Cost')
|
||||
plt.colorbar(im, ax=ax, label="Danger Cost")
|
||||
ax.set_title("Danger Map (Proximity Costs)")
|
||||
return fig, ax
|
||||
|
||||
|
|
@ -176,7 +176,7 @@ def plot_expanded_nodes(
|
|||
if ax is None:
|
||||
fig, ax = plt.subplots(figsize=(10, 10))
|
||||
else:
|
||||
fig = ax.get_figure()
|
||||
fig = cast("Figure", ax.get_figure())
|
||||
|
||||
if not nodes:
|
||||
return fig, ax
|
||||
|
|
@ -206,7 +206,7 @@ def plot_expansion_density(
|
|||
if ax is None:
|
||||
fig, ax = plt.subplots(figsize=(12, 12))
|
||||
else:
|
||||
fig = ax.get_figure()
|
||||
fig = cast("Figure", ax.get_figure())
|
||||
|
||||
if not nodes:
|
||||
ax.text(0.5, 0.5, "No Expansion Data", ha='center', va='center', transform=ax.transAxes)
|
||||
|
|
@ -224,14 +224,14 @@ def plot_expansion_density(
|
|||
# Plot as image
|
||||
im = ax.imshow(
|
||||
h.T,
|
||||
origin='lower',
|
||||
extent=[bounds[0], bounds[2], bounds[1], bounds[3]],
|
||||
cmap='plasma',
|
||||
interpolation='nearest',
|
||||
alpha=0.7
|
||||
origin="lower",
|
||||
extent=(bounds[0], bounds[2], bounds[1], bounds[3]),
|
||||
cmap="plasma",
|
||||
interpolation="nearest",
|
||||
alpha=0.7,
|
||||
)
|
||||
|
||||
plt.colorbar(im, ax=ax, label='Expansion Count')
|
||||
plt.colorbar(im, ax=ax, label="Expansion Count")
|
||||
ax.set_title("Search Expansion Density")
|
||||
ax.set_xlim(bounds[0], bounds[2])
|
||||
ax.set_ylim(bounds[1], bounds[3])
|
||||
|
|
|
|||
|
|
@ -74,6 +74,18 @@ lint.ignore = [
|
|||
"TRY003", # Long exception message
|
||||
]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"inire/tests/*.py" = ["ANN", "ARG005", "PT009"]
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.11"
|
||||
warn_unused_configs = true
|
||||
exclude = ["^examples/", "^inire/tests/"]
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = ["scipy.*"]
|
||||
ignore_missing_imports = true
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
addopts = "-rsXx"
|
||||
testpaths = ["inire"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue