Fix core geometry snapping, A* target lookahead, and test configurations

This commit is contained in:
Jan Petykiewicz 2026-03-15 21:14:42 -07:00
commit d438c5b7c7
88 changed files with 1463 additions and 476 deletions

View file

@ -0,0 +1,42 @@
# Cost and Collision Engine Spec
This document describes the methods for ensuring "analytic correctness" while maintaining a computationally efficient cost function.
## 1. Analytic Correctness
The router balances speed and verification using a two-tier approach.
### 1.1. R-Tree Geometry Engine
The router uses an **R-Tree of Polygons** for all geometric queries.
* **Move Validation:** Every "Move" proposed by the search is checked for intersection against the R-Tree.
* **Pre-dilation for Clearance:** All obstacles and paths use a global clearance $C$. At the start of the session, all user-provided obstacles are **pre-dilated by $(W_{max} + C)/2$** for initial broad pruning. However, individual path dilation for intersection tests uses $(W_i + C)/2$ for a specific net's width $W_i$.
* **Safety Zone:** To prevent immediate collision flags for ports placed on or near obstacle boundaries, the router ignores collisions within a radius of **2nm** of start and end ports.
* **Single Layer:** All routing and collision detection occur on a single layer.
### 1.2. Cost Calculation (Soft Constraint)
The "Danger Cost" $g_{proximity}(n)$ is a function of the distance $d$ to the nearest obstacle:
$$g_{proximity}(n) = \begin{cases} \infty & \text{if } d < (W_i + C)/2 \\ \frac{k}{d^2} & \text{if } (W_i + C)/2 \le d < \text{Safety Threshold} \\ 0 & \text{if } d \ge \text{Safety Threshold} \end{cases}$$
To optimize A* search, a **Static Danger Map (Precomputed Grid)** is used for the heuristic.
* **Grid Resolution:** Default **1000nm (1µm)**.
* **Static Nature:** The grid only accounts for fixed obstacles. It is computed once at the start of the session and is **not re-computed** during the Negotiated Congestion loop.
* **Efficiency:** For a 20x20mm layout, this results in a 20k x 20k matrix.
* **Memory:** Using a `uint8` or `float16` representation, this consumes ~400-800MB (Default < 2GB). For extremely high resolution or larger areas, the system supports up to **20GB** allocation.
* **Precision:** Strict intersection checks still use the R-Tree for "analytic correctness."
## 2. Collision Detection Implementation
The system relies on `shapely` for geometry and `rtree` for spatial indexing.
1. **Arc Resolution:** Arcs are represented as polygons approximated by segments with a maximum deviation (sagitta).
2. **Intersection Test:** A "Move" is valid only if its geometry does not intersect any obstacle in the R-Tree.
3. **Self-Intersection:** Paths from the same net must not intersect themselves.
4. **No Crossings:** Strictly 2D; no crossings (vias or bridges) are supported.
## 3. Negotiated Congestion (Path R-Tree)
To handle multiple nets, the router maintains a separate R-Tree containing the dilated geometries ($C/2$) of all currently routed paths.
* **Congestion Cost:** $P \times (\text{Overlaps in Path R-Tree})$.
* **Failure Policy:** If no collision-free path is found after the max iterations, the router returns the **"least-bad" (lowest cost)** path. These paths MUST be explicitly flagged as invalid (e.g., via an `is_valid=False` attribute or a non-zero `collision_count`) so the user can identify and manually fix the failure.
## 4. Handling Global Clearances
Clearances are global. Both obstacles and paths are pre-dilated once. This ensures that any two objects maintain at least $C$ distance if their dilated versions do not intersect.
## 5. Locked Paths
The router supports **Locked Paths**—existing geometries inserted into the static Obstacle R-Tree, ensuring they are never modified or rerouted.

View file

@ -0,0 +1,37 @@
# Geometric Representation and Data Structures
This document defines the core data structures for representing the routing problem.
## 1. Port Definitions (Connectivity)
Routing requests are defined as a mapping of source ports to destination ports.
* **Port Structure:** `(x, y, orientation)`
* `x, y`: Coordinates snapped to **1nm** grid.
* `orientation`: Strictly $\{0, 90, 180, 270\}$ degrees.
* **Netlist:** A dictionary or list of tuples: `{(start_port): (end_port)}`. Each entry represents a single-layer path that must be routed.
* **Heterogeneous Widths:** The router supports different widths $W_i$ per net. Dilation for a specific net $i$ is calculated as $(W_i + C)/2$ to maintain the global clearance $C$.
## 2. Component Library (Move Generator)
The router uses a discrete set of components to expand states in the A* search.
### 2.1. Straight Waveguides
* **A* Expansion:** Generates "Straight" moves of varying lengths (e.g., $1\mu m, 5\mu m, 25\mu m$).
* **Snap-to-Target:** If the destination port $T$ is directly ahead and the current state's orientation matches $T$'s entry orientation, a special move is generated to close the gap exactly.
### 2.2. Fixed 90° Bends (PDK Cells)
* **Parameters:** `radius`, `width`.
* **A* Expansion:** A discrete move that changes orientation by $\pm 90^\circ$ and shifts the coordinate by the radius.
* **Grid Alignment:** If the bend radius $R$ is not a multiple of the search grid (default $1\mu m$), the resulting state is **snapped to the nearest grid point**, and a warning is issued to the user.
### 2.3. Parametric S-Bends (Compact)
* **Parameters:** `radius`, `width`.
* **A* Expansion:** Used ONLY for lateral offsets $O < 2R$.
* **Large Offsets ($O \ge 2R$):** The router does not use a single S-bend move for large offsets. Instead, the A* search naturally finds the optimal path by combining two 90° bends and a straight segment. This ensures maximum flexibility in obstacle avoidance for large shifts.
## 3. Obstacle Representation
Obstacles are provided as raw polygons on a single layer.
* **Pre-processing:** All input polygons are inserted into an **R-Tree**.
* **Buffer/Dilation:** Obstacles are pre-dilated by $(W_{max} + Clearance)/2$ for initial pruning, but final collision tests use the net-specific width $W_i$.
* **No Multi-layer:** The router assumes all obstacles and paths share the same plane.
* **Safety Zone:** Ignore collisions within **2nm** of start and end ports for robustness.

View file

@ -0,0 +1,13 @@
# High-Level Notes: Auto-Routing for Integrated Circuits
We're implementing auto-routing for photonic and RF integrated circuits. The problem space has the following features:
- **Single Layer:** All paths are on a single layer; multi-layer routing is not supported.
- **No Crossings:** Crossings are to be avoided and are not supported by the router's automatic placement. The user must manually handle any required crossings.
- **Large Bend Radii:** Bends use large radii ($R$), and are usually pre-generated (e.g. 90-degree cells).
- **Proximity Sensitivity:** Paths are sensitive to proximity to other paths and obstacles (crosstalk/coupling).
- **Manhattan Preference:** Manhattan pathing is sufficient for most cases; any-angle is rare.
- **S-Bends:** S-bends are necessary for small lateral offsets ($O < 2R$).
- **Hybrid Search:** A* state-lattice search is used for discrete component placement.
- **Dilation:** A $Clearance/2$ dilation is applied to all obstacles and paths for efficient collision avoidance.
- **Negotiated Congestion:** A multi-net "PathFinder" loop iteratively reroutes nets to resolve congestion.

View file

@ -0,0 +1,64 @@
# Implementation Plan
This plan outlines the step-by-step implementation of the `inire` auto-router. For detailed test cases, refer to [Testing Plan](./testing_plan.md).
## Phase 1: Core Geometry & Move Generation
**Goal:** Implement Ports, Polygons, and Component Library with high geometric fidelity.
1. **Project Setup:** Initialize `inire/` structure and `pytest` configuration. Include `hypothesis` for property-based testing.
2. **`geometry.primitives`:**
* `Port` with **1nm** snapping.
* Basic 2D transformations (rotate, translate).
* **Property-Based Tests:** Verify transform invariants (e.g., $90^\circ$ rotation cycles).
3. **`geometry.components`:**
* `Straight`, `Bend90`, `SBend`.
* **Search Grid Snapping:** Implement 1µm snapping for expanded ports.
* **Small S-Bends ($O < 2R$):** Logic for parametric generation.
* **Edge Cases:** Handle $O=2R$ and $L < 1\mu m$.
4. **Tests:**
* Verify geometric correctness (refer to Testing Plan Section 1).
* Unit tests for `Port` snapping and component transformations.
## Phase 2: Collision Engine & Cost
**Goal:** Build the R-Tree wrapper and the analytic cost function.
1. **`geometry.collision`:** Implement `CollisionEngine`.
* **Pre-dilation:** Obstacles/Paths dilated by $Clearance/2$.
* **Safety Zone:** Ignore collisions within **2nm** of start/end ports.
2. **`router.danger_map`:**
* Implement **1µm** pre-computed proximity grid.
* Optimize for design sizes up to **20x20mm** (< 2GB memory).
3. **`router.cost`:** Implement `CostEvaluator`.
* Bend cost: $10 \times (\text{Manhattan distance between ports})$.
* Integrate R-Tree for strict checks and Danger Map for heuristic.
4. **Tests:**
* Verify collision detection with simple overlapping shapes (Testing Plan Section 2.1).
* Verify Danger Map accuracy and memory footprint (Testing Plan Section 2.2).
* **Post-Route Validator:** Implement the independent `validate_path` utility.
## Phase 3: Single-Net A* Search
**Goal:** Route a single net from A to B with 1nm precision.
1. **`router.astar`:** Implement the priority queue loop.
* State representation: `(x_µm, y_µm, theta)`.
* Move expansion loop with 1µm grid.
* **Natural S-Bends:** Ensure search can find $O \ge 2R$ shifts by combining moves.
* **Look-ahead Snapping:** Actively bridge to the 1nm target when in the capture radius (10µm).
2. **Heuristic:** Manhattan distance $h(n)$ + orientation penalty + Danger Map lookup.
3. **Tests:**
* Solve simple maze problems and verify path optimality (Testing Plan Section 3).
* Verify snap-to-target precision at 1nm resolution.
* **Determinism:** Verify same seed = same path.
## Phase 4: Multi-Net PathFinder
**Goal:** Implement the "Negotiated Congestion" loop for multiple nets.
1. **`router.pathfinder`:**
* Sequential routing -> Identify congestion -> Inflate cost -> Reroute.
* **R-Tree Congestion:** Store dilated path geometries.
2. **Explicit Results:** Return `RoutingResult` objects with `is_valid` and `collisions` metadata.
3. **Tests:**
* Full multi-net benchmarks (Testing Plan Section 4).
* Verify rerouting behavior in crowded environments.
## Phase 5: Visualization, Benchmarking & Fuzzing
1. **`utils.visualization`:** Plot paths using `matplotlib`. Highlight collisions in red.
2. **Benchmarks:** Stress test with 50+ nets. Verify performance and node limits (Testing Plan Section 5).
3. **Fuzzing:** Run A* on randomized layouts to ensure stability.
4. **Final Validation:** Ensure all `is_valid=True` results pass the independent `validate_path` check.

View file

@ -0,0 +1,57 @@
# Python Package Structure
This document outlines the directory structure and module organization for the `inire` auto-router package.
## 1. Directory Layout
```
inire/
├── __init__.py # Exposes the main `Router` class and key types
├── geometry/ # Core geometric primitives and operations
│ ├── __init__.py
│ ├── primitives.py # Point, Port, Polygon, Arc classes
│ ├── collision.py # R-Tree wrapper and intersection logic
│ └── components.py # Move generators (Straight, Bend90, SBend)
├── router/ # Search algorithms and pathfinding
│ ├── __init__.py
│ ├── astar.py # Hybrid State-Lattice A* implementation
│ ├── graph.py # Node, Edge, and Graph data structures
│ ├── cost.py # Cost functions (length, bend, proximity)
│ ├── danger_map.py # Pre-computed grid for heuristic proximity costs
│ └── pathfinder.py # Multi-net "Negotiated Congestion" manager
├── utils/ # Utility functions
│ ├── __init__.py
│ └── visualization.py # Plotting tools for debug/heatmaps (matplotlib/klayout)
└── tests/ # Unit and integration tests
├── __init__.py
├── conftest.py # Pytest fixtures (common shapes, PDK cells)
├── test_primitives.py # Tests for Port and coordinate transforms
├── test_components.py # Tests for Straight, Bend90, SBend generation
├── test_collision.py # Tests for R-Tree and dilation logic
├── test_cost.py # Tests for Danger Map and cost evaluation
├── test_astar.py # Tests for single-net routing (mazes, snapping)
└── test_pathfinder.py # Multi-net "Negotiated Congestion" benchmarks
```
## 2. Module Responsibilities
### `inire.geometry`
* **`primitives.py`**: Defines the `Port` named tuple `(x, y, theta)` and helper functions for coordinate transforms.
* **`collision.py`**: Wraps the `rtree` or `shapely` library. Handles the "Analytic Correctness" checks (exact polygon distance).
* **`components.py`**: Logic to generate "Moves" from a start port. E.g., `SBend.generate(start_port, offset, radius)` returns a list of polygons and the end port. Handles $O > 2R$ logic.
### `inire.router`
* **`astar.py`**: The heavy lifter. Maintains the `OpenSet` (priority queue) and `ClosedSet`. Implements the "Snap-to-Target" logic.
* **`cost.py`**: compute $f(n) = g(n) + h(n)$. encapsulates the "Danger Map" and Path R-Tree lookups.
* **`danger_map.py`**: Manages the pre-computed proximity grid used for $O(1)$ heuristic calculations.
* **`pathfinder.py`**: Orchestrates the multi-net loop. Tracks the Path R-Tree for negotiated congestion and triggers reroutes.
### `inire.tests`
* **Structure:** Tests are co-located within the package for ease of access.
* **Fixtures:** `conftest.py` will provide standard PDK cells (e.g., a $10\mu m$ radius bend) to avoid repetition in test cases.
## 3. Dependencies
* `numpy`: Vector math.
* `shapely`: Polygon geometry and intersection.
* `rtree`: Spatial indexing.
* `networkx` (Optional): Not used for core search to ensure performance.

View file

@ -0,0 +1,67 @@
# Architecture Decision Record: Auto-Routing for Photonic & RF ICs
## 1. Problem Context
Photonic and RF routing differ significantly from digital VLSI due to physical constraints:
* **Geometric Rigidity:** 90° bends are pre-rendered PDK cells with fixed bounding boxes.
* **Parametric Flexibility:** S-bends must be generated on-the-fly to handle arbitrary offsets, provided they maintain a constant radius $R$.
* **Signal Integrity:** High sensitivity to proximity (coupling/crosstalk) and a strong preference for single-layer, non-crossing paths.
* **Manual Intervention:** The router is strictly 2D and avoids all other geometries on the same layer. No crossings (e.g. vias or bridges) are supported by the automatic routing engine. The user must manually handle any required crossings by placing components (e.g. crossing cells) and splitting the net list accordingly. This simplifies the router's task to 2D obstacle avoidance and spacing optimization.
---
## 2. Candidate Algorithms & Tradeoffs
### 2.1. Rubber-Band (Topological) Routing
This approach treats paths as elastic bands stretched around obstacles, later "inflating" them to have width and curvature.
| Feature | Analysis |
| :--- | :--- |
| **Strengths** | Excellent at "River Routing" and maintaining minimum clearances. Inherently avoids crossings. |
| **Downsides** | **The Inflation Gap:** A valid thin-line topology may be physically un-routable once the large radius $R$ is applied. It struggles to integrate rigid, pre-rendered 90° blocks into a continuous elastic model. |
| **Future Potential** | High, if a "Post-Processing" engine can reliably snap elastic curves to discrete PDK cells without breaking connectivity. |
### 2.2. Voronoi-Based (Medial Axis) Routing
Uses a Voronoi diagram to find paths that are maximally distant from all obstacles.
| Feature | Analysis |
| :--- | :--- |
| **Strengths** | Theoretically optimal for "Safety" and crosstalk reduction. Guaranteed maximum clearance. |
| **Downsides** | **Manhattan Incompatibility:** Voronoi edges are any-angle and often jagged. Mapping these to a Manhattan-heavy PDK (90° bends) requires a lossy "snapping" phase that often violates the very safety the algorithm intended to provide. |
| **Future Potential** | Useful as a "Channel Finder" to guide a more rigid router, but unsuitable as a standalone geometric solver. |
### 2.3. Integer Linear Programming (ILP)
Formulates routing as a massive optimization problem where a solver picks the best path from a pool of candidates.
| Feature | Analysis |
| :--- | :--- |
| **Strengths** | Can find the mathematically "best" layout (e.g., minimum total length or total bends) across all nets simultaneously. |
| **Downsides** | **Candidate Explosion:** Because S-bends are generated on-the-fly, the number of possible candidate shapes is infinite. To use ILP, one must "discretize" the search space, which may miss the one specific geometry needed for a tight fit. |
| **Future Potential** | Effective for small, high-congestion "Switchbox" areas where all possible geometries can be pre-tabulated. |
---
## 3. Selected Approach: Hybrid State-Lattice A*
### 3.1. Rationale
The **State-Lattice** variant of the A* algorithm is selected as the primary routing engine. Unlike standard A* which moves between grid cells, State-Lattice moves between **states** defined as $(x, y, \theta)$.
1. **Native PDK Integration:** The router treats the pre-rendered 90° bend cell as a discrete "Move" in the search tree. The algorithm only considers placing a bend if the cell's bounding box is clear of obstacles.
2. **Parametric S-Bends:** When the search needs to bridge a lateral gap, it triggers a "Procedural Move." It calculates a fixed-radius S-bend on-the-fly. If the resulting arc is valid and collision-free, it is added as an edge in the search graph.
3. **Predictable Costing:** It allows for a sophisticated cost function $f(n) = g(n) + h(n)$ where:
* $g(n)$ penalizes path length and proximity to obstacles (using a distance-transform field).
* $h(n)$ (the heuristic) guides the search toward the destination while favoring Manhattan alignments.
### 3.2. Implementation Strategy
* **Step 1: Distance Transform.** Pre-calculate a "Danger Map" of the layout. Cells close to obstacles have a high cost; cells far away have low cost. This handles the **Proximity Sensitivity** constraint.
* **Step 2: State Expansion.** From the current point, explore:
* `Straight(length)`
* `PDK_Bend_90(direction)`
* `S_Bend(target_offset, R)`
* **Step 3: Rip-up and Reroute.** To handle the sequential nature of A*, implement a "Negotiated Congestion" scheme (PathFinder algorithm) where nets "pay" more to occupy areas that other nets also want.
---
## 4. Summary of Tradeoffs for Future Review
* **Why not pure Manhattan?** Photonic/RF requirements for large $R$ and S-bends make standard grid-based maze routers obsolete.
* **Why not any-angle?** While any-angle is possible, the PDK's reliance on pre-rendered 90° blocks makes a lattice-based approach more manufacturing-stable.
* **Risk:** The primary risk is **Search Time**. As the library of moves grows (more S-bend options), the branching factor increases. This must be managed with aggressive pruning and spatial indexing (e.g., R-trees).

View file

