further performance improvements
This commit is contained in:
parent
8bf0ff279f
commit
c36bce9978
8 changed files with 168 additions and 58 deletions
Binary file not shown.
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 43 KiB |
|
|
@ -18,7 +18,8 @@ def main() -> None:
|
||||||
danger_map.precompute([])
|
danger_map.precompute([])
|
||||||
|
|
||||||
evaluator = CostEvaluator(engine, danger_map, greedy_h_weight=1.1)
|
evaluator = CostEvaluator(engine, danger_map, greedy_h_weight=1.1)
|
||||||
router = AStarRouter(evaluator, node_limit=100000)
|
# router = AStarRouter(evaluator, node_limit=100000)
|
||||||
|
router = AStarRouter(evaluator, node_limit=100000, bend_collision_type="clipped_bbox", bend_clip_margin=1.0)
|
||||||
pf = PathFinder(router, evaluator)
|
pf = PathFinder(router, evaluator)
|
||||||
|
|
||||||
# 2. Define Netlist with various orientation challenges
|
# 2. Define Netlist with various orientation challenges
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 46 KiB |
|
|
@ -17,7 +17,7 @@ class CollisionEngine:
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
'clearance', 'max_net_width', 'safety_zone_radius',
|
'clearance', 'max_net_width', 'safety_zone_radius',
|
||||||
'static_index', 'static_geometries', 'static_dilated', 'static_prepared', '_static_id_counter',
|
'static_index', 'static_geometries', 'static_dilated', 'static_prepared', '_static_id_counter',
|
||||||
'dynamic_index', 'dynamic_geometries', 'dynamic_dilated', '_dynamic_id_counter'
|
'dynamic_index', 'dynamic_geometries', 'dynamic_dilated', 'dynamic_prepared', '_dynamic_id_counter'
|
||||||
)
|
)
|
||||||
|
|
||||||
clearance: float
|
clearance: float
|
||||||
|
|
@ -60,6 +60,7 @@ class CollisionEngine:
|
||||||
self.dynamic_geometries: dict[int, tuple[str, Polygon]] = {}
|
self.dynamic_geometries: dict[int, tuple[str, Polygon]] = {}
|
||||||
# obj_id -> dilated_geometry (by clearance/2)
|
# obj_id -> dilated_geometry (by clearance/2)
|
||||||
self.dynamic_dilated: dict[int, Polygon] = {}
|
self.dynamic_dilated: dict[int, Polygon] = {}
|
||||||
|
self.dynamic_prepared: dict[int, PreparedGeometry] = {}
|
||||||
self._dynamic_id_counter = 0
|
self._dynamic_id_counter = 0
|
||||||
|
|
||||||
def add_static_obstacle(self, polygon: Polygon) -> None:
|
def add_static_obstacle(self, polygon: Polygon) -> None:
|
||||||
|
|
@ -96,6 +97,7 @@ class CollisionEngine:
|
||||||
|
|
||||||
self.dynamic_geometries[obj_id] = (net_id, poly)
|
self.dynamic_geometries[obj_id] = (net_id, poly)
|
||||||
self.dynamic_dilated[obj_id] = dil
|
self.dynamic_dilated[obj_id] = dil
|
||||||
|
self.dynamic_prepared[obj_id] = prep(dil)
|
||||||
self.dynamic_index.insert(obj_id, dil.bounds)
|
self.dynamic_index.insert(obj_id, dil.bounds)
|
||||||
|
|
||||||
def remove_path(self, net_id: str) -> None:
|
def remove_path(self, net_id: str) -> None:
|
||||||
|
|
@ -109,6 +111,7 @@ class CollisionEngine:
|
||||||
for obj_id in to_remove:
|
for obj_id in to_remove:
|
||||||
nid, poly = self.dynamic_geometries.pop(obj_id)
|
nid, poly = self.dynamic_geometries.pop(obj_id)
|
||||||
dilated = self.dynamic_dilated.pop(obj_id)
|
dilated = self.dynamic_dilated.pop(obj_id)
|
||||||
|
self.dynamic_prepared.pop(obj_id)
|
||||||
self.dynamic_index.delete(obj_id, dilated.bounds)
|
self.dynamic_index.delete(obj_id, dilated.bounds)
|
||||||
|
|
||||||
def lock_net(self, net_id: str) -> None:
|
def lock_net(self, net_id: str) -> None:
|
||||||
|
|
@ -122,6 +125,7 @@ class CollisionEngine:
|
||||||
for obj_id in to_move:
|
for obj_id in to_move:
|
||||||
nid, poly = self.dynamic_geometries.pop(obj_id)
|
nid, poly = self.dynamic_geometries.pop(obj_id)
|
||||||
dilated = self.dynamic_dilated.pop(obj_id)
|
dilated = self.dynamic_dilated.pop(obj_id)
|
||||||
|
self.dynamic_prepared.pop(obj_id)
|
||||||
self.dynamic_index.delete(obj_id, dilated.bounds)
|
self.dynamic_index.delete(obj_id, dilated.bounds)
|
||||||
# Re-buffer for static clearance if necessary.
|
# Re-buffer for static clearance if necessary.
|
||||||
# Note: dynamic is clearance/2, static is clearance.
|
# Note: dynamic is clearance/2, static is clearance.
|
||||||
|
|
@ -178,20 +182,28 @@ class CollisionEngine:
|
||||||
for obj_id in candidates:
|
for obj_id in candidates:
|
||||||
if self.static_prepared[obj_id].intersects(geometry):
|
if self.static_prepared[obj_id].intersects(geometry):
|
||||||
if start_port or end_port:
|
if start_port or end_port:
|
||||||
# Optimization: Instead of expensive buffer + intersection,
|
# Optimization: Skip expensive intersection if neither port is near the obstacle's bounds
|
||||||
# use distance() and check if it's within clearance only near ports.
|
# (Plus a small margin for safety zone)
|
||||||
raw_obstacle = self.static_geometries[obj_id]
|
|
||||||
# If the intersection is within clearance, distance will be < clearance.
|
|
||||||
# We already know it intersects the dilated obstacle, so distance < clearance.
|
|
||||||
|
|
||||||
is_safe = False
|
|
||||||
sz = self.safety_zone_radius
|
sz = self.safety_zone_radius
|
||||||
|
is_near_port = False
|
||||||
|
for p in [start_port, end_port]:
|
||||||
|
if p:
|
||||||
|
# Quick bounds check
|
||||||
|
b = self.static_dilated[obj_id].bounds
|
||||||
|
if (b[0] - sz <= p.x <= b[2] + sz and
|
||||||
|
b[1] - sz <= p.y <= b[3] + sz):
|
||||||
|
is_near_port = True
|
||||||
|
break
|
||||||
|
|
||||||
# Use intersection bounds to check proximity to ports
|
if not is_near_port:
|
||||||
# We need the intersection of the geometry and the RAW obstacle
|
return True # Collision, and not near any port safety zone
|
||||||
|
|
||||||
|
# Only if near port, do the expensive check
|
||||||
|
raw_obstacle = self.static_geometries[obj_id]
|
||||||
intersection = geometry.intersection(raw_obstacle)
|
intersection = geometry.intersection(raw_obstacle)
|
||||||
if not intersection.is_empty:
|
if not intersection.is_empty:
|
||||||
ix_minx, ix_miny, ix_maxx, ix_maxy = intersection.bounds
|
ix_minx, ix_miny, ix_maxx, ix_maxy = intersection.bounds
|
||||||
|
is_safe = False
|
||||||
for p in [start_port, end_port]:
|
for p in [start_port, end_port]:
|
||||||
if p and (abs(ix_minx - p.x) < sz and
|
if p and (abs(ix_minx - p.x) < sz and
|
||||||
abs(ix_maxx - p.x) < sz and
|
abs(ix_maxx - p.x) < sz and
|
||||||
|
|
@ -199,9 +211,9 @@ class CollisionEngine:
|
||||||
abs(ix_maxy - p.y) < sz):
|
abs(ix_maxy - p.y) < sz):
|
||||||
is_safe = True
|
is_safe = True
|
||||||
break
|
break
|
||||||
|
|
||||||
if is_safe:
|
if is_safe:
|
||||||
continue
|
continue
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
@ -213,6 +225,6 @@ class CollisionEngine:
|
||||||
count = 0
|
count = 0
|
||||||
for obj_id in candidates:
|
for obj_id in candidates:
|
||||||
other_net_id, _ = self.dynamic_geometries[obj_id]
|
other_net_id, _ = self.dynamic_geometries[obj_id]
|
||||||
if other_net_id != net_id and test_poly.intersects(self.dynamic_dilated[obj_id]):
|
if other_net_id != net_id and self.dynamic_prepared[obj_id].intersects(test_poly):
|
||||||
count += 1
|
count += 1
|
||||||
return count
|
return count
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import Literal, cast
|
from typing import Literal, cast
|
||||||
import numpy
|
import numpy
|
||||||
|
import shapely
|
||||||
from shapely.geometry import Polygon, box
|
from shapely.geometry import Polygon, box
|
||||||
from shapely.ops import unary_union
|
from shapely.ops import unary_union
|
||||||
|
|
||||||
|
|
@ -44,10 +45,10 @@ class ComponentResult:
|
||||||
length: float
|
length: float
|
||||||
""" Physical length of the component path """
|
""" Physical length of the component path """
|
||||||
|
|
||||||
bounds: list[tuple[float, float, float, float]]
|
bounds: numpy.ndarray
|
||||||
""" Pre-calculated bounds for each polygon in geometry """
|
""" Pre-calculated bounds for each polygon in geometry """
|
||||||
|
|
||||||
dilated_bounds: list[tuple[float, float, float, float]] | None
|
dilated_bounds: numpy.ndarray | None
|
||||||
""" Pre-calculated bounds for each polygon in dilated_geometry """
|
""" Pre-calculated bounds for each polygon in dilated_geometry """
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
|
@ -61,16 +62,26 @@ class ComponentResult:
|
||||||
self.dilated_geometry = dilated_geometry
|
self.dilated_geometry = dilated_geometry
|
||||||
self.end_port = end_port
|
self.end_port = end_port
|
||||||
self.length = length
|
self.length = length
|
||||||
self.bounds = [p.bounds for p in geometry]
|
# Vectorized bounds calculation
|
||||||
self.dilated_bounds = [p.bounds for p in dilated_geometry] if dilated_geometry else None
|
self.bounds = shapely.bounds(geometry)
|
||||||
|
self.dilated_bounds = shapely.bounds(dilated_geometry) if dilated_geometry else None
|
||||||
|
|
||||||
def translate(self, dx: float, dy: float) -> ComponentResult:
|
def translate(self, dx: float, dy: float) -> ComponentResult:
|
||||||
"""
|
"""
|
||||||
Create a new ComponentResult translated by (dx, dy).
|
Create a new ComponentResult translated by (dx, dy).
|
||||||
"""
|
"""
|
||||||
|
# Vectorized translation if possible, else list comp
|
||||||
|
# Shapely 2.x affinity functions still work on single geometries efficiently
|
||||||
|
geoms = self.geometry
|
||||||
|
if self.dilated_geometry:
|
||||||
|
geoms = geoms + self.dilated_geometry
|
||||||
|
|
||||||
from shapely.affinity import translate
|
from shapely.affinity import translate
|
||||||
new_geom = [translate(p, dx, dy) for p in self.geometry]
|
translated = [translate(p, dx, dy) for p in geoms]
|
||||||
new_dil = [translate(p, dx, dy) for p in self.dilated_geometry] if self.dilated_geometry else None
|
|
||||||
|
new_geom = translated[:len(self.geometry)]
|
||||||
|
new_dil = translated[len(self.geometry):] if self.dilated_geometry else None
|
||||||
|
|
||||||
new_port = Port(self.end_port.x + dx, self.end_port.y + dy, self.end_port.orientation)
|
new_port = Port(self.end_port.x + dx, self.end_port.y + dy, self.end_port.orientation)
|
||||||
return ComponentResult(new_geom, new_port, self.length, new_dil)
|
return ComponentResult(new_geom, new_port, self.length, new_dil)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ class AStarNode:
|
||||||
"""
|
"""
|
||||||
A node in the A* search graph.
|
A node in the A* search graph.
|
||||||
"""
|
"""
|
||||||
__slots__ = ('port', 'g_cost', 'h_cost', 'f_cost', 'parent', 'component_result', 'count')
|
__slots__ = ('port', 'g_cost', 'h_cost', 'f_cost', 'parent', 'component_result', 'count', 'path_bbox')
|
||||||
|
|
||||||
port: Port
|
port: Port
|
||||||
""" Port representing the state at this node """
|
""" Port representing the state at this node """
|
||||||
|
|
@ -47,6 +47,9 @@ class AStarNode:
|
||||||
count: int
|
count: int
|
||||||
""" Unique insertion order for tie-breaking """
|
""" Unique insertion order for tie-breaking """
|
||||||
|
|
||||||
|
path_bbox: tuple[float, float, float, float] | None
|
||||||
|
""" Bounding box of the entire path up to this node """
|
||||||
|
|
||||||
_count = 0
|
_count = 0
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
|
@ -66,6 +69,33 @@ class AStarNode:
|
||||||
self.count = AStarNode._count
|
self.count = AStarNode._count
|
||||||
AStarNode._count += 1
|
AStarNode._count += 1
|
||||||
|
|
||||||
|
# Calculate path_bbox
|
||||||
|
if parent is None:
|
||||||
|
self.path_bbox = None
|
||||||
|
else:
|
||||||
|
# Union of parent's bbox and current move's bbox
|
||||||
|
if component_result:
|
||||||
|
# Merge all polygon bounds in the result
|
||||||
|
minx, miny, maxx, maxy = 1e15, 1e15, -1e15, -1e15
|
||||||
|
for b in component_result.dilated_bounds if component_result.dilated_bounds is not None else component_result.bounds:
|
||||||
|
minx = min(minx, b[0])
|
||||||
|
miny = min(miny, b[1])
|
||||||
|
maxx = max(maxx, b[2])
|
||||||
|
maxy = max(maxy, b[3])
|
||||||
|
|
||||||
|
if parent.path_bbox:
|
||||||
|
self.path_bbox = (
|
||||||
|
min(minx, parent.path_bbox[0]),
|
||||||
|
min(miny, parent.path_bbox[1]),
|
||||||
|
max(maxx, parent.path_bbox[2]),
|
||||||
|
max(maxy, parent.path_bbox[3])
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.path_bbox = (minx, miny, maxx, maxy)
|
||||||
|
else:
|
||||||
|
self.path_bbox = parent.path_bbox
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def __lt__(self, other: AStarNode) -> bool:
|
def __lt__(self, other: AStarNode) -> bool:
|
||||||
|
|
@ -142,7 +172,7 @@ class AStarRouter:
|
||||||
self.cost_evaluator = cost_evaluator
|
self.cost_evaluator = cost_evaluator
|
||||||
self.config = RouterConfig(
|
self.config = RouterConfig(
|
||||||
node_limit=node_limit,
|
node_limit=node_limit,
|
||||||
straight_lengths=straight_lengths if straight_lengths is not None else [1.0, 5.0, 25.0],
|
straight_lengths=straight_lengths if straight_lengths is not None else [1.0, 5.0, 25.0, 100.0],
|
||||||
bend_radii=bend_radii if bend_radii is not None else [10.0],
|
bend_radii=bend_radii if bend_radii is not None else [10.0],
|
||||||
sbend_offsets=sbend_offsets if sbend_offsets is not None else [-5.0, -2.0, 2.0, 5.0],
|
sbend_offsets=sbend_offsets if sbend_offsets is not None else [-5.0, -2.0, 2.0, 5.0],
|
||||||
sbend_radii=sbend_radii if sbend_radii is not None else [10.0],
|
sbend_radii=sbend_radii if sbend_radii is not None else [10.0],
|
||||||
|
|
@ -283,7 +313,14 @@ class AStarRouter:
|
||||||
# Level 2: Relative cache (orientation only)
|
# Level 2: Relative cache (orientation only)
|
||||||
rel_key = (base_ori, 'S', length, net_width, self._self_dilation)
|
rel_key = (base_ori, 'S', length, net_width, self._self_dilation)
|
||||||
if rel_key in self._move_cache:
|
if rel_key in self._move_cache:
|
||||||
res = self._move_cache[rel_key].translate(cp.x, cp.y)
|
res_rel = self._move_cache[rel_key]
|
||||||
|
# Check closed set before translating
|
||||||
|
ex = res_rel.end_port.x + cp.x
|
||||||
|
ey = res_rel.end_port.y + cp.y
|
||||||
|
end_state = (round(ex, 3), round(ey, 3), round(res_rel.end_port.orientation, 2))
|
||||||
|
if end_state in closed_set:
|
||||||
|
continue
|
||||||
|
res = res_rel.translate(cp.x, cp.y)
|
||||||
else:
|
else:
|
||||||
res_rel = Straight.generate(Port(0, 0, base_ori), length, net_width, dilation=self._self_dilation)
|
res_rel = Straight.generate(Port(0, 0, base_ori), length, net_width, dilation=self._self_dilation)
|
||||||
self._move_cache[rel_key] = res_rel
|
self._move_cache[rel_key] = res_rel
|
||||||
|
|
@ -300,7 +337,14 @@ class AStarRouter:
|
||||||
else:
|
else:
|
||||||
rel_key = (base_ori, 'B', radius, direction, net_width, self.config.bend_collision_type, self._self_dilation)
|
rel_key = (base_ori, 'B', radius, direction, net_width, self.config.bend_collision_type, self._self_dilation)
|
||||||
if rel_key in self._move_cache:
|
if rel_key in self._move_cache:
|
||||||
res = self._move_cache[rel_key].translate(cp.x, cp.y)
|
res_rel = self._move_cache[rel_key]
|
||||||
|
# Check closed set before translating
|
||||||
|
ex = res_rel.end_port.x + cp.x
|
||||||
|
ey = res_rel.end_port.y + cp.y
|
||||||
|
end_state = (round(ex, 3), round(ey, 3), round(res_rel.end_port.orientation, 2))
|
||||||
|
if end_state in closed_set:
|
||||||
|
continue
|
||||||
|
res = res_rel.translate(cp.x, cp.y)
|
||||||
else:
|
else:
|
||||||
res_rel = Bend90.generate(
|
res_rel = Bend90.generate(
|
||||||
Port(0, 0, base_ori),
|
Port(0, 0, base_ori),
|
||||||
|
|
@ -325,14 +369,21 @@ class AStarRouter:
|
||||||
else:
|
else:
|
||||||
rel_key = (base_ori, 'SB', offset, radius, net_width, self.config.bend_collision_type, self._self_dilation)
|
rel_key = (base_ori, 'SB', offset, radius, net_width, self.config.bend_collision_type, self._self_dilation)
|
||||||
if rel_key in self._move_cache:
|
if rel_key in self._move_cache:
|
||||||
res = self._move_cache[rel_key].translate(cp.x, cp.y)
|
res_rel = self._move_cache[rel_key]
|
||||||
|
# Check closed set before translating
|
||||||
|
ex = res_rel.end_port.x + cp.x
|
||||||
|
ey = res_rel.end_port.y + cp.y
|
||||||
|
end_state = (round(ex, 3), round(ey, 3), round(res_rel.end_port.orientation, 2))
|
||||||
|
if end_state in closed_set:
|
||||||
|
continue
|
||||||
|
res = res_rel.translate(cp.x, cp.y)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
res_rel = SBend.generate(
|
res_rel = SBend.generate(
|
||||||
Port(0, 0, base_ori),
|
Port(0, 0, base_ori),
|
||||||
offset,
|
offset,
|
||||||
radius,
|
radius,
|
||||||
net_width,
|
width=net_width,
|
||||||
collision_type=self.config.bend_collision_type,
|
collision_type=self.config.bend_collision_type,
|
||||||
clip_margin=self.config.bend_clip_margin,
|
clip_margin=self.config.bend_clip_margin,
|
||||||
dilation=self._self_dilation
|
dilation=self._self_dilation
|
||||||
|
|
@ -387,29 +438,48 @@ class AStarRouter:
|
||||||
return
|
return
|
||||||
|
|
||||||
# 3. Check for Self-Intersection (Limited to last 100 segments for performance)
|
# 3. Check for Self-Intersection (Limited to last 100 segments for performance)
|
||||||
# Optimization: use pre-dilated geometries and pre-calculated bounds
|
|
||||||
if result.dilated_geometry:
|
if result.dilated_geometry:
|
||||||
for dm_idx, dilated_move in enumerate(result.dilated_geometry):
|
# Union of current move's bounds for fast path-wide pruning
|
||||||
dm_bounds = result.dilated_bounds[dm_idx]
|
m_minx, m_miny, m_maxx, m_maxy = 1e15, 1e15, -1e15, -1e15
|
||||||
curr_p: AStarNode | None = parent
|
for b in result.dilated_bounds if result.dilated_bounds is not None else result.bounds:
|
||||||
seg_idx = 0
|
|
||||||
while curr_p and curr_p.component_result and seg_idx < 100:
|
m_minx = min(m_minx, b[0])
|
||||||
if seg_idx > 0:
|
m_miny = min(m_miny, b[1])
|
||||||
res_p = curr_p.component_result
|
m_maxx = max(m_maxx, b[2])
|
||||||
if res_p.dilated_geometry:
|
m_maxy = max(m_maxy, b[3])
|
||||||
for dp_idx, dilated_prev in enumerate(res_p.dilated_geometry):
|
|
||||||
dp_bounds = res_p.dilated_bounds[dp_idx]
|
# If current move doesn't overlap the entire parent path bbox, we can skip individual checks
|
||||||
# Quick bounds overlap check
|
# (Except the immediate parent which we usually skip anyway)
|
||||||
if not (dm_bounds[0] > dp_bounds[2] or
|
if parent.path_bbox and not (m_minx > parent.path_bbox[2] or
|
||||||
dm_bounds[2] < dp_bounds[0] or
|
m_maxx < parent.path_bbox[0] or
|
||||||
dm_bounds[1] > dp_bounds[3] or
|
m_miny > parent.path_bbox[3] or
|
||||||
dm_bounds[3] < dp_bounds[1]):
|
m_maxy < parent.path_bbox[1]):
|
||||||
if dilated_move.intersects(dilated_prev):
|
|
||||||
overlap = dilated_move.intersection(dilated_prev)
|
for dm_idx, dilated_move in enumerate(result.dilated_geometry):
|
||||||
if not overlap.is_empty and overlap.area > 1e-6:
|
dm_bounds = result.dilated_bounds[dm_idx]
|
||||||
return
|
curr_p: AStarNode | None = parent
|
||||||
curr_p = curr_p.parent
|
seg_idx = 0
|
||||||
seg_idx += 1
|
while curr_p and curr_p.component_result and seg_idx < 100:
|
||||||
|
# Skip immediate parent to avoid tangent/port-safety issues
|
||||||
|
if seg_idx > 0:
|
||||||
|
res_p = curr_p.component_result
|
||||||
|
if res_p.dilated_geometry:
|
||||||
|
for dp_idx, dilated_prev in enumerate(res_p.dilated_geometry):
|
||||||
|
dp_bounds = res_p.dilated_bounds[dp_idx]
|
||||||
|
# Quick bounds overlap check
|
||||||
|
if not (dm_bounds[0] > dp_bounds[2] or
|
||||||
|
dm_bounds[2] < dp_bounds[0] or
|
||||||
|
dm_bounds[1] > dp_bounds[3] or
|
||||||
|
dm_bounds[3] < dp_bounds[1]):
|
||||||
|
# Use intersects() which is much faster than intersection()
|
||||||
|
if dilated_move.intersects(dilated_prev):
|
||||||
|
# Only do expensive area check if absolutely necessary
|
||||||
|
overlap = dilated_move.intersection(dilated_prev)
|
||||||
|
if not overlap.is_empty and overlap.area > 1e-6:
|
||||||
|
return
|
||||||
|
curr_p = curr_p.parent
|
||||||
|
seg_idx += 1
|
||||||
|
|
||||||
|
|
||||||
move_cost = self.cost_evaluator.evaluate_move(
|
move_cost = self.cost_evaluator.evaluate_move(
|
||||||
result.geometry,
|
result.geometry,
|
||||||
|
|
@ -418,7 +488,8 @@ class AStarRouter:
|
||||||
net_id,
|
net_id,
|
||||||
start_port=parent.port,
|
start_port=parent.port,
|
||||||
length=result.length,
|
length=result.length,
|
||||||
dilated_geometry=result.dilated_geometry
|
dilated_geometry=result.dilated_geometry,
|
||||||
|
skip_static=True
|
||||||
)
|
)
|
||||||
|
|
||||||
if move_cost > 1e12:
|
if move_cost > 1e12:
|
||||||
|
|
|
||||||
|
|
@ -28,3 +28,4 @@ class CostConfig:
|
||||||
unit_length_cost: float = 1.0
|
unit_length_cost: float = 1.0
|
||||||
greedy_h_weight: float = 1.1
|
greedy_h_weight: float = 1.1
|
||||||
congestion_penalty: float = 10000.0
|
congestion_penalty: float = 10000.0
|
||||||
|
bend_penalty: float = 50.0
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ class CostEvaluator:
|
||||||
unit_length_cost: float = 1.0,
|
unit_length_cost: float = 1.0,
|
||||||
greedy_h_weight: float = 1.1,
|
greedy_h_weight: float = 1.1,
|
||||||
congestion_penalty: float = 10000.0,
|
congestion_penalty: float = 10000.0,
|
||||||
|
bend_penalty: float = 50.0,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Initialize the Cost Evaluator.
|
Initialize the Cost Evaluator.
|
||||||
|
|
@ -49,6 +50,7 @@ class CostEvaluator:
|
||||||
unit_length_cost: Cost multiplier per micrometer of path length.
|
unit_length_cost: Cost multiplier per micrometer of path length.
|
||||||
greedy_h_weight: Heuristic weighting (A* greedy factor).
|
greedy_h_weight: Heuristic weighting (A* greedy factor).
|
||||||
congestion_penalty: Multiplier for path overlaps in negotiated congestion.
|
congestion_penalty: Multiplier for path overlaps in negotiated congestion.
|
||||||
|
bend_penalty: Base cost for 90-degree bends.
|
||||||
"""
|
"""
|
||||||
self.collision_engine = collision_engine
|
self.collision_engine = collision_engine
|
||||||
self.danger_map = danger_map
|
self.danger_map = danger_map
|
||||||
|
|
@ -56,6 +58,7 @@ class CostEvaluator:
|
||||||
unit_length_cost=unit_length_cost,
|
unit_length_cost=unit_length_cost,
|
||||||
greedy_h_weight=greedy_h_weight,
|
greedy_h_weight=greedy_h_weight,
|
||||||
congestion_penalty=congestion_penalty,
|
congestion_penalty=congestion_penalty,
|
||||||
|
bend_penalty=bend_penalty,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Use config values
|
# Use config values
|
||||||
|
|
@ -63,6 +66,7 @@ class CostEvaluator:
|
||||||
self.greedy_h_weight = self.config.greedy_h_weight
|
self.greedy_h_weight = self.config.greedy_h_weight
|
||||||
self.congestion_penalty = self.config.congestion_penalty
|
self.congestion_penalty = self.config.congestion_penalty
|
||||||
|
|
||||||
|
|
||||||
def g_proximity(self, x: float, y: float) -> float:
|
def g_proximity(self, x: float, y: float) -> float:
|
||||||
"""
|
"""
|
||||||
Get proximity cost from the Danger Map.
|
Get proximity cost from the Danger Map.
|
||||||
|
|
@ -86,14 +90,21 @@ class CostEvaluator:
|
||||||
Returns:
|
Returns:
|
||||||
Heuristic cost estimate.
|
Heuristic cost estimate.
|
||||||
"""
|
"""
|
||||||
dist = abs(current.x - target.x) + abs(current.y - target.y)
|
dx = abs(current.x - target.x)
|
||||||
|
dy = abs(current.y - target.y)
|
||||||
|
dist = dx + dy
|
||||||
|
|
||||||
# Orientation penalty if not aligned with target entry
|
# 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
|
penalty = 0.0
|
||||||
if current.orientation != target.orientation:
|
if current.orientation != target.orientation:
|
||||||
penalty += 50.0 # Arbitrary high cost for mismatch
|
# 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)
|
||||||
|
|
||||||
return self.greedy_h_weight * (dist + penalty)
|
|
||||||
|
|
||||||
def evaluate_move(
|
def evaluate_move(
|
||||||
self,
|
self,
|
||||||
|
|
@ -104,6 +115,7 @@ class CostEvaluator:
|
||||||
start_port: Port | None = None,
|
start_port: Port | None = None,
|
||||||
length: float = 0.0,
|
length: float = 0.0,
|
||||||
dilated_geometry: list[Polygon] | None = None,
|
dilated_geometry: list[Polygon] | None = None,
|
||||||
|
skip_static: bool = False,
|
||||||
) -> float:
|
) -> float:
|
||||||
"""
|
"""
|
||||||
Calculate the cost of a single move (Straight, Bend, SBend).
|
Calculate the cost of a single move (Straight, Bend, SBend).
|
||||||
|
|
@ -116,6 +128,7 @@ class CostEvaluator:
|
||||||
start_port: Port at the start of the move.
|
start_port: Port at the start of the move.
|
||||||
length: Physical path length of the move.
|
length: Physical path length of the move.
|
||||||
dilated_geometry: Pre-calculated dilated polygons.
|
dilated_geometry: Pre-calculated dilated polygons.
|
||||||
|
skip_static: If True, bypass static collision checks (e.g. if already done).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Total cost of the move, or 1e15 if invalid.
|
Total cost of the move, or 1e15 if invalid.
|
||||||
|
|
@ -131,11 +144,12 @@ class CostEvaluator:
|
||||||
for i, poly in enumerate(geometry):
|
for i, poly in enumerate(geometry):
|
||||||
dil_poly = dilated_geometry[i] if dilated_geometry else None
|
dil_poly = dilated_geometry[i] if dilated_geometry else None
|
||||||
# Hard Collision (Static obstacles)
|
# Hard Collision (Static obstacles)
|
||||||
if self.collision_engine.check_collision(
|
if not skip_static:
|
||||||
poly, net_id, buffer_mode='static', start_port=start_port, end_port=end_port,
|
if self.collision_engine.check_collision(
|
||||||
dilated_geometry=dil_poly
|
poly, net_id, buffer_mode='static', start_port=start_port, end_port=end_port,
|
||||||
):
|
dilated_geometry=dil_poly
|
||||||
return 1e15
|
):
|
||||||
|
return 1e15
|
||||||
|
|
||||||
# Soft Collision (Negotiated Congestion)
|
# Soft Collision (Negotiated Congestion)
|
||||||
overlaps = self.collision_engine.check_collision(
|
overlaps = self.collision_engine.check_collision(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue