diff --git a/DOCS.md b/DOCS.md index fe585ea..4b0b693 100644 --- a/DOCS.md +++ b/DOCS.md @@ -51,3 +51,28 @@ The `CostEvaluator` defines the "goodness" of a path. - **Coordinates**: Micrometers (µm). - **Grid Snapping**: The router internally operates on a **1nm** grid for final ports and a **1µm** lattice for expansion moves. - **Search Space**: Assumptions are optimized for design areas up to **20mm x 20mm**. + +--- + +## 5. Best Practices & Tuning Advice + +### Speed vs. Optimality +The `greedy_h_weight` is your primary lever for search performance. +- **`1.0`**: Dijkstra-like behavior. Guarantees the shortest path but is very slow. +- **`1.1` to `1.2`**: Recommended range. Balances wire length with fast convergence. +- **`> 1.5`**: Extremely fast "greedy" search. May produce zig-zags or suboptimal detours. + +### Avoiding "Zig-Zags" +If the router produces many small bends instead of a long straight line: +1. Increase `bend_penalty` (e.g., set to `100.0` or higher). +2. Ensure `straight_lengths` includes larger values like `25.0` or `100.0`. +3. Decrease `greedy_h_weight` closer to `1.0`. + +### Handling Congestion +In multi-net designs, if nets are overlapping: +1. Increase `congestion_penalty` in `CostEvaluator`. +2. Increase `max_iterations` in `PathFinder`. +3. If a solution is still not found, check if the `clearance` is physically possible given the design's narrowest bottlenecks. + +### 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. diff --git a/examples/04_sbends_and_radii.py b/examples/04_sbends_and_radii.py index 2234878..44c3d46 100644 --- a/examples/04_sbends_and_radii.py +++ b/examples/04_sbends_and_radii.py @@ -3,7 +3,6 @@ from shapely.geometry import Polygon from inire.geometry.collision import CollisionEngine from inire.geometry.primitives import Port from inire.router.astar import AStarRouter -from inire.router.config import CostConfig, RouterConfig from inire.router.cost import CostEvaluator from inire.router.danger_map import DangerMap from inire.router.pathfinder import PathFinder diff --git a/examples/sbends_radii.png b/examples/sbends_radii.png index 1a8f9c5..a736212 100644 Binary files a/examples/sbends_radii.png and b/examples/sbends_radii.png differ diff --git a/inire/tests/test_components.py b/inire/tests/test_components.py index ffb43a4..37da884 100644 --- a/inire/tests/test_components.py +++ b/inire/tests/test_components.py @@ -1,7 +1,9 @@ +import numpy as np import pytest +from shapely.geometry import Point from inire.geometry.components import Bend90, SBend, Straight -from inire.geometry.primitives import Port +from inire.geometry.primitives import Port, rotate_port, translate_port def test_straight_generation() -> None: @@ -90,3 +92,67 @@ def test_sbend_collision_models() -> None: res_arc = SBend.generate(start, offset, radius, width, collision_type="arc") assert res_bbox.geometry[0].area > res_arc.geometry[0].area + + +def test_sbend_continuity() -> None: + # Verify SBend endpoints and continuity math + start = Port(10, 20, 90) # Starting facing up + offset = 4.0 + radius = 20.0 + width = 1.0 + + res = SBend.generate(start, offset, radius, width) + + # Target orientation should be same as start + assert abs(res.end_port.orientation - 90.0) < 1e-6 + + # For a port at 90 deg, +offset is a shift in -x direction + assert abs(res.end_port.x - (10.0 - offset)) < 1e-6 + + # Geometry should be connected (unary_union results in 1 polygon) + assert len(res.geometry) == 1 + assert res.geometry[0].is_valid + + +def test_arc_sagitta_precision() -> None: + # Verify that requested sagitta actually controls segment count + start = Port(0, 0, 0) + radius = 100.0 # Large radius to make sagitta significant + width = 2.0 + + # Coarse: 1um sagitta + res_coarse = Bend90.generate(start, radius, width, sagitta=1.0) + # Fine: 0.01um (10nm) sagitta + res_fine = Bend90.generate(start, radius, width, sagitta=0.01) + + # Number of segments should be significantly higher for fine + # Exterior points = (segments + 1) * 2 + pts_coarse = len(res_coarse.geometry[0].exterior.coords) + pts_fine = len(res_fine.geometry[0].exterior.coords) + + assert pts_fine > pts_coarse * 2 + + +def test_component_transform_invariance() -> None: + # Verify that generating at (0,0) then transforming + # is same as generating at the transformed port. + start0 = Port(0, 0, 0) + radius = 10.0 + width = 2.0 + + res0 = Bend90.generate(start0, radius, width, direction="CCW") + + # Transform: Translate (10, 10) then Rotate 90 + dx, dy = 10.0, 5.0 + angle = 90.0 + + # 1. Transform the generated geometry + p_end_transformed = rotate_port(translate_port(res0.end_port, dx, dy), angle) + + # 2. Generate at transformed start + start_transformed = rotate_port(translate_port(start0, dx, dy), angle) + res_transformed = Bend90.generate(start_transformed, radius, width, direction="CCW") + + assert abs(res_transformed.end_port.x - p_end_transformed.x) < 1e-6 + assert abs(res_transformed.end_port.y - p_end_transformed.y) < 1e-6 + assert abs(res_transformed.end_port.orientation - p_end_transformed.orientation) < 1e-6