linter fixes

This commit is contained in:
Jan Petykiewicz 2026-03-30 23:54:30 -07:00
commit 1849075b11
26 changed files with 152 additions and 104 deletions

View file

@ -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")

View file

@ -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))

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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]

View file

@ -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))

View file

@ -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

View file

@ -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)

View file

@ -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,

View file

@ -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]:

View file

@ -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,

View file

@ -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,
)

View file

@ -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]:

View file

@ -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:

View file

@ -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:

View file

@ -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))

View file

@ -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)

View file

@ -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()

View file

@ -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,

View file

@ -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(

View file

@ -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

View file

@ -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])

View file

@ -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"]