@ -0,0 +1,66 @@
# Routing Search Specification
This document details the Hybrid State-Lattice A* implementation and the "PathFinder" (Negotiated Congestion) algorithm for multi-net routing.
## 1. Hybrid State-Lattice A* Search
The core router operates on states $S = (x, y, \theta)$, where $\theta \in \{0, 90, 180, 270\}$.
### 1.1. State Representation & Snapping
To ensure search stability and hash-map performance:
* **Intermediate Ports:** Every state expanded by A* is snapped to a search grid.
* **Search Grid:** Default snap is **1000nm (1µm)**.
* **Final Alignment:** The "Snap-to-Target" logic bridges the gap from the coarse search grid to the final **1nm** port coordinates.
* **Max Design Size:** Guidelines for memory/performance assume up to **20mm x 20mm** routing area.
### 1.2. Move Expansion Logic
From state $S_n$, the following moves are evaluated:
1. **Tiered Straight Steps:** Expand by a set of distances $L \in \{1\mu m, 5\mu m, 25\mu m\}$.
2. **Snap-to-Target:** A "last-inch" look-ahead move. If the target $T$ is within a **Capture Radius (Default: 10µm)** and a straight segment or single bend can reach it, a special move is generated to close the gap exactly at 1nm resolution.
3. **90° Bend:** Try clockwise/counter-clockwise turns using fixed PDK cells.
4. **Small-Offset S-Bend:**
* **Only for $O < 2R$:** Parametric S-bend (two tangent arcs).
* **$O \ge 2R$:** Search naturally finds these by combining 90° bends and straight segments.
### 1.3. Cost Function $f(n) = g(n) + h(n)$
The search uses a flexible, component-based cost model.
* **$g(n)$ (Actual Cost):** $\sum \text{ComponentCost}_i + \text{ProximityCost} + \text{CongestionCost}$
* **Straight Segment:** $L \times C_{unit\_length}$.
* **90° Bend:** $10 \times (\text{Manhattan distance between ports})$.
* **S-Bend:** $f(O, R)$.
* **Proximity Cost:** $k/d^2$ penalty (strict checks use R-Tree).
* **Congestion Cost:** $P \times (\text{Overlaps in Path R-Tree})$.
* **$h(n)$ (Heuristic):**
* Manhattan distance $L_1$ to the target.
* Orientation Penalty: High cost if the state's orientation doesn't match the target's entry orientation.
* **Greedy Weighting:** The A* search uses a weighted heuristic (e.g., $1.1 \times h(n)$) to prioritize search speed over strict path optimality.
* **Danger Map Heuristic:** Fast lookups from the **1µm** pre-computed proximity grid.
## 2. Multi-Net "PathFinder" Strategy (Negotiated Congestion)
1. **Iteration:** Identify "Congestion Areas" using Path R-Tree intersections.
2. **Inflation:** Increase penalty multiplier $P$ for congested areas.
3. **Repeat:** Continue until no overlaps exist or the max iteration count is reached (Default: **20 iterations**).
### 2.1. Convergence & Result Policy
* **Least Bad Attempt:** If no 100% collision-free solution is found, return the result with the lowest total cost (including overlaps).
* **Explicit Reporting:** Results MUST include a `RoutingResult` object containing:
* `path_geometry`: The actual polygon sequence.
* `is_valid`: Boolean (True only if no collisions).
* `collisions`: A count or list of detected overlap points/polygons.
* **Visualization:** Overlapping regions are highlighted (e.g., in red) in the heatmaps.
## 3. Search Limits & Scaling
* **Node Limit:** A* search is capped at **50,000 nodes** per net per iteration.
* **Dynamic Timeout:** Session-level timeout based on problem size:
* `Timeout = max(2s, 0.05s * num_nets * num_iterations)`.
* *Example:* A 100-net problem over 20 iterations times out at **100 seconds**.
## 4. Determinism
All search and rip-up operations are strictly deterministic.
* **Seed:** A user-provided `seed` (int) MUST be used to initialize any random number generators (e.g., if used for tie-breaking or initial net ordering).
* **Tie-Breaking:** If two nodes have the same $f(n)$, a consistent tie-breaking rule (e.g., based on node insertion order or state hash) must be used.
## 5. Optimizations
* **A* Pruning:** Head toward the target and prune suboptimal orientations.
* **Safety Zones:** Ignore collisions within **2nm** of start/end ports.
* **Spatial Indexing:** R-Tree queries are limited to the bounding box of the current move.

138
docs/plans/testing_plan.md Normal file
View file

@ -0,0 +1,138 @@
# Testing Plan (Refined)
This document defines the comprehensive testing strategy for the `inire` auto-router, ensuring analytic correctness and performance.
## 1. Unit Tests: Geometry & Components (`inire/geometry`)
### 1.1. Primitives (`primitives.py`)
* **Port Snapping:** Verify that `Port(x, y, orientation)` snaps `x` and `y` to the nearest **1nm** grid point.
* **Coordinate Transforms:**
* Translate a port by `(dx, dy)`.
* Rotate a port by `90`, `180`, `270` degrees around an origin.
* Verify orientation wrapping (e.g., $270^\circ + 90^\circ \to 0^\circ$).
* **Polygon Creation:**
* Generate a polygon from a list of points.
* Verify bounding box calculation.
* **Property-Based Testing (Hypothesis):**
* Verify that any `Port` after transformation remains snapped to the **1nm** grid.
* Verify that $Rotate(Rotate(Port, 90), -90)$ returns the original `Port` (up to snapping).
### 1.2. Component Generation (`components.py`)
* **Straight Moves:**
* Generate a `Straight(length=10.0, width=2.0)` segment.
* Verify the end port is exactly `length` away in the correct orientation.
* Verify the resulting polygon's dimensions.
* **Edge Case:** $L < 1\mu m$ (below search grid). Verify it still generates a valid 1nm segment.
* **Bend90:**
* Generate a `Bend90(radius=10.0, width=2.0, direction='CW')`.
* Verify the end port's orientation is changed by $-90^\circ$.
* Verify the end port's position is exactly `(R, -R)` relative to the start (for $0^\circ \to 270^\circ$).
* **Grid Snapping:** Verify that if the bend radius results in a non-1µm aligned port, it is snapped to the nearest **1µm** search grid point (with a warning).
* **SBend (Small Offset $O < 2R$):**
* Generate an `SBend(offset=5.0, radius=10.0, width=2.0)`.
* Verify the total length matches the analytical $L = 2\sqrt{O(2R - O/4)}$ (or equivalent arc-based formula).
* Verify the tangent continuity at the junction of the two arcs.
* **Edge Case:** $O = 2R$. Verify it either generates two 90-degree bends or fails gracefully with a clear error.
* Verify it fails/warns if $O > 2R$.
### 1.3. Geometric Fidelity
* **Arc Resolution (Sagitta):**
* Verify that `Bend90` and `SBend` polygons are approximated by segments such that the maximum deviation (sagitta) is within a user-defined tolerance (e.g., 10nm).
* Test with varying radii to ensure segment count scales appropriately.
## 2. Unit Tests: Collision & Cost (`inire/geometry/collision` & `router/cost`)
### 2.1. Collision Engine
* **Pre-dilation Logic:**
* Verify that an obstacle (polygon) is correctly dilated by $(W_{max} + C)/2$.
* **Heterogeneous Widths:** Verify that a path for Net A (width $W_1$) is dilated by $(W_1 + C)/2$, while Net B (width $W_2$) uses $(W_2 + C)/2$.
* **Locked Paths:**
* Insert an existing path geometry into the "Static Obstacle" R-Tree.
* Verify that the router treats it as an unmovable obstacle and avoids it.
* **R-Tree Queries:**
* Test intersection detection between two overlapping polygons.
* Test non-intersection between adjacent but non-overlapping polygons (exactly $C$ distance apart).
* **Safety Zone (2nm):**
* Create a port exactly on the edge of an obstacle.
* Verify that a "Move" starting from this port is NOT flagged for collision if the intersection occurs within **2nm** of the port.
* **Self-Intersection:**
* Verify that a path consisting of multiple segments is flagged if it loops back on itself.
### 2.2. Danger Map & Cost Evaluator
* **Danger Map Generation:**
* Initialize a map for a $100\mu m \times 100\mu m$ area with a single obstacle.
* Verify the cost $g_{proximity}$ matches $k/d^2$ for cells near the obstacle.
* Verify cost is $0$ for cells beyond the **Safety Threshold**.
* **Memory Check:**
* Mock a $20mm \times 20mm$ grid and verify memory allocation stays within limits (e.g., `< 2GB` for standard `uint8` resolution).
* **Cost Calculation:**
* Verify total cost $f(n)$ correctly sums length, bend penalties ($10 \times$ Manhattan), and proximity costs.
### 2.3. Robustness & Limits
* **Design Bounds:**
* Test routing at the extreme edges of the $20mm \times 20mm$ coordinate space.
* Verify that moves extending outside the design bounds are correctly pruned or flagged.
* **Empty/Invalid Inputs:**
* Test with an empty netlist.
* Test with start and end ports at the exact same location.
## 3. Integration Tests: Single-Net A* Search (`inire/router/astar`)
### 3.1. Open Space Scenarios
* **Straight Line:** Route from `(0,0,0)` to `(100,0,0)`. Verify it uses only `Straight` moves.
* **Simple Turn:** Route from `(0,0,0)` to `(20,20,90)`. Verify it uses a `Bend90` and `Straight` segments.
* **Small S-Bend:** Route with an offset of $5\mu m$ and radius $10\mu m$. Verify it uses the `SBend` component.
* **Large S-Bend ($O \ge 2R$):** Route with an offset of $50\mu m$ and radius $10\mu m$. Verify it combines two `Bend90`s and a `Straight` segment.
### 3.2. Obstacle Avoidance (The "Maze" Tests)
* **L-Obstacle:** Place an obstacle blocking the direct path. Verify the router goes around it.
* **Narrow Channel:** Create two obstacles with a gap slightly wider than $W_i + C$. Verify the router passes through.
* **Dead End:** Create a U-shaped obstacle. Verify the search explores alternatives and fails gracefully if no path exists.
### 3.3. Snapping & Precision
* **Snap-to-Target Lookahead:**
* Route to a target at `(100.005, 0, 0)` (not on 1µm grid).
* Verify the search reaches the vicinity via the 1µm grid and the final segment bridges the **5nm** gap exactly.
* **Grid Alignment:**
* Start from a port at `(0.5, 0.5, 0)`. Verify it snaps to the 1µm search grid correctly for the first move expansion.
### 3.4. Failure Modes
* **Unreachable Target:** Create a target completely enclosed by obstacles. Verify the search terminates after exploring all options (or hitting the 50,000 node limit) and returns an invalid result.
* **Start/End Collision:** Place a port deep inside an obstacle (beyond the 2nm safety zone). Verify the router identifies the immediate collision and fails gracefully.
## 4. Integration Tests: Multi-Net PathFinder (`inire/router/pathfinder`)
### 4.1. Congestion Scenarios
* **Parallel Paths:** Route two nets that can both take straight paths. Verify no reroutes occur.
* **The "Cross" Test:** Two nets must cross paths in 2D.
* Since crossings are illegal, verify the second net finds a detour.
* Verify the `Negotiated Congestion` loop increases the cost of the shared region.
* **Bottleneck:** Force 3 nets through a channel that only fits 2.
* Verify the router returns 2 valid paths and 1 "least bad" path (with collisions flagged).
* Verify the `is_valid=False` attribute is set for the failing net.
### 4.2. Determinism & Performance
* **Seed Consistency:** Run the same multi-net problem twice with the same seed; verify identical results (pixel-perfect).
* **Node Limit Enforcement:** Trigger a complex search that exceeds **50,000 nodes**. Verify it terminates and returns the best-so-far or failure.
* **Timeout:** Verify the session-level timeout stops the process for extremely large problems.
## 5. Benchmarking & Regression
* **Standard Benchmark Suite:** A set of 5-10 layouts with varying net counts (1 to 50).
* **Metrics to Track:**
* Total wire length.
* Total number of bends.
* Execution time per net.
* Success rate (percentage of nets routed without collisions).
* **Node Expansion Rate:** Nodes per second.
* **Memory Usage:** Peak RSS during 20x20mm routing.
* **Fuzz Testing:**
* Generate random obstacles and ports within a 1mm x 1mm area.
* Verify that the router never crashes.
* Verify that every result marked `is_valid=True` is confirmed collision-free by a high-precision (slow) check.
## 6. Analytic Correctness Guarantees
* **Post-Route Validation:**
* Implement an independent `validate_path(path, obstacles, clearance)` function using `shapely`'s most precise intersection tests.
* Run this on every test result to ensure the `CollisionEngine` (which uses R-Tree for speed) hasn't missed any edge cases.
* **Orientation Check:**
* Verify that the final port of every path matches the target orientation exactly $\{0, 90, 180, 270\}$.