back to working after bend-aware straights

This commit is contained in:
Jan Petykiewicz 2026-03-18 12:10:14 -07:00
commit 51d8ddca51
3 changed files with 88 additions and 29 deletions

View file

@ -30,7 +30,7 @@ def main() -> None:
evaluator = CostEvaluator(engine, danger_map, greedy_h_weight=2.0, unit_length_cost=0.1, bend_penalty=100.0, sbend_penalty=400.0, congestion_penalty=20.0)
router = AStarRouter(evaluator, node_limit=2000000, snap_size=5.0, bend_radii=[50.0], sbend_radii=[50.0], use_analytical_sbends=False)
pf = PathFinder(router, evaluator, max_iterations=50, base_congestion_penalty=20.0, congestion_multiplier=1.2)
pf = PathFinder(router, evaluator, max_iterations=15, base_congestion_penalty=20.0, congestion_multiplier=1.2)
# 2. Define Netlist
netlist = {}
@ -103,7 +103,8 @@ def main() -> None:
})
# Save plots only for certain iterations to save time
if idx % 20 == 0 or idx == pf.max_iterations - 1:
#if idx % 20 == 0 or idx == pf.max_iterations - 1:
if True:
# Save a plot of this iteration's result
fig, ax = plot_routing_results(current_results, obstacles, bounds, netlist=netlist)
plot_danger_map(danger_map, ax=ax)

View file

