233 lines
8.2 KiB
Python
233 lines
8.2 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
import numpy as np
|
|
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',
|
|
'_target_x', '_target_y', '_target_ori', '_target_cos', '_target_sin')
|
|
|
|
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.5,
|
|
congestion_penalty: float = 10000.0,
|
|
bend_penalty: float = 250.0,
|
|
sbend_penalty: float = 500.0,
|
|
min_bend_radius: 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.
|
|
sbend_penalty: Base cost for parametric S-bends.
|
|
min_bend_radius: Minimum radius for 90-degree bends (used for alignment heuristic).
|
|
"""
|
|
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,
|
|
sbend_penalty=sbend_penalty,
|
|
min_bend_radius=min_bend_radius,
|
|
)
|
|
|
|
# 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
|
|
|
|
# Target cache
|
|
self._target_x = 0.0
|
|
self._target_y = 0.0
|
|
self._target_ori = 0.0
|
|
self._target_cos = 1.0
|
|
self._target_sin = 0.0
|
|
|
|
def set_target(self, target: Port) -> None:
|
|
""" Pre-calculate target-dependent values for faster heuristic. """
|
|
self._target_x = target.x
|
|
self._target_y = target.y
|
|
self._target_ori = target.orientation
|
|
rad = np.radians(target.orientation)
|
|
self._target_cos = np.cos(rad)
|
|
self._target_sin = np.sin(rad)
|
|
|
|
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 + mandatory turn penalties.
|
|
"""
|
|
tx, ty = target.x, target.y
|
|
t_ori = target.orientation
|
|
|
|
# Avoid repeated trig for target orientation
|
|
if abs(tx - self._target_x) > 1e-6 or abs(ty - self._target_y) > 1e-6:
|
|
self.set_target(target)
|
|
|
|
t_cos, t_sin = self._target_cos, self._target_sin
|
|
|
|
dx = abs(current.x - tx)
|
|
dy = abs(current.y - ty)
|
|
dist = dx + dy
|
|
|
|
bp = self.config.bend_penalty
|
|
penalty = 0.0
|
|
|
|
# 1. Orientation Difference
|
|
# Optimization: use integer comparison for common orientations
|
|
curr_ori = current.orientation
|
|
diff = abs(curr_ori - t_ori) % 360
|
|
if diff > 0.1:
|
|
if abs(diff - 180) < 0.1:
|
|
penalty += 2 * bp
|
|
else: # 90 or 270 degree rotation
|
|
penalty += 1 * bp
|
|
|
|
# 2. Side Check (Entry half-plane)
|
|
v_dx = tx - current.x
|
|
v_dy = ty - current.y
|
|
side_proj = v_dx * t_cos + v_dy * t_sin
|
|
perp_dist = abs(v_dx * t_sin - v_dy * t_cos)
|
|
min_radius = self.config.min_bend_radius
|
|
|
|
if side_proj < -0.1 or (side_proj < min_radius and perp_dist > 0.1):
|
|
penalty += 2 * bp
|
|
|
|
# 3. Traveling Away
|
|
# Optimization: avoid np.radians/cos/sin if current_ori is standard 0,90,180,270
|
|
if curr_ori == 0: c_cos, c_sin = 1.0, 0.0
|
|
elif curr_ori == 90: c_cos, c_sin = 0.0, 1.0
|
|
elif curr_ori == 180: c_cos, c_sin = -1.0, 0.0
|
|
elif curr_ori == 270: c_cos, c_sin = 0.0, -1.0
|
|
else:
|
|
curr_rad = np.radians(curr_ori)
|
|
c_cos, c_sin = np.cos(curr_rad), np.sin(curr_rad)
|
|
|
|
move_proj = v_dx * c_cos + v_dy * c_sin
|
|
if move_proj < -0.1:
|
|
penalty += 2 * bp
|
|
|
|
# 4. Jog Alignment
|
|
if diff < 0.1:
|
|
if perp_dist > 0.1:
|
|
penalty += 2 * bp
|
|
|
|
return self.greedy_h_weight * (dist + penalty)
|
|
|
|
|
|
def evaluate_move(
|
|
self,
|
|
geometry: list[Polygon] | None,
|
|
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,
|
|
skip_congestion: bool = False,
|
|
penalty: float = 0.0,
|
|
) -> 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.
|
|
skip_congestion: If True, bypass congestion checks.
|
|
penalty: Fixed cost penalty for the move type.
|
|
|
|
Returns:
|
|
Total cost of the move, or 1e15 if invalid.
|
|
"""
|
|
_ = net_width # Unused
|
|
|
|
# 1. Boundary Check
|
|
danger_map = self.danger_map
|
|
if not danger_map.is_within_bounds(end_port.x, end_port.y):
|
|
return 1e15
|
|
|
|
total_cost = length * self.unit_length_cost + penalty
|
|
|
|
# 2. Collision Check
|
|
if not skip_static or not skip_congestion:
|
|
collision_engine = self.collision_engine
|
|
# Ensure geometry is provided if collision checks are enabled
|
|
if geometry is None:
|
|
return 1e15
|
|
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 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)
|
|
if not skip_congestion:
|
|
overlaps = 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 += danger_map.get_cost(end_port.x, end_port.y)
|
|
return total_cost
|