performance improvements

This commit is contained in:
Jan Petykiewicz 2026-03-09 12:23:17 -07:00
commit 8424171946
12 changed files with 212 additions and 118 deletions

View file

@ -16,8 +16,8 @@ class CollisionEngine:
"""
__slots__ = (
'clearance', 'max_net_width', 'safety_zone_radius',
'static_index', 'static_geometries', 'static_prepared', '_static_id_counter',
'dynamic_index', 'dynamic_geometries', '_dynamic_id_counter'
'static_index', 'static_geometries', 'static_dilated', 'static_prepared', '_static_id_counter',
'dynamic_index', 'dynamic_geometries', 'dynamic_dilated', '_dynamic_id_counter'
)
clearance: float
@ -47,21 +47,24 @@ class CollisionEngine:
self.max_net_width = max_net_width
self.safety_zone_radius = safety_zone_radius
# Static obstacles: store raw geometries to avoid double-dilation
# Static obstacles
self.static_index = rtree.index.Index()
self.static_geometries: dict[int, Polygon] = {} # ID -> Polygon
self.static_prepared: dict[int, PreparedGeometry] = {} # ID -> PreparedGeometry
self.static_geometries: dict[int, Polygon] = {} # ID -> Raw Polygon
self.static_dilated: dict[int, Polygon] = {} # ID -> Dilated Polygon (by clearance)
self.static_prepared: dict[int, PreparedGeometry] = {} # ID -> Prepared Dilated
self._static_id_counter = 0
# Dynamic paths for multi-net congestion
self.dynamic_index = rtree.index.Index()
# obj_id -> (net_id, raw_geometry)
self.dynamic_geometries: dict[int, tuple[str, Polygon]] = {}
# obj_id -> dilated_geometry (by clearance/2)
self.dynamic_dilated: dict[int, Polygon] = {}
self._dynamic_id_counter = 0
def add_static_obstacle(self, polygon: Polygon) -> None:
"""
Add a static obstacle (raw geometry) to the engine.
Add a static obstacle to the engine.
Args:
polygon: Raw obstacle geometry.
@ -69,23 +72,31 @@ class CollisionEngine:
obj_id = self._static_id_counter
self._static_id_counter += 1
dilated = polygon.buffer(self.clearance)
self.static_geometries[obj_id] = polygon
self.static_prepared[obj_id] = prep(polygon)
self.static_index.insert(obj_id, polygon.bounds)
self.static_dilated[obj_id] = dilated
self.static_prepared[obj_id] = prep(dilated)
self.static_index.insert(obj_id, dilated.bounds)
def add_path(self, net_id: str, geometry: list[Polygon]) -> None:
def add_path(self, net_id: str, geometry: list[Polygon], dilated_geometry: list[Polygon] | None = None) -> None:
"""
Add a net's routed path (raw geometry) to the dynamic index.
Add a net's routed path to the dynamic index.
Args:
net_id: Identifier for the net.
geometry: List of raw polygons in the path.
dilated_geometry: Optional list of pre-dilated polygons (by clearance/2).
"""
for poly in geometry:
dilation = self.clearance / 2.0
for i, poly in enumerate(geometry):
obj_id = self._dynamic_id_counter
self._dynamic_id_counter += 1
dil = dilated_geometry[i] if dilated_geometry else poly.buffer(dilation)
self.dynamic_geometries[obj_id] = (net_id, poly)
self.dynamic_index.insert(obj_id, poly.bounds)
self.dynamic_dilated[obj_id] = dil
self.dynamic_index.insert(obj_id, dil.bounds)
def remove_path(self, net_id: str) -> None:
"""
@ -97,7 +108,8 @@ class CollisionEngine:
to_remove = [obj_id for obj_id, (nid, _) in self.dynamic_geometries.items() if nid == net_id]
for obj_id in to_remove:
nid, poly = self.dynamic_geometries.pop(obj_id)
self.dynamic_index.delete(obj_id, poly.bounds)
dilated = self.dynamic_dilated.pop(obj_id)
self.dynamic_index.delete(obj_id, dilated.bounds)
def lock_net(self, net_id: str) -> None:
"""
@ -109,7 +121,10 @@ class CollisionEngine:
to_move = [obj_id for obj_id, (nid, _) in self.dynamic_geometries.items() if nid == net_id]
for obj_id in to_move:
nid, poly = self.dynamic_geometries.pop(obj_id)
self.dynamic_index.delete(obj_id, poly.bounds)
dilated = self.dynamic_dilated.pop(obj_id)
self.dynamic_index.delete(obj_id, dilated.bounds)
# Re-buffer for static clearance if necessary.
# Note: dynamic is clearance/2, static is clearance.
self.add_static_obstacle(poly)
def is_collision(
@ -121,15 +136,6 @@ class CollisionEngine:
) -> bool:
"""
Alias for check_collision(buffer_mode='static') for backward compatibility.
Args:
geometry: Move geometry to check.
net_width: Width of the net (unused).
start_port: Starting port for safety check.
end_port: Ending port for safety check.
Returns:
True if collision detected.
"""
_ = net_width
res = self.check_collision(geometry, 'default', buffer_mode='static', start_port=start_port, end_port=end_port)
@ -138,13 +144,6 @@ class CollisionEngine:
def count_congestion(self, geometry: Polygon, net_id: str) -> int:
"""
Alias for check_collision(buffer_mode='congestion') for backward compatibility.
Args:
geometry: Move geometry to check.
net_id: Identifier for the net.
Returns:
Number of overlapping nets.
"""
res = self.check_collision(geometry, net_id, buffer_mode='congestion')
return int(res)
@ -156,6 +155,7 @@ class CollisionEngine:
buffer_mode: Literal['static', 'congestion'] = 'static',
start_port: Port | None = None,
end_port: Port | None = None,
dilated_geometry: Polygon | None = None,
) -> bool | int:
"""
Check for collisions using unified dilation logic.
@ -166,17 +166,24 @@ class CollisionEngine:
buffer_mode: 'static' (full clearance) or 'congestion' (shared).
start_port: Optional start port for safety zone.
end_port: Optional end port for safety zone.
dilated_geometry: Optional pre-buffered geometry (clearance/2).
Returns:
Boolean if static, integer count if congestion.
"""
if buffer_mode == 'static':
test_poly = geometry.buffer(self.clearance)
candidates = self.static_index.intersection(test_poly.bounds)
# Use raw query against pre-dilated obstacles
candidates = self.static_index.intersection(geometry.bounds)
for obj_id in candidates:
if self.static_prepared[obj_id].intersects(test_poly):
if self.static_prepared[obj_id].intersects(geometry):
if start_port or end_port:
# Safety zone check: requires intersection of DILATED query and RAW obstacle.
# Always re-buffer here because static check needs full clearance dilation,
# whereas the provided dilated_geometry is usually clearance/2.
dilation = self.clearance
test_poly = geometry.buffer(dilation)
intersection = test_poly.intersection(self.static_geometries[obj_id])
if intersection.is_empty:
continue
@ -198,12 +205,12 @@ class CollisionEngine:
# buffer_mode == 'congestion'
dilation = self.clearance / 2.0
test_poly = geometry.buffer(dilation)
test_poly = dilated_geometry if dilated_geometry else geometry.buffer(dilation)
candidates = self.dynamic_index.intersection(test_poly.bounds)
count = 0
for obj_id in candidates:
other_net_id, other_poly = self.dynamic_geometries[obj_id]
if other_net_id != net_id and test_poly.intersects(other_poly.buffer(dilation)):
other_net_id, _ = self.dynamic_geometries[obj_id]
if other_net_id != net_id and test_poly.intersects(self.dynamic_dilated[obj_id]):
count += 1
return count