parametrized s-bend
This commit is contained in:
parent
b1feaa89f8
commit
22ec194560
5 changed files with 55 additions and 36 deletions
7
DOCS.md
7
DOCS.md
|
|
@ -11,7 +11,7 @@ The `AStarRouter` is the core pathfinding engine. It can be configured directly
|
||||||
| `node_limit` | `int` | 1,000,000 | Maximum number of states to explore per net. Increase for very complex paths. |
|
| `node_limit` | `int` | 1,000,000 | Maximum number of states to explore per net. Increase for very complex paths. |
|
||||||
| `straight_lengths` | `list[float]` | `[1.0, 5.0, 25.0]` | Discrete step sizes for straight waveguides (µm). Larger steps speed up search. |
|
| `straight_lengths` | `list[float]` | `[1.0, 5.0, 25.0]` | Discrete step sizes for straight waveguides (µm). Larger steps speed up search. |
|
||||||
| `bend_radii` | `list[float]` | `[10.0]` | Available radii for 90-degree turns (µm). Multiple values allow best-fit selection. |
|
| `bend_radii` | `list[float]` | `[10.0]` | Available radii for 90-degree turns (µm). Multiple values allow best-fit selection. |
|
||||||
| `sbend_offsets` | `list[float]` | `[-5, -2, 2, 5]` | Lateral offsets for parametric S-bends (µm). |
|
| `sbend_offsets` | `list[float] \| None` | `None` (Auto) | Lateral offsets for parametric S-bends. `None` uses automatic grid-aligned steps. |
|
||||||
| `sbend_radii` | `list[float]` | `[10.0]` | Available radii for S-bends (µm). |
|
| `sbend_radii` | `list[float]` | `[10.0]` | Available radii for S-bends (µm). |
|
||||||
| `snap_to_target_dist` | `float` | 20.0 | Distance (µm) at which the router attempts an exact bridge to the target port. |
|
| `snap_to_target_dist` | `float` | 20.0 | Distance (µm) at which the router attempts an exact bridge to the target port. |
|
||||||
| `bend_penalty` | `float` | 50.0 | Flat cost added for every 90-degree bend. Higher values favor straight lines. |
|
| `bend_penalty` | `float` | 50.0 | Flat cost added for every 90-degree bend. Higher values favor straight lines. |
|
||||||
|
|
@ -87,4 +87,7 @@ In multi-net designs, if nets are overlapping:
|
||||||
3. If a solution is still not found, check if the `clearance` is physically possible given the design's narrowest bottlenecks.
|
3. If a solution is still not found, check if the `clearance` is physically possible given the design's narrowest bottlenecks.
|
||||||
|
|
||||||
### S-Bend Usage
|
### S-Bend Usage
|
||||||
Parametric S-bends are triggered by the `sbend_offsets` list. If you need a specific lateral shift (e.g., 5.86µm for a 45° switchover), add it to `sbend_offsets`. The router will only use an S-bend if it can reach a state that is exactly on the lattice or the target.
|
Parametric S-bends bridge lateral gaps without changing the waveguide's orientation.
|
||||||
|
- **Automatic Selection**: If `sbend_offsets` is set to `None` (the default), the router automatically chooses from a set of "natural" offsets (Fibonacci-aligned grid steps) and the offset needed to hit the target.
|
||||||
|
- **Specific Offsets**: To use specific offsets (e.g., 5.86µm for a 45° switchover), provide them in the `sbend_offsets` list. The router will prioritize these but will still try to align with the target if possible.
|
||||||
|
- **Constraints**: S-bends are only used for offsets $O < 2R$. For larger shifts, the router naturally combines two 90° bends and a straight segment.
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ def main() -> None:
|
||||||
|
|
||||||
evaluator = CostEvaluator(engine, danger_map, greedy_h_weight=1.5, unit_length_cost=0.1, bend_penalty=100.0, sbend_penalty=400.0, congestion_penalty=100.0)
|
evaluator = CostEvaluator(engine, danger_map, greedy_h_weight=1.5, unit_length_cost=0.1, bend_penalty=100.0, sbend_penalty=400.0, congestion_penalty=100.0)
|
||||||
|
|
||||||
router = AStarRouter(evaluator, node_limit=2000000, snap_size=5.0, bend_radii=[50.0], sbend_radii=[50.0], use_analytical_sbends=False)
|
router = AStarRouter(evaluator, node_limit=2000000, snap_size=5.0, bend_radii=[50.0], sbend_radii=[50.0])
|
||||||
pf = PathFinder(router, evaluator, max_iterations=15, base_congestion_penalty=100.0, congestion_multiplier=1.4)
|
pf = PathFinder(router, evaluator, max_iterations=15, base_congestion_penalty=100.0, congestion_multiplier=1.4)
|
||||||
|
|
||||||
# 2. Define Netlist
|
# 2. Define Netlist
|
||||||
|
|
|
||||||
|
|
@ -204,24 +204,6 @@ class AStarRouter:
|
||||||
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, skip_static=True, snap_to_grid=False)
|
self._process_move(current, target, net_width, net_id, open_set, closed_set, snap, f'S{proj_t}', 'S', (proj_t,), skip_congestion, skip_static=True, snap_to_grid=False)
|
||||||
|
|
||||||
# B. SBend Jump (Direct to Target)
|
|
||||||
if self.config.use_analytical_sbends and proj_t > 0 and abs(cp.orientation - target.orientation) < 0.1 and abs(perp_t) > 1e-3:
|
|
||||||
# Calculate required radius to hit target exactly: R = (dx^2 + dy^2) / (4*|dy|)
|
|
||||||
req_radius = (proj_t**2 + perp_t**2) / (4.0 * abs(perp_t))
|
|
||||||
|
|
||||||
min_radius = min(self.config.sbend_radii) if self.config.sbend_radii else 50.0
|
|
||||||
|
|
||||||
if req_radius >= min_radius:
|
|
||||||
# We can hit it exactly!
|
|
||||||
self._process_move(current, target, net_width, net_id, open_set, closed_set, snap, f'SB_Direct_R{req_radius:.1f}', 'SB', (perp_t, req_radius), skip_congestion, snap_to_grid=False)
|
|
||||||
else:
|
|
||||||
# Required radius is too small. We must use a larger radius and some straight segments.
|
|
||||||
# A* will handle this through Priority 3 SBends + Priority 2 Straights.
|
|
||||||
pass
|
|
||||||
|
|
||||||
# In super sparse mode, we can return here, but A* needs other options for optimality.
|
|
||||||
# return
|
|
||||||
|
|
||||||
# 2. VISIBILITY JUMPS & MAX REACH (Priority 2)
|
# 2. VISIBILITY JUMPS & MAX REACH (Priority 2)
|
||||||
max_reach = self.cost_evaluator.collision_engine.ray_cast(cp, base_ori, self.config.max_straight_length)
|
max_reach = self.cost_evaluator.collision_engine.ray_cast(cp, base_ori, self.config.max_straight_length)
|
||||||
|
|
||||||
|
|
@ -309,14 +291,35 @@ class AStarRouter:
|
||||||
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)
|
self._process_move(current, target, net_width, net_id, open_set, closed_set, snap, f'B{radius}{direction}', 'B', (radius, direction), skip_congestion)
|
||||||
|
|
||||||
if dist_sq < 400*400:
|
# 4. SBENDS
|
||||||
offsets = set(self.config.sbend_offsets)
|
max_sbend_r = max(self.config.sbend_radii) if self.config.sbend_radii else 0
|
||||||
|
if max_sbend_r > 0:
|
||||||
|
user_offsets = self.config.sbend_offsets
|
||||||
|
offsets: set[float] = set(user_offsets) if user_offsets is not None else set()
|
||||||
|
|
||||||
dx_local = (target.x - cp.x) * cos_v + (target.y - cp.y) * sin_v
|
dx_local = (target.x - cp.x) * cos_v + (target.y - cp.y) * sin_v
|
||||||
dy_local = -(target.x - cp.x) * sin_v + (target.y - cp.y) * cos_v
|
dy_local = -(target.x - cp.x) * sin_v + (target.y - cp.y) * cos_v
|
||||||
if 0 < dx_local < self.config.snap_to_target_dist:
|
|
||||||
|
# Always try aligning with target if it's forward and within reach
|
||||||
|
if dx_local > 0 and abs(dy_local) < 2 * max_sbend_r:
|
||||||
|
# Check if we have enough distance for the SBend
|
||||||
|
# Min distance D = sqrt(4RO - O^2). Smallest R is O/2.
|
||||||
|
min_d = numpy.sqrt(max(0, 4 * (abs(dy_local)/2.0) * abs(dy_local) - dy_local**2))
|
||||||
|
if dx_local >= min_d:
|
||||||
offsets.add(dy_local)
|
offsets.add(dy_local)
|
||||||
|
|
||||||
for offset in offsets:
|
# If no offsets provided by user (None), the router "chooses" offsets
|
||||||
|
# by trying grid-aligned steps up to the reach of the largest radius.
|
||||||
|
if user_offsets is None:
|
||||||
|
# Try a selection of grid-aligned offsets.
|
||||||
|
# Fibonacci-ish steps are useful to cover different scales efficiently.
|
||||||
|
for sign in [-1, 1]:
|
||||||
|
for i in [1, 2, 3, 5, 8, 13, 21, 34, 55, 89]:
|
||||||
|
o = sign * i * snap
|
||||||
|
if abs(o) < 2 * max_sbend_r:
|
||||||
|
offsets.add(o)
|
||||||
|
|
||||||
|
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)
|
self._process_move(current, target, net_width, net_id, open_set, closed_set, snap, f'SB{offset}R{radius}', 'SB', (offset, radius), skip_congestion)
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@ class RouterConfig:
|
||||||
num_straight_samples: int = 5
|
num_straight_samples: int = 5
|
||||||
min_straight_length: float = 5.0
|
min_straight_length: float = 5.0
|
||||||
|
|
||||||
# Offsets for SBends (still list-based for now, or could range)
|
# Offsets for SBends (None = automatic grid-based selection)
|
||||||
sbend_offsets: list[float] = field(default_factory=lambda: [-100.0, -50.0, -10.0, 10.0, 50.0, 100.0])
|
sbend_offsets: list[float] | None = None
|
||||||
|
|
||||||
# Deprecated but kept for compatibility during refactor
|
# Deprecated but kept for compatibility during refactor
|
||||||
straight_lengths: list[float] = field(default_factory=list)
|
straight_lengths: list[float] = field(default_factory=list)
|
||||||
|
|
@ -25,7 +25,6 @@ class RouterConfig:
|
||||||
bend_radii: list[float] = field(default_factory=lambda: [50.0, 100.0])
|
bend_radii: list[float] = field(default_factory=lambda: [50.0, 100.0])
|
||||||
sbend_radii: list[float] = field(default_factory=lambda: [50.0, 100.0, 500.0])
|
sbend_radii: list[float] = field(default_factory=lambda: [50.0, 100.0, 500.0])
|
||||||
snap_to_target_dist: float = 1000.0
|
snap_to_target_dist: float = 1000.0
|
||||||
use_analytical_sbends: bool = True
|
|
||||||
bend_penalty: float = 250.0
|
bend_penalty: float = 250.0
|
||||||
sbend_penalty: float = 500.0
|
sbend_penalty: float = 500.0
|
||||||
bend_collision_type: Literal["arc", "bbox", "clipped_bbox"] | Any = "arc"
|
bend_collision_type: Literal["arc", "bbox", "clipped_bbox"] | Any = "arc"
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,31 @@ def test_cost_calculation() -> None:
|
||||||
# 50x50 um area, 1um resolution
|
# 50x50 um area, 1um resolution
|
||||||
danger_map = DangerMap(bounds=(0, 0, 50, 50))
|
danger_map = DangerMap(bounds=(0, 0, 50, 50))
|
||||||
danger_map.precompute([])
|
danger_map.precompute([])
|
||||||
evaluator = CostEvaluator(engine, danger_map)
|
# Use small penalties for testing
|
||||||
|
evaluator = CostEvaluator(engine, danger_map, bend_penalty=10.0)
|
||||||
|
|
||||||
p1 = Port(0, 0, 0)
|
p1 = Port(0, 0, 0)
|
||||||
p2 = Port(10, 10, 0)
|
p2 = Port(10, 10, 0)
|
||||||
|
|
||||||
h = evaluator.h_manhattan(p1, p2)
|
h = evaluator.h_manhattan(p1, p2)
|
||||||
# Manhattan distance = 20. Orientation penalty = 0.
|
# Manhattan distance = 20.
|
||||||
# Weighted by 1.1 -> 22.0
|
# Jog alignment penalty = 2*bp = 20.
|
||||||
assert abs(h - 22.0) < 1e-6
|
# Side check penalty = 2*bp = 20.
|
||||||
|
# Total = 1.1 * (20 + 40) = 66.0
|
||||||
|
assert abs(h - 66.0) < 1e-6
|
||||||
|
|
||||||
# Orientation penalty
|
# Orientation difference
|
||||||
p3 = Port(10, 10, 90)
|
p3 = Port(10, 10, 90)
|
||||||
h_wrong = evaluator.h_manhattan(p1, p3)
|
h_90 = evaluator.h_manhattan(p1, p3)
|
||||||
assert h_wrong > h
|
# diff = 90. penalty += 1*bp = 10.
|
||||||
|
# Side check: 2*bp = 20. (Total penalty = 30)
|
||||||
|
# Total = 1.1 * (20 + 30) = 55.0
|
||||||
|
assert abs(h_90 - 55.0) < 1e-6
|
||||||
|
|
||||||
|
# Traveling away
|
||||||
|
p4 = Port(10, 10, 180)
|
||||||
|
h_away = evaluator.h_manhattan(p1, p4)
|
||||||
|
# diff = 180. penalty += 2*bp = 20.
|
||||||
|
# Side check: 2*bp = 20.
|
||||||
|
# Total = 1.1 * (20 + 40) = 66.0
|
||||||
|
assert h_away >= h_90
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue