Refactor: Remove AStarRouter, introduce AStarContext/AStarMetrics

This commit is contained in:
Jan Petykiewicz 2026-03-21 22:50:45 -07:00
commit a77ae781a7
23 changed files with 226 additions and 276 deletions

View file

@ -3,7 +3,7 @@ from inire.geometry.primitives import Port
from inire.geometry.collision import CollisionEngine
from inire.router.danger_map import DangerMap
from inire.router.cost import CostEvaluator
from inire.router.astar import AStarRouter
from inire.router.astar import AStarContext, AStarMetrics
from inire.router.pathfinder import PathFinder
def benchmark_scaling() -> None:
@ -26,8 +26,9 @@ def benchmark_scaling() -> None:
danger_map = DangerMap(bounds=routing_bounds)
danger_map.precompute([])
evaluator = CostEvaluator(engine, danger_map)
router = AStarRouter(evaluator)
pf = PathFinder(router, evaluator)
context = AStarContext(evaluator)
metrics = AStarMetrics()
pf = PathFinder(context, metrics)
num_nets = 50
netlist = {}
@ -45,7 +46,7 @@ def benchmark_scaling() -> None:
print(f"Time per net: {total_time/num_nets:.4f} s")
if total_time > 0:
nodes_per_sec = router.total_nodes_expanded / total_time
nodes_per_sec = metrics.total_nodes_expanded / total_time
print(f"Node expansion rate: {nodes_per_sec:.2f} nodes/s")
# Success rate

View file

@ -3,7 +3,7 @@ from shapely.geometry import Polygon
from inire.geometry.collision import CollisionEngine
from inire.geometry.primitives import Port
from inire.router.astar import AStarRouter
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
@ -19,10 +19,10 @@ def basic_evaluator() -> CostEvaluator:
def test_astar_straight(basic_evaluator: CostEvaluator) -> None:
router = AStarRouter(basic_evaluator, snap_size=1.0)
context = AStarContext(basic_evaluator, snap_size=1.0)
start = Port(0, 0, 0)
target = Port(50, 0, 0)
path = router.route(start, target, net_width=2.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)
@ -35,11 +35,11 @@ def test_astar_straight(basic_evaluator: CostEvaluator) -> None:
def test_astar_bend(basic_evaluator: CostEvaluator) -> None:
router = AStarRouter(basic_evaluator, snap_size=1.0, bend_radii=[10.0])
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 = router.route(start, target, net_width=2.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)
@ -56,11 +56,10 @@ def test_astar_obstacle(basic_evaluator: CostEvaluator) -> None:
basic_evaluator.collision_engine.add_static_obstacle(obstacle)
basic_evaluator.danger_map.precompute([obstacle])
router = AStarRouter(basic_evaluator, snap_size=1.0, bend_radii=[10.0])
router.node_limit = 1000000 # Give it more room for detour
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 = router.route(start, target, net_width=2.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)
@ -72,11 +71,11 @@ def test_astar_obstacle(basic_evaluator: CostEvaluator) -> None:
def test_astar_snap_to_target_lookahead(basic_evaluator: CostEvaluator) -> None:
router = AStarRouter(basic_evaluator, snap_size=1.0)
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 = router.route(start, target, net_width=2.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)

View file

@ -3,7 +3,7 @@ from shapely.geometry import Polygon
from inire.geometry.collision import CollisionEngine
from inire.geometry.primitives import Port
from inire.router.astar import AStarRouter
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 PathFinder
@ -19,12 +19,12 @@ def basic_evaluator() -> CostEvaluator:
def test_astar_sbend(basic_evaluator: CostEvaluator) -> None:
router = AStarRouter(basic_evaluator, snap_size=1.0, sbend_offsets=[2.0, 5.0])
context = AStarContext(basic_evaluator, snap_size=1.0, sbend_offsets=[2.0, 5.0])
# Start at (0,0), target at (50, 2) -> 2um lateral offset
# This matches one of our discretized SBend offsets.
start = Port(0, 0, 0)
target = Port(50, 2, 0)
path = router.route(start, target, net_width=2.0)
path = route_astar(start, target, net_width=2.0, context=context)
assert path is not None
# Check if any component in the path is an SBend
@ -39,9 +39,9 @@ def test_astar_sbend(basic_evaluator: CostEvaluator) -> None:
def test_pathfinder_negotiated_congestion_resolution(basic_evaluator: CostEvaluator) -> None:
router = AStarRouter(basic_evaluator, snap_size=1.0, bend_radii=[5.0, 10.0])
context = AStarContext(basic_evaluator, snap_size=1.0, bend_radii=[5.0, 10.0])
# Increase base penalty to force detour immediately
pf = PathFinder(router, basic_evaluator, max_iterations=10, base_congestion_penalty=1000.0)
pf = PathFinder(context, max_iterations=10, base_congestion_penalty=1000.0)
netlist = {
"net1": (Port(0, 0, 0), Port(50, 0, 0)),

View file

@ -4,7 +4,7 @@ import numpy
from inire.geometry.primitives import Port
from inire.geometry.collision import CollisionEngine
from inire.router.cost import CostEvaluator
from inire.router.astar import AStarRouter
from inire.router.astar import AStarContext
from inire.router.pathfinder import PathFinder
from inire.router.danger_map import DangerMap
@ -24,12 +24,8 @@ def test_failed_net_visibility():
evaluator = CostEvaluator(engine, dm)
# 2. Configure Router with low limit to FORCE failure
# node_limit=5 is extremely low, likely allowing only a few moves.
# node_limit=10 is extremely low, likely allowing only a few moves.
# Start (0,0) -> Target (100,0) is 100um away.
# If snap is 1.0, direct jump S100 might be tried.
# If direct jump works, it might succeed in 1 expansion.
# So we need to block the direct jump or make the limit VERY small (0?).
# Or place a static obstacle that forces a search.
# Let's add a static obstacle that blocks the direct path.
from shapely.geometry import box
@ -38,11 +34,11 @@ def test_failed_net_visibility():
# With obstacle, direct jump fails. A* must search around.
# Limit=10 should be enough to fail to find a path around.
router = AStarRouter(evaluator, node_limit=10)
context = AStarContext(evaluator, node_limit=10)
# 3. Configure PathFinder
# max_iterations=1 because we only need to check the state after the first attempt.
pf = PathFinder(router, evaluator, max_iterations=1, warm_start=None)
pf = PathFinder(context, max_iterations=1, warm_start=None)
netlist = {
"net1": (Port(0, 0, 0), Port(100, 0, 0))

View file

@ -6,7 +6,7 @@ from shapely.geometry import Polygon
from inire.geometry.collision import CollisionEngine
from inire.geometry.primitives import Port
from inire.router.astar import AStarRouter
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
@ -41,13 +41,12 @@ def test_fuzz_astar_no_crash(obstacles: list[Polygon], start: Port, target: Port
danger_map.precompute(obstacles)
evaluator = CostEvaluator(engine, danger_map)
router = AStarRouter(evaluator)
router.node_limit = 5000 # Lower limit for fuzzing stability
context = AStarContext(evaluator, node_limit=5000) # Lower limit for fuzzing stability
# Check if start/target are inside obstacles (safety zone check)
# The router should handle this gracefully (either route or return None)
try:
path = router.route(start, target, net_width=2.0)
path = route_astar(start, target, net_width=2.0, context=context)
# Analytic Correctness: if path is returned, verify it's collision-free
if path:

View file

@ -2,7 +2,7 @@ import pytest
from inire.geometry.collision import CollisionEngine
from inire.geometry.primitives import Port
from inire.router.astar import AStarRouter
from inire.router.astar import AStarContext
from inire.router.cost import CostEvaluator
from inire.router.danger_map import DangerMap
from inire.router.pathfinder import PathFinder
@ -17,8 +17,8 @@ def basic_evaluator() -> CostEvaluator:
def test_pathfinder_parallel(basic_evaluator: CostEvaluator) -> None:
router = AStarRouter(basic_evaluator)
pf = PathFinder(router, basic_evaluator)
context = AStarContext(basic_evaluator)
pf = PathFinder(context)
netlist = {
"net1": (Port(0, 0, 0), Port(50, 0, 0)),

View file

@ -1,7 +1,7 @@
from inire.geometry.collision import CollisionEngine
from inire.geometry.components import Bend90
from inire.geometry.primitives import Port
from inire.router.astar import AStarRouter
from inire.router.astar import AStarContext
from inire.router.cost import CostEvaluator
from inire.router.danger_map import DangerMap
from inire.router.pathfinder import PathFinder
@ -29,8 +29,8 @@ def test_locked_paths() -> None:
danger_map = DangerMap(bounds=(0, -50, 100, 50))
danger_map.precompute([])
evaluator = CostEvaluator(engine, danger_map)
router = AStarRouter(evaluator, bend_radii=[5.0, 10.0])
pf = PathFinder(router, evaluator)
context = AStarContext(evaluator, bend_radii=[5.0, 10.0])
pf = PathFinder(context)
# 1. Route Net A
netlist_a = {"netA": (Port(0, 0, 0), Port(50, 0, 0))}