163 lines
5.5 KiB
Python
163 lines
5.5 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
from inire.router.config import CostConfig
|
|
|
|
if TYPE_CHECKING:
|
|
from shapely.geometry import Polygon
|
|
|
|
from inire.geometry.collision import CollisionEngine
|
|
from inire.geometry.primitives import Port
|
|
from inire.router.danger_map import DangerMap
|
|
|
|
|
|
class CostEvaluator:
|
|
"""
|
|
Calculates total path and proximity costs.
|
|
"""
|
|
__slots__ = ('collision_engine', 'danger_map', 'config', 'unit_length_cost', 'greedy_h_weight', 'congestion_penalty')
|
|
|
|
collision_engine: CollisionEngine
|
|
""" The engine for intersection checks """
|
|
|
|
danger_map: DangerMap
|
|
""" Pre-computed grid for heuristic proximity costs """
|
|
|
|
config: CostConfig
|
|
""" Parameter configuration """
|
|
|
|
unit_length_cost: float
|
|
greedy_h_weight: float
|
|
congestion_penalty: float
|
|
""" Cached weight values for performance """
|
|
|
|
def __init__(
|
|
self,
|
|
collision_engine: CollisionEngine,
|
|
danger_map: DangerMap,
|
|
unit_length_cost: float = 1.0,
|
|
greedy_h_weight: float = 1.1,
|
|
congestion_penalty: float = 10000.0,
|
|
bend_penalty: float = 50.0,
|
|
) -> None:
|
|
"""
|
|
Initialize the Cost Evaluator.
|
|
|
|
Args:
|
|
collision_engine: The engine for intersection checks.
|
|
danger_map: Pre-computed grid for heuristic proximity costs.
|
|
unit_length_cost: Cost multiplier per micrometer of path length.
|
|
greedy_h_weight: Heuristic weighting (A* greedy factor).
|
|
congestion_penalty: Multiplier for path overlaps in negotiated congestion.
|
|
bend_penalty: Base cost for 90-degree bends.
|
|
"""
|
|
self.collision_engine = collision_engine
|
|
self.danger_map = danger_map
|
|
self.config = CostConfig(
|
|
unit_length_cost=unit_length_cost,
|
|
greedy_h_weight=greedy_h_weight,
|
|
congestion_penalty=congestion_penalty,
|
|
bend_penalty=bend_penalty,
|
|
)
|
|
|
|
# Use config values
|
|
self.unit_length_cost = self.config.unit_length_cost
|
|
self.greedy_h_weight = self.config.greedy_h_weight
|
|
self.congestion_penalty = self.config.congestion_penalty
|
|
|
|
|
|
def g_proximity(self, x: float, y: float) -> float:
|
|
"""
|
|
Get proximity cost from the Danger Map.
|
|
|
|
Args:
|
|
x, y: Coordinate to check.
|
|
|
|
Returns:
|
|
Proximity cost at location.
|
|
"""
|
|
return self.danger_map.get_cost(x, y)
|
|
|
|
def h_manhattan(self, current: Port, target: Port) -> float:
|
|
"""
|
|
Heuristic: weighted Manhattan distance + orientation penalty.
|
|
|
|
Args:
|
|
current: Current port state.
|
|
target: Target port state.
|
|
|
|
Returns:
|
|
Heuristic cost estimate.
|
|
"""
|
|
dx = abs(current.x - target.x)
|
|
dy = abs(current.y - target.y)
|
|
dist = dx + dy
|
|
|
|
# Orientation penalty if not aligned with target entry
|
|
# If we need to turn, the cost is at least min_bend_radius * pi/2
|
|
# But we also need to account for the physical distance required for the turn.
|
|
penalty = 0.0
|
|
if current.orientation != target.orientation:
|
|
# 90-degree turn cost: radius 10 -> ~15.7 um + penalty
|
|
penalty += 15.7 + self.config.bend_penalty
|
|
|
|
# Add 1.5 multiplier for greediness (faster search)
|
|
return 1.5 * (dist + penalty)
|
|
|
|
|
|
def evaluate_move(
|
|
self,
|
|
geometry: list[Polygon],
|
|
end_port: Port,
|
|
net_width: float,
|
|
net_id: str,
|
|
start_port: Port | None = None,
|
|
length: float = 0.0,
|
|
dilated_geometry: list[Polygon] | None = None,
|
|
skip_static: bool = False,
|
|
) -> float:
|
|
"""
|
|
Calculate the cost of a single move (Straight, Bend, SBend).
|
|
|
|
Args:
|
|
geometry: List of polygons in the move.
|
|
end_port: Port at the end of the move.
|
|
net_width: Width of the waveguide (unused).
|
|
net_id: Identifier for the net.
|
|
start_port: Port at the start of the move.
|
|
length: Physical path length of the move.
|
|
dilated_geometry: Pre-calculated dilated polygons.
|
|
skip_static: If True, bypass static collision checks (e.g. if already done).
|
|
|
|
Returns:
|
|
Total cost of the move, or 1e15 if invalid.
|
|
"""
|
|
_ = net_width # Unused
|
|
total_cost = length * self.unit_length_cost
|
|
|
|
# 1. Boundary Check
|
|
if not self.danger_map.is_within_bounds(end_port.x, end_port.y):
|
|
return 1e15
|
|
|
|
# 2. Collision Check
|
|
for i, poly in enumerate(geometry):
|
|
dil_poly = dilated_geometry[i] if dilated_geometry else None
|
|
# Hard Collision (Static obstacles)
|
|
if not skip_static:
|
|
if self.collision_engine.check_collision(
|
|
poly, net_id, buffer_mode='static', start_port=start_port, end_port=end_port,
|
|
dilated_geometry=dil_poly
|
|
):
|
|
return 1e15
|
|
|
|
# Soft Collision (Negotiated Congestion)
|
|
overlaps = self.collision_engine.check_collision(
|
|
poly, net_id, buffer_mode='congestion', dilated_geometry=dil_poly
|
|
)
|
|
if isinstance(overlaps, int) and overlaps > 0:
|
|
total_cost += overlaps * self.congestion_penalty
|
|
|
|
# 3. Proximity cost from Danger Map
|
|
total_cost += self.g_proximity(end_port.x, end_port.y)
|
|
return total_cost
|