warm start and some perf
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 77 KiB |
|
|
@ -104,7 +104,7 @@ def main() -> None:
|
||||||
|
|
||||||
# Save plots only for certain iterations to save time
|
# 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 False:
|
if True:
|
||||||
# Save a plot of this iteration's result
|
# Save a plot of this iteration's result
|
||||||
fig, ax = plot_routing_results(current_results, obstacles, bounds, netlist=netlist)
|
fig, ax = plot_routing_results(current_results, obstacles, bounds, netlist=netlist)
|
||||||
plot_danger_map(danger_map, ax=ax)
|
plot_danger_map(danger_map, ax=ax)
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 51 KiB |
|
|
@ -96,7 +96,7 @@ class CollisionEngine:
|
||||||
f" Congestion: {m['congestion_tree_queries']} checks\n"
|
f" Congestion: {m['congestion_tree_queries']} checks\n"
|
||||||
f" Safety Zone: {m['safety_zone_checks']} full intersections performed")
|
f" Safety Zone: {m['safety_zone_checks']} full intersections performed")
|
||||||
|
|
||||||
def add_static_obstacle(self, polygon: Polygon) -> None:
|
def add_static_obstacle(self, polygon: Polygon) -> int:
|
||||||
obj_id = self._static_id_counter
|
obj_id = self._static_id_counter
|
||||||
self._static_id_counter += 1
|
self._static_id_counter += 1
|
||||||
|
|
||||||
|
|
@ -115,6 +115,26 @@ class CollisionEngine:
|
||||||
b = dilated.bounds
|
b = dilated.bounds
|
||||||
area = (b[2] - b[0]) * (b[3] - b[1])
|
area = (b[2] - b[0]) * (b[3] - b[1])
|
||||||
self.static_is_rect[obj_id] = (abs(dilated.area - area) < 1e-4)
|
self.static_is_rect[obj_id] = (abs(dilated.area - area) < 1e-4)
|
||||||
|
return obj_id
|
||||||
|
|
||||||
|
def remove_static_obstacle(self, obj_id: int) -> None:
|
||||||
|
"""
|
||||||
|
Remove a static obstacle by ID.
|
||||||
|
"""
|
||||||
|
if obj_id not in self.static_geometries:
|
||||||
|
return
|
||||||
|
|
||||||
|
bounds = self.static_dilated[obj_id].bounds
|
||||||
|
self.static_index.delete(obj_id, bounds)
|
||||||
|
|
||||||
|
del self.static_geometries[obj_id]
|
||||||
|
del self.static_dilated[obj_id]
|
||||||
|
del self.static_prepared[obj_id]
|
||||||
|
del self.static_is_rect[obj_id]
|
||||||
|
|
||||||
|
self.static_tree = None
|
||||||
|
self._static_raw_tree = None
|
||||||
|
self.static_grid = {}
|
||||||
|
|
||||||
def _ensure_static_tree(self) -> None:
|
def _ensure_static_tree(self) -> None:
|
||||||
if self.static_tree is None and self.static_dilated:
|
if self.static_tree is None and self.static_dilated:
|
||||||
|
|
@ -229,26 +249,43 @@ class CollisionEngine:
|
||||||
tb = result.total_dilated_bounds
|
tb = result.total_dilated_bounds
|
||||||
if tb is None: return 0
|
if tb is None: return 0
|
||||||
self._ensure_dynamic_grid()
|
self._ensure_dynamic_grid()
|
||||||
if not self.dynamic_grid: return 0
|
dynamic_grid = self.dynamic_grid
|
||||||
|
if not dynamic_grid: return 0
|
||||||
|
|
||||||
cs_inv = self._inv_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_min = int(tb[0] * cs_inv)
|
||||||
gx_max, gy_max = int(tb[2] * cs_inv), int(tb[3] * cs_inv)
|
gy_min = int(tb[1] * cs_inv)
|
||||||
|
gx_max = int(tb[2] * cs_inv)
|
||||||
|
gy_max = int(tb[3] * cs_inv)
|
||||||
|
|
||||||
any_possible = False
|
|
||||||
dynamic_grid = self.dynamic_grid
|
|
||||||
dynamic_geometries = self.dynamic_geometries
|
dynamic_geometries = self.dynamic_geometries
|
||||||
|
|
||||||
|
# Fast path for single cell
|
||||||
|
if gx_min == gx_max and gy_min == gy_max:
|
||||||
|
cell = (gx_min, gy_min)
|
||||||
|
if cell in dynamic_grid:
|
||||||
|
for obj_id in dynamic_grid[cell]:
|
||||||
|
if dynamic_geometries[obj_id][0] != net_id:
|
||||||
|
return self._check_real_congestion(result, net_id)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# General case
|
||||||
|
any_possible = False
|
||||||
for gx in range(gx_min, gx_max + 1):
|
for gx in range(gx_min, gx_max + 1):
|
||||||
for gy in range(gy_min, gy_max + 1):
|
for gy in range(gy_min, gy_max + 1):
|
||||||
cell = (gx, gy)
|
cell = (gx, gy)
|
||||||
if cell in dynamic_grid:
|
if cell in dynamic_grid:
|
||||||
for obj_id in dynamic_grid[cell]:
|
for obj_id in dynamic_grid[cell]:
|
||||||
if dynamic_geometries[obj_id][0] != net_id:
|
if dynamic_geometries[obj_id][0] != net_id:
|
||||||
any_possible = True; break
|
any_possible = True
|
||||||
|
break
|
||||||
if any_possible: break
|
if any_possible: break
|
||||||
if any_possible: break
|
if any_possible: break
|
||||||
if not any_possible: return 0
|
|
||||||
|
|
||||||
|
if not any_possible: return 0
|
||||||
|
return self._check_real_congestion(result, net_id)
|
||||||
|
|
||||||
|
def _check_real_congestion(self, result: ComponentResult, net_id: str) -> int:
|
||||||
self.metrics['congestion_tree_queries'] += 1
|
self.metrics['congestion_tree_queries'] += 1
|
||||||
self._ensure_dynamic_tree()
|
self._ensure_dynamic_tree()
|
||||||
if self.dynamic_tree is None: return 0
|
if self.dynamic_tree is None: return 0
|
||||||
|
|
@ -259,28 +296,19 @@ class CollisionEngine:
|
||||||
possible_total = (tb[0] < d_bounds[:, 2]) & (tb[2] > d_bounds[:, 0]) & \
|
possible_total = (tb[0] < d_bounds[:, 2]) & (tb[2] > d_bounds[:, 0]) & \
|
||||||
(tb[1] < d_bounds[:, 3]) & (tb[3] > d_bounds[:, 1])
|
(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)
|
valid_hits = (self._dynamic_net_ids_array != net_id)
|
||||||
if not numpy.any(possible_total & valid_hits):
|
if not numpy.any(possible_total & valid_hits):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
# 2. Per-polygon AABB check using query on geometries (LAZY triggering)
|
# 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
|
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')
|
res_indices, tree_indices = self.dynamic_tree.query(geoms_to_test, predicate='intersects')
|
||||||
|
|
||||||
if tree_indices.size == 0:
|
if tree_indices.size == 0:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
# Filter out self-overlaps (from same net)
|
|
||||||
hit_net_ids = numpy.take(self._dynamic_net_ids_array, tree_indices)
|
hit_net_ids = numpy.take(self._dynamic_net_ids_array, tree_indices)
|
||||||
valid_geoms_hits = (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))
|
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:
|
def _is_in_safety_zone(self, geometry: Polygon, obj_id: int, start_port: Port | None, end_port: Port | None) -> bool:
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ class ComponentResult:
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
'_geometry', '_dilated_geometry', '_proxy_geometry', '_actual_geometry', '_dilated_actual_geometry',
|
'_geometry', '_dilated_geometry', '_proxy_geometry', '_actual_geometry', '_dilated_actual_geometry',
|
||||||
'end_port', 'length', 'move_type', '_bounds', '_dilated_bounds',
|
'end_port', 'length', 'move_type', '_bounds', '_dilated_bounds',
|
||||||
'_total_bounds', '_total_dilated_bounds', '_t_cache', '_total_geom_list', '_offsets', '_coords_cache',
|
'_total_bounds', '_total_dilated_bounds', '_bounds_cached', '_total_geom_list', '_offsets', '_coords_cache',
|
||||||
'_base_result', '_offset', '_lazy_evaluated', 'rel_gx', 'rel_gy', 'rel_go'
|
'_base_result', '_offset', '_lazy_evaluated', 'rel_gx', 'rel_gy', 'rel_go'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -58,11 +58,11 @@ class ComponentResult:
|
||||||
self.end_port = end_port
|
self.end_port = end_port
|
||||||
self.length = length
|
self.length = length
|
||||||
self.move_type = move_type
|
self.move_type = move_type
|
||||||
self._t_cache = {}
|
|
||||||
|
|
||||||
self._base_result = _base_result
|
self._base_result = _base_result
|
||||||
self._offset = _offset
|
self._offset = _offset
|
||||||
self._lazy_evaluated = False
|
self._lazy_evaluated = False
|
||||||
|
self._bounds_cached = False
|
||||||
|
|
||||||
if rel_gx is not None:
|
if rel_gx is not None:
|
||||||
self.rel_gx = rel_gx
|
self.rel_gx = rel_gx
|
||||||
|
|
@ -138,6 +138,7 @@ class ComponentResult:
|
||||||
self._total_bounds = None
|
self._total_bounds = None
|
||||||
self._dilated_bounds = None
|
self._dilated_bounds = None
|
||||||
self._total_dilated_bounds = None
|
self._total_dilated_bounds = None
|
||||||
|
self._bounds_cached = True
|
||||||
|
|
||||||
def _ensure_evaluated(self) -> None:
|
def _ensure_evaluated(self) -> None:
|
||||||
if self._base_result is None or self._lazy_evaluated:
|
if self._base_result is None or self._lazy_evaluated:
|
||||||
|
|
@ -145,16 +146,11 @@ class ComponentResult:
|
||||||
|
|
||||||
# Perform Translation
|
# Perform Translation
|
||||||
dx, dy = self._offset
|
dx, dy = self._offset
|
||||||
# Base uses its own coords cache
|
|
||||||
base_coords = self._base_result._coords_cache
|
|
||||||
if base_coords is None:
|
|
||||||
self._lazy_evaluated = True
|
|
||||||
return
|
|
||||||
|
|
||||||
new_coords = base_coords + [dx, dy]
|
# Use shapely.transform which is vectorized and doesn't modify in-place.
|
||||||
|
# This is MUCH faster than cloning with copy.copy and then set_coordinates.
|
||||||
# Translate ALL geometries at once
|
import shapely
|
||||||
new_total_arr = shapely.set_coordinates(list(self._base_result._total_geom_list), new_coords)
|
new_total_arr = shapely.transform(self._base_result._total_geom_list, lambda x: x + [dx, dy])
|
||||||
new_total = new_total_arr.tolist()
|
new_total = new_total_arr.tolist()
|
||||||
|
|
||||||
o = self._base_result._offsets
|
o = self._base_result._offsets
|
||||||
|
|
@ -193,47 +189,53 @@ class ComponentResult:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bounds(self) -> numpy.ndarray:
|
def bounds(self) -> numpy.ndarray:
|
||||||
if self._bounds is None:
|
if not self._bounds_cached:
|
||||||
if self._base_result is not None:
|
self._ensure_bounds_evaluated()
|
||||||
dx, dy = self._offset
|
|
||||||
self._bounds = self._base_result.bounds + [dx, dy, dx, dy]
|
|
||||||
return self._bounds
|
return self._bounds
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total_bounds(self) -> numpy.ndarray:
|
def total_bounds(self) -> numpy.ndarray:
|
||||||
if self._total_bounds is None:
|
if not self._bounds_cached:
|
||||||
if self._base_result is not None:
|
self._ensure_bounds_evaluated()
|
||||||
dx, dy = self._offset
|
|
||||||
self._total_bounds = self._base_result.total_bounds + [dx, dy, dx, dy]
|
|
||||||
return self._total_bounds
|
return self._total_bounds
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dilated_bounds(self) -> numpy.ndarray | None:
|
def dilated_bounds(self) -> numpy.ndarray | None:
|
||||||
if self._dilated_bounds is None:
|
if not self._bounds_cached:
|
||||||
if self._base_result is not None and self._base_result.dilated_bounds is not None:
|
self._ensure_bounds_evaluated()
|
||||||
dx, dy = self._offset
|
|
||||||
self._dilated_bounds = self._base_result.dilated_bounds + [dx, dy, dx, dy]
|
|
||||||
return self._dilated_bounds
|
return self._dilated_bounds
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total_dilated_bounds(self) -> numpy.ndarray | None:
|
def total_dilated_bounds(self) -> numpy.ndarray | None:
|
||||||
if self._total_dilated_bounds is None:
|
if not self._bounds_cached:
|
||||||
if self._base_result is not None and self._base_result.total_dilated_bounds is not None:
|
self._ensure_bounds_evaluated()
|
||||||
dx, dy = self._offset
|
|
||||||
self._total_dilated_bounds = self._base_result.total_dilated_bounds + [dx, dy, dx, dy]
|
|
||||||
return self._total_dilated_bounds
|
return self._total_dilated_bounds
|
||||||
|
|
||||||
|
def _ensure_bounds_evaluated(self) -> None:
|
||||||
|
if self._bounds_cached: return
|
||||||
|
base = self._base_result
|
||||||
|
if base is not None:
|
||||||
|
dx, dy = self._offset
|
||||||
|
# Vectorized addition is faster if we avoid creating new lists/arrays in the loop
|
||||||
|
if base._bounds is not None:
|
||||||
|
self._bounds = base._bounds + [dx, dy, dx, dy]
|
||||||
|
if base._total_bounds is not None:
|
||||||
|
b = base._total_bounds
|
||||||
|
self._total_bounds = numpy.array([b[0]+dx, b[1]+dy, b[2]+dx, b[3]+dy])
|
||||||
|
if base._dilated_bounds is not None:
|
||||||
|
self._dilated_bounds = base._dilated_bounds + [dx, dy, dx, dy]
|
||||||
|
if base._total_dilated_bounds is not None:
|
||||||
|
b = base._total_dilated_bounds
|
||||||
|
self._total_dilated_bounds = numpy.array([b[0]+dx, b[1]+dy, b[2]+dx, b[3]+dy])
|
||||||
|
self._bounds_cached = True
|
||||||
|
|
||||||
def translate(self, dx: float, dy: float, rel_gx: int | None = None, rel_gy: int | None = None, rel_go: int | None = None) -> ComponentResult:
|
def translate(self, dx: float, dy: float, rel_gx: int | None = None, rel_gy: int | None = None, rel_go: int | None = None) -> ComponentResult:
|
||||||
"""
|
"""
|
||||||
Create a new ComponentResult translated by (dx, dy).
|
Create a new ComponentResult translated by (dx, dy).
|
||||||
"""
|
"""
|
||||||
dxr, dyr = round(dx, 3), round(dy, 3)
|
# Optimized: no internal cache (already cached in router) and no rounding
|
||||||
if (dxr, dyr) == (0.0, 0.0):
|
# Also skip snapping since parent and relative move are already snapped
|
||||||
return self
|
new_port = Port(self.end_port.x + dx, self.end_port.y + dy, self.end_port.orientation, snap=False)
|
||||||
if (dxr, dyr) in self._t_cache:
|
|
||||||
return self._t_cache[(dxr, dyr)]
|
|
||||||
|
|
||||||
new_port = Port(self.end_port.x + dx, self.end_port.y + dy, self.end_port.orientation)
|
|
||||||
|
|
||||||
# LAZY TRANSLATE
|
# LAZY TRANSLATE
|
||||||
if self._base_result:
|
if self._base_result:
|
||||||
|
|
@ -244,7 +246,7 @@ class ComponentResult:
|
||||||
base = self
|
base = self
|
||||||
new_offset = [dx, dy]
|
new_offset = [dx, dy]
|
||||||
|
|
||||||
res = ComponentResult(
|
return ComponentResult(
|
||||||
end_port=new_port,
|
end_port=new_port,
|
||||||
length=self.length,
|
length=self.length,
|
||||||
move_type=self.move_type,
|
move_type=self.move_type,
|
||||||
|
|
@ -255,9 +257,6 @@ class ComponentResult:
|
||||||
rel_go=rel_go
|
rel_go=rel_go
|
||||||
)
|
)
|
||||||
|
|
||||||
self._t_cache[(dxr, dyr)] = res
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
class Straight:
|
class Straight:
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,20 @@ class Port:
|
||||||
x: float,
|
x: float,
|
||||||
y: float,
|
y: float,
|
||||||
orientation: float,
|
orientation: float,
|
||||||
|
snap: bool = True
|
||||||
) -> None:
|
) -> None:
|
||||||
self.x = snap_nm(x)
|
if snap:
|
||||||
self.y = snap_nm(y)
|
self.x = round(x * 1000) / 1000
|
||||||
|
self.y = round(y * 1000) / 1000
|
||||||
|
# Faster orientation normalization for common cases
|
||||||
|
if 0 <= orientation < 360:
|
||||||
|
self.orientation = float(orientation)
|
||||||
|
else:
|
||||||
self.orientation = float(orientation % 360)
|
self.orientation = float(orientation % 360)
|
||||||
|
else:
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.orientation = float(orientation)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'Port(x={self.x}, y={self.y}, orientation={self.orientation})'
|
return f'Port(x={self.x}, y={self.y}, orientation={self.orientation})'
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,7 @@ class AStarRouter:
|
||||||
return_partial: bool = False,
|
return_partial: bool = False,
|
||||||
store_expanded: bool = False,
|
store_expanded: bool = False,
|
||||||
skip_congestion: bool = False,
|
skip_congestion: bool = False,
|
||||||
|
max_cost: float | None = None
|
||||||
) -> list[ComponentResult] | None:
|
) -> list[ComponentResult] | None:
|
||||||
"""
|
"""
|
||||||
Route a single net using A*.
|
Route a single net using A*.
|
||||||
|
|
@ -155,6 +156,11 @@ class AStarRouter:
|
||||||
|
|
||||||
current = heapq.heappop(open_set)
|
current = heapq.heappop(open_set)
|
||||||
|
|
||||||
|
# Cost Pruning (Fail Fast)
|
||||||
|
if max_cost is not None and current.f_cost > max_cost:
|
||||||
|
self.metrics['pruned_cost'] += 1
|
||||||
|
continue
|
||||||
|
|
||||||
if current.h_cost < best_node.h_cost:
|
if current.h_cost < best_node.h_cost:
|
||||||
best_node = current
|
best_node = current
|
||||||
|
|
||||||
|
|
@ -177,7 +183,7 @@ class AStarRouter:
|
||||||
return self._reconstruct_path(current)
|
return self._reconstruct_path(current)
|
||||||
|
|
||||||
# Expansion
|
# Expansion
|
||||||
self._expand_moves(current, target, net_width, net_id, open_set, closed_set, snap, nodes_expanded, skip_congestion=skip_congestion, inv_snap=inv_snap, parent_state=state)
|
self._expand_moves(current, target, net_width, net_id, open_set, closed_set, snap, nodes_expanded, skip_congestion=skip_congestion, inv_snap=inv_snap, parent_state=state, max_cost=max_cost)
|
||||||
|
|
||||||
return self._reconstruct_path(best_node) if return_partial else None
|
return self._reconstruct_path(best_node) if return_partial else None
|
||||||
|
|
||||||
|
|
@ -193,7 +199,8 @@ class AStarRouter:
|
||||||
nodes_expanded: int = 0,
|
nodes_expanded: int = 0,
|
||||||
skip_congestion: bool = False,
|
skip_congestion: bool = False,
|
||||||
inv_snap: float | None = None,
|
inv_snap: float | None = None,
|
||||||
parent_state: tuple[int, int, int] | None = None
|
parent_state: tuple[int, int, int] | None = None,
|
||||||
|
max_cost: float | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
cp = current.port
|
cp = current.port
|
||||||
if inv_snap is None: inv_snap = 1.0 / snap
|
if inv_snap is None: inv_snap = 1.0 / snap
|
||||||
|
|
@ -214,7 +221,7 @@ class AStarRouter:
|
||||||
if proj_t > 0 and abs(perp_t) < 1e-3 and abs(cp.orientation - target.orientation) < 0.1:
|
if proj_t > 0 and abs(perp_t) < 1e-3 and abs(cp.orientation - target.orientation) < 0.1:
|
||||||
max_reach = self.cost_evaluator.collision_engine.ray_cast(cp, cp.orientation, proj_t + 1.0)
|
max_reach = self.cost_evaluator.collision_engine.ray_cast(cp, cp.orientation, proj_t + 1.0)
|
||||||
if max_reach >= proj_t - 0.01:
|
if max_reach >= proj_t - 0.01:
|
||||||
self._process_move(current, target, net_width, net_id, open_set, closed_set, snap, f'S{proj_t}', 'S', (proj_t,), skip_congestion, inv_snap=inv_snap, snap_to_grid=False, parent_state=parent_state)
|
self._process_move(current, target, net_width, net_id, open_set, closed_set, snap, f'S{proj_t}', 'S', (proj_t,), skip_congestion, inv_snap=inv_snap, snap_to_grid=False, parent_state=parent_state, max_cost=max_cost)
|
||||||
|
|
||||||
# 2. VISIBILITY JUMPS & MAX REACH
|
# 2. VISIBILITY JUMPS & MAX REACH
|
||||||
max_reach = self.cost_evaluator.collision_engine.ray_cast(cp, cp.orientation, self.config.max_straight_length)
|
max_reach = self.cost_evaluator.collision_engine.ray_cast(cp, cp.orientation, self.config.max_straight_length)
|
||||||
|
|
@ -261,7 +268,7 @@ class AStarRouter:
|
||||||
if s_l <= max_reach and s_l > 0.1: straight_lengths.add(s_l)
|
if s_l <= max_reach and s_l > 0.1: straight_lengths.add(s_l)
|
||||||
|
|
||||||
for length in sorted(straight_lengths, reverse=True):
|
for length in sorted(straight_lengths, reverse=True):
|
||||||
self._process_move(current, target, net_width, net_id, open_set, closed_set, snap, f'S{length}', 'S', (length,), skip_congestion, inv_snap=inv_snap, parent_state=parent_state)
|
self._process_move(current, target, net_width, net_id, open_set, closed_set, snap, f'S{length}', 'S', (length,), skip_congestion, inv_snap=inv_snap, parent_state=parent_state, max_cost=max_cost)
|
||||||
|
|
||||||
# 3. BENDS & SBENDS
|
# 3. BENDS & SBENDS
|
||||||
angle_to_target = numpy.degrees(numpy.arctan2(target.y - cp.y, target.x - cp.x))
|
angle_to_target = numpy.degrees(numpy.arctan2(target.y - cp.y, target.x - cp.x))
|
||||||
|
|
@ -275,7 +282,7 @@ class AStarRouter:
|
||||||
new_diff = (angle_to_target - new_ori + 180) % 360 - 180
|
new_diff = (angle_to_target - new_ori + 180) % 360 - 180
|
||||||
if abs(new_diff) > 135:
|
if abs(new_diff) > 135:
|
||||||
continue
|
continue
|
||||||
self._process_move(current, target, net_width, net_id, open_set, closed_set, snap, f'B{radius}{direction}', 'B', (radius, direction), skip_congestion, inv_snap=inv_snap, parent_state=parent_state)
|
self._process_move(current, target, net_width, net_id, open_set, closed_set, snap, f'B{radius}{direction}', 'B', (radius, direction), skip_congestion, inv_snap=inv_snap, parent_state=parent_state, max_cost=max_cost)
|
||||||
|
|
||||||
# 4. SBENDS
|
# 4. SBENDS
|
||||||
max_sbend_r = max(self.config.sbend_radii) if self.config.sbend_radii else 0
|
max_sbend_r = max(self.config.sbend_radii) if self.config.sbend_radii else 0
|
||||||
|
|
@ -298,7 +305,7 @@ class AStarRouter:
|
||||||
for offset in sorted(offsets):
|
for offset in sorted(offsets):
|
||||||
for radius in self.config.sbend_radii:
|
for radius in self.config.sbend_radii:
|
||||||
if abs(offset) >= 2 * radius: continue
|
if abs(offset) >= 2 * radius: continue
|
||||||
self._process_move(current, target, net_width, net_id, open_set, closed_set, snap, f'SB{offset}R{radius}', 'SB', (offset, radius), skip_congestion, inv_snap=inv_snap, parent_state=parent_state)
|
self._process_move(current, target, net_width, net_id, open_set, closed_set, snap, f'SB{offset}R{radius}', 'SB', (offset, radius), skip_congestion, inv_snap=inv_snap, parent_state=parent_state, max_cost=max_cost)
|
||||||
|
|
||||||
def _process_move(
|
def _process_move(
|
||||||
self,
|
self,
|
||||||
|
|
@ -315,7 +322,8 @@ class AStarRouter:
|
||||||
skip_congestion: bool,
|
skip_congestion: bool,
|
||||||
inv_snap: float | None = None,
|
inv_snap: float | None = None,
|
||||||
snap_to_grid: bool = True,
|
snap_to_grid: bool = True,
|
||||||
parent_state: tuple[int, int, int] | None = None
|
parent_state: tuple[int, int, int] | None = None,
|
||||||
|
max_cost: float | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
cp = parent.port
|
cp = parent.port
|
||||||
if inv_snap is None: inv_snap = 1.0 / snap
|
if inv_snap is None: inv_snap = 1.0 / snap
|
||||||
|
|
@ -333,7 +341,7 @@ class AStarRouter:
|
||||||
if abs_key in self._move_cache:
|
if abs_key in self._move_cache:
|
||||||
res = self._move_cache[abs_key]
|
res = self._move_cache[abs_key]
|
||||||
move_radius = params[0] if move_class == 'B' else (params[1] if move_class == 'SB' else None)
|
move_radius = params[0] if move_class == 'B' else (params[1] if move_class == 'SB' else None)
|
||||||
self._add_node(parent, res, target, net_width, net_id, open_set, closed_set, move_type, move_radius=move_radius, snap=snap, skip_congestion=skip_congestion, inv_snap=inv_snap, parent_state=parent_state)
|
self._add_node(parent, res, target, net_width, net_id, open_set, closed_set, move_type, move_radius=move_radius, snap=snap, skip_congestion=skip_congestion, inv_snap=inv_snap, parent_state=parent_state, max_cost=max_cost)
|
||||||
return
|
return
|
||||||
|
|
||||||
rel_key = (base_ori, move_class, params, net_width, self.config.bend_collision_type, self._self_dilation, snap_to_grid)
|
rel_key = (base_ori, move_class, params, net_width, self.config.bend_collision_type, self._self_dilation, snap_to_grid)
|
||||||
|
|
@ -344,7 +352,6 @@ class AStarRouter:
|
||||||
|
|
||||||
if rel_key in self._move_cache:
|
if rel_key in self._move_cache:
|
||||||
res_rel = self._move_cache[rel_key]
|
res_rel = self._move_cache[rel_key]
|
||||||
res = res_rel.translate(cp.x, cp.y, rel_gx=res_rel.rel_gx + gx, rel_gy=res_rel.rel_gy + gy, rel_go=res_rel.rel_go)
|
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
p0 = Port(0, 0, base_ori)
|
p0 = Port(0, 0, base_ori)
|
||||||
|
|
@ -357,13 +364,13 @@ class AStarRouter:
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
self._move_cache[rel_key] = res_rel
|
self._move_cache[rel_key] = res_rel
|
||||||
res = res_rel.translate(cp.x, cp.y, rel_gx=res_rel.rel_gx + gx, rel_gy=res_rel.rel_gy + gy, rel_go=res_rel.rel_go)
|
|
||||||
except (ValueError, ZeroDivisionError):
|
except (ValueError, ZeroDivisionError):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
res = res_rel.translate(cp.x, cp.y, rel_gx=res_rel.rel_gx + gx, rel_gy=res_rel.rel_gy + gy, rel_go=res_rel.rel_go)
|
||||||
self._move_cache[abs_key] = res
|
self._move_cache[abs_key] = res
|
||||||
move_radius = params[0] if move_class == 'B' else (params[1] if move_class == 'SB' else None)
|
move_radius = params[0] if move_class == 'B' else (params[1] if move_class == 'SB' else None)
|
||||||
self._add_node(parent, res, target, net_width, net_id, open_set, closed_set, move_type, move_radius=move_radius, snap=snap, skip_congestion=skip_congestion, inv_snap=inv_snap, parent_state=parent_state)
|
self._add_node(parent, res, target, net_width, net_id, open_set, closed_set, move_type, move_radius=move_radius, snap=snap, skip_congestion=skip_congestion, inv_snap=inv_snap, parent_state=parent_state, max_cost=max_cost)
|
||||||
|
|
||||||
def _add_node(
|
def _add_node(
|
||||||
self,
|
self,
|
||||||
|
|
@ -379,7 +386,8 @@ class AStarRouter:
|
||||||
snap: float = 1.0,
|
snap: float = 1.0,
|
||||||
skip_congestion: bool = False,
|
skip_congestion: bool = False,
|
||||||
inv_snap: float | None = None,
|
inv_snap: float | None = None,
|
||||||
parent_state: tuple[int, int, int] | None = None
|
parent_state: tuple[int, int, int] | None = None,
|
||||||
|
max_cost: float | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
self.metrics['moves_generated'] += 1
|
self.metrics['moves_generated'] += 1
|
||||||
state = (result.rel_gx, result.rel_gy, result.rel_go)
|
state = (result.rel_gx, result.rel_gy, result.rel_go)
|
||||||
|
|
@ -400,6 +408,15 @@ class AStarRouter:
|
||||||
self.metrics['pruned_hard_collision'] += 1
|
self.metrics['pruned_hard_collision'] += 1
|
||||||
return
|
return
|
||||||
|
|
||||||
|
new_g_cost = parent.g_cost + result.length
|
||||||
|
|
||||||
|
# Pre-check cost pruning before evaluation (using heuristic)
|
||||||
|
if max_cost is not None:
|
||||||
|
new_h_cost = self.cost_evaluator.h_manhattan(end_p, target)
|
||||||
|
if new_g_cost + new_h_cost > max_cost:
|
||||||
|
self.metrics['pruned_cost'] += 1
|
||||||
|
return
|
||||||
|
|
||||||
is_static_safe = (cache_key in self._static_safe_cache)
|
is_static_safe = (cache_key in self._static_safe_cache)
|
||||||
if not is_static_safe:
|
if not is_static_safe:
|
||||||
ce = self.cost_evaluator.collision_engine
|
ce = self.cost_evaluator.collision_engine
|
||||||
|
|
|
||||||
|
|
@ -106,16 +106,14 @@ class CostEvaluator:
|
||||||
"""
|
"""
|
||||||
Heuristic: weighted Manhattan distance + mandatory turn penalties.
|
Heuristic: weighted Manhattan distance + mandatory turn penalties.
|
||||||
"""
|
"""
|
||||||
tx = target.x
|
tx, ty = target.x, target.y
|
||||||
ty = target.y
|
|
||||||
t_ori = target.orientation
|
t_ori = target.orientation
|
||||||
t_cos = self._target_cos
|
|
||||||
t_sin = self._target_sin
|
|
||||||
|
|
||||||
|
# Avoid repeated trig for target orientation
|
||||||
if abs(tx - self._target_x) > 1e-6 or abs(ty - self._target_y) > 1e-6:
|
if abs(tx - self._target_x) > 1e-6 or abs(ty - self._target_y) > 1e-6:
|
||||||
rad = np.radians(t_ori)
|
self.set_target(target)
|
||||||
t_cos = np.cos(rad)
|
|
||||||
t_sin = np.sin(rad)
|
t_cos, t_sin = self._target_cos, self._target_sin
|
||||||
|
|
||||||
dx = abs(current.x - tx)
|
dx = abs(current.x - tx)
|
||||||
dy = abs(current.y - ty)
|
dy = abs(current.y - ty)
|
||||||
|
|
@ -125,7 +123,9 @@ class CostEvaluator:
|
||||||
penalty = 0.0
|
penalty = 0.0
|
||||||
|
|
||||||
# 1. Orientation Difference
|
# 1. Orientation Difference
|
||||||
diff = abs(current.orientation - t_ori) % 360
|
# Optimization: use integer comparison for common orientations
|
||||||
|
curr_ori = current.orientation
|
||||||
|
diff = abs(curr_ori - t_ori) % 360
|
||||||
if diff > 0.1:
|
if diff > 0.1:
|
||||||
if abs(diff - 180) < 0.1:
|
if abs(diff - 180) < 0.1:
|
||||||
penalty += 2 * bp
|
penalty += 2 * bp
|
||||||
|
|
@ -143,8 +143,16 @@ class CostEvaluator:
|
||||||
penalty += 2 * bp
|
penalty += 2 * bp
|
||||||
|
|
||||||
# 3. Traveling Away
|
# 3. Traveling Away
|
||||||
curr_rad = np.radians(current.orientation)
|
# Optimization: avoid np.radians/cos/sin if current_ori is standard 0,90,180,270
|
||||||
move_proj = v_dx * np.cos(curr_rad) + v_dy * np.sin(curr_rad)
|
if curr_ori == 0: c_cos, c_sin = 1.0, 0.0
|
||||||
|
elif curr_ori == 90: c_cos, c_sin = 0.0, 1.0
|
||||||
|
elif curr_ori == 180: c_cos, c_sin = -1.0, 0.0
|
||||||
|
elif curr_ori == 270: c_cos, c_sin = 0.0, -1.0
|
||||||
|
else:
|
||||||
|
curr_rad = np.radians(curr_ori)
|
||||||
|
c_cos, c_sin = np.cos(curr_rad), np.sin(curr_rad)
|
||||||
|
|
||||||
|
move_proj = v_dx * c_cos + v_dy * c_sin
|
||||||
if move_proj < -0.1:
|
if move_proj < -0.1:
|
||||||
penalty += 2 * bp
|
penalty += 2 * bp
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import logging
|
||||||
import time
|
import time
|
||||||
import random
|
import random
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING, Callable
|
from typing import TYPE_CHECKING, Callable, Literal, Any
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from inire.geometry.components import ComponentResult
|
from inire.geometry.components import ComponentResult
|
||||||
|
|
@ -40,7 +40,7 @@ class PathFinder:
|
||||||
"""
|
"""
|
||||||
Multi-net router using Negotiated Congestion.
|
Multi-net router using Negotiated Congestion.
|
||||||
"""
|
"""
|
||||||
__slots__ = ('router', 'cost_evaluator', 'max_iterations', 'base_congestion_penalty', 'use_tiered_strategy', 'congestion_multiplier', 'accumulated_expanded_nodes')
|
__slots__ = ('router', 'cost_evaluator', 'max_iterations', 'base_congestion_penalty', 'use_tiered_strategy', 'congestion_multiplier', 'accumulated_expanded_nodes', 'warm_start')
|
||||||
|
|
||||||
router: AStarRouter
|
router: AStarRouter
|
||||||
""" The A* search engine """
|
""" The A* search engine """
|
||||||
|
|
@ -60,6 +60,9 @@ class PathFinder:
|
||||||
use_tiered_strategy: bool
|
use_tiered_strategy: bool
|
||||||
""" If True, use simpler collision models in early iterations for speed """
|
""" If True, use simpler collision models in early iterations for speed """
|
||||||
|
|
||||||
|
warm_start: Literal['shortest', 'longest', 'user'] | None
|
||||||
|
""" Heuristic sorting for the initial greedy pass """
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
router: AStarRouter,
|
router: AStarRouter,
|
||||||
|
|
@ -68,6 +71,7 @@ class PathFinder:
|
||||||
base_congestion_penalty: float = 100.0,
|
base_congestion_penalty: float = 100.0,
|
||||||
congestion_multiplier: float = 1.5,
|
congestion_multiplier: float = 1.5,
|
||||||
use_tiered_strategy: bool = True,
|
use_tiered_strategy: bool = True,
|
||||||
|
warm_start: Literal['shortest', 'longest', 'user'] | None = 'shortest',
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Initialize the PathFinder.
|
Initialize the PathFinder.
|
||||||
|
|
@ -79,6 +83,7 @@ class PathFinder:
|
||||||
base_congestion_penalty: Starting penalty for overlaps.
|
base_congestion_penalty: Starting penalty for overlaps.
|
||||||
congestion_multiplier: Multiplier for congestion penalty per iteration.
|
congestion_multiplier: Multiplier for congestion penalty per iteration.
|
||||||
use_tiered_strategy: Whether to use simplified collision models in early iterations.
|
use_tiered_strategy: Whether to use simplified collision models in early iterations.
|
||||||
|
warm_start: Initial ordering strategy for a fast greedy pass.
|
||||||
"""
|
"""
|
||||||
self.router = router
|
self.router = router
|
||||||
self.cost_evaluator = cost_evaluator
|
self.cost_evaluator = cost_evaluator
|
||||||
|
|
@ -86,8 +91,59 @@ class PathFinder:
|
||||||
self.base_congestion_penalty = base_congestion_penalty
|
self.base_congestion_penalty = base_congestion_penalty
|
||||||
self.congestion_multiplier = congestion_multiplier
|
self.congestion_multiplier = congestion_multiplier
|
||||||
self.use_tiered_strategy = use_tiered_strategy
|
self.use_tiered_strategy = use_tiered_strategy
|
||||||
|
self.warm_start = warm_start
|
||||||
self.accumulated_expanded_nodes: list[tuple[float, float, float]] = []
|
self.accumulated_expanded_nodes: list[tuple[float, float, float]] = []
|
||||||
|
|
||||||
|
def _perform_greedy_pass(
|
||||||
|
self,
|
||||||
|
netlist: dict[str, tuple[Port, Port]],
|
||||||
|
net_widths: dict[str, float],
|
||||||
|
order: Literal['shortest', 'longest', 'user']
|
||||||
|
) -> dict[str, list[ComponentResult]]:
|
||||||
|
"""
|
||||||
|
Internal greedy pass: route nets sequentially and freeze them as static.
|
||||||
|
"""
|
||||||
|
all_net_ids = list(netlist.keys())
|
||||||
|
if order != 'user':
|
||||||
|
def get_dist(nid):
|
||||||
|
s, t = netlist[nid]
|
||||||
|
return abs(t.x - s.x) + abs(t.y - s.y)
|
||||||
|
all_net_ids.sort(key=get_dist, reverse=(order == 'longest'))
|
||||||
|
|
||||||
|
greedy_paths = {}
|
||||||
|
temp_obj_ids = []
|
||||||
|
|
||||||
|
logger.info(f"PathFinder: Starting Greedy Warm-Start ({order} order)...")
|
||||||
|
|
||||||
|
for net_id in all_net_ids:
|
||||||
|
start, target = netlist[net_id]
|
||||||
|
width = net_widths.get(net_id, 2.0)
|
||||||
|
|
||||||
|
# Heuristic max cost for fail-fast
|
||||||
|
h_start = self.cost_evaluator.h_manhattan(start, target)
|
||||||
|
max_cost_limit = max(h_start * 3.0, 2000.0)
|
||||||
|
|
||||||
|
path = self.router.route(
|
||||||
|
start, target, width, net_id=net_id,
|
||||||
|
skip_congestion=True, max_cost=max_cost_limit
|
||||||
|
)
|
||||||
|
|
||||||
|
if path:
|
||||||
|
greedy_paths[net_id] = path
|
||||||
|
# Freeze as static
|
||||||
|
for res in path:
|
||||||
|
geoms = res.actual_geometry if res.actual_geometry is not None else res.geometry
|
||||||
|
for poly in geoms:
|
||||||
|
obj_id = self.cost_evaluator.collision_engine.add_static_obstacle(poly)
|
||||||
|
temp_obj_ids.append(obj_id)
|
||||||
|
|
||||||
|
# Clean up temporary static obstacles
|
||||||
|
for obj_id in temp_obj_ids:
|
||||||
|
self.cost_evaluator.collision_engine.remove_static_obstacle(obj_id)
|
||||||
|
|
||||||
|
logger.info(f"PathFinder: Greedy Warm-Start finished. Seeding {len(greedy_paths)}/{len(netlist)} nets.")
|
||||||
|
return greedy_paths
|
||||||
|
|
||||||
def route_all(
|
def route_all(
|
||||||
self,
|
self,
|
||||||
netlist: dict[str, tuple[Port, Port]],
|
netlist: dict[str, tuple[Port, Port]],
|
||||||
|
|
@ -95,6 +151,8 @@ class PathFinder:
|
||||||
store_expanded: bool = False,
|
store_expanded: bool = False,
|
||||||
iteration_callback: Callable[[int, dict[str, RoutingResult]], None] | None = None,
|
iteration_callback: Callable[[int, dict[str, RoutingResult]], None] | None = None,
|
||||||
shuffle_nets: bool = False,
|
shuffle_nets: bool = False,
|
||||||
|
sort_nets: Literal['shortest', 'longest', 'user', None] = None,
|
||||||
|
initial_paths: dict[str, list[ComponentResult]] | None = None,
|
||||||
seed: int | None = None,
|
seed: int | None = None,
|
||||||
) -> dict[str, RoutingResult]:
|
) -> dict[str, RoutingResult]:
|
||||||
"""
|
"""
|
||||||
|
|
@ -106,6 +164,8 @@ class PathFinder:
|
||||||
store_expanded: Whether to store expanded nodes for ALL iterations and nets.
|
store_expanded: Whether to store expanded nodes for ALL iterations and nets.
|
||||||
iteration_callback: Optional callback(iteration_idx, current_results).
|
iteration_callback: Optional callback(iteration_idx, current_results).
|
||||||
shuffle_nets: Whether to randomize the order of nets each iteration.
|
shuffle_nets: Whether to randomize the order of nets each iteration.
|
||||||
|
sort_nets: Heuristic sorting for the initial iteration order (overrides self.warm_start).
|
||||||
|
initial_paths: Pre-computed paths to use for Iteration 0 (overrides warm_start).
|
||||||
seed: Optional seed for randomization (enables reproducibility).
|
seed: Optional seed for randomization (enables reproducibility).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
@ -121,6 +181,21 @@ class PathFinder:
|
||||||
|
|
||||||
all_net_ids = list(netlist.keys())
|
all_net_ids = list(netlist.keys())
|
||||||
|
|
||||||
|
# Determine initial paths (Warm Start)
|
||||||
|
if initial_paths is None:
|
||||||
|
ws_order = sort_nets if sort_nets is not None else self.warm_start
|
||||||
|
if ws_order is not None:
|
||||||
|
initial_paths = self._perform_greedy_pass(netlist, net_widths, ws_order)
|
||||||
|
|
||||||
|
# Apply initial sorting heuristic if requested (for the main NC loop)
|
||||||
|
if sort_nets:
|
||||||
|
def get_dist(nid):
|
||||||
|
s, t = netlist[nid]
|
||||||
|
return abs(t.x - s.x) + abs(t.y - s.y)
|
||||||
|
|
||||||
|
if sort_nets != 'user':
|
||||||
|
all_net_ids.sort(key=get_dist, reverse=(sort_nets == 'longest'))
|
||||||
|
|
||||||
for iteration in range(self.max_iterations):
|
for iteration in range(self.max_iterations):
|
||||||
any_congestion = False
|
any_congestion = False
|
||||||
# Clear accumulation for this iteration so callback gets fresh data
|
# Clear accumulation for this iteration so callback gets fresh data
|
||||||
|
|
@ -148,7 +223,15 @@ class PathFinder:
|
||||||
# 1. Rip-up existing path
|
# 1. Rip-up existing path
|
||||||
self.cost_evaluator.collision_engine.remove_path(net_id)
|
self.cost_evaluator.collision_engine.remove_path(net_id)
|
||||||
|
|
||||||
# 2. Reroute with current congestion info
|
# 2. Reroute or Use Initial Path
|
||||||
|
path = None
|
||||||
|
|
||||||
|
# Warm Start Logic: Use provided path for Iteration 0
|
||||||
|
if iteration == 0 and initial_paths and net_id in initial_paths:
|
||||||
|
path = initial_paths[net_id]
|
||||||
|
logger.debug(f' Net {net_id} used Warm Start path.')
|
||||||
|
else:
|
||||||
|
# Standard Routing Logic
|
||||||
target_coll_model = self.router.config.bend_collision_type
|
target_coll_model = self.router.config.bend_collision_type
|
||||||
coll_model = target_coll_model
|
coll_model = target_coll_model
|
||||||
skip_cong = False
|
skip_cong = False
|
||||||
|
|
@ -157,15 +240,12 @@ class PathFinder:
|
||||||
if target_coll_model == "arc":
|
if target_coll_model == "arc":
|
||||||
coll_model = "clipped_bbox"
|
coll_model = "clipped_bbox"
|
||||||
|
|
||||||
# Dynamic node limit: increase if it failed previously
|
|
||||||
base_node_limit = self.router.config.node_limit
|
base_node_limit = self.router.config.node_limit
|
||||||
current_node_limit = base_node_limit
|
current_node_limit = base_node_limit
|
||||||
if net_id in results and not results[net_id].reached_target:
|
if net_id in results and not results[net_id].reached_target:
|
||||||
current_node_limit = base_node_limit * (iteration + 1)
|
current_node_limit = base_node_limit * (iteration + 1)
|
||||||
|
|
||||||
net_start = time.monotonic()
|
net_start = time.monotonic()
|
||||||
|
|
||||||
# Temporarily override node_limit
|
|
||||||
original_limit = self.router.node_limit
|
original_limit = self.router.node_limit
|
||||||
self.router.node_limit = current_node_limit
|
self.router.node_limit = current_node_limit
|
||||||
|
|
||||||
|
|
@ -174,9 +254,7 @@ class PathFinder:
|
||||||
if store_expanded and self.router.last_expanded_nodes:
|
if store_expanded and self.router.last_expanded_nodes:
|
||||||
self.accumulated_expanded_nodes.extend(self.router.last_expanded_nodes)
|
self.accumulated_expanded_nodes.extend(self.router.last_expanded_nodes)
|
||||||
|
|
||||||
# Restore
|
|
||||||
self.router.node_limit = original_limit
|
self.router.node_limit = original_limit
|
||||||
|
|
||||||
logger.debug(f' Net {net_id} routed in {time.monotonic() - net_start:.4f}s using {coll_model}')
|
logger.debug(f' Net {net_id} routed in {time.monotonic() - net_start:.4f}s using {coll_model}')
|
||||||
|
|
||||||
if path:
|
if path:
|
||||||
|
|
|
||||||