diff --git a/README.md b/README.md index 4aea8d0..6b9858d 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,8 @@ Check the `examples/` directory for ready-to-run scripts demonstrating core feat * **`examples/01_simple_route.py`**: Basic single-net routing with visualization. * **`examples/02_congestion_resolution.py`**: Multi-net routing resolving bottlenecks using Negotiated Congestion. * **`examples/03_locked_paths.py`**: Incremental workflow using `lock_net()` to route around previously fixed paths. +* **`examples/04_sbends_and_radii.py`**: Complex paths using parametric S-bends and multiple bend radii. +* **`examples/05_orientation_stress.py`**: Stress test for various port orientation combinations (U-turns, opposite directions). Run an example: ```bash diff --git a/examples/05_orientation_stress.py b/examples/05_orientation_stress.py new file mode 100644 index 0000000..1f48487 --- /dev/null +++ b/examples/05_orientation_stress.py @@ -0,0 +1,56 @@ +from inire.geometry.collision import CollisionEngine +from inire.geometry.primitives import Port +from inire.router.astar import AStarRouter +from inire.router.cost import CostEvaluator +from inire.router.danger_map import DangerMap +from inire.router.pathfinder import PathFinder +from inire.utils.visualization import plot_routing_results + + +def main() -> None: + print("Running Example 05: Orientation Stress Test...") + + # 1. Setup Environment + # Give some breathing room (-20 to 120) for U-turns and flips (R=10) + bounds = (-20, -20, 120, 120) + engine = CollisionEngine(clearance=2.0) + danger_map = DangerMap(bounds=bounds) + danger_map.precompute([]) + + evaluator = CostEvaluator(engine, danger_map, greedy_h_weight=1.1) + router = AStarRouter(evaluator, node_limit=100000) + pf = PathFinder(router, evaluator) + + # 2. Define Netlist with various orientation challenges + netlist = { + # Opposite directions: requires two 90-degree bends to flip orientation + "opposite": (Port(10, 80, 0), Port(90, 80, 180)), + + # 90-degree turn: standard L-shape + "turn_90": (Port(10, 60, 0), Port(40, 90, 90)), + + # Output behind input: requires a full U-turn + "behind": (Port(80, 40, 0), Port(20, 40, 0)), + + # Sharp return: output is behind and oriented towards the input + "return_loop": (Port(80, 20, 0), Port(40, 10, 180)), + } + net_widths = {nid: 2.0 for nid in netlist} + + # 3. Route + results = pf.route_all(netlist, net_widths) + + # 4. Check Results + for nid, res in results.items(): + status = "Success" if res.is_valid else "Failed" + total_len = sum(comp.length for comp in res.path) if res.path else 0 + print(f" {nid:12}: {status}, total_length={total_len:.1f}") + + # 5. Visualize + fig, ax = plot_routing_results(results, [], bounds, netlist=netlist) + fig.savefig("examples/orientation_stress.png") + print("Saved plot to examples/orientation_stress.png") + + +if __name__ == "__main__": + main() diff --git a/examples/orientation_stress.png b/examples/orientation_stress.png new file mode 100644 index 0000000..20556c3 Binary files /dev/null and b/examples/orientation_stress.png differ diff --git a/inire/router/danger_map.py b/inire/router/danger_map.py index 2588e80..1ebf617 100644 --- a/inire/router/danger_map.py +++ b/inire/router/danger_map.py @@ -70,6 +70,10 @@ class DangerMap: safe_distances = np.maximum(distances, 0.1) self.grid = np.where(distances < self.safety_threshold, self.k / (safe_distances**2), 0.0).astype(np.float32) + def is_within_bounds(self, x: float, y: float) -> bool: + """Check if a coordinate is within the design bounds.""" + return self.minx <= x <= self.maxx and self.miny <= y <= self.maxy + def get_cost(self, x: float, y: float) -> float: """Get the proximity cost at a specific coordinate.""" ix = int((x - self.minx) / self.resolution) @@ -77,4 +81,4 @@ class DangerMap: if 0 <= ix < self.width_cells and 0 <= iy < self.height_cells: return float(self.grid[ix, iy]) - return 1e6 # Outside bounds is expensive + return 1e15 # Outside bounds is impossible