diff --git a/examples/07_large_scale_routing.py b/examples/07_large_scale_routing.py index e58f781..2e5274e 100644 --- a/examples/07_large_scale_routing.py +++ b/examples/07_large_scale_routing.py @@ -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) diff --git a/inire/geometry/components.py b/inire/geometry/components.py index 617b067..656aa65 100644 --- a/inire/geometry/components.py +++ b/inire/geometry/components.py @@ -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] diff --git a/inire/router/pathfinder.py b/inire/router/pathfinder.py index 6cb1410..9139b73 100644 --- a/inire/router/pathfinder.py +++ b/inire/router/pathfinder.py @@ -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: