some perf improvements

This commit is contained in:
Jan Petykiewicz 2026-03-19 21:07:27 -07:00
commit 7e6be50a86
22 changed files with 301 additions and 135 deletions

View file

@ -28,7 +28,7 @@ class CollisionEngine:
'dynamic_tree', 'dynamic_obj_ids', 'dynamic_grid', '_dynamic_id_counter',
'metrics', '_dynamic_tree_dirty', '_dynamic_net_ids_array', '_inv_grid_cell_size',
'_static_bounds_array', '_static_is_rect_array', '_locked_nets',
'_static_raw_tree', '_static_raw_obj_ids'
'_static_raw_tree', '_static_raw_obj_ids', '_dynamic_bounds_array'
)
def __init__(
@ -72,6 +72,7 @@ class CollisionEngine:
self._dynamic_id_counter = 0
self._dynamic_tree_dirty = True
self._dynamic_net_ids_array = numpy.array([], dtype='<U32')
self._dynamic_bounds_array = numpy.array([], dtype=numpy.float64).reshape(0, 4)
self._locked_nets: set[str] = set()
self.metrics = {
@ -135,6 +136,7 @@ class CollisionEngine:
geoms = [self.dynamic_dilated[i] for i in ids]
self.dynamic_tree = STRtree(geoms)
self.dynamic_obj_ids = numpy.array(ids, dtype=numpy.int32)
self._dynamic_bounds_array = numpy.array([g.bounds for g in geoms])
nids = [self.dynamic_geometries[obj_id][0] for obj_id in self.dynamic_obj_ids]
self._dynamic_net_ids_array = numpy.array(nids, dtype='<U32')
self._dynamic_tree_dirty = False
@ -191,20 +193,29 @@ class CollisionEngine:
self._ensure_static_tree()
if self.static_tree is None: return False
# In sparse A*, result.dilated_geometry is buffered by C/2.
# static_dilated is also buffered by C/2.
# Total separation = C. Correct for waveguide-waveguide and waveguide-obstacle?
# Actually, if result.geometry is width Wi, then dilated is Wi + C.
# Wait, result.dilated_geometry is buffered by self._self_dilation = C/2.
# So dilated poly is Wi + C.
# Obstacle dilated by C/2 is Wo + C.
# Intersection means dist < (Wi+C)/2 + (Wo+C)/2? No.
# Let's keep it simple:
# result.geometry is the REAL waveguide polygon (width Wi).
# dilated_geometry is buffered by C/2.
# static_dilated is buffered by C/2.
# Intersecting them means dist < C. This is correct!
# 1. Fast total bounds check
tb = result.total_bounds
s_bounds = self._static_bounds_array
possible_total = (tb[0] < s_bounds[:, 2]) & (tb[2] > s_bounds[:, 0]) & \
(tb[1] < s_bounds[:, 3]) & (tb[3] > s_bounds[:, 1])
if not numpy.any(possible_total):
return False
# 2. Per-polygon AABB check
bounds_list = result.bounds
any_possible = False
for b in bounds_list:
possible = (b[0] < s_bounds[:, 2]) & (b[2] > s_bounds[:, 0]) & \
(b[1] < s_bounds[:, 3]) & (b[3] > s_bounds[:, 1])
if numpy.any(possible):
any_possible = True
break
if not any_possible:
return False
# 3. Real geometry check (Triggers Lazy Evaluation)
test_geoms = result.dilated_geometry if result.dilated_geometry else result.geometry
for i, poly in enumerate(result.geometry):
hits = self.static_tree.query(test_geoms[i], predicate='intersects')
@ -215,15 +226,20 @@ class CollisionEngine:
return False
def check_move_congestion(self, result: ComponentResult, net_id: str) -> int:
if result.total_dilated_bounds is None: return 0
tb = result.total_dilated_bounds
if tb is None: return 0
self._ensure_dynamic_grid()
if not self.dynamic_grid: return 0
b = result.total_dilated_bounds; cs = self.grid_cell_size
cs_inv = self._inv_grid_cell_size
gx_min, gy_min = int(tb[0] * cs_inv), int(tb[1] * cs_inv)
gx_max, gy_max = int(tb[2] * cs_inv), int(tb[3] * cs_inv)
any_possible = False
dynamic_grid = self.dynamic_grid
dynamic_geometries = self.dynamic_geometries
for gx in range(int(b[0]/cs), int(b[2]/cs)+1):
for gy in range(int(b[1]/cs), int(b[3]/cs)+1):
for gx in range(gx_min, gx_max + 1):
for gy in range(gy_min, gy_max + 1):
cell = (gx, gy)
if cell in dynamic_grid:
for obj_id in dynamic_grid[cell]:
@ -232,14 +248,40 @@ class CollisionEngine:
if any_possible: break
if any_possible: break
if not any_possible: return 0
self.metrics['congestion_tree_queries'] += 1
self._ensure_dynamic_tree()
if self.dynamic_tree is None: return 0
# 1. Fast total bounds check (LAZY SAFE)
tb = result.total_dilated_bounds
d_bounds = self._dynamic_bounds_array
possible_total = (tb[0] < d_bounds[:, 2]) & (tb[2] > d_bounds[:, 0]) & \
(tb[1] < d_bounds[:, 3]) & (tb[3] > d_bounds[:, 1])
# Filter by net_id (important for negotiated congestion)
valid_hits = (self._dynamic_net_ids_array != net_id)
if not numpy.any(possible_total & valid_hits):
return 0
# 2. Per-polygon AABB check using query on geometries (LAZY triggering)
# We only trigger evaluation if total bounds intersect with other nets.
geoms_to_test = result.dilated_geometry if result.dilated_geometry else result.geometry
res_indices, tree_indices = self.dynamic_tree.query(geoms_to_test, predicate='intersects')
if tree_indices.size == 0: return 0
if tree_indices.size == 0:
return 0
# Filter out self-overlaps (from same net)
hit_net_ids = numpy.take(self._dynamic_net_ids_array, tree_indices)
return int(numpy.sum(hit_net_ids != net_id))
valid_geoms_hits = (hit_net_ids != net_id)
if not numpy.any(valid_geoms_hits):
return 0
# 3. Real geometry check (Only if AABBs intersect with other nets)
# We already have hits from STRtree which are accurate for polygons too.
# But wait, query(..., predicate='intersects') ALREADY does real check!
return int(numpy.sum(valid_geoms_hits))
def _is_in_safety_zone(self, geometry: Polygon, obj_id: int, start_port: Port | None, end_port: Port | None) -> bool:
"""