@ -269,25 +269,26 @@ def _clip_bbox(
# Define vertices in local space (center at 0,0, symmetry axis along +X)
# 1. Start Inner
# 2. Start Outer
# 3. Peak Outer (intersection of tangents at start/end)
# 4. End Outer
# 5. End Inner
# 6. Peak Inner (ensures convexity and inner clipping)
# 3. Peak Outer Start (tangent intersection approximation)
# 4. Peak Outer End
# 5. End Outer
# 6. End Inner
# 7. Peak Inner (ensures convexity and inner clipping)
# Outer tangent intersection point
# Tangent at -hs: x*cos(hs) - y*sin(hs) = r_out
# Tangent at +hs: x*cos(hs) + y*sin(hs) = r_out
# Intersection: y=0, x = r_out / cos(hs)
# To clip the outer corner, we use two peak vertices that follow the arc tighter.
cos_hs = numpy.cos(half_sweep)
if cos_hs < 1e-3: # Sweep near 180 deg
peak_out_x = r_out * 2.0 # Fallback
else:
peak_out_x = r_out / cos_hs
cos_hs2 = numpy.cos(half_sweep / 2.0)
tan_hs2 = numpy.tan(half_sweep / 2.0)
# Distance to peak from center: r_out / cos(hs/2)
# At angles +/- hs/2
peak_r = r_out / cos_hs2
local_verts = [
[r_in * numpy.cos(-half_sweep), r_in * numpy.sin(-half_sweep)],
[r_out * numpy.cos(-half_sweep), r_out * numpy.sin(-half_sweep)],
[peak_out_x, 0.0],
[peak_r * numpy.cos(-half_sweep/2), peak_r * numpy.sin(-half_sweep/2)],
[peak_r * numpy.cos(half_sweep/2), peak_r * numpy.sin(half_sweep/2)],
[r_out * numpy.cos(half_sweep), r_out * numpy.sin(half_sweep)],
[r_in * numpy.cos(half_sweep), r_in * numpy.sin(half_sweep)],
[r_in, 0.0]

View file

@ -192,7 +192,10 @@ class PathFinder:
# (Prevents failed paths from blocking others forever)
if reached:
for res in path:
# Use the search geometry (could be proxy or arc) for indexing
# to ensure consistency with what other nets use for their search.
all_geoms.extend(res.geometry)
if res.dilated_geometry:
all_dilated.extend(res.dilated_geometry)
else:
@ -206,12 +209,48 @@ class PathFinder:
collision_count = 0
# Always check for congestion to decide if more iterations are needed
if reached:
for i, poly in enumerate(all_geoms):
overlaps = self.cost_evaluator.collision_engine.check_congestion(
poly, net_id, dilated_geometry=all_dilated[i]
)
if overlaps > 0:
collision_count += overlaps
# For FINAL verification of this net's success, we should ideally
# use high-fidelity geometry if available, but since Negotiated
# Congestion relies on what is IN the index, we check the indexed geoms.
# BUT, to fix the "false failed" issue where clipped_bbox overlaps
# even if arcs don't, we should verify with actual_geometry.
verif_geoms = []
verif_dilated = []
for res in path:
is_proxy = (res.actual_geometry is not None)
g = res.actual_geometry if is_proxy else res.geometry
verif_geoms.extend(g)
# If we are using actual_geometry as high-fidelity replacement for a proxy,
# we MUST ensure we use the high-fidelity dilation too.
if is_proxy:
# ComponentResult stores dilated_geometry for the 'geometry' (proxy).
# It does NOT store it for 'actual_geometry' unless we re-buffer.
dilation = self.cost_evaluator.collision_engine.clearance / 2.0
verif_dilated.extend([p.buffer(dilation) for p in g])
else:
# Use existing dilated geometry if it matches the current geom
if res.dilated_geometry:
verif_dilated.extend(res.dilated_geometry)
else:
dilation = self.cost_evaluator.collision_engine.clearance / 2.0
verif_dilated.extend([p.buffer(dilation) for p in g])
for i, poly in enumerate(verif_geoms):
# IMPORTANT: We check against OTHER nets.
# If we just check self.check_congestion(poly, net_id),
# it checks against the dynamic index which ALREADY contains this net's
# path (added in step 3 above).
# To correctly count REAL overlaps with others:
self.cost_evaluator.collision_engine._ensure_dynamic_tree()
if self.cost_evaluator.collision_engine.dynamic_tree:
hits = self.cost_evaluator.collision_engine.dynamic_tree.query(verif_dilated[i], predicate='intersects')
for hit_idx in hits:
obj_id = self.cost_evaluator.collision_engine.dynamic_obj_ids[hit_idx]
other_net_id, _ = self.cost_evaluator.collision_engine.dynamic_geometries[obj_id]
if other_net_id != net_id:
collision_count += 1
if collision_count > 0:
any_congestion = True
@ -259,14 +298,32 @@ class PathFinder:
continue
collision_count = 0
# Use high-fidelity verification against OTHER nets
verif_geoms = []
verif_dilated = []
for comp in res.path:
for i, poly in enumerate(comp.geometry):
dil_poly = comp.dilated_geometry[i] if comp.dilated_geometry else None
overlaps = self.cost_evaluator.collision_engine.check_collision(
poly, net_id, buffer_mode='congestion', dilated_geometry=dil_poly
)
if isinstance(overlaps, int):
collision_count += overlaps
is_proxy = (comp.actual_geometry is not None)
g = comp.actual_geometry if is_proxy else comp.geometry
verif_geoms.extend(g)
if is_proxy:
dilation = self.cost_evaluator.collision_engine.clearance / 2.0
verif_dilated.extend([p.buffer(dilation) for p in g])
else:
if comp.dilated_geometry:
verif_dilated.extend(comp.dilated_geometry)
else:
dilation = self.cost_evaluator.collision_engine.clearance / 2.0
verif_dilated.extend([p.buffer(dilation) for p in g])
self.cost_evaluator.collision_engine._ensure_dynamic_tree()
if self.cost_evaluator.collision_engine.dynamic_tree:
for i, poly in enumerate(verif_geoms):
hits = self.cost_evaluator.collision_engine.dynamic_tree.query(verif_dilated[i], predicate='intersects')
for hit_idx in hits:
obj_id = self.cost_evaluator.collision_engine.dynamic_obj_ids[hit_idx]
other_net_id, _ = self.cost_evaluator.collision_engine.dynamic_geometries[obj_id]
if other_net_id != net_id:
collision_count += 1
reached = False
if res.path: