diff --git a/DOCS.md b/DOCS.md index 4b0b693..ea6292c 100644 --- a/DOCS.md +++ b/DOCS.md @@ -38,7 +38,18 @@ The `CostEvaluator` defines the "goodness" of a path. --- -## 3. CollisionEngine Parameters +## 3. PathFinder Parameters + +The `PathFinder` orchestrates multi-net routing using the Negotiated Congestion algorithm. + +| Parameter | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `max_iterations` | `int` | 10 | Maximum number of rip-up and reroute iterations to resolve congestion. | +| `base_congestion_penalty` | `float` | 100.0 | Starting penalty for overlaps. This value is multiplied by `1.5` each iteration if congestion persists. | + +--- + +## 4. CollisionEngine Parameters | Parameter | Type | Default | Description | | :--- | :--- | :--- | :--- | diff --git a/README.md b/README.md index 6b9858d..af1915f 100644 --- a/README.md +++ b/README.md @@ -39,9 +39,19 @@ danger_map = DangerMap(bounds=(0, 0, 1000, 1000)) danger_map.precompute([]) # Add polygons here for obstacles # 2. Configure Router -evaluator = CostEvaluator(engine, danger_map) -router = AStarRouter(evaluator) -pf = PathFinder(router, evaluator) +evaluator = CostEvaluator( + collision_engine=engine, + danger_map=danger_map, + greedy_h_weight=1.2 +) +router = AStarRouter( + cost_evaluator=evaluator, + bend_penalty=10.0 +) +pf = PathFinder( + router=router, + cost_evaluator=evaluator +) # 3. Define Netlist netlist = { @@ -59,11 +69,11 @@ if results["net1"].is_valid: Check the `examples/` directory for ready-to-run scripts demonstrating core features: -* **`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). +* **`examples/01_simple_route.py`**: Basic single-net routing with visualization. Generates `01_simple_route.png`. +* **`examples/02_congestion_resolution.py`**: Multi-net routing resolving bottlenecks using Negotiated Congestion. Generates `02_congestion_resolution.png`. +* **`examples/03_locked_paths.py`**: Incremental workflow using `lock_net()` to route around previously fixed paths. Generates `03_locked_paths.png`. +* **`examples/04_sbends_and_radii.py`**: Complex paths using parametric S-bends and multiple bend radii. Generates `04_sbends_and_radii.png`. +* **`examples/05_orientation_stress.py`**: Stress test for various port orientation combinations (U-turns, opposite directions). Generates `05_orientation_stress.png`. Run an example: ```bash @@ -79,6 +89,10 @@ python3 examples/01_simple_route.py For multi-net problems, the **PathFinder** loop handles rip-up and reroute logic, ensuring that paths find the globally optimal configuration without crossings. +## Configuration + +`inire` is highly tunable. Every major component (Router, CostEvaluator, PathFinder) accepts explicit named arguments in its constructor to control expansion rules, cost weights, and convergence limits. See `DOCS.md` for a full parameter reference. + ## License This project is licensed under the GNU Affero General Public License v3. See `LICENSE.md` for details. diff --git a/examples/simple_route.png b/examples/01_simple_route.png similarity index 100% rename from examples/simple_route.png rename to examples/01_simple_route.png diff --git a/examples/01_simple_route.py b/examples/01_simple_route.py index 0c81782..e8c0e8c 100644 --- a/examples/01_simple_route.py +++ b/examples/01_simple_route.py @@ -50,8 +50,8 @@ def main() -> None: # 5. Visualize fig, ax = plot_routing_results(results, [obstacle], bounds, netlist=netlist) - fig.savefig("examples/simple_route.png") - print("Saved plot to examples/simple_route.png") + fig.savefig("examples/01_simple_route.png") + print("Saved plot to examples/01_simple_route.png") if __name__ == "__main__": diff --git a/examples/congestion.png b/examples/02_congestion_resolution.png similarity index 100% rename from examples/congestion.png rename to examples/02_congestion_resolution.png diff --git a/examples/02_congestion_resolution.py b/examples/02_congestion_resolution.py index 0cb4b0e..34e39f5 100644 --- a/examples/02_congestion_resolution.py +++ b/examples/02_congestion_resolution.py @@ -32,7 +32,7 @@ def main() -> None: # 3. Route with Negotiated Congestion # We increase the base penalty to encourage faster divergence - pf.base_congestion_penalty = 1000.0 + pf = PathFinder(router, evaluator, base_congestion_penalty=1000.0) results = pf.route_all(netlist, net_widths) # 4. Check Results @@ -46,8 +46,8 @@ def main() -> None: # 5. Visualize fig, ax = plot_routing_results(results, [], bounds, netlist=netlist) - fig.savefig("examples/congestion.png") - print("Saved plot to examples/congestion.png") + fig.savefig("examples/02_congestion_resolution.png") + print("Saved plot to examples/02_congestion_resolution.png") if __name__ == "__main__": diff --git a/examples/locked.png b/examples/03_locked_paths.png similarity index 100% rename from examples/locked.png rename to examples/03_locked_paths.png diff --git a/examples/03_locked_paths.py b/examples/03_locked_paths.py index d01aec1..ad630ed 100644 --- a/examples/03_locked_paths.py +++ b/examples/03_locked_paths.py @@ -65,8 +65,8 @@ def main() -> None: all_netlists = {**netlist_p1, **netlist_p2} fig, ax = plot_routing_results(all_results, [], bounds, netlist=all_netlists) - fig.savefig("examples/locked.png") - print("Saved plot to examples/locked.png") + fig.savefig("examples/03_locked_paths.png") + print("Saved plot to examples/03_locked_paths.png") if __name__ == "__main__": diff --git a/examples/sbends_radii.png b/examples/04_sbends_and_radii.png similarity index 100% rename from examples/sbends_radii.png rename to examples/04_sbends_and_radii.png diff --git a/examples/04_sbends_and_radii.py b/examples/04_sbends_and_radii.py index 44c3d46..3b8fa28 100644 --- a/examples/04_sbends_and_radii.py +++ b/examples/04_sbends_and_radii.py @@ -61,8 +61,8 @@ def main() -> None: # 6. Visualize fig, ax = plot_routing_results(results, [], bounds, netlist=netlist) - fig.savefig("examples/sbends_radii.png") - print("Saved plot to examples/sbends_radii.png") + fig.savefig("examples/04_sbends_and_radii.png") + print("Saved plot to examples/04_sbends_and_radii.png") if __name__ == "__main__": diff --git a/examples/orientation_stress.png b/examples/05_orientation_stress.png similarity index 100% rename from examples/orientation_stress.png rename to examples/05_orientation_stress.png diff --git a/examples/05_orientation_stress.py b/examples/05_orientation_stress.py index 1f48487..a84306d 100644 --- a/examples/05_orientation_stress.py +++ b/examples/05_orientation_stress.py @@ -48,8 +48,8 @@ def main() -> None: # 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") + fig.savefig("examples/05_orientation_stress.png") + print("Saved plot to examples/05_orientation_stress.png") if __name__ == "__main__": diff --git a/inire/router/pathfinder.py b/inire/router/pathfinder.py index c592044..a61993a 100644 --- a/inire/router/pathfinder.py +++ b/inire/router/pathfinder.py @@ -25,11 +25,26 @@ class RoutingResult: class PathFinder: """Multi-net router using Negotiated Congestion.""" - def __init__(self, router: AStarRouter, cost_evaluator: CostEvaluator) -> None: + def __init__( + self, + router: AStarRouter, + cost_evaluator: CostEvaluator, + max_iterations: int = 10, + base_congestion_penalty: float = 100.0, + ) -> None: + """ + Initialize the PathFinder. + + Args: + router: The A* search engine. + cost_evaluator: The evaluator for path costs. + max_iterations: Maximum number of rip-up and reroute iterations. + base_congestion_penalty: Starting penalty for overlaps. + """ self.router = router self.cost_evaluator = cost_evaluator - self.max_iterations = 10 - self.base_congestion_penalty = 100.0 + self.max_iterations = max_iterations + self.base_congestion_penalty = base_congestion_penalty def route_all(self, netlist: dict[str, tuple[Port, Port]], net_widths: dict[str, float]) -> dict[str, RoutingResult]: """Route all nets in the netlist using Negotiated Congestion.""" diff --git a/inire/tests/test_congestion.py b/inire/tests/test_congestion.py index 6126055..395e1e3 100644 --- a/inire/tests/test_congestion.py +++ b/inire/tests/test_congestion.py @@ -40,8 +40,8 @@ def test_astar_sbend(basic_evaluator: CostEvaluator) -> None: def test_pathfinder_negotiated_congestion_resolution(basic_evaluator: CostEvaluator) -> None: router = AStarRouter(basic_evaluator) - pf = PathFinder(router, basic_evaluator) - pf.max_iterations = 10 + # Increase base penalty to force detour immediately + pf = PathFinder(router, basic_evaluator, max_iterations=10, base_congestion_penalty=1000.0) netlist = { "net1": (Port(0, 0, 0), Port(50, 0, 0)), @@ -57,9 +57,6 @@ def test_pathfinder_negotiated_congestion_resolution(basic_evaluator: CostEvalua basic_evaluator.collision_engine.add_static_obstacle(obs_bottom) basic_evaluator.danger_map.precompute([obs_top, obs_bottom]) - # Increase base penalty to force detour immediately - pf.base_congestion_penalty = 1000.0 - results = pf.route_all(netlist, net_widths) assert results["net1"].is_valid