inire/inire/tests/test_astar.py

84 lines
3.6 KiB
Python

import pytest
from shapely.geometry import Polygon
from inire.geometry.collision import CollisionEngine
from inire.geometry.primitives import Port
from inire.router.astar import AStarContext, route_astar
from inire.router.cost import CostEvaluator
from inire.router.danger_map import DangerMap
from inire.router.pathfinder import RoutingResult
from inire.utils.validation import validate_routing_result
@pytest.fixture
def basic_evaluator() -> CostEvaluator:
engine = CollisionEngine(clearance=2.0)
danger_map = DangerMap(bounds=(0, -50, 150, 150))
danger_map.precompute([])
return CostEvaluator(engine, danger_map, bend_penalty=50.0, sbend_penalty=150.0)
def test_astar_straight(basic_evaluator: CostEvaluator) -> None:
context = AStarContext(basic_evaluator, snap_size=1.0)
start = Port(0, 0, 0)
target = Port(50, 0, 0)
path = route_astar(start, target, net_width=2.0, context=context)
assert path is not None
result = RoutingResult(net_id="test", path=path, is_valid=True, collisions=0)
validation = validate_routing_result(result, [], clearance=2.0, expected_start=start, expected_end=target)
assert validation["is_valid"], f"Validation failed: {validation.get('reason')}"
assert validation["connectivity_ok"]
# Path should be exactly 50um (or slightly more if it did weird things, but here it's straight)
assert abs(validation["total_length"] - 50.0) < 1e-6
def test_astar_bend(basic_evaluator: CostEvaluator) -> None:
context = AStarContext(basic_evaluator, snap_size=1.0, bend_radii=[10.0])
start = Port(0, 0, 0)
# 20um right, 20um up. Needs a 10um bend and a 10um bend.
target = Port(20, 20, 0)
path = route_astar(start, target, net_width=2.0, context=context)
assert path is not None
result = RoutingResult(net_id="test", path=path, is_valid=True, collisions=0)
validation = validate_routing_result(result, [], clearance=2.0, expected_start=start, expected_end=target)
assert validation["is_valid"], f"Validation failed: {validation.get('reason')}"
assert validation["connectivity_ok"]
def test_astar_obstacle(basic_evaluator: CostEvaluator) -> None:
# Add an obstacle in the middle of a straight path
# Obstacle from x=20 to 40, y=-20 to 20
obstacle = Polygon([(20, -20), (40, -20), (40, 20), (20, 20)])
basic_evaluator.collision_engine.add_static_obstacle(obstacle)
basic_evaluator.danger_map.precompute([obstacle])
context = AStarContext(basic_evaluator, snap_size=1.0, bend_radii=[10.0], node_limit=1000000)
start = Port(0, 0, 0)
target = Port(60, 0, 0)
path = route_astar(start, target, net_width=2.0, context=context)
assert path is not None
result = RoutingResult(net_id="test", path=path, is_valid=True, collisions=0)
validation = validate_routing_result(result, [obstacle], clearance=2.0, expected_start=start, expected_end=target)
assert validation["is_valid"], f"Validation failed: {validation.get('reason')}"
# Path should have detoured, so length > 50
assert validation["total_length"] > 50.0
def test_astar_snap_to_target_lookahead(basic_evaluator: CostEvaluator) -> None:
context = AStarContext(basic_evaluator, snap_size=1.0)
# Target is NOT on 1um grid
start = Port(0, 0, 0)
target = Port(10.1, 0, 0)
path = route_astar(start, target, net_width=2.0, context=context)
assert path is not None
result = RoutingResult(net_id="test", path=path, is_valid=True, collisions=0)
validation = validate_routing_result(result, [], clearance=2.0, expected_start=start, expected_end=target)
assert validation["is_valid"], f"Validation failed: {validation.get('reason')}"