diff --git a/DOCS.md b/DOCS.md index aa45f38..28280e2 100644 --- a/DOCS.md +++ b/DOCS.md @@ -128,8 +128,65 @@ Use `RoutingProblem.initial_paths` to provide semantic per-net seeds. Seeds are | Field | Default | Description | | :-- | :-- | :-- | | `capture_expanded` | `False` | Record expanded nodes for diagnostics and visualization. | +| `capture_conflict_trace` | `False` | Capture authoritative post-reverify conflict trace entries for debugging negotiated-congestion failures. | +| `capture_frontier_trace` | `False` | Run an analysis-only reroute for reached-but-colliding nets and capture prune causes near their final conflict hotspots. | -## 7. RouteMetrics +## 7. Conflict Trace + +`RoutingRunResult.conflict_trace` is an immutable tuple of post-reverify conflict snapshots. It is empty unless `RoutingOptions.diagnostics.capture_conflict_trace=True`. + +Trace types: + +- `ConflictTraceEntry` + - `stage`: `"iteration"`, `"restored_best"`, or `"final"` + - `iteration`: Iteration index for `"iteration"` entries, otherwise `None` + - `completed_net_ids`: Nets with collision-free reached-target paths at that stage + - `conflict_edges`: Undirected dynamic-conflict net pairs seen after full reverify + - `nets`: Per-net trace payloads in routing-order order +- `NetConflictTrace` + - `net_id` + - `outcome` + - `reached_target` + - `report` + - `conflicting_net_ids`: Dynamic conflicting nets for that stage + - `component_conflicts`: Dynamic component-pair overlaps for that stage +- `ComponentConflictTrace` + - `other_net_id` + - `self_component_index` + - `other_component_index` + +The conflict trace only records dynamic net-vs-net component overlaps. Static-obstacle and self-collision details remain count-only in `RoutingReport`. + +Use `scripts/record_conflict_trace.py` to capture JSON and Markdown trace artifacts for the built-in trace scenarios. The default target is `example_07_large_scale_routing_no_warm_start`. + +## 8. Frontier Trace + +`RoutingRunResult.frontier_trace` is an immutable tuple of per-net post-run frontier analyses. It is empty unless `RoutingOptions.diagnostics.capture_frontier_trace=True`. + +Trace types: + +- `NetFrontierTrace` + - `net_id` + - `hotspot_bounds`: Buffered bounds around the net's final dynamic component-overlap hotspots + - `pruned_closed_set` + - `pruned_hard_collision` + - `pruned_self_collision` + - `pruned_cost` + - `samples`: First traced prune events near those hotspots +- `FrontierPruneSample` + - `reason`: `"closed_set"`, `"hard_collision"`, `"self_collision"`, or `"cost"` + - `move_type` + - `hotspot_index` + - `parent_state` + - `end_state` + +The frontier trace is observational only. It reruns only the final reached-but-colliding nets in analysis mode, with scratch metrics, after the routed result is already fixed. + +Use `scripts/record_frontier_trace.py` to capture JSON and Markdown frontier-prune artifacts for the built-in trace scenarios. The default target is `example_07_large_scale_routing_no_warm_start`. + +Separately from the observational trace tooling, the router may run a bounded post-loop pair-local scratch reroute before refinement when the restored best snapshot ends with final two-net reached-target dynamic conflicts. That repair phase is part of normal routing behavior and is reported through the `pair_local_search_*` counters below. + +## 9. RouteMetrics `RoutingRunResult.metrics` is an immutable per-run snapshot. @@ -158,6 +215,10 @@ Use `RoutingProblem.initial_paths` to provide semantic per-net seeds. Seeds are - `move_cache_abs_hits` / `move_cache_abs_misses`: Absolute move-geometry cache activity. - `move_cache_rel_hits` / `move_cache_rel_misses`: Relative move-geometry cache activity. +- `guidance_match_moves`: Number of moves that matched the reroute guidance seed and received the guidance bonus. +- `guidance_match_moves_straight`, `guidance_match_moves_bend90`, `guidance_match_moves_sbend`: Guidance-match counts split by move type. +- `guidance_bonus_applied`: Total reroute-guidance bonus subtracted from move costs across the run. +- `guidance_bonus_applied_straight`, `guidance_bonus_applied_bend90`, `guidance_bonus_applied_sbend`: Guidance bonus totals split by move type. - `static_safe_cache_hits`: Reuse count for the static-safe admission cache. - `hard_collision_cache_hits`: Reuse count for the hard-collision cache. - `congestion_cache_hits` / `congestion_cache_misses`: Per-search congestion-cache activity. @@ -199,14 +260,21 @@ Use `RoutingProblem.initial_paths` to provide semantic per-net seeds. Seeds are - `verify_dynamic_candidate_nets`: Total candidate net ids returned by the dynamic net-envelope broad phase during final verification. - `verify_dynamic_exact_pair_checks`: Number of exact geometry-pair checks performed during dynamic-path verification. -## 8. Internal Modules +### Local Search Counters + +- `pair_local_search_pairs_considered`: Number of final reached-target conflict pairs considered by the bounded post-loop pair-local-search phase. +- `pair_local_search_attempts`: Number of pair-local-search reroute attempts executed across all considered pairs. +- `pair_local_search_accepts`: Number of pair-local-search attempts accepted into the whole routed result set. +- `pair_local_search_nodes_expanded`: Total A* node expansions spent inside pair-local-search attempts. + +## 10. Internal Modules Lower-level search and collision modules are semi-private implementation details. They remain accessible through deep imports for advanced use, but they are unstable and may change without notice. The stable supported entrypoint is `route(problem, options=...)`. The current implementation structure is summarized in **[docs/architecture.md](docs/architecture.md)**. The committed example-corpus counter baseline is tracked in **[docs/performance.md](docs/performance.md)**. -Use `scripts/diff_performance_baseline.py` to compare a fresh local run against that baseline. The counter baseline is currently observational and is not enforced as a CI gate. +Use `scripts/diff_performance_baseline.py` to compare a fresh local run against that baseline. Use `scripts/record_conflict_trace.py` for opt-in conflict-hotspot traces and `scripts/record_frontier_trace.py` for hotspot-adjacent prune traces. The counter baseline is currently observational and is not enforced as a CI gate. -## 9. Tuning Notes +## 11. Tuning Notes ### Speed vs. optimality diff --git a/docs/architecture.md b/docs/architecture.md index dff4583..5e147d1 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -42,6 +42,7 @@ The search state is a snapped Manhattan `(x, y, r)` port. From each state the ro - The visibility subsystem keeps a lazy static corner index for default `tangent_corner` guidance and only builds the exact corner-to-corner graph on demand for `exact_corner` queries. - `use_tiered_strategy` can swap in a cheaper bend proxy on the first congestion iteration. - Negotiated congestion now re-verifies every reached-target path at the end of each iteration against the final installed dynamic geometry, and it stops early if the conflict graph stalls for consecutive iterations. +- After best-snapshot restoration, the router runs a bounded pair-local scratch reroute on final two-net reached-target conflict pairs. That repair phase clones static obstacles from the live collision world, treats all outside-pair geometry as fixed blockers, tries both pair orders, and only keeps the result if whole-set reverify improves. - Final `RoutingResult` validity is determined by explicit post-route verification, not only by search-time pruning. ## Performance Visibility diff --git a/docs/conflict_trace.json b/docs/conflict_trace.json new file mode 100644 index 0000000..90fc817 --- /dev/null +++ b/docs/conflict_trace.json @@ -0,0 +1,2533 @@ +{ + "generated_at": "2026-04-02T14:24:39-07:00", + "generator": "scripts/record_conflict_trace.py", + "scenarios": [ + { + "conflict_trace": [ + { + "completed_net_ids": [ + "net_04" + ], + "conflict_edges": [ + [ + "net_00", + "net_01" + ], + [ + "net_00", + "net_02" + ], + [ + "net_00", + "net_03" + ], + [ + "net_00", + "net_04" + ], + [ + "net_01", + "net_02" + ], + [ + "net_01", + "net_03" + ], + [ + "net_02", + "net_03" + ], + [ + "net_03", + "net_04" + ], + [ + "net_05", + "net_06" + ], + [ + "net_05", + "net_09" + ], + [ + "net_06", + "net_07" + ], + [ + "net_06", + "net_08" + ], + [ + "net_06", + "net_09" + ], + [ + "net_07", + "net_08" + ], + [ + "net_07", + "net_09" + ], + [ + "net_08", + "net_09" + ] + ], + "iteration": 0, + "nets": [ + { + "component_conflicts": [ + { + "other_component_index": 1, + "other_net_id": "net_06", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_07", + "self_component_index": 1 + }, + { + "other_component_index": 0, + "other_net_id": "net_09", + "self_component_index": 1 + }, + { + "other_component_index": 2, + "other_net_id": "net_06", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_07", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_09", + "self_component_index": 3 + } + ], + "conflicting_net_ids": [ + "net_06", + "net_07", + "net_09" + ], + "net_id": "net_08", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 6, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1138.0796326794896 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 1, + "other_net_id": "net_05", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_07", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_08", + "self_component_index": 1 + }, + { + "other_component_index": 0, + "other_net_id": "net_09", + "self_component_index": 1 + }, + { + "other_component_index": 2, + "other_net_id": "net_07", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_08", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_07", + "self_component_index": 3 + }, + { + "other_component_index": 2, + "other_net_id": "net_08", + "self_component_index": 3 + }, + { + "other_component_index": 2, + "other_net_id": "net_09", + "self_component_index": 3 + } + ], + "conflicting_net_ids": [ + "net_05", + "net_07", + "net_08", + "net_09" + ], + "net_id": "net_06", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 9, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 980.0796326794897 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 0, + "other_net_id": "net_00", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_01", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_02", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_04", + "self_component_index": 1 + }, + { + "other_component_index": 2, + "other_net_id": "net_02", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_00", + "self_component_index": 3 + }, + { + "other_component_index": 2, + "other_net_id": "net_01", + "self_component_index": 3 + }, + { + "other_component_index": 2, + "other_net_id": "net_02", + "self_component_index": 3 + } + ], + "conflicting_net_ids": [ + "net_00", + "net_01", + "net_02", + "net_04" + ], + "net_id": "net_03", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 8, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 970.0796326794897 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 1, + "other_net_id": "net_01", + "self_component_index": 0 + }, + { + "other_component_index": 1, + "other_net_id": "net_02", + "self_component_index": 0 + }, + { + "other_component_index": 1, + "other_net_id": "net_03", + "self_component_index": 0 + }, + { + "other_component_index": 1, + "other_net_id": "net_04", + "self_component_index": 1 + } + ], + "conflicting_net_ids": [ + "net_01", + "net_02", + "net_03", + "net_04" + ], + "net_id": "net_00", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 4, + "self_collision_count": 1, + "static_collision_count": 0, + "total_length": 1621.236812357892 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 1, + "other_net_id": "net_06", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_08", + "self_component_index": 1 + }, + { + "other_component_index": 0, + "other_net_id": "net_09", + "self_component_index": 1 + }, + { + "other_component_index": 2, + "other_net_id": "net_06", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_08", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_08", + "self_component_index": 3 + }, + { + "other_component_index": 2, + "other_net_id": "net_09", + "self_component_index": 3 + } + ], + "conflicting_net_ids": [ + "net_06", + "net_08", + "net_09" + ], + "net_id": "net_07", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 7, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1059.0796326794896 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 0, + "other_net_id": "net_00", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_02", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_03", + "self_component_index": 1 + }, + { + "other_component_index": 2, + "other_net_id": "net_02", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_00", + "self_component_index": 3 + } + ], + "conflicting_net_ids": [ + "net_00", + "net_02", + "net_03" + ], + "net_id": "net_01", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 5, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1128.0796326794896 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 1, + "other_net_id": "net_06", + "self_component_index": 0 + }, + { + "other_component_index": 1, + "other_net_id": "net_07", + "self_component_index": 0 + }, + { + "other_component_index": 1, + "other_net_id": "net_08", + "self_component_index": 0 + } + ], + "conflicting_net_ids": [ + "net_06", + "net_07", + "net_08" + ], + "net_id": "net_09", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 3, + "self_collision_count": 1, + "static_collision_count": 0, + "total_length": 1631.236812357892 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 0, + "other_net_id": "net_00", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_01", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_03", + "self_component_index": 1 + }, + { + "other_component_index": 2, + "other_net_id": "net_01", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_03", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_00", + "self_component_index": 3 + }, + { + "other_component_index": 2, + "other_net_id": "net_01", + "self_component_index": 3 + } + ], + "conflicting_net_ids": [ + "net_00", + "net_01", + "net_03" + ], + "net_id": "net_02", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 7, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1049.0796326794896 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_04", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 909.9977565924808 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 1, + "other_net_id": "net_09", + "self_component_index": 1 + } + ], + "conflicting_net_ids": [ + "net_09" + ], + "net_id": "net_05", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 1, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 914.6410526793834 + } + } + ], + "stage": "iteration" + }, + { + "completed_net_ids": [ + "net_04", + "net_05" + ], + "conflict_edges": [ + [ + "net_00", + "net_01" + ], + [ + "net_00", + "net_02" + ], + [ + "net_00", + "net_03" + ], + [ + "net_01", + "net_02" + ], + [ + "net_01", + "net_03" + ], + [ + "net_02", + "net_03" + ], + [ + "net_06", + "net_07" + ], + [ + "net_06", + "net_08" + ], + [ + "net_06", + "net_09" + ], + [ + "net_07", + "net_08" + ], + [ + "net_07", + "net_09" + ], + [ + "net_08", + "net_09" + ] + ], + "iteration": 1, + "nets": [ + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_04", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 909.9977565924808 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 0, + "other_net_id": "net_07", + "self_component_index": 2 + }, + { + "other_component_index": 0, + "other_net_id": "net_08", + "self_component_index": 2 + }, + { + "other_component_index": 0, + "other_net_id": "net_09", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_07", + "self_component_index": 5 + }, + { + "other_component_index": 2, + "other_net_id": "net_08", + "self_component_index": 5 + }, + { + "other_component_index": 2, + "other_net_id": "net_09", + "self_component_index": 5 + } + ], + "conflicting_net_ids": [ + "net_07", + "net_08", + "net_09" + ], + "net_id": "net_06", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 6, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 980.0796326794897 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 2, + "other_net_id": "net_03", + "self_component_index": 0 + }, + { + "other_component_index": 1, + "other_net_id": "net_00", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_02", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_00", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_02", + "self_component_index": 2 + }, + { + "other_component_index": 5, + "other_net_id": "net_03", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_00", + "self_component_index": 3 + } + ], + "conflicting_net_ids": [ + "net_00", + "net_02", + "net_03" + ], + "net_id": "net_01", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 7, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1128.0796326794896 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 2, + "other_net_id": "net_06", + "self_component_index": 0 + }, + { + "other_component_index": 1, + "other_net_id": "net_07", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_08", + "self_component_index": 1 + }, + { + "other_component_index": 5, + "other_net_id": "net_06", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_07", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_08", + "self_component_index": 2 + } + ], + "conflicting_net_ids": [ + "net_06", + "net_07", + "net_08" + ], + "net_id": "net_09", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 6, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1217.0796326794896 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_05", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 914.6410526793834 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 2, + "other_net_id": "net_03", + "self_component_index": 0 + }, + { + "other_component_index": 1, + "other_net_id": "net_00", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_01", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_00", + "self_component_index": 2 + }, + { + "other_component_index": 1, + "other_net_id": "net_01", + "self_component_index": 2 + }, + { + "other_component_index": 5, + "other_net_id": "net_03", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_00", + "self_component_index": 3 + }, + { + "other_component_index": 2, + "other_net_id": "net_01", + "self_component_index": 3 + } + ], + "conflicting_net_ids": [ + "net_00", + "net_01", + "net_03" + ], + "net_id": "net_02", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 8, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1049.0796326794896 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 2, + "other_net_id": "net_03", + "self_component_index": 0 + }, + { + "other_component_index": 1, + "other_net_id": "net_01", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_02", + "self_component_index": 1 + }, + { + "other_component_index": 2, + "other_net_id": "net_01", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_02", + "self_component_index": 2 + }, + { + "other_component_index": 5, + "other_net_id": "net_03", + "self_component_index": 2 + } + ], + "conflicting_net_ids": [ + "net_01", + "net_02", + "net_03" + ], + "net_id": "net_00", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 6, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1207.0796326794896 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 0, + "other_net_id": "net_00", + "self_component_index": 2 + }, + { + "other_component_index": 0, + "other_net_id": "net_01", + "self_component_index": 2 + }, + { + "other_component_index": 0, + "other_net_id": "net_02", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_00", + "self_component_index": 5 + }, + { + "other_component_index": 2, + "other_net_id": "net_01", + "self_component_index": 5 + }, + { + "other_component_index": 2, + "other_net_id": "net_02", + "self_component_index": 5 + } + ], + "conflicting_net_ids": [ + "net_00", + "net_01", + "net_02" + ], + "net_id": "net_03", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 6, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 970.0796326794897 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 2, + "other_net_id": "net_06", + "self_component_index": 0 + }, + { + "other_component_index": 1, + "other_net_id": "net_08", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_09", + "self_component_index": 1 + }, + { + "other_component_index": 5, + "other_net_id": "net_06", + "self_component_index": 2 + }, + { + "other_component_index": 1, + "other_net_id": "net_08", + "self_component_index": 2 + }, + { + "other_component_index": 1, + "other_net_id": "net_09", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_08", + "self_component_index": 3 + }, + { + "other_component_index": 2, + "other_net_id": "net_09", + "self_component_index": 3 + } + ], + "conflicting_net_ids": [ + "net_06", + "net_08", + "net_09" + ], + "net_id": "net_07", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 8, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1059.0796326794896 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 2, + "other_net_id": "net_06", + "self_component_index": 0 + }, + { + "other_component_index": 1, + "other_net_id": "net_07", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_09", + "self_component_index": 1 + }, + { + "other_component_index": 5, + "other_net_id": "net_06", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_07", + "self_component_index": 2 + }, + { + "other_component_index": 1, + "other_net_id": "net_09", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_09", + "self_component_index": 3 + } + ], + "conflicting_net_ids": [ + "net_06", + "net_07", + "net_09" + ], + "net_id": "net_08", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 7, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1138.0796326794896 + } + } + ], + "stage": "iteration" + }, + { + "completed_net_ids": [ + "net_04", + "net_05", + "net_08", + "net_09" + ], + "conflict_edges": [ + [ + "net_00", + "net_01" + ], + [ + "net_00", + "net_02" + ], + [ + "net_00", + "net_03" + ], + [ + "net_01", + "net_02" + ], + [ + "net_06", + "net_07" + ] + ], + "iteration": 2, + "nets": [ + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_05", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 914.6410526793834 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 0, + "other_net_id": "net_00", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_01", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_01", + "self_component_index": 3 + }, + { + "other_component_index": 3, + "other_net_id": "net_01", + "self_component_index": 4 + }, + { + "other_component_index": 2, + "other_net_id": "net_00", + "self_component_index": 5 + } + ], + "conflicting_net_ids": [ + "net_00", + "net_01" + ], + "net_id": "net_02", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 5, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1049.0796326794896 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_04", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 909.9977565924808 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 0, + "other_net_id": "net_00", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_02", + "self_component_index": 2 + }, + { + "other_component_index": 3, + "other_net_id": "net_02", + "self_component_index": 3 + }, + { + "other_component_index": 2, + "other_net_id": "net_00", + "self_component_index": 5 + } + ], + "conflicting_net_ids": [ + "net_00", + "net_02" + ], + "net_id": "net_01", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 4, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1128.0796326794896 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_08", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1138.0796326794896 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_09", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1217.0796326794896 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 1, + "other_net_id": "net_00", + "self_component_index": 2 + }, + { + "other_component_index": 1, + "other_net_id": "net_00", + "self_component_index": 3 + } + ], + "conflicting_net_ids": [ + "net_00" + ], + "net_id": "net_03", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 2, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 970.0796326794897 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 1, + "other_net_id": "net_07", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_07", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_07", + "self_component_index": 3 + } + ], + "conflicting_net_ids": [ + "net_07" + ], + "net_id": "net_06", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 3, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 980.0796326794897 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 1, + "other_net_id": "net_06", + "self_component_index": 1 + }, + { + "other_component_index": 2, + "other_net_id": "net_06", + "self_component_index": 2 + } + ], + "conflicting_net_ids": [ + "net_06" + ], + "net_id": "net_07", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 2, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1059.0796326794896 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 2, + "other_net_id": "net_01", + "self_component_index": 0 + }, + { + "other_component_index": 2, + "other_net_id": "net_02", + "self_component_index": 0 + }, + { + "other_component_index": 2, + "other_net_id": "net_03", + "self_component_index": 1 + }, + { + "other_component_index": 5, + "other_net_id": "net_01", + "self_component_index": 2 + }, + { + "other_component_index": 5, + "other_net_id": "net_02", + "self_component_index": 2 + }, + { + "other_component_index": 3, + "other_net_id": "net_03", + "self_component_index": 2 + } + ], + "conflicting_net_ids": [ + "net_01", + "net_02", + "net_03" + ], + "net_id": "net_00", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 6, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1207.0796326794896 + } + } + ], + "stage": "iteration" + }, + { + "completed_net_ids": [ + "net_02", + "net_03", + "net_04", + "net_05", + "net_08", + "net_09" + ], + "conflict_edges": [ + [ + "net_00", + "net_01" + ], + [ + "net_06", + "net_07" + ] + ], + "iteration": 3, + "nets": [ + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_02", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1049.0796326794896 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 2, + "other_net_id": "net_00", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_00", + "self_component_index": 3 + }, + { + "other_component_index": 3, + "other_net_id": "net_00", + "self_component_index": 4 + } + ], + "conflicting_net_ids": [ + "net_00" + ], + "net_id": "net_01", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 3, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1128.0796326794896 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_09", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1217.0796326794896 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 2, + "other_net_id": "net_01", + "self_component_index": 2 + }, + { + "other_component_index": 3, + "other_net_id": "net_01", + "self_component_index": 3 + } + ], + "conflicting_net_ids": [ + "net_01" + ], + "net_id": "net_00", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 2, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1207.0796326794896 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 1, + "other_net_id": "net_06", + "self_component_index": 1 + }, + { + "other_component_index": 2, + "other_net_id": "net_06", + "self_component_index": 2 + } + ], + "conflicting_net_ids": [ + "net_06" + ], + "net_id": "net_07", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 2, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1059.0796326794896 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_05", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 914.6410526793834 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_04", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 909.9977565924808 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 1, + "other_net_id": "net_07", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_07", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_07", + "self_component_index": 3 + } + ], + "conflicting_net_ids": [ + "net_07" + ], + "net_id": "net_06", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 3, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 980.0796326794897 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_03", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 970.0796326794897 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_08", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1138.0796326794896 + } + } + ], + "stage": "iteration" + }, + { + "completed_net_ids": [ + "net_00", + "net_03", + "net_04", + "net_05", + "net_08", + "net_09" + ], + "conflict_edges": [ + [ + "net_01", + "net_02" + ], + [ + "net_06", + "net_07" + ] + ], + "iteration": 4, + "nets": [ + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_00", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1207.0796326794896 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 1, + "other_net_id": "net_07", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_07", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_07", + "self_component_index": 3 + } + ], + "conflicting_net_ids": [ + "net_07" + ], + "net_id": "net_06", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 3, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 980.0796326794897 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_09", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1217.0796326794896 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_05", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 914.6410526793834 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_08", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1138.0796326794896 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_03", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 970.0796326794897 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 1, + "other_net_id": "net_06", + "self_component_index": 1 + }, + { + "other_component_index": 2, + "other_net_id": "net_06", + "self_component_index": 2 + } + ], + "conflicting_net_ids": [ + "net_06" + ], + "net_id": "net_07", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 2, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1059.0796326794896 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 3, + "other_net_id": "net_01", + "self_component_index": 2 + }, + { + "other_component_index": 3, + "other_net_id": "net_01", + "self_component_index": 3 + }, + { + "other_component_index": 4, + "other_net_id": "net_01", + "self_component_index": 4 + } + ], + "conflicting_net_ids": [ + "net_01" + ], + "net_id": "net_02", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 3, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1049.0796326794896 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_04", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 909.9977565924808 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 2, + "other_net_id": "net_02", + "self_component_index": 3 + }, + { + "other_component_index": 3, + "other_net_id": "net_02", + "self_component_index": 4 + } + ], + "conflicting_net_ids": [ + "net_02" + ], + "net_id": "net_01", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 2, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1128.0796326794896 + } + } + ], + "stage": "iteration" + }, + { + "completed_net_ids": [ + "net_00", + "net_01", + "net_04", + "net_05", + "net_08", + "net_09" + ], + "conflict_edges": [ + [ + "net_02", + "net_03" + ], + [ + "net_06", + "net_07" + ] + ], + "iteration": 5, + "nets": [ + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_00", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1207.0796326794896 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_01", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1128.0796326794896 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_04", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 909.9977565924808 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_09", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1217.0796326794896 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 1, + "other_net_id": "net_03", + "self_component_index": 1 + }, + { + "other_component_index": 2, + "other_net_id": "net_03", + "self_component_index": 2 + } + ], + "conflicting_net_ids": [ + "net_03" + ], + "net_id": "net_02", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 2, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1049.0796326794896 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_05", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 914.6410526793834 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_08", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1138.0796326794896 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 1, + "other_net_id": "net_06", + "self_component_index": 1 + }, + { + "other_component_index": 2, + "other_net_id": "net_06", + "self_component_index": 2 + } + ], + "conflicting_net_ids": [ + "net_06" + ], + "net_id": "net_07", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 2, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1059.0796326794896 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 1, + "other_net_id": "net_07", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_07", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_07", + "self_component_index": 3 + } + ], + "conflicting_net_ids": [ + "net_07" + ], + "net_id": "net_06", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 3, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 980.0796326794897 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 1, + "other_net_id": "net_02", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_02", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_02", + "self_component_index": 3 + } + ], + "conflicting_net_ids": [ + "net_02" + ], + "net_id": "net_03", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 3, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 970.0796326794897 + } + } + ], + "stage": "iteration" + }, + { + "completed_net_ids": [ + "net_02", + "net_03", + "net_04", + "net_05", + "net_08", + "net_09" + ], + "conflict_edges": [ + [ + "net_00", + "net_01" + ], + [ + "net_06", + "net_07" + ] + ], + "iteration": null, + "nets": [ + { + "component_conflicts": [ + { + "other_component_index": 2, + "other_net_id": "net_01", + "self_component_index": 2 + }, + { + "other_component_index": 3, + "other_net_id": "net_01", + "self_component_index": 3 + } + ], + "conflicting_net_ids": [ + "net_01" + ], + "net_id": "net_00", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 2, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1207.0796326794896 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 2, + "other_net_id": "net_00", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_00", + "self_component_index": 3 + }, + { + "other_component_index": 3, + "other_net_id": "net_00", + "self_component_index": 4 + } + ], + "conflicting_net_ids": [ + "net_00" + ], + "net_id": "net_01", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 3, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1128.0796326794896 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_04", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 909.9977565924808 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_09", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1217.0796326794896 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_02", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1049.0796326794896 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_05", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 914.6410526793834 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_08", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1138.0796326794896 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 1, + "other_net_id": "net_06", + "self_component_index": 1 + }, + { + "other_component_index": 2, + "other_net_id": "net_06", + "self_component_index": 2 + } + ], + "conflicting_net_ids": [ + "net_06" + ], + "net_id": "net_07", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 2, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1059.0796326794896 + } + }, + { + "component_conflicts": [ + { + "other_component_index": 1, + "other_net_id": "net_07", + "self_component_index": 1 + }, + { + "other_component_index": 1, + "other_net_id": "net_07", + "self_component_index": 2 + }, + { + "other_component_index": 2, + "other_net_id": "net_07", + "self_component_index": 3 + } + ], + "conflicting_net_ids": [ + "net_07" + ], + "net_id": "net_06", + "outcome": "colliding", + "reached_target": true, + "report": { + "dynamic_collision_count": 3, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 980.0796326794897 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_03", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 970.0796326794897 + } + } + ], + "stage": "restored_best" + }, + { + "completed_net_ids": [ + "net_00", + "net_01", + "net_02", + "net_03", + "net_04", + "net_05", + "net_06", + "net_07", + "net_08", + "net_09" + ], + "conflict_edges": [], + "iteration": null, + "nets": [ + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_00", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1207.0796326794896 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_01", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1128.0796326794896 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_04", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 909.9977565924808 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_09", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1217.0796326794896 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_02", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1049.0796326794896 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_05", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 914.6410526793834 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_08", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1138.0796326794896 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_07", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1059.0796326794896 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_06", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 980.0796326794897 + } + }, + { + "component_conflicts": [], + "conflicting_net_ids": [], + "net_id": "net_03", + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 970.0796326794897 + } + } + ], + "stage": "final" + } + ], + "metrics": { + "congestion_cache_hits": 31, + "congestion_cache_misses": 4625, + "congestion_candidate_ids": 9924, + "congestion_candidate_nets": 9979, + "congestion_candidate_precheck_hits": 2562, + "congestion_candidate_precheck_misses": 2165, + "congestion_candidate_precheck_skips": 71, + "congestion_check_calls": 4625, + "congestion_exact_pair_checks": 8122, + "congestion_grid_net_cache_hits": 2457, + "congestion_grid_net_cache_misses": 3942, + "congestion_grid_span_cache_hits": 2283, + "congestion_grid_span_cache_misses": 1948, + "congestion_lazy_requeues": 0, + "congestion_lazy_resolutions": 0, + "congestion_net_envelope_cache_hits": 2673, + "congestion_net_envelope_cache_misses": 4139, + "congestion_presence_cache_hits": 2858, + "congestion_presence_cache_misses": 2556, + "congestion_presence_skips": 687, + "danger_map_cache_hits": 16878, + "danger_map_cache_misses": 7425, + "danger_map_lookup_calls": 24303, + "danger_map_query_calls": 7425, + "danger_map_total_ns": 222763960, + "dynamic_grid_rebuilds": 0, + "dynamic_path_objects_added": 471, + "dynamic_path_objects_removed": 423, + "dynamic_tree_rebuilds": 0, + "guidance_bonus_applied": 11000.0, + "guidance_bonus_applied_bend90": 3500.0, + "guidance_bonus_applied_sbend": 625.0, + "guidance_bonus_applied_straight": 6875.0, + "guidance_match_moves": 176, + "guidance_match_moves_bend90": 56, + "guidance_match_moves_sbend": 10, + "guidance_match_moves_straight": 110, + "hard_collision_cache_hits": 0, + "iteration_conflict_edges": 39, + "iteration_conflicting_nets": 36, + "iteration_reverified_nets": 60, + "iteration_reverify_calls": 6, + "move_cache_abs_hits": 2559, + "move_cache_abs_misses": 6494, + "move_cache_rel_hits": 5872, + "move_cache_rel_misses": 622, + "moves_added": 8081, + "moves_generated": 9053, + "nets_carried_forward": 0, + "nets_reached_target": 60, + "nets_routed": 60, + "nodes_expanded": 1764, + "pair_local_search_accepts": 2, + "pair_local_search_attempts": 2, + "pair_local_search_nodes_expanded": 68, + "pair_local_search_pairs_considered": 2, + "path_cost_calls": 0, + "pruned_closed_set": 439, + "pruned_cost": 533, + "pruned_hard_collision": 0, + "ray_cast_calls": 5477, + "ray_cast_calls_expand_forward": 1704, + "ray_cast_calls_expand_snap": 46, + "ray_cast_calls_other": 0, + "ray_cast_calls_straight_static": 3721, + "ray_cast_calls_visibility_build": 0, + "ray_cast_calls_visibility_query": 0, + "ray_cast_calls_visibility_tangent": 6, + "ray_cast_candidate_bounds": 305, + "ray_cast_exact_geometry_checks": 0, + "refine_path_calls": 10, + "refinement_candidate_side_extents": 0, + "refinement_candidates_accepted": 0, + "refinement_candidates_built": 0, + "refinement_candidates_verified": 0, + "refinement_dynamic_bounds_checked": 0, + "refinement_static_bounds_checked": 0, + "refinement_windows_considered": 0, + "route_iterations": 6, + "score_component_calls": 8634, + "score_component_total_ns": 252458856, + "static_net_tree_rebuilds": 1, + "static_raw_tree_rebuilds": 1, + "static_safe_cache_hits": 2482, + "static_tree_rebuilds": 1, + "timeout_events": 0, + "verify_dynamic_candidate_nets": 2106, + "verify_dynamic_exact_pair_checks": 558, + "verify_path_report_calls": 190, + "verify_static_buffer_ops": 895, + "visibility_builds": 0, + "visibility_corner_hits_exact": 0, + "visibility_corner_index_builds": 1, + "visibility_corner_pairs_checked": 0, + "visibility_corner_queries_exact": 0, + "visibility_point_cache_hits": 0, + "visibility_point_cache_misses": 0, + "visibility_point_queries": 0, + "visibility_tangent_candidate_corner_checks": 6, + "visibility_tangent_candidate_ray_tests": 6, + "visibility_tangent_candidate_scans": 1704, + "warm_start_paths_built": 0, + "warm_start_paths_used": 0 + }, + "name": "example_07_large_scale_routing_no_warm_start", + "summary": { + "reached_targets": 10, + "results_by_net": { + "net_00": { + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1207.0796326794896 + } + }, + "net_01": { + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1128.0796326794896 + } + }, + "net_02": { + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1049.0796326794896 + } + }, + "net_03": { + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 970.0796326794897 + } + }, + "net_04": { + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 909.9977565924808 + } + }, + "net_05": { + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 914.6410526793834 + } + }, + "net_06": { + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 980.0796326794897 + } + }, + "net_07": { + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1059.0796326794896 + } + }, + "net_08": { + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1138.0796326794896 + } + }, + "net_09": { + "outcome": "completed", + "reached_target": true, + "report": { + "dynamic_collision_count": 0, + "self_collision_count": 0, + "static_collision_count": 0, + "total_length": 1217.0796326794896 + } + } + }, + "total_results": 10, + "valid_results": 10 + } + } + ] +} diff --git a/docs/conflict_trace.md b/docs/conflict_trace.md new file mode 100644 index 0000000..75d3c8b --- /dev/null +++ b/docs/conflict_trace.md @@ -0,0 +1,57 @@ +# Conflict Trace + +Generated at 2026-04-02T14:24:39-07:00 by `scripts/record_conflict_trace.py`. + +## example_07_large_scale_routing_no_warm_start + +Results: 10 valid / 10 reached / 10 total. + +| Stage | Iteration | Conflicting Nets | Conflict Edges | Completed Nets | +| :-- | --: | --: | --: | --: | +| iteration | 0 | 9 | 16 | 1 | +| iteration | 1 | 8 | 12 | 2 | +| iteration | 2 | 6 | 5 | 4 | +| iteration | 3 | 4 | 2 | 6 | +| iteration | 4 | 4 | 2 | 6 | +| iteration | 5 | 4 | 2 | 6 | +| restored_best | | 4 | 2 | 6 | +| final | | 0 | 0 | 10 | + +Top nets by traced dynamic-collision stages: + +- `net_06`: 7 +- `net_07`: 7 +- `net_01`: 6 +- `net_00`: 5 +- `net_02`: 5 +- `net_03`: 4 +- `net_08`: 2 +- `net_09`: 2 +- `net_05`: 1 + +Top net pairs by frequency: + +- `net_06` <-> `net_07`: 7 +- `net_00` <-> `net_01`: 5 +- `net_01` <-> `net_02`: 4 +- `net_00` <-> `net_02`: 3 +- `net_00` <-> `net_03`: 3 +- `net_02` <-> `net_03`: 3 +- `net_01` <-> `net_03`: 2 +- `net_06` <-> `net_08`: 2 +- `net_06` <-> `net_09`: 2 +- `net_07` <-> `net_08`: 2 + +Top component pairs by frequency: + +- `net_06[2]` <-> `net_07[2]`: 6 +- `net_06[3]` <-> `net_07[2]`: 6 +- `net_06[1]` <-> `net_07[1]`: 6 +- `net_06[2]` <-> `net_07[1]`: 5 +- `net_00[2]` <-> `net_01[3]`: 4 +- `net_01[2]` <-> `net_02[2]`: 3 +- `net_01[2]` <-> `net_02[3]`: 3 +- `net_00[2]` <-> `net_01[2]`: 3 +- `net_07[3]` <-> `net_08[2]`: 2 +- `net_02[1]` <-> `net_03[1]`: 2 + diff --git a/docs/frontier_trace.json b/docs/frontier_trace.json new file mode 100644 index 0000000..130350c --- /dev/null +++ b/docs/frontier_trace.json @@ -0,0 +1,120 @@ +{ + "generated_at": "2026-04-02T14:24:39-07:00", + "generator": "scripts/record_frontier_trace.py", + "scenarios": [ + { + "frontier_trace": [], + "metrics": { + "congestion_cache_hits": 31, + "congestion_cache_misses": 4625, + "congestion_candidate_ids": 9924, + "congestion_candidate_nets": 9979, + "congestion_candidate_precheck_hits": 2562, + "congestion_candidate_precheck_misses": 2165, + "congestion_candidate_precheck_skips": 71, + "congestion_check_calls": 4625, + "congestion_exact_pair_checks": 8122, + "congestion_grid_net_cache_hits": 2457, + "congestion_grid_net_cache_misses": 3942, + "congestion_grid_span_cache_hits": 2283, + "congestion_grid_span_cache_misses": 1948, + "congestion_lazy_requeues": 0, + "congestion_lazy_resolutions": 0, + "congestion_net_envelope_cache_hits": 2673, + "congestion_net_envelope_cache_misses": 4139, + "congestion_presence_cache_hits": 2858, + "congestion_presence_cache_misses": 2556, + "congestion_presence_skips": 687, + "danger_map_cache_hits": 16878, + "danger_map_cache_misses": 7425, + "danger_map_lookup_calls": 24303, + "danger_map_query_calls": 7425, + "danger_map_total_ns": 212814061, + "dynamic_grid_rebuilds": 0, + "dynamic_path_objects_added": 471, + "dynamic_path_objects_removed": 423, + "dynamic_tree_rebuilds": 0, + "guidance_bonus_applied": 11000.0, + "guidance_bonus_applied_bend90": 3500.0, + "guidance_bonus_applied_sbend": 625.0, + "guidance_bonus_applied_straight": 6875.0, + "guidance_match_moves": 176, + "guidance_match_moves_bend90": 56, + "guidance_match_moves_sbend": 10, + "guidance_match_moves_straight": 110, + "hard_collision_cache_hits": 0, + "iteration_conflict_edges": 39, + "iteration_conflicting_nets": 36, + "iteration_reverified_nets": 60, + "iteration_reverify_calls": 6, + "move_cache_abs_hits": 2559, + "move_cache_abs_misses": 6494, + "move_cache_rel_hits": 5872, + "move_cache_rel_misses": 622, + "moves_added": 8081, + "moves_generated": 9053, + "nets_carried_forward": 0, + "nets_reached_target": 60, + "nets_routed": 60, + "nodes_expanded": 1764, + "pair_local_search_accepts": 2, + "pair_local_search_attempts": 2, + "pair_local_search_nodes_expanded": 68, + "pair_local_search_pairs_considered": 2, + "path_cost_calls": 0, + "pruned_closed_set": 439, + "pruned_cost": 533, + "pruned_hard_collision": 0, + "ray_cast_calls": 5477, + "ray_cast_calls_expand_forward": 1704, + "ray_cast_calls_expand_snap": 46, + "ray_cast_calls_other": 0, + "ray_cast_calls_straight_static": 3721, + "ray_cast_calls_visibility_build": 0, + "ray_cast_calls_visibility_query": 0, + "ray_cast_calls_visibility_tangent": 6, + "ray_cast_candidate_bounds": 305, + "ray_cast_exact_geometry_checks": 0, + "refine_path_calls": 10, + "refinement_candidate_side_extents": 0, + "refinement_candidates_accepted": 0, + "refinement_candidates_built": 0, + "refinement_candidates_verified": 0, + "refinement_dynamic_bounds_checked": 0, + "refinement_static_bounds_checked": 0, + "refinement_windows_considered": 0, + "route_iterations": 6, + "score_component_calls": 8634, + "score_component_total_ns": 241025335, + "static_net_tree_rebuilds": 1, + "static_raw_tree_rebuilds": 1, + "static_safe_cache_hits": 2482, + "static_tree_rebuilds": 1, + "timeout_events": 0, + "verify_dynamic_candidate_nets": 2106, + "verify_dynamic_exact_pair_checks": 558, + "verify_path_report_calls": 190, + "verify_static_buffer_ops": 895, + "visibility_builds": 0, + "visibility_corner_hits_exact": 0, + "visibility_corner_index_builds": 1, + "visibility_corner_pairs_checked": 0, + "visibility_corner_queries_exact": 0, + "visibility_point_cache_hits": 0, + "visibility_point_cache_misses": 0, + "visibility_point_queries": 0, + "visibility_tangent_candidate_corner_checks": 6, + "visibility_tangent_candidate_ray_tests": 6, + "visibility_tangent_candidate_scans": 1704, + "warm_start_paths_built": 0, + "warm_start_paths_used": 0 + }, + "name": "example_07_large_scale_routing_no_warm_start", + "summary": { + "reached_targets": 10, + "total_results": 10, + "valid_results": 10 + } + } + ] +} diff --git a/docs/frontier_trace.md b/docs/frontier_trace.md new file mode 100644 index 0000000..4351f37 --- /dev/null +++ b/docs/frontier_trace.md @@ -0,0 +1,23 @@ +# Frontier Trace + +Generated at 2026-04-02T14:24:39-07:00 by `scripts/record_frontier_trace.py`. + +## example_07_large_scale_routing_no_warm_start + +Results: 10 valid / 10 reached / 10 total. + +| Net | Hotspots | Closed-Set | Hard Collision | Self Collision | Cost | Samples | +| :-- | --: | --: | --: | --: | --: | --: | + +Prune totals by reason: + +- None + +Top traced hotspots by sample count: + +- None + +Per-net sampled reason/move breakdown: + +- None + diff --git a/docs/optimization_pass_01_log.md b/docs/optimization_pass_01_log.md index 9e71486..2f5c5e7 100644 --- a/docs/optimization_pass_01_log.md +++ b/docs/optimization_pass_01_log.md @@ -1651,3 +1651,1981 @@ Findings: | example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | | example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 6567.0000 | - | | example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 4420.0000 | - | +## Step 32 tiered-iteration dynamic hard blocks + +Measured on 2026-04-01T21:42:45-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Treat already-routed dynamic paths as hard blockers only during iteration 0 when warm-start is disabled. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2348 | 0.2466 | +0.0118 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 286.0000 | 286.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 155.0000 | 226.0000 | +71.0000 | +| example_05_orientation_stress | tiered_dynamic_hard_prunes | - | 0.0000 | - | +| example_05_orientation_stress | congestion_candidate_nets | 15.0000 | 51.0000 | +36.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 19.0000 | 67.0000 | +48.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 18.0000 | 56.0000 | +38.0000 | +| example_07_large_scale_routing | duration_s | 0.1945 | 0.1931 | -0.0014 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | tiered_dynamic_hard_prunes | - | 0.0000 | - | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 8.7624 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 6.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 11200.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 5355.0000 | - | +| example_07_large_scale_routing_no_warm_start | tiered_dynamic_hard_prunes | - | 632.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 13623.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 13469.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 11346.0000 | - | +## Step 32 reverted + +Measured on 2026-04-01T21:44:47-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Reverted tiered-iteration dynamic hard blockers after regressions in example_05 and the no-warm-start example_07 canary. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2348 | 0.2462 | +0.0114 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 286.0000 | 286.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 155.0000 | 155.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 15.0000 | 15.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 19.0000 | 19.0000 | +0.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 18.0000 | 18.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.1945 | 0.1940 | -0.0005 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 5.5497 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 6567.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 4420.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 12879.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 13342.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 11116.0000 | - | + +## Step 34 rejected + +Measured on 2026-04-01T22:08:00-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Explored order/seed and reroute-heuristic sensitivity before the slice. One-iteration no-warm-start probes were flat at `1` valid route across `user`, `shortest`, `longest`, and seeds `41`-`44`; two-iteration probes were flat at `2` valid routes across the same variants. +- Implemented a conflict-weighted congestion objective that weighted overlapping nets by prior completed status, conflict degree, and dynamic collision count. +- Rejected the slice after the no-warm-start canary failed to finish within a reasonable window and ran far longer than the accepted `~5.6s` state. The code was reverted before accepting any documentation or baseline changes. +## Step 33 carry forward completed nets + +Measured on 2026-04-01T21:58:50-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- After each reverify, reroute only nets that are still colliding, partial, or unroutable; keep completed nets installed as ordinary soft blockers. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2348 | 0.2528 | +0.0179 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 5.0000 | -1.0000 | +| example_05_orientation_stress | nets_carried_forward | 0.0000 | 1.0000 | +1.0000 | +| example_05_orientation_stress | nodes_expanded | 286.0000 | 284.0000 | -2.0000 | +| example_05_orientation_stress | congestion_check_calls | 155.0000 | 152.0000 | -3.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 15.0000 | 15.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 19.0000 | 19.0000 | +0.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 18.0000 | 18.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.1945 | 0.1960 | +0.0015 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nets_carried_forward | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 49.8770 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 8.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 70.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_carried_forward | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 71162.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 80342.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 152845.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 136026.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 116066.0000 | - | +## Step 33 reverted + +Measured on 2026-04-01T21:59:33-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Reverted completed-net carry-forward after the no-warm-start canary exploded in iterations, nodes, and congestion checks without improving validity. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2348 | 0.2572 | +0.0224 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nets_carried_forward | 0.0000 | 0.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 286.0000 | 286.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 155.0000 | 155.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 15.0000 | 15.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 19.0000 | 19.0000 | +0.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 18.0000 | 18.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.1945 | 0.2024 | +0.0079 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nets_carried_forward | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 5.6270 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_carried_forward | - | 0.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 6567.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 4420.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 12879.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 13342.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 11116.0000 | - | +## Step 35 reroute seed guidance + +Measured on 2026-04-01T22:22:54-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Guide post-tiered reroutes with the previous reached-target path seed by giving matching path-prefix moves a small cost bonus. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2348 | 0.2313 | -0.0036 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 286.0000 | 299.0000 | +13.0000 | +| example_05_orientation_stress | congestion_check_calls | 155.0000 | 149.0000 | -6.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 15.0000 | 23.0000 | +8.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 19.0000 | 32.0000 | +13.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 18.0000 | 30.0000 | +12.0000 | +| example_07_large_scale_routing | duration_s | 0.1945 | 0.1881 | -0.0064 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 3.5099 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4250.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3388.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 9604.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 10010.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 8312.0000 | - | + +## Step 39 rejected + +Measured on 2026-04-01T22:58:58-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Tried adding a reroute-only uncongested `max_cost` ceiling derived from the previous reached-target path cost plus slack for two extra bends. +- Rejected the slice before running the standard performance probe because it broke the stalled-conflict guardrail: the simple crossing case in `test_reverify_iterations_stop_early_on_stalled_conflict_graph` ran all `10` iterations instead of stopping early. +- Reverted the slice and restored the accepted Step 35 full-seed guidance state. + +## Step 36 rejected + +Measured on 2026-04-01T22:31:00-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Explored adding the longest still-valid prefix of the prior reached-target path as an additional seeded start node for later reroutes. +- Rejected the slice on runtime: the no-warm-start `example_07` measurement ran far beyond the accepted Step 35 `~3.5s` state before completion, indicating the extra seeded branch was expanding too much search. +- Reverted the slice and kept only the lighter Step 35 reroute-seed-guidance behavior. +## Step 34 conflict-weighted congestion objective + +Measured on 2026-04-01T22:38:11-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Weight congestion overlap cost by prior-iteration completed status, conflict degree, and dynamic collision count instead of counting every overlapping net equally. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2348 | 0.2390 | +0.0041 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 286.0000 | 282.0000 | -4.0000 | +| example_05_orientation_stress | congestion_check_calls | 155.0000 | 145.0000 | -10.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 15.0000 | 15.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 19.0000 | 19.0000 | +0.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 18.0000 | 18.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.1945 | 0.1918 | -0.0027 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 1795.1890 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 1.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 16.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 1501303.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 1622620.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 5074171.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 4556689.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 4129698.0000 | - | +## Step 36 reusable prefix start nodes + +Measured on 2026-04-01T22:49:16-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- On later reroutes, add the longest still-valid prefix of the previous reached-target path as an additional seeded start node while keeping the original start branch available. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2239 | 0.2249 | +0.0010 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | reroute_prefix_paths_used | - | 3.0000 | - | +| example_05_orientation_stress | reroute_prefix_components_reused | - | 8.0000 | - | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 292.0000 | -7.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 125.0000 | -24.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 23.0000 | 22.0000 | -1.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 32.0000 | 31.0000 | -1.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 30.0000 | 29.0000 | -1.0000 | +| example_07_large_scale_routing | duration_s | 0.1912 | 0.1916 | +0.0004 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reroute_prefix_paths_used | - | 0.0000 | - | +| example_07_large_scale_routing | reroute_prefix_components_reused | - | 0.0000 | - | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 229.2009 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 9.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 90.0000 | - | +| example_07_large_scale_routing_no_warm_start | reroute_prefix_paths_used | - | 65.0000 | - | +| example_07_large_scale_routing_no_warm_start | reroute_prefix_components_reused | - | 172.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 350958.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 470234.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 929523.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 853594.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 712783.0000 | - | +## Step 37 capped guidance rejected + +Measured on 2026-04-01T22:53:55-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Exploratory probe showed a mild runtime win when reroute guidance was capped to 4 segments, but the code/test slice did not land cleanly and was reverted before acceptance. +- Restored the accepted Step 35 full-seed guidance state and remeasured the standard probe set. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2239 | 0.2304 | +0.0065 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 23.0000 | 23.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 32.0000 | 32.0000 | +0.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 30.0000 | 30.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.1912 | 0.1862 | -0.0050 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 3.4899 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4250.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3388.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 9604.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 10010.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 8312.0000 | - | +## Step 38 capped reroute guidance + +Measured on 2026-04-01T22:55:29-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Cap reroute guidance seeds to the first 4 segments on later iterations; keep the existing prefix-match bonus unchanged. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2239 | 0.2388 | +0.0150 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 23.0000 | 23.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 32.0000 | 32.0000 | +0.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 30.0000 | 30.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.1912 | 0.1924 | +0.0012 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 3.6174 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4250.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3388.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 9604.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 10010.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 8312.0000 | - | +## Step 38 reverted + +Measured on 2026-04-01T22:56:22-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Reverted the 4-segment reroute-guidance cap after the standard probe set showed no counter improvement and only runtime noise. +- Restored the accepted Step 35 full-seed guidance state. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2239 | 0.2463 | +0.0224 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 23.0000 | 23.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 32.0000 | 32.0000 | +0.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 30.0000 | 30.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.1912 | 0.1912 | -0.0000 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 3.5580 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4250.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3388.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 9604.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 10010.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 8312.0000 | - | +## Step 40 partial straight guidance + +Measured on 2026-04-01T23:01:12-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Allow reroute guidance to consume straight seeds incrementally so split straight moves can continue following a prior reached-target path. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2239 | 0.2329 | +0.0090 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 23.0000 | 23.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 32.0000 | 32.0000 | +0.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 30.0000 | 30.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.1912 | 0.1879 | -0.0033 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 3.5305 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4250.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3388.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 9604.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 10010.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 8312.0000 | - | +## Step 40 reverted + +Measured on 2026-04-01T23:02:26-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Reverted partial straight-seed guidance after the standard probe set showed no counter deltas and no correctness gain. +- Restored the accepted Step 35 full-seed exact-match guidance state. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2239 | 0.2353 | +0.0115 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 23.0000 | 23.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 32.0000 | 32.0000 | +0.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 30.0000 | 30.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.1912 | 0.1897 | -0.0015 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 3.5522 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4250.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3388.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 9604.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 10010.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 8312.0000 | - | +## Step 41 guidance counters + +Measured on 2026-04-01T23:04:31-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Add public counters for reroute guidance matches and total guidance bonus applied so later guidance changes can be measured directly. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2239 | 0.2544 | +0.0306 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves | - | 11.0000 | - | +| example_05_orientation_stress | guidance_bonus_applied | - | 687.5000 | - | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 23.0000 | 23.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 32.0000 | 32.0000 | +0.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 30.0000 | 30.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.1912 | 0.1940 | +0.0027 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves | - | 0.0000 | - | +| example_07_large_scale_routing | guidance_bonus_applied | - | 0.0000 | - | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 3.7438 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4250.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves | - | 101.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied | - | 6312.5000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3388.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 9604.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 10010.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 8312.0000 | - | +## Step 42 scaled guidance bonus + +Measured on 2026-04-02T00:18:11-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Scale reroute guidance bonus by matched move cost, capped at 50% of the move's own cost, so matched straights no longer become nearly free. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2300 | 0.2276 | -0.0024 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 275.0000 | -24.0000 | +| example_05_orientation_stress | guidance_match_moves | 11.0000 | 10.0000 | -1.0000 | +| example_05_orientation_stress | guidance_bonus_applied | 687.5000 | 368.6994 | -318.8006 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 120.0000 | -29.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 23.0000 | 15.0000 | -8.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 32.0000 | 19.0000 | -13.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 30.0000 | 18.0000 | -12.0000 | +| example_07_large_scale_routing | duration_s | 0.2043 | 0.1861 | -0.0182 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 4.0027 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4747.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves | - | 101.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied | - | 2854.9698 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 4010.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 11537.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 12027.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 9988.0000 | - | +## Step 42 reverted + +Measured on 2026-04-02T00:19:47-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Reverted the scaled guidance bonus after the no-warm-start example_07 canary regressed in runtime, nodes, and congestion work without any validity gain. +- Restored the accepted fixed-bonus Step 35 guidance behavior. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2300 | 0.2339 | +0.0039 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves | 11.0000 | 11.0000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied | 687.5000 | 687.5000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 23.0000 | 23.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 32.0000 | 32.0000 | +0.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 30.0000 | 30.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.2043 | 0.1895 | -0.0148 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 3.5410 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4250.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves | - | 101.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied | - | 6312.5000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3388.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 9604.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 10010.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 8312.0000 | - | +## Step 43 guidance counters by move type + +Measured on 2026-04-02T00:24:23-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Split guidance matches and total applied bonus by move type so the next selective-guidance slice can target the dominant match class on the no-warm-start canary. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2300 | 0.2329 | +0.0030 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves | 11.0000 | 11.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves_straight | - | 3.0000 | - | +| example_05_orientation_stress | guidance_match_moves_bend90 | - | 8.0000 | - | +| example_05_orientation_stress | guidance_match_moves_sbend | - | 0.0000 | - | +| example_05_orientation_stress | guidance_bonus_applied | 687.5000 | 687.5000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied_straight | - | 187.5000 | - | +| example_05_orientation_stress | guidance_bonus_applied_bend90 | - | 500.0000 | - | +| example_05_orientation_stress | guidance_bonus_applied_sbend | - | 0.0000 | - | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 23.0000 | 23.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 32.0000 | 32.0000 | +0.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 30.0000 | 30.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.2043 | 0.1899 | -0.0143 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_straight | - | 0.0000 | - | +| example_07_large_scale_routing | guidance_match_moves_bend90 | - | 0.0000 | - | +| example_07_large_scale_routing | guidance_match_moves_sbend | - | 0.0000 | - | +| example_07_large_scale_routing | guidance_bonus_applied | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_straight | - | 0.0000 | - | +| example_07_large_scale_routing | guidance_bonus_applied_bend90 | - | 0.0000 | - | +| example_07_large_scale_routing | guidance_bonus_applied_sbend | - | 0.0000 | - | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 3.5484 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4250.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves | - | 101.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_straight | - | 52.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_bend90 | - | 43.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_sbend | - | 6.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied | - | 6312.5000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_straight | - | 3250.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_bend90 | - | 2687.5000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_sbend | - | 375.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3388.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 9604.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 10010.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 8312.0000 | - | +## Step 50 terminal repair rejected + +Measured on 2026-04-02T10:18:00-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Added a bounded terminal repair phase for reached-but-colliding nets after best-snapshot restoration, with up to two sequential repair sweeps and completed nets treated as hard blockers during repair reroutes. +- The slice stayed within the node and congestion guardrails, but it did not improve the only important outcome: the no-warm-start `example_07` canary remained at `2/10/10`. +- Rejected the slice and reverted the code. The tree was restored to the accepted fixed-guidance branch without refreshing `docs/performance.md` or `docs/performance_baseline.json`. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2275 | 0.2400 | +0.0125 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_05_orientation_stress | terminal_repair_sweeps | - | 0.0000 | - | +| example_05_orientation_stress | terminal_repair_nets_considered | - | 0.0000 | - | +| example_05_orientation_stress | terminal_repair_nets_accepted | - | 0.0000 | - | +| example_05_orientation_stress | terminal_repair_nets_rejected | - | 0.0000 | - | +| example_05_orientation_stress | terminal_repair_completed_promotions | - | 0.0000 | - | +| example_05_orientation_stress | repair_frozen_net_prunes | - | 0.0000 | - | +| example_07_large_scale_routing | duration_s | 0.1907 | 0.1969 | +0.0062 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | terminal_repair_sweeps | - | 0.0000 | - | +| example_07_large_scale_routing | terminal_repair_nets_considered | - | 0.0000 | - | +| example_07_large_scale_routing | terminal_repair_nets_accepted | - | 0.0000 | - | +| example_07_large_scale_routing | terminal_repair_nets_rejected | - | 0.0000 | - | +| example_07_large_scale_routing | terminal_repair_completed_promotions | - | 0.0000 | - | +| example_07_large_scale_routing | repair_frozen_net_prunes | - | 0.0000 | - | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 6.5413 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 56.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 10691.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 4805.0000 | - | +| example_07_large_scale_routing_no_warm_start | terminal_repair_sweeps | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | terminal_repair_nets_considered | - | 16.0000 | - | +| example_07_large_scale_routing_no_warm_start | terminal_repair_nets_accepted | - | 8.0000 | - | +| example_07_large_scale_routing_no_warm_start | terminal_repair_nets_rejected | - | 8.0000 | - | +| example_07_large_scale_routing_no_warm_start | terminal_repair_completed_promotions | - | 0.0000 | - | +| example_07_large_scale_routing_no_warm_start | repair_frozen_net_prunes | - | 300.0000 | - | +## Step 50 rejected + +Measured on 2026-04-02T10:02:00-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Tried per-net incumbent acceptance with a second accepted-state reverify after each full candidate iteration. +- Rejected the slice immediately: the no-warm-start `example_07` canary stayed at `2/10/10` valid but regressed to `179.0440s`, `8` iterations, `80` nets routed, `234493` expanded nodes, and `255901` congestion checks. +- Reverted the code and restored the prior accepted fixed-guidance state without refreshing the committed baseline artifacts. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2275 | 0.2406 | +0.0132 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_05_orientation_stress | iteration_candidate_results_accepted | - | 5.0000 | - | +| example_05_orientation_stress | iteration_incumbent_results_kept | - | 1.0000 | - | +| example_05_orientation_stress | iteration_accept_reverify_calls | - | 2.0000 | - | +| example_07_large_scale_routing | duration_s | 0.1907 | 0.2029 | +0.0122 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | iteration_candidate_results_accepted | - | 10.0000 | - | +| example_07_large_scale_routing | iteration_incumbent_results_kept | - | 0.0000 | - | +| example_07_large_scale_routing | iteration_accept_reverify_calls | - | 1.0000 | - | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 179.0440 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 8.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 80.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 234493.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 255901.0000 | - | +| example_07_large_scale_routing_no_warm_start | iteration_candidate_results_accepted | - | 30.0000 | - | +| example_07_large_scale_routing_no_warm_start | iteration_incumbent_results_kept | - | 50.0000 | - | +| example_07_large_scale_routing_no_warm_start | iteration_accept_reverify_calls | - | 8.0000 | - | +## Step 44 selective straight guidance + +Measured on 2026-04-02T00:25:53-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Keep full reroute guidance bonus for bend90 and sbend matches, but cut straight-match bonus in half. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2300 | 0.2361 | +0.0061 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves | 11.0000 | 11.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves_straight | - | 3.0000 | - | +| example_05_orientation_stress | guidance_match_moves_bend90 | - | 8.0000 | - | +| example_05_orientation_stress | guidance_match_moves_sbend | - | 0.0000 | - | +| example_05_orientation_stress | guidance_bonus_applied | 687.5000 | 593.7500 | -93.7500 | +| example_05_orientation_stress | guidance_bonus_applied_straight | - | 93.7500 | - | +| example_05_orientation_stress | guidance_bonus_applied_bend90 | - | 500.0000 | - | +| example_05_orientation_stress | guidance_bonus_applied_sbend | - | 0.0000 | - | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 23.0000 | 23.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 32.0000 | 32.0000 | +0.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 30.0000 | 30.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.2043 | 0.1912 | -0.0131 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_straight | - | 0.0000 | - | +| example_07_large_scale_routing | guidance_match_moves_bend90 | - | 0.0000 | - | +| example_07_large_scale_routing | guidance_match_moves_sbend | - | 0.0000 | - | +| example_07_large_scale_routing | guidance_bonus_applied | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_straight | - | 0.0000 | - | +| example_07_large_scale_routing | guidance_bonus_applied_bend90 | - | 0.0000 | - | +| example_07_large_scale_routing | guidance_bonus_applied_sbend | - | 0.0000 | - | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 4.0413 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4747.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves | - | 101.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_straight | - | 52.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_bend90 | - | 43.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_sbend | - | 6.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied | - | 4687.5000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_straight | - | 1625.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_bend90 | - | 2687.5000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_sbend | - | 375.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3721.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 10835.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 11093.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 9328.0000 | - | +## Step 45 selective bend guidance + +Measured on 2026-04-02T00:27:37-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Restore full straight guidance, but cut bend90-match bonus in half while keeping sbend matches at full bonus. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2300 | 0.2211 | -0.0089 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 272.0000 | -27.0000 | +| example_05_orientation_stress | guidance_match_moves | 11.0000 | 10.0000 | -1.0000 | +| example_05_orientation_stress | guidance_match_moves_straight | - | 3.0000 | - | +| example_05_orientation_stress | guidance_match_moves_bend90 | - | 7.0000 | - | +| example_05_orientation_stress | guidance_match_moves_sbend | - | 0.0000 | - | +| example_05_orientation_stress | guidance_bonus_applied | 687.5000 | 406.2500 | -281.2500 | +| example_05_orientation_stress | guidance_bonus_applied_straight | - | 187.5000 | - | +| example_05_orientation_stress | guidance_bonus_applied_bend90 | - | 218.7500 | - | +| example_05_orientation_stress | guidance_bonus_applied_sbend | - | 0.0000 | - | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 108.0000 | -41.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 23.0000 | 15.0000 | -8.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 32.0000 | 19.0000 | -13.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 30.0000 | 18.0000 | -12.0000 | +| example_07_large_scale_routing | duration_s | 0.2043 | 0.1873 | -0.0170 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_straight | - | 0.0000 | - | +| example_07_large_scale_routing | guidance_match_moves_bend90 | - | 0.0000 | - | +| example_07_large_scale_routing | guidance_match_moves_sbend | - | 0.0000 | - | +| example_07_large_scale_routing | guidance_bonus_applied | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_straight | - | 0.0000 | - | +| example_07_large_scale_routing | guidance_bonus_applied_bend90 | - | 0.0000 | - | +| example_07_large_scale_routing | guidance_bonus_applied_sbend | - | 0.0000 | - | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 3.5628 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4320.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves | - | 101.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_straight | - | 52.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_bend90 | - | 43.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_sbend | - | 6.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied | - | 4968.7500 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_straight | - | 3250.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_bend90 | - | 1343.7500 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_sbend | - | 375.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3594.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 10245.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 10859.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 8944.0000 | - | +## Step 45 reverted + +Measured on 2026-04-02T00:29:03-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Reverted bend-selective guidance after the no-warm-start canary still regressed in nodes and congestion work without any validity gain. +- Restored the accepted fixed-bonus guidance state while keeping the move-type counters. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2300 | 0.2387 | +0.0088 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves | 11.0000 | 11.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves_straight | - | 3.0000 | - | +| example_05_orientation_stress | guidance_match_moves_bend90 | - | 8.0000 | - | +| example_05_orientation_stress | guidance_match_moves_sbend | - | 0.0000 | - | +| example_05_orientation_stress | guidance_bonus_applied | 687.5000 | 687.5000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied_straight | - | 187.5000 | - | +| example_05_orientation_stress | guidance_bonus_applied_bend90 | - | 500.0000 | - | +| example_05_orientation_stress | guidance_bonus_applied_sbend | - | 0.0000 | - | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 23.0000 | 23.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 32.0000 | 32.0000 | +0.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 30.0000 | 30.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.2043 | 0.1906 | -0.0137 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_straight | - | 0.0000 | - | +| example_07_large_scale_routing | guidance_match_moves_bend90 | - | 0.0000 | - | +| example_07_large_scale_routing | guidance_match_moves_sbend | - | 0.0000 | - | +| example_07_large_scale_routing | guidance_bonus_applied | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_straight | - | 0.0000 | - | +| example_07_large_scale_routing | guidance_bonus_applied_bend90 | - | 0.0000 | - | +| example_07_large_scale_routing | guidance_bonus_applied_sbend | - | 0.0000 | - | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 3.5968 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4250.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves | - | 101.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_straight | - | 52.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_bend90 | - | 43.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_sbend | - | 6.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied | - | 6312.5000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_straight | - | 3250.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_bend90 | - | 2687.5000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_sbend | - | 375.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3388.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 9604.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 10010.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 8312.0000 | - | +## Step 46 later-iteration guidance decay + +Measured on 2026-04-02T00:38:26-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Keep the first reroute guidance bonus unchanged, then halve the guidance bonus on iteration 2 and later reroutes. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2275 | 0.2316 | +0.0041 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves | 11.0000 | 11.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves_straight | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves_bend90 | 8.0000 | 8.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied | 687.5000 | 687.5000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied_straight | 187.5000 | 187.5000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied_bend90 | 500.0000 | 500.0000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 23.0000 | 23.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 32.0000 | 32.0000 | +0.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 30.0000 | 30.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.1907 | 0.1923 | +0.0016 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_straight | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_bend90 | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_straight | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_bend90 | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 4.1601 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4850.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves | - | 101.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_straight | - | 52.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_bend90 | - | 43.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_sbend | - | 6.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied | - | 4093.7500 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_straight | - | 2125.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_bend90 | - | 1718.7500 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_sbend | - | 250.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3917.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 11398.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 11839.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 9870.0000 | - | +## Step 46 reverted + +Measured on 2026-04-02T00:39:37-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Reverted later-iteration guidance decay after the no-warm-start canary regressed in runtime, nodes, and congestion work with no validity gain. +- Restored the accepted fixed-bonus guidance state while keeping the move-type counters. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2275 | 0.2379 | +0.0105 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves | 11.0000 | 11.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves_straight | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves_bend90 | 8.0000 | 8.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied | 687.5000 | 687.5000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied_straight | 187.5000 | 187.5000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied_bend90 | 500.0000 | 500.0000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 23.0000 | 23.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 32.0000 | 32.0000 | +0.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 30.0000 | 30.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.1907 | 0.2109 | +0.0202 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_straight | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_bend90 | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_straight | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_bend90 | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 3.6956 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4250.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves | - | 101.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_straight | - | 52.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_bend90 | - | 43.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_sbend | - | 6.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied | - | 6312.5000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_straight | - | 3250.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_bend90 | - | 2687.5000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_sbend | - | 375.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3388.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 9604.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 10010.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 8312.0000 | - | +## Step 47 conflict-aware guidance decay + +Measured on 2026-04-02T00:44:37-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Reduce reroute guidance bonus only when the net's own prior reached-target path still had dynamic collisions; keep full bonus for already-clean prior paths. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2275 | 0.2439 | +0.0164 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 286.0000 | -13.0000 | +| example_05_orientation_stress | guidance_match_moves | 11.0000 | 10.0000 | -1.0000 | +| example_05_orientation_stress | guidance_match_moves_straight | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves_bend90 | 8.0000 | 7.0000 | -1.0000 | +| example_05_orientation_stress | guidance_match_moves_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied | 687.5000 | 500.0000 | -187.5000 | +| example_05_orientation_stress | guidance_bonus_applied_straight | 187.5000 | 171.8750 | -15.6250 | +| example_05_orientation_stress | guidance_bonus_applied_bend90 | 500.0000 | 328.1250 | -171.8750 | +| example_05_orientation_stress | guidance_bonus_applied_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 130.0000 | -19.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 23.0000 | 15.0000 | -8.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 32.0000 | 19.0000 | -13.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 30.0000 | 18.0000 | -12.0000 | +| example_07_large_scale_routing | duration_s | 0.1907 | 0.1986 | +0.0079 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_straight | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_bend90 | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_straight | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_bend90 | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 4.0364 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4421.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves | - | 101.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_straight | - | 52.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_bend90 | - | 43.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_sbend | - | 6.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied | - | 4890.6250 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_straight | - | 2515.6250 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_bend90 | - | 2015.6250 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_sbend | - | 359.3750 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3565.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 10202.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 10558.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 8814.0000 | - | +## Step 47 reverted + +Measured on 2026-04-02T00:45:52-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Reverted conflict-aware guidance decay after the no-warm-start canary still regressed in runtime, nodes, and congestion work without any validity gain. +- Restored the accepted fixed-bonus guidance state while keeping the move-type counters. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2275 | 0.2330 | +0.0056 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves | 11.0000 | 11.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves_straight | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves_bend90 | 8.0000 | 8.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied | 687.5000 | 687.5000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied_straight | 187.5000 | 187.5000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied_bend90 | 500.0000 | 500.0000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 23.0000 | 23.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 32.0000 | 32.0000 | +0.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 30.0000 | 30.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.1907 | 0.1878 | -0.0029 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_straight | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_bend90 | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_straight | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_bend90 | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 3.5878 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4250.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves | - | 101.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_straight | - | 52.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_bend90 | - | 43.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_sbend | - | 6.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied | - | 6312.5000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_straight | - | 3250.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_bend90 | - | 2687.5000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_sbend | - | 375.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3388.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 9604.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 10010.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 8312.0000 | - | +## Step 48 best reverified guidance source + +Measured on 2026-04-02T08:41:26-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Prefer the best reverified per-net path snapshot as the reroute guidance seed, falling back to the most recent reached-target path only when no best snapshot exists. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2275 | 0.2347 | +0.0073 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves | 11.0000 | 11.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves_straight | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves_bend90 | 8.0000 | 8.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied | 687.5000 | 687.5000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied_straight | 187.5000 | 187.5000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied_bend90 | 500.0000 | 500.0000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 23.0000 | 23.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 32.0000 | 32.0000 | +0.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 30.0000 | 30.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.1907 | 0.1934 | +0.0027 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_straight | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_bend90 | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_straight | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_bend90 | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 3.5687 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4250.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves | - | 101.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_straight | - | 52.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_bend90 | - | 43.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_sbend | - | 6.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied | - | 6312.5000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_straight | - | 3250.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_bend90 | - | 2687.5000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_sbend | - | 375.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3388.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 9604.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 10010.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 8312.0000 | - | +## Step 49 per-net best reverified guidance + +Measured on 2026-04-02T08:43:09-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Track the best reverified reached-target path per net, ranked by dynamic collisions, total collisions, then total length, and prefer that path as reroute guidance. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2275 | 0.2432 | +0.0158 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves | 11.0000 | 11.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves_straight | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves_bend90 | 8.0000 | 8.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied | 687.5000 | 687.5000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied_straight | 187.5000 | 187.5000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied_bend90 | 500.0000 | 500.0000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 23.0000 | 23.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 32.0000 | 32.0000 | +0.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 30.0000 | 30.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.1907 | 0.1923 | +0.0016 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_straight | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_bend90 | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_straight | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_bend90 | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 4.7028 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 5664.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves | - | 95.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_straight | - | 49.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_bend90 | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_sbend | - | 6.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied | - | 5937.5000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_straight | - | 3062.5000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_bend90 | - | 2500.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_sbend | - | 375.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3406.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 9679.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 10059.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 8361.0000 | - | +## Step 49 reverted + +Measured on 2026-04-02T08:44:09-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Reverted per-net best reverified guidance after the no-warm-start canary regressed sharply in runtime and nodes without any validity gain. +- Restored the accepted fixed-bonus guidance state while keeping the move-type counters. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2275 | 0.2367 | +0.0093 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves | 11.0000 | 11.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves_straight | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves_bend90 | 8.0000 | 8.0000 | +0.0000 | +| example_05_orientation_stress | guidance_match_moves_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied | 687.5000 | 687.5000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied_straight | 187.5000 | 187.5000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied_bend90 | 500.0000 | 500.0000 | +0.0000 | +| example_05_orientation_stress | guidance_bonus_applied_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_nets | 23.0000 | 23.0000 | +0.0000 | +| example_05_orientation_stress | congestion_candidate_ids | 32.0000 | 32.0000 | +0.0000 | +| example_05_orientation_stress | congestion_exact_pair_checks | 30.0000 | 30.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.1907 | 0.1904 | -0.0003 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_straight | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_bend90 | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_match_moves_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_straight | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_bend90 | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | guidance_bonus_applied_sbend | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_nets | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_candidate_ids | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_exact_pair_checks | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 3.6154 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4250.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves | - | 101.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_straight | - | 52.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_bend90 | - | 43.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_match_moves_sbend | - | 6.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied | - | 6312.5000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_straight | - | 3250.0000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_bend90 | - | 2687.5000 | - | +| example_07_large_scale_routing_no_warm_start | guidance_bonus_applied_sbend | - | 375.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3388.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_nets | - | 9604.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_candidate_ids | - | 10010.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_exact_pair_checks | - | 8312.0000 | - | +## Step 51 conflict trace instrumentation + +Measured on 2026-04-02T11:20:41-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Added opt-in conflict-trace capture and a separate recorder script without changing routing policy. +- The no-warm-start canary still finishes 2/10/10 with 4 iterations; tracing shows the same conflict structure at iteration, restored-best, and final stages. +- Recurring hotspot edges cluster in two groups: net_00-net_03 and net_06-net_09. +- Recurring component pairs also repeat across all traced stages, which points to stable geometric bottlenecks rather than seed noise. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2275 | 0.2338 | +0.0064 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.1907 | 0.1896 | -0.0011 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 3.5423 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4250.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3388.0000 | - | +## Step 52 prefix-preserving hotspot repair rejected + +Measured on 2026-04-02T11:45:37-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Tried a bounded post-loop hotspot repair that preserved clean prefixes and rerouted only conflicted suffixes. +- The no-warm-start canary stayed at 2/10/10 and the repair counters showed zero accepted repairs, so the slice did not move correctness. +- The experiment temporarily raised the canary to 4258 nodes with one repair pass, two clusters attempted, and eight nets considered before it was reverted. +- The tree is restored to the accepted fixed-guidance state: example_07_no_warm_start is back at 2 valid, 10 reached, 4250 nodes, and 3388 congestion checks. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2275 | 0.2384 | +0.0110 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.1907 | 0.1909 | +0.0002 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 3.5590 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4250.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3388.0000 | - | +## Step 53 cluster repack repair rejected + +Measured on 2026-04-02T12:04:21-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Tried bounded post-loop clique reroute against frozen outside geometry. +- No-warm-start canary stayed 2/10/10. +- Final conflict edges improved 12 -> 10, but correctness did not improve and search cost rose materially. +- Tree restored to the accepted fixed-guidance state after revert. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2275 | 0.2364 | +0.0089 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.1907 | 0.1915 | +0.0008 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 3.5998 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4250.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3388.0000 | - | +## Step 54 hotspot keep-out repair rejected + +Measured on 2026-04-02T12:22:14-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Tried bounded post-loop clique reroute with temporary hotspot keep-out obstacles and frozen outside geometry. +- The no-warm-start canary failed a bounded probe: a single-scenario diff run timed out at 20 seconds, far above the accepted ~3.5 second state. +- Because the slice was clearly losing on runtime before showing any correctness gain, it was reverted. +- Tree restored to the accepted fixed-guidance branch after revert. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2275 | 0.2490 | +0.0216 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.1907 | 0.2004 | +0.0097 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 3.8387 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4250.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3388.0000 | - | +## Step 55 bounded portfolio rerun rejected + +Measured on 2026-04-02T12:50:58-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Tried a conservative top-level best-of-three portfolio for warm-start-disabled runs using alternate shuffle/order settings. +- The no-warm-start canary was unchanged at 2/10/10, 4250 nodes, and 3388 congestion checks. +- Because the added control-path complexity produced no correctness or cost improvement, the slice was reverted. +- Tree restored to the accepted fixed-guidance branch after revert. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2275 | 0.2408 | +0.0133 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.1907 | 0.1953 | +0.0046 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 3.6741 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4250.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3388.0000 | - | +## Step 56 frontier hotspot instrumentation + +Measured on 2026-04-02T13:00:47-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Added opt-in frontier_trace diagnostics and recorder tooling without changing routing behavior. +- No-warm-start canary stayed on the accepted 2/10/10, 4250-node, 3388-congestion-check shape. +- Frontier trace shows hotspot-adjacent prunes are dominated by cost (3412) and closed-set (626), with zero hard-collision prunes near the traced hotspots. +- Self-collision pruning is concentrated in net_02 (455 traced self-collision prunes); the other hotspot nets are mostly cost- and closed-set-limited. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2275 | 0.2300 | +0.0026 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.1907 | 0.1891 | -0.0016 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 3.5576 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4250.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3388.0000 | - | +## Step 57 frontier hotspot readout refinement + +Measured on 2026-04-02T13:11:47-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Extended frontier_trace reporting with per-net sampled reason x move_type and hotspot splits. +- net_02 samples split across hotspots 0 and 6 and include heavy straight self-collision pruning; sampled mix is 36 cost, 23 self-collision, 5 closed-set. +- net_03 and net_06 are almost entirely straight-move cost prunes at hotspot 0; their few traced bend90 samples are closed-set dominated, not geometry-blocked. +- The sampled cost prunes are overshoot moves well outside the 0..1000 board bounds, so the current cost bucket is largely boundary-rejection rather than local corridor pricing. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2275 | 0.2365 | +0.0090 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.1907 | 0.1927 | +0.0020 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 3.6435 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 4.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 40.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 4250.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 3388.0000 | - | +## Step 58 large no-warm straight-bound cap + +Measured on 2026-04-02T13:22:28-07:00. +Baseline: `/tmp/inire_pre_step58_baseline.json`. + +Findings: + +- Applied a board-boundary cap to straight candidate reach only for large warm-start-disabled multi-net runs. +- The no-warm-start example_07 canary improved from 2/10/10 to 6/10/10. +- Nodes fell from 4250 to 1764 and runtime fell to about 1.95s, while congestion checks rose modestly from 3388 to 4625 as the search converged through six lighter iterations. +- Warmed example_07 stayed 10/10/10 and example_05 kept identical routing counters and validity. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2348 | 0.2387 | +0.0038 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 286.0000 | 299.0000 | +13.0000 | +| example_05_orientation_stress | congestion_check_calls | 155.0000 | 149.0000 | -6.0000 | +| example_07_large_scale_routing | duration_s | 0.1945 | 0.1944 | -0.0000 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 1.9279 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 6.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 6.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 60.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 1764.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 4625.0000 | - | +## Step 59 pair-local exact-conflict repair rejected + +Measured on 2026-04-02T13:50:00-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Tried a bounded post-loop two-net repair portfolio on the final conflict pairs, with ranked vs reversed reroute order and optional exact-overlap keep-outs. +- The no-warm-start canary stayed at `6/10/10`; the repair phase accepted `0` whole-set improvements across `8` attempts on `2` pairs. +- Cost regressed sharply during the failed slice to about `9.56s`, `11126` expanded nodes, and `9087` congestion checks, with `4257` frozen-net hard prunes. +- The slice was reverted and the tree restored to the accepted straight-boundary-cap branch. + +## Step 60 trimmed exploratory straight heuristics rejected + +Measured on 2026-04-02T14:00:00-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Tried removing the generic `max_reach / 2` and `max_reach - 5` straight candidates only for the same large no-warm runs that use the accepted straight-boundary cap. +- The no-warm-start canary regressed from `6/10/10` to `2/10/10` and runtime climbed to about `26s`. +- Search work exploded to about `27619` expanded nodes and `53804` congestion checks, so the generic exploratory straight candidates are still necessary for the accepted branch. +- The slice was reverted and the tree restored to the accepted straight-boundary-cap branch. + +## Step 61 staggered bend-anchor diversification rejected + +Measured on 2026-04-02T14:10:00-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Tried adding deterministic staggered pre-bend straight candidates for odd-indexed nets only in the large no-warm regime to separate the remaining colliding pairs. +- The no-warm-start canary regressed from `6/10/10` to `3/10/10` and runtime rose to about `8.6s`. +- The heuristic fired heavily (`1051` staggered bend-anchor candidates and `785` staggered visible candidates) but search still got worse, with about `6724` expanded nodes and `21577` congestion checks. +- The slice was reverted and the tree restored to the accepted straight-boundary-cap branch. +## Step 62 pair-local scratch reroute accepted + +Measured on 2026-04-02T14:15:04-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Built a bounded post-loop pair-local reroute that treats all outside-pair nets as frozen static blockers in a scratch routing world. The no-warm-start example_07 canary improved from 6/10/10 to 9/10/10 with two accepted pair repairs and only 33 extra search expansions. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2591 | 0.2440 | -0.0151 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_05_orientation_stress | pair_local_search_pairs_considered | - | 0.0000 | - | +| example_05_orientation_stress | pair_local_search_attempts | - | 0.0000 | - | +| example_05_orientation_stress | pair_local_search_accepts | - | 0.0000 | - | +| example_05_orientation_stress | pair_local_search_nodes_expanded | - | 0.0000 | - | +| example_07_large_scale_routing | duration_s | 0.2027 | 0.2029 | +0.0002 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | pair_local_search_pairs_considered | - | 0.0000 | - | +| example_07_large_scale_routing | pair_local_search_attempts | - | 0.0000 | - | +| example_07_large_scale_routing | pair_local_search_accepts | - | 0.0000 | - | +| example_07_large_scale_routing | pair_local_search_nodes_expanded | - | 0.0000 | - | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 2.1244 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 9.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 6.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 60.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 1764.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 4625.0000 | - | +| example_07_large_scale_routing_no_warm_start | pair_local_search_pairs_considered | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | pair_local_search_attempts | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | pair_local_search_accepts | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | pair_local_search_nodes_expanded | - | 33.0000 | - | +## Step 63 pair-local static obstacle cloning fix + +Measured on 2026-04-02T14:24:40-07:00. +Baseline: `docs/performance_baseline.json`. + +Findings: + +- Fixed the scratch pair-local routing world to clone static obstacles from the live collision engine instead of the RoutingProblem wrapper. That removed the false-clean net_00 reroute and lifted the no-warm-start example_07 canary from 9/10/10 to 10/10/10 with the same main search workload. + +# Performance Baseline Diff + +| Scenario | Metric | Baseline | Current | Delta | +| :-- | :-- | --: | --: | --: | +| example_05_orientation_stress | duration_s | 0.2361 | 0.2495 | +0.0134 | +| example_05_orientation_stress | valid_results | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | reached_targets | 3.0000 | 3.0000 | +0.0000 | +| example_05_orientation_stress | route_iterations | 2.0000 | 2.0000 | +0.0000 | +| example_05_orientation_stress | nets_routed | 6.0000 | 6.0000 | +0.0000 | +| example_05_orientation_stress | nodes_expanded | 299.0000 | 299.0000 | +0.0000 | +| example_05_orientation_stress | congestion_check_calls | 149.0000 | 149.0000 | +0.0000 | +| example_05_orientation_stress | pair_local_search_pairs_considered | 0.0000 | 0.0000 | +0.0000 | +| example_05_orientation_stress | pair_local_search_attempts | 0.0000 | 0.0000 | +0.0000 | +| example_05_orientation_stress | pair_local_search_accepts | 0.0000 | 0.0000 | +0.0000 | +| example_05_orientation_stress | pair_local_search_nodes_expanded | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | duration_s | 0.2025 | 0.2059 | +0.0034 | +| example_07_large_scale_routing | valid_results | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | reached_targets | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | route_iterations | 1.0000 | 1.0000 | +0.0000 | +| example_07_large_scale_routing | nets_routed | 10.0000 | 10.0000 | +0.0000 | +| example_07_large_scale_routing | nodes_expanded | 78.0000 | 78.0000 | +0.0000 | +| example_07_large_scale_routing | congestion_check_calls | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | pair_local_search_pairs_considered | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | pair_local_search_attempts | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | pair_local_search_accepts | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing | pair_local_search_nodes_expanded | 0.0000 | 0.0000 | +0.0000 | +| example_07_large_scale_routing_no_warm_start | duration_s | - | 2.1497 | - | +| example_07_large_scale_routing_no_warm_start | valid_results | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | reached_targets | - | 10.0000 | - | +| example_07_large_scale_routing_no_warm_start | route_iterations | - | 6.0000 | - | +| example_07_large_scale_routing_no_warm_start | nets_routed | - | 60.0000 | - | +| example_07_large_scale_routing_no_warm_start | nodes_expanded | - | 1764.0000 | - | +| example_07_large_scale_routing_no_warm_start | congestion_check_calls | - | 4625.0000 | - | +| example_07_large_scale_routing_no_warm_start | pair_local_search_pairs_considered | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | pair_local_search_attempts | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | pair_local_search_accepts | - | 2.0000 | - | +| example_07_large_scale_routing_no_warm_start | pair_local_search_nodes_expanded | - | 68.0000 | - | diff --git a/docs/performance.md b/docs/performance.md index 5f083a4..ed68e16 100644 --- a/docs/performance.md +++ b/docs/performance.md @@ -1,20 +1,22 @@ # Performance Baseline -Generated on 2026-04-01 by `scripts/record_performance_baseline.py`. +Generated on 2026-04-02 by `scripts/record_performance_baseline.py`. The full machine-readable snapshot lives in `docs/performance_baseline.json`. Use `scripts/diff_performance_baseline.py` to compare a fresh run against that snapshot. +The default baseline table below covers the standard example corpus only. The heavier `example_07_large_scale_routing_no_warm_start` canary remains performance-only and is tracked through targeted diffs plus the conflict/frontier trace artifacts. + | Scenario | Duration (s) | Total | Valid | Reached | Iter | Nets Routed | Nodes | Ray Casts | Moves Gen | Moves Added | Dyn Tree | Visibility Builds | Congestion Checks | Verify Calls | | :-- | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | -| example_01_simple_route | 0.0036 | 1 | 1 | 1 | 1 | 1 | 2 | 10 | 11 | 7 | 0 | 0 | 0 | 3 | -| example_02_congestion_resolution | 0.3297 | 3 | 3 | 3 | 1 | 3 | 366 | 1164 | 1413 | 668 | 0 | 0 | 0 | 35 | -| example_03_locked_paths | 0.1832 | 2 | 2 | 2 | 2 | 2 | 191 | 657 | 904 | 307 | 0 | 0 | 0 | 14 | -| example_04_sbends_and_radii | 0.0260 | 2 | 2 | 2 | 1 | 2 | 15 | 70 | 123 | 65 | 0 | 0 | 0 | 6 | -| example_05_orientation_stress | 0.2348 | 3 | 3 | 3 | 2 | 6 | 286 | 1243 | 1624 | 681 | 0 | 0 | 155 | 15 | -| example_06_bend_collision_models | 0.1953 | 3 | 3 | 3 | 3 | 3 | 240 | 682 | 1026 | 629 | 0 | 0 | 0 | 9 | -| example_07_large_scale_routing | 0.1945 | 10 | 10 | 10 | 1 | 10 | 78 | 383 | 372 | 227 | 0 | 0 | 0 | 30 | -| example_08_custom_bend_geometry | 0.0177 | 2 | 2 | 2 | 2 | 2 | 18 | 56 | 78 | 56 | 0 | 0 | 0 | 6 | +| example_01_simple_route | 0.0040 | 1 | 1 | 1 | 1 | 1 | 2 | 10 | 11 | 7 | 0 | 0 | 0 | 4 | +| example_02_congestion_resolution | 0.3378 | 3 | 3 | 3 | 1 | 3 | 366 | 1164 | 1413 | 668 | 0 | 0 | 0 | 38 | +| example_03_locked_paths | 0.1929 | 2 | 2 | 2 | 2 | 2 | 191 | 657 | 904 | 307 | 0 | 0 | 0 | 16 | +| example_04_sbends_and_radii | 0.0279 | 2 | 2 | 2 | 1 | 2 | 15 | 70 | 123 | 65 | 0 | 0 | 0 | 8 | +| example_05_orientation_stress | 0.2367 | 3 | 3 | 3 | 2 | 6 | 299 | 1284 | 1691 | 696 | 0 | 0 | 149 | 18 | +| example_06_bend_collision_models | 0.1998 | 3 | 3 | 3 | 3 | 3 | 240 | 682 | 1026 | 629 | 0 | 0 | 0 | 12 | +| example_07_large_scale_routing | 0.2005 | 10 | 10 | 10 | 1 | 10 | 78 | 383 | 372 | 227 | 0 | 0 | 0 | 40 | +| example_08_custom_bend_geometry | 0.0176 | 2 | 2 | 2 | 2 | 2 | 18 | 56 | 78 | 56 | 0 | 0 | 0 | 8 | | example_09_unroutable_best_effort | 0.0058 | 1 | 0 | 0 | 1 | 1 | 3 | 13 | 16 | 10 | 0 | 0 | 0 | 1 | ## Full Counter Set @@ -22,6 +24,13 @@ Use `scripts/diff_performance_baseline.py` to compare a fresh run against that s Each scenario entry in `docs/performance_baseline.json` records the full `RouteMetrics` snapshot, including cache, index, congestion, and verification counters. These counters are currently observational only and are not enforced as CI regression gates. +For the current accepted branch, the most important performance-only canary is `example_07_large_scale_routing_no_warm_start`, which now finishes `10/10/10` after a bounded post-loop pair-local scratch reroute. The relevant counters for that phase are: + +- `pair_local_search_pairs_considered` +- `pair_local_search_attempts` +- `pair_local_search_accepts` +- `pair_local_search_nodes_expanded` + Tracked metric keys: -nodes_expanded, moves_generated, moves_added, pruned_closed_set, pruned_hard_collision, pruned_cost, route_iterations, nets_routed, nets_reached_target, warm_start_paths_built, warm_start_paths_used, refine_path_calls, timeout_events, iteration_reverify_calls, iteration_reverified_nets, iteration_conflicting_nets, iteration_conflict_edges, nets_carried_forward, score_component_calls, score_component_total_ns, path_cost_calls, danger_map_lookup_calls, danger_map_cache_hits, danger_map_cache_misses, danger_map_query_calls, danger_map_total_ns, move_cache_abs_hits, move_cache_abs_misses, move_cache_rel_hits, move_cache_rel_misses, static_safe_cache_hits, hard_collision_cache_hits, congestion_cache_hits, congestion_cache_misses, congestion_presence_cache_hits, congestion_presence_cache_misses, congestion_presence_skips, congestion_candidate_precheck_hits, congestion_candidate_precheck_misses, congestion_candidate_precheck_skips, congestion_grid_net_cache_hits, congestion_grid_net_cache_misses, congestion_grid_span_cache_hits, congestion_grid_span_cache_misses, congestion_candidate_nets, congestion_net_envelope_cache_hits, congestion_net_envelope_cache_misses, dynamic_path_objects_added, dynamic_path_objects_removed, dynamic_tree_rebuilds, dynamic_grid_rebuilds, static_tree_rebuilds, static_raw_tree_rebuilds, static_net_tree_rebuilds, visibility_corner_index_builds, visibility_builds, visibility_corner_pairs_checked, visibility_corner_queries_exact, visibility_corner_hits_exact, visibility_point_queries, visibility_point_cache_hits, visibility_point_cache_misses, visibility_tangent_candidate_scans, visibility_tangent_candidate_corner_checks, visibility_tangent_candidate_ray_tests, ray_cast_calls, ray_cast_calls_straight_static, ray_cast_calls_expand_snap, ray_cast_calls_expand_forward, ray_cast_calls_visibility_build, ray_cast_calls_visibility_query, ray_cast_calls_visibility_tangent, ray_cast_calls_other, ray_cast_candidate_bounds, ray_cast_exact_geometry_checks, congestion_check_calls, congestion_lazy_resolutions, congestion_lazy_requeues, congestion_candidate_ids, congestion_exact_pair_checks, verify_path_report_calls, verify_static_buffer_ops, verify_dynamic_candidate_nets, verify_dynamic_exact_pair_checks, refinement_windows_considered, refinement_static_bounds_checked, refinement_dynamic_bounds_checked, refinement_candidate_side_extents, refinement_candidates_built, refinement_candidates_verified, refinement_candidates_accepted +nodes_expanded, moves_generated, moves_added, pruned_closed_set, pruned_hard_collision, pruned_cost, route_iterations, nets_routed, nets_reached_target, warm_start_paths_built, warm_start_paths_used, refine_path_calls, timeout_events, iteration_reverify_calls, iteration_reverified_nets, iteration_conflicting_nets, iteration_conflict_edges, nets_carried_forward, score_component_calls, score_component_total_ns, path_cost_calls, danger_map_lookup_calls, danger_map_cache_hits, danger_map_cache_misses, danger_map_query_calls, danger_map_total_ns, move_cache_abs_hits, move_cache_abs_misses, move_cache_rel_hits, move_cache_rel_misses, guidance_match_moves, guidance_match_moves_straight, guidance_match_moves_bend90, guidance_match_moves_sbend, guidance_bonus_applied, guidance_bonus_applied_straight, guidance_bonus_applied_bend90, guidance_bonus_applied_sbend, static_safe_cache_hits, hard_collision_cache_hits, congestion_cache_hits, congestion_cache_misses, congestion_presence_cache_hits, congestion_presence_cache_misses, congestion_presence_skips, congestion_candidate_precheck_hits, congestion_candidate_precheck_misses, congestion_candidate_precheck_skips, congestion_grid_net_cache_hits, congestion_grid_net_cache_misses, congestion_grid_span_cache_hits, congestion_grid_span_cache_misses, congestion_candidate_nets, congestion_net_envelope_cache_hits, congestion_net_envelope_cache_misses, dynamic_path_objects_added, dynamic_path_objects_removed, dynamic_tree_rebuilds, dynamic_grid_rebuilds, static_tree_rebuilds, static_raw_tree_rebuilds, static_net_tree_rebuilds, visibility_corner_index_builds, visibility_builds, visibility_corner_pairs_checked, visibility_corner_queries_exact, visibility_corner_hits_exact, visibility_point_queries, visibility_point_cache_hits, visibility_point_cache_misses, visibility_tangent_candidate_scans, visibility_tangent_candidate_corner_checks, visibility_tangent_candidate_ray_tests, ray_cast_calls, ray_cast_calls_straight_static, ray_cast_calls_expand_snap, ray_cast_calls_expand_forward, ray_cast_calls_visibility_build, ray_cast_calls_visibility_query, ray_cast_calls_visibility_tangent, ray_cast_calls_other, ray_cast_candidate_bounds, ray_cast_exact_geometry_checks, congestion_check_calls, congestion_lazy_resolutions, congestion_lazy_requeues, congestion_candidate_ids, congestion_exact_pair_checks, verify_path_report_calls, verify_static_buffer_ops, verify_dynamic_candidate_nets, verify_dynamic_exact_pair_checks, refinement_windows_considered, refinement_static_bounds_checked, refinement_dynamic_bounds_checked, refinement_candidate_side_extents, refinement_candidates_built, refinement_candidates_verified, refinement_candidates_accepted, pair_local_search_pairs_considered, pair_local_search_attempts, pair_local_search_accepts, pair_local_search_nodes_expanded diff --git a/docs/performance_baseline.json b/docs/performance_baseline.json index c95def6..a0477d3 100644 --- a/docs/performance_baseline.json +++ b/docs/performance_baseline.json @@ -1,9 +1,9 @@ { - "generated_on": "2026-04-01", + "generated_on": "2026-04-02", "generator": "scripts/record_performance_baseline.py", "scenarios": [ { - "duration_s": 0.0035884700482711196, + "duration_s": 0.003964120987802744, "metrics": { "congestion_cache_hits": 0, "congestion_cache_misses": 0, @@ -34,6 +34,14 @@ "dynamic_path_objects_added": 3, "dynamic_path_objects_removed": 2, "dynamic_tree_rebuilds": 0, + "guidance_bonus_applied": 0.0, + "guidance_bonus_applied_bend90": 0.0, + "guidance_bonus_applied_sbend": 0.0, + "guidance_bonus_applied_straight": 0.0, + "guidance_match_moves": 0, + "guidance_match_moves_bend90": 0, + "guidance_match_moves_sbend": 0, + "guidance_match_moves_straight": 0, "hard_collision_cache_hits": 0, "iteration_conflict_edges": 0, "iteration_conflicting_nets": 0, @@ -49,6 +57,10 @@ "nets_reached_target": 1, "nets_routed": 1, "nodes_expanded": 2, + "pair_local_search_accepts": 0, + "pair_local_search_attempts": 0, + "pair_local_search_nodes_expanded": 0, + "pair_local_search_pairs_considered": 0, "path_cost_calls": 0, "pruned_closed_set": 0, "pruned_cost": 4, @@ -73,7 +85,7 @@ "refinement_windows_considered": 0, "route_iterations": 1, "score_component_calls": 11, - "score_component_total_ns": 16010, + "score_component_total_ns": 18064, "static_net_tree_rebuilds": 1, "static_raw_tree_rebuilds": 0, "static_safe_cache_hits": 1, @@ -81,7 +93,7 @@ "timeout_events": 0, "verify_dynamic_candidate_nets": 0, "verify_dynamic_exact_pair_checks": 0, - "verify_path_report_calls": 3, + "verify_path_report_calls": 4, "verify_static_buffer_ops": 0, "visibility_builds": 0, "visibility_corner_hits_exact": 0, @@ -103,7 +115,7 @@ "valid_results": 1 }, { - "duration_s": 0.32969290704932064, + "duration_s": 0.3377689190674573, "metrics": { "congestion_cache_hits": 0, "congestion_cache_misses": 0, @@ -134,6 +146,14 @@ "dynamic_path_objects_added": 49, "dynamic_path_objects_removed": 34, "dynamic_tree_rebuilds": 0, + "guidance_bonus_applied": 0.0, + "guidance_bonus_applied_bend90": 0.0, + "guidance_bonus_applied_sbend": 0.0, + "guidance_bonus_applied_straight": 0.0, + "guidance_match_moves": 0, + "guidance_match_moves_bend90": 0, + "guidance_match_moves_sbend": 0, + "guidance_match_moves_straight": 0, "hard_collision_cache_hits": 0, "iteration_conflict_edges": 0, "iteration_conflicting_nets": 0, @@ -149,6 +169,10 @@ "nets_reached_target": 3, "nets_routed": 3, "nodes_expanded": 366, + "pair_local_search_accepts": 0, + "pair_local_search_attempts": 0, + "pair_local_search_nodes_expanded": 0, + "pair_local_search_pairs_considered": 0, "path_cost_calls": 14, "pruned_closed_set": 157, "pruned_cost": 208, @@ -173,15 +197,15 @@ "refinement_windows_considered": 10, "route_iterations": 1, "score_component_calls": 976, - "score_component_total_ns": 1091130, + "score_component_total_ns": 1140704, "static_net_tree_rebuilds": 3, "static_raw_tree_rebuilds": 0, "static_safe_cache_hits": 1, "static_tree_rebuilds": 2, "timeout_events": 0, - "verify_dynamic_candidate_nets": 84, - "verify_dynamic_exact_pair_checks": 82, - "verify_path_report_calls": 35, + "verify_dynamic_candidate_nets": 88, + "verify_dynamic_exact_pair_checks": 86, + "verify_path_report_calls": 38, "verify_static_buffer_ops": 0, "visibility_builds": 0, "visibility_corner_hits_exact": 0, @@ -203,7 +227,7 @@ "valid_results": 3 }, { - "duration_s": 0.18321374501101673, + "duration_s": 0.1929313091095537, "metrics": { "congestion_cache_hits": 0, "congestion_cache_misses": 0, @@ -234,6 +258,14 @@ "dynamic_path_objects_added": 27, "dynamic_path_objects_removed": 20, "dynamic_tree_rebuilds": 0, + "guidance_bonus_applied": 0.0, + "guidance_bonus_applied_bend90": 0.0, + "guidance_bonus_applied_sbend": 0.0, + "guidance_bonus_applied_straight": 0.0, + "guidance_match_moves": 0, + "guidance_match_moves_bend90": 0, + "guidance_match_moves_sbend": 0, + "guidance_match_moves_straight": 0, "hard_collision_cache_hits": 0, "iteration_conflict_edges": 0, "iteration_conflicting_nets": 0, @@ -249,6 +281,10 @@ "nets_reached_target": 2, "nets_routed": 2, "nodes_expanded": 191, + "pair_local_search_accepts": 0, + "pair_local_search_attempts": 0, + "pair_local_search_nodes_expanded": 0, + "pair_local_search_pairs_considered": 0, "path_cost_calls": 9, "pruned_closed_set": 97, "pruned_cost": 140, @@ -273,16 +309,16 @@ "refinement_windows_considered": 2, "route_iterations": 2, "score_component_calls": 504, - "score_component_total_ns": 556716, + "score_component_total_ns": 565410, "static_net_tree_rebuilds": 2, "static_raw_tree_rebuilds": 1, "static_safe_cache_hits": 1, "static_tree_rebuilds": 1, "timeout_events": 0, - "verify_dynamic_candidate_nets": 8, - "verify_dynamic_exact_pair_checks": 8, - "verify_path_report_calls": 14, - "verify_static_buffer_ops": 72, + "verify_dynamic_candidate_nets": 9, + "verify_dynamic_exact_pair_checks": 9, + "verify_path_report_calls": 16, + "verify_static_buffer_ops": 81, "visibility_builds": 0, "visibility_corner_hits_exact": 0, "visibility_corner_index_builds": 2, @@ -303,7 +339,7 @@ "valid_results": 2 }, { - "duration_s": 0.026024609920568764, + "duration_s": 0.02791503700427711, "metrics": { "congestion_cache_hits": 0, "congestion_cache_misses": 0, @@ -334,6 +370,14 @@ "dynamic_path_objects_added": 21, "dynamic_path_objects_removed": 14, "dynamic_tree_rebuilds": 0, + "guidance_bonus_applied": 0.0, + "guidance_bonus_applied_bend90": 0.0, + "guidance_bonus_applied_sbend": 0.0, + "guidance_bonus_applied_straight": 0.0, + "guidance_match_moves": 0, + "guidance_match_moves_bend90": 0, + "guidance_match_moves_sbend": 0, + "guidance_match_moves_straight": 0, "hard_collision_cache_hits": 0, "iteration_conflict_edges": 0, "iteration_conflicting_nets": 0, @@ -349,6 +393,10 @@ "nets_reached_target": 2, "nets_routed": 2, "nodes_expanded": 15, + "pair_local_search_accepts": 0, + "pair_local_search_attempts": 0, + "pair_local_search_nodes_expanded": 0, + "pair_local_search_pairs_considered": 0, "path_cost_calls": 0, "pruned_closed_set": 2, "pruned_cost": 25, @@ -373,15 +421,15 @@ "refinement_windows_considered": 0, "route_iterations": 1, "score_component_calls": 90, - "score_component_total_ns": 97738, + "score_component_total_ns": 100083, "static_net_tree_rebuilds": 2, "static_raw_tree_rebuilds": 0, "static_safe_cache_hits": 1, "static_tree_rebuilds": 1, "timeout_events": 0, - "verify_dynamic_candidate_nets": 6, + "verify_dynamic_candidate_nets": 9, "verify_dynamic_exact_pair_checks": 0, - "verify_path_report_calls": 6, + "verify_path_report_calls": 8, "verify_static_buffer_ops": 0, "visibility_builds": 0, "visibility_corner_hits_exact": 0, @@ -403,28 +451,28 @@ "valid_results": 2 }, { - "duration_s": 0.23484283208381385, + "duration_s": 0.23665715800598264, "metrics": { - "congestion_cache_hits": 2, - "congestion_cache_misses": 155, - "congestion_candidate_ids": 19, - "congestion_candidate_nets": 15, - "congestion_candidate_precheck_hits": 135, + "congestion_cache_hits": 4, + "congestion_cache_misses": 149, + "congestion_candidate_ids": 32, + "congestion_candidate_nets": 23, + "congestion_candidate_precheck_hits": 131, "congestion_candidate_precheck_misses": 22, "congestion_candidate_precheck_skips": 0, - "congestion_check_calls": 155, - "congestion_exact_pair_checks": 18, - "congestion_grid_net_cache_hits": 12, - "congestion_grid_net_cache_misses": 25, - "congestion_grid_span_cache_hits": 11, - "congestion_grid_span_cache_misses": 4, + "congestion_check_calls": 149, + "congestion_exact_pair_checks": 30, + "congestion_grid_net_cache_hits": 16, + "congestion_grid_net_cache_misses": 28, + "congestion_grid_span_cache_hits": 15, + "congestion_grid_span_cache_misses": 7, "congestion_lazy_requeues": 0, "congestion_lazy_resolutions": 0, - "congestion_net_envelope_cache_hits": 134, + "congestion_net_envelope_cache_hits": 128, "congestion_net_envelope_cache_misses": 43, - "congestion_presence_cache_hits": 185, + "congestion_presence_cache_hits": 200, "congestion_presence_cache_misses": 30, - "congestion_presence_skips": 58, + "congestion_presence_skips": 77, "danger_map_cache_hits": 0, "danger_map_cache_misses": 0, "danger_map_lookup_calls": 0, @@ -434,30 +482,42 @@ "dynamic_path_objects_added": 49, "dynamic_path_objects_removed": 37, "dynamic_tree_rebuilds": 0, + "guidance_bonus_applied": 687.5, + "guidance_bonus_applied_bend90": 500.0, + "guidance_bonus_applied_sbend": 0.0, + "guidance_bonus_applied_straight": 187.5, + "guidance_match_moves": 11, + "guidance_match_moves_bend90": 8, + "guidance_match_moves_sbend": 0, + "guidance_match_moves_straight": 3, "hard_collision_cache_hits": 0, "iteration_conflict_edges": 1, "iteration_conflicting_nets": 2, "iteration_reverified_nets": 6, "iteration_reverify_calls": 2, - "move_cache_abs_hits": 253, - "move_cache_abs_misses": 1371, - "move_cache_rel_hits": 1269, + "move_cache_abs_hits": 385, + "move_cache_abs_misses": 1306, + "move_cache_rel_hits": 1204, "move_cache_rel_misses": 102, - "moves_added": 681, - "moves_generated": 1624, + "moves_added": 696, + "moves_generated": 1691, "nets_carried_forward": 0, "nets_reached_target": 6, "nets_routed": 6, - "nodes_expanded": 286, + "nodes_expanded": 299, + "pair_local_search_accepts": 0, + "pair_local_search_attempts": 0, + "pair_local_search_nodes_expanded": 0, + "pair_local_search_pairs_considered": 0, "path_cost_calls": 2, - "pruned_closed_set": 139, - "pruned_cost": 505, + "pruned_closed_set": 159, + "pruned_cost": 537, "pruned_hard_collision": 14, - "ray_cast_calls": 1243, - "ray_cast_calls_expand_forward": 280, + "ray_cast_calls": 1284, + "ray_cast_calls_expand_forward": 293, "ray_cast_calls_expand_snap": 3, "ray_cast_calls_other": 0, - "ray_cast_calls_straight_static": 951, + "ray_cast_calls_straight_static": 979, "ray_cast_calls_visibility_build": 0, "ray_cast_calls_visibility_query": 0, "ray_cast_calls_visibility_tangent": 9, @@ -472,16 +532,16 @@ "refinement_static_bounds_checked": 0, "refinement_windows_considered": 0, "route_iterations": 2, - "score_component_calls": 1198, - "score_component_total_ns": 1194981, + "score_component_calls": 1245, + "score_component_total_ns": 1260961, "static_net_tree_rebuilds": 3, "static_raw_tree_rebuilds": 0, - "static_safe_cache_hits": 3, + "static_safe_cache_hits": 9, "static_tree_rebuilds": 1, "timeout_events": 0, "verify_dynamic_candidate_nets": 8, "verify_dynamic_exact_pair_checks": 12, - "verify_path_report_calls": 15, + "verify_path_report_calls": 18, "verify_static_buffer_ops": 0, "visibility_builds": 0, "visibility_corner_hits_exact": 0, @@ -493,7 +553,7 @@ "visibility_point_queries": 0, "visibility_tangent_candidate_corner_checks": 70, "visibility_tangent_candidate_ray_tests": 9, - "visibility_tangent_candidate_scans": 280, + "visibility_tangent_candidate_scans": 293, "warm_start_paths_built": 2, "warm_start_paths_used": 2 }, @@ -503,7 +563,7 @@ "valid_results": 3 }, { - "duration_s": 0.19533946400042623, + "duration_s": 0.19982667709700763, "metrics": { "congestion_cache_hits": 0, "congestion_cache_misses": 0, @@ -529,11 +589,19 @@ "danger_map_cache_misses": 731, "danger_map_lookup_calls": 1914, "danger_map_query_calls": 731, - "danger_map_total_ns": 18697751, + "danger_map_total_ns": 18959782, "dynamic_grid_rebuilds": 0, "dynamic_path_objects_added": 54, "dynamic_path_objects_removed": 36, "dynamic_tree_rebuilds": 0, + "guidance_bonus_applied": 0.0, + "guidance_bonus_applied_bend90": 0.0, + "guidance_bonus_applied_sbend": 0.0, + "guidance_bonus_applied_straight": 0.0, + "guidance_match_moves": 0, + "guidance_match_moves_bend90": 0, + "guidance_match_moves_sbend": 0, + "guidance_match_moves_straight": 0, "hard_collision_cache_hits": 18, "iteration_conflict_edges": 0, "iteration_conflicting_nets": 0, @@ -549,6 +617,10 @@ "nets_reached_target": 3, "nets_routed": 3, "nodes_expanded": 240, + "pair_local_search_accepts": 0, + "pair_local_search_attempts": 0, + "pair_local_search_nodes_expanded": 0, + "pair_local_search_pairs_considered": 0, "path_cost_calls": 0, "pruned_closed_set": 108, "pruned_cost": 204, @@ -573,7 +645,7 @@ "refinement_windows_considered": 0, "route_iterations": 3, "score_component_calls": 842, - "score_component_total_ns": 21016472, + "score_component_total_ns": 21338709, "static_net_tree_rebuilds": 3, "static_raw_tree_rebuilds": 3, "static_safe_cache_hits": 141, @@ -581,8 +653,8 @@ "timeout_events": 0, "verify_dynamic_candidate_nets": 0, "verify_dynamic_exact_pair_checks": 0, - "verify_path_report_calls": 9, - "verify_static_buffer_ops": 54, + "verify_path_report_calls": 12, + "verify_static_buffer_ops": 72, "visibility_builds": 0, "visibility_corner_hits_exact": 0, "visibility_corner_index_builds": 3, @@ -603,7 +675,7 @@ "valid_results": 3 }, { - "duration_s": 0.19448363897390664, + "duration_s": 0.20046633295714855, "metrics": { "congestion_cache_hits": 0, "congestion_cache_misses": 0, @@ -629,11 +701,19 @@ "danger_map_cache_misses": 448, "danger_map_lookup_calls": 681, "danger_map_query_calls": 448, - "danger_map_total_ns": 10973251, + "danger_map_total_ns": 11017087, "dynamic_grid_rebuilds": 0, "dynamic_path_objects_added": 132, "dynamic_path_objects_removed": 88, "dynamic_tree_rebuilds": 0, + "guidance_bonus_applied": 0.0, + "guidance_bonus_applied_bend90": 0.0, + "guidance_bonus_applied_sbend": 0.0, + "guidance_bonus_applied_straight": 0.0, + "guidance_match_moves": 0, + "guidance_match_moves_bend90": 0, + "guidance_match_moves_sbend": 0, + "guidance_match_moves_straight": 0, "hard_collision_cache_hits": 0, "iteration_conflict_edges": 0, "iteration_conflicting_nets": 0, @@ -649,6 +729,10 @@ "nets_reached_target": 10, "nets_routed": 10, "nodes_expanded": 78, + "pair_local_search_accepts": 0, + "pair_local_search_attempts": 0, + "pair_local_search_nodes_expanded": 0, + "pair_local_search_pairs_considered": 0, "path_cost_calls": 0, "pruned_closed_set": 20, "pruned_cost": 64, @@ -673,16 +757,16 @@ "refinement_windows_considered": 0, "route_iterations": 1, "score_component_calls": 291, - "score_component_total_ns": 11824081, + "score_component_total_ns": 11869917, "static_net_tree_rebuilds": 10, "static_raw_tree_rebuilds": 1, "static_safe_cache_hits": 6, "static_tree_rebuilds": 10, "timeout_events": 0, - "verify_dynamic_candidate_nets": 264, - "verify_dynamic_exact_pair_checks": 40, - "verify_path_report_calls": 30, - "verify_static_buffer_ops": 132, + "verify_dynamic_candidate_nets": 370, + "verify_dynamic_exact_pair_checks": 56, + "verify_path_report_calls": 40, + "verify_static_buffer_ops": 176, "visibility_builds": 0, "visibility_corner_hits_exact": 0, "visibility_corner_index_builds": 10, @@ -703,7 +787,7 @@ "valid_results": 10 }, { - "duration_s": 0.017700672964565456, + "duration_s": 0.01759456400759518, "metrics": { "congestion_cache_hits": 0, "congestion_cache_misses": 0, @@ -734,6 +818,14 @@ "dynamic_path_objects_added": 18, "dynamic_path_objects_removed": 12, "dynamic_tree_rebuilds": 0, + "guidance_bonus_applied": 0.0, + "guidance_bonus_applied_bend90": 0.0, + "guidance_bonus_applied_sbend": 0.0, + "guidance_bonus_applied_straight": 0.0, + "guidance_match_moves": 0, + "guidance_match_moves_bend90": 0, + "guidance_match_moves_sbend": 0, + "guidance_match_moves_straight": 0, "hard_collision_cache_hits": 0, "iteration_conflict_edges": 0, "iteration_conflicting_nets": 0, @@ -749,6 +841,10 @@ "nets_reached_target": 2, "nets_routed": 2, "nodes_expanded": 18, + "pair_local_search_accepts": 0, + "pair_local_search_attempts": 0, + "pair_local_search_nodes_expanded": 0, + "pair_local_search_pairs_considered": 0, "path_cost_calls": 0, "pruned_closed_set": 6, "pruned_cost": 16, @@ -773,7 +869,7 @@ "refinement_windows_considered": 0, "route_iterations": 2, "score_component_calls": 72, - "score_component_total_ns": 85969, + "score_component_total_ns": 85864, "static_net_tree_rebuilds": 2, "static_raw_tree_rebuilds": 0, "static_safe_cache_hits": 2, @@ -781,7 +877,7 @@ "timeout_events": 0, "verify_dynamic_candidate_nets": 0, "verify_dynamic_exact_pair_checks": 0, - "verify_path_report_calls": 6, + "verify_path_report_calls": 8, "verify_static_buffer_ops": 0, "visibility_builds": 0, "visibility_corner_hits_exact": 0, @@ -803,7 +899,7 @@ "valid_results": 2 }, { - "duration_s": 0.005781985004432499, + "duration_s": 0.005838233977556229, "metrics": { "congestion_cache_hits": 0, "congestion_cache_misses": 0, @@ -829,11 +925,19 @@ "danger_map_cache_misses": 20, "danger_map_lookup_calls": 30, "danger_map_query_calls": 20, - "danger_map_total_ns": 536009, + "danger_map_total_ns": 523870, "dynamic_grid_rebuilds": 0, "dynamic_path_objects_added": 2, "dynamic_path_objects_removed": 1, "dynamic_tree_rebuilds": 0, + "guidance_bonus_applied": 0.0, + "guidance_bonus_applied_bend90": 0.0, + "guidance_bonus_applied_sbend": 0.0, + "guidance_bonus_applied_straight": 0.0, + "guidance_match_moves": 0, + "guidance_match_moves_bend90": 0, + "guidance_match_moves_sbend": 0, + "guidance_match_moves_straight": 0, "hard_collision_cache_hits": 0, "iteration_conflict_edges": 0, "iteration_conflicting_nets": 0, @@ -849,6 +953,10 @@ "nets_reached_target": 0, "nets_routed": 1, "nodes_expanded": 3, + "pair_local_search_accepts": 0, + "pair_local_search_attempts": 0, + "pair_local_search_nodes_expanded": 0, + "pair_local_search_pairs_considered": 0, "path_cost_calls": 0, "pruned_closed_set": 0, "pruned_cost": 4, @@ -873,7 +981,7 @@ "refinement_windows_considered": 0, "route_iterations": 1, "score_component_calls": 14, - "score_component_total_ns": 574907, + "score_component_total_ns": 563611, "static_net_tree_rebuilds": 1, "static_raw_tree_rebuilds": 1, "static_safe_cache_hits": 0, diff --git a/inire/__init__.py b/inire/__init__.py index f6ee2d5..9c63046 100644 --- a/inire/__init__.py +++ b/inire/__init__.py @@ -14,7 +14,15 @@ from .model import ( RoutingProblem as RoutingProblem, SearchOptions as SearchOptions, ) # noqa: PLC0414 -from .results import RoutingResult as RoutingResult, RoutingRunResult as RoutingRunResult # noqa: PLC0414 +from .results import ( # noqa: PLC0414 + ComponentConflictTrace as ComponentConflictTrace, + ConflictTraceEntry as ConflictTraceEntry, + FrontierPruneSample as FrontierPruneSample, + NetConflictTrace as NetConflictTrace, + NetFrontierTrace as NetFrontierTrace, + RoutingResult as RoutingResult, + RoutingRunResult as RoutingRunResult, +) from .seeds import Bend90Seed as Bend90Seed, PathSeed as PathSeed, SBendSeed as SBendSeed, StraightSeed as StraightSeed # noqa: PLC0414 __author__ = 'Jan Petykiewicz' @@ -37,16 +45,23 @@ def route( results_by_net=results, metrics=finder.metrics.snapshot(), expanded_nodes=tuple(finder.accumulated_expanded_nodes), + conflict_trace=tuple(finder.conflict_trace), + frontier_trace=tuple(finder.frontier_trace), ) __all__ = [ "Bend90Seed", "CongestionOptions", + "ComponentConflictTrace", + "ConflictTraceEntry", "DiagnosticsOptions", "NetSpec", + "NetConflictTrace", + "NetFrontierTrace", "ObjectiveWeights", "PathSeed", "Port", + "FrontierPruneSample", "RefinementOptions", "RoutingOptions", "RoutingProblem", diff --git a/inire/geometry/collision.py b/inire/geometry/collision.py index c877cfa..60b008a 100644 --- a/inire/geometry/collision.py +++ b/inire/geometry/collision.py @@ -64,6 +64,7 @@ def _span_to_bounds( class PathVerificationDetail: report: RoutingReport conflicting_net_ids: tuple[str, ...] = () + component_conflicts: tuple[tuple[int, str, int], ...] = () @dataclass(frozen=True, slots=True) @@ -140,8 +141,19 @@ class RoutingWorld: def _ensure_dynamic_grid(self) -> None: self._dynamic_paths.ensure_grid() - def add_path(self, net_id: str, geometry: Sequence[Polygon], dilated_geometry: Sequence[Polygon]) -> None: - self._dynamic_paths.add_path(net_id, geometry, dilated_geometry=dilated_geometry) + def add_path( + self, + net_id: str, + geometry: Sequence[Polygon], + dilated_geometry: Sequence[Polygon], + component_indexes: Sequence[int] | None = None, + ) -> None: + self._dynamic_paths.add_path( + net_id, + geometry, + dilated_geometry=dilated_geometry, + component_indexes=component_indexes, + ) def remove_path(self, net_id: str) -> None: self._dynamic_paths.remove_path(net_id) @@ -651,7 +663,13 @@ class RoutingWorld: frozen_net_ids=frozen_net_ids, ) - def verify_path_details(self, net_id: str, components: Sequence[ComponentResult]) -> PathVerificationDetail: + def verify_path_details( + self, + net_id: str, + components: Sequence[ComponentResult], + *, + capture_component_conflicts: bool = False, + ) -> PathVerificationDetail: if self.metrics is not None: self.metrics.total_verify_path_report_calls += 1 static_collision_count = 0 @@ -659,6 +677,7 @@ class RoutingWorld: self_collision_count = 0 total_length = sum(component.length for component in components) conflicting_net_ids: set[str] = set() + component_conflicts: set[tuple[int, str, int]] = set() static_obstacles = self._static_obstacles dynamic_paths = self._dynamic_paths @@ -682,7 +701,7 @@ class RoutingWorld: static_collision_count += 1 if dynamic_paths.dilated: - for component in components: + for component_index, component in enumerate(components): test_geometries = component.dilated_physical_geometry component_hits = [] for new_geometry in test_geometries: @@ -695,6 +714,14 @@ class RoutingWorld: tree_geometry = dynamic_paths.dilated[obj_id] if _has_non_touching_overlap(new_geometry, tree_geometry): component_hits.append(hit_net_id) + if capture_component_conflicts: + component_conflicts.add( + ( + component_index, + hit_net_id, + dynamic_paths.component_indexes[obj_id], + ) + ) break if component_hits: @@ -715,6 +742,7 @@ class RoutingWorld: total_length=total_length, ), conflicting_net_ids=tuple(sorted(conflicting_net_ids)), + component_conflicts=tuple(sorted(component_conflicts)), ) def verify_path_report(self, net_id: str, components: Sequence[ComponentResult]) -> RoutingReport: diff --git a/inire/geometry/dynamic_path_index.py b/inire/geometry/dynamic_path_index.py index 78b3f22..70d4b9f 100644 --- a/inire/geometry/dynamic_path_index.py +++ b/inire/geometry/dynamic_path_index.py @@ -21,6 +21,7 @@ class DynamicPathIndex: "engine", "index", "geometries", + "component_indexes", "dilated", "dilated_bounds", "net_envelope_index", @@ -43,6 +44,7 @@ class DynamicPathIndex: self.engine = engine self.index = rtree.index.Index() self.geometries: dict[int, tuple[str, Polygon]] = {} + self.component_indexes: dict[int, int] = {} self.dilated: dict[int, Polygon] = {} self.dilated_bounds: dict[int, tuple[float, float, float, float]] = {} self.net_envelope_index = rtree.index.Index() @@ -176,7 +178,13 @@ class DynamicPathIndex: if not net_counts: self.grid_net_counts.pop(cell, None) - def add_path(self, net_id: str, geometry: Sequence[Polygon], dilated_geometry: Sequence[Polygon]) -> None: + def add_path( + self, + net_id: str, + geometry: Sequence[Polygon], + dilated_geometry: Sequence[Polygon], + component_indexes: Sequence[int] | None = None, + ) -> None: if self.engine.metrics is not None: self.engine.metrics.total_dynamic_path_objects_added += len(geometry) cell_size = self.engine.grid_cell_size @@ -186,6 +194,7 @@ class DynamicPathIndex: dilated = dilated_geometry[index] dilated_bounds = dilated.bounds self.geometries[obj_id] = (net_id, polygon) + self.component_indexes[obj_id] = index if component_indexes is None else component_indexes[index] self.dilated[obj_id] = dilated self.dilated_bounds[obj_id] = dilated_bounds self.index.insert(obj_id, dilated_bounds) @@ -211,6 +220,7 @@ class DynamicPathIndex: self._unregister_grid_membership(obj_id, net_id) self.index.delete(obj_id, self.dilated_bounds[obj_id]) del self.geometries[obj_id] + del self.component_indexes[obj_id] del self.dilated[obj_id] del self.dilated_bounds[obj_id] obj_id_set = self.net_to_obj_ids.get(net_id) diff --git a/inire/model.py b/inire/model.py index 05e923a..5860102 100644 --- a/inire/model.py +++ b/inire/model.py @@ -105,6 +105,8 @@ class RefinementOptions: @dataclass(frozen=True, slots=True) class DiagnosticsOptions: capture_expanded: bool = False + capture_conflict_trace: bool = False + capture_frontier_trace: bool = False @dataclass(frozen=True, slots=True) diff --git a/inire/results.py b/inire/results.py index 673ca80..88dd3a3 100644 --- a/inire/results.py +++ b/inire/results.py @@ -12,6 +12,8 @@ if TYPE_CHECKING: RoutingOutcome = Literal["completed", "colliding", "partial", "unroutable"] +ConflictTraceStage = Literal["iteration", "restored_best", "final"] +FrontierTraceReason = Literal["closed_set", "hard_collision", "self_collision", "cost"] @dataclass(frozen=True, slots=True) @@ -30,6 +32,52 @@ class RoutingReport: return self.collision_count == 0 +@dataclass(frozen=True, slots=True) +class ComponentConflictTrace: + other_net_id: str + self_component_index: int + other_component_index: int + + +@dataclass(frozen=True, slots=True) +class NetConflictTrace: + net_id: str + outcome: RoutingOutcome + reached_target: bool + report: RoutingReport + conflicting_net_ids: tuple[str, ...] = () + component_conflicts: tuple[ComponentConflictTrace, ...] = () + + +@dataclass(frozen=True, slots=True) +class ConflictTraceEntry: + stage: ConflictTraceStage + iteration: int | None + completed_net_ids: tuple[str, ...] + conflict_edges: tuple[tuple[str, str], ...] + nets: tuple[NetConflictTrace, ...] + + +@dataclass(frozen=True, slots=True) +class FrontierPruneSample: + reason: FrontierTraceReason + move_type: str + hotspot_index: int + parent_state: tuple[int, int, int] + end_state: tuple[int, int, int] + + +@dataclass(frozen=True, slots=True) +class NetFrontierTrace: + net_id: str + hotspot_bounds: tuple[tuple[float, float, float, float], ...] + pruned_closed_set: int + pruned_hard_collision: int + pruned_self_collision: int + pruned_cost: int + samples: tuple[FrontierPruneSample, ...] = () + + @dataclass(frozen=True, slots=True) class RouteMetrics: nodes_expanded: int @@ -62,6 +110,14 @@ class RouteMetrics: move_cache_abs_misses: int move_cache_rel_hits: int move_cache_rel_misses: int + guidance_match_moves: int + guidance_match_moves_straight: int + guidance_match_moves_bend90: int + guidance_match_moves_sbend: int + guidance_bonus_applied: float + guidance_bonus_applied_straight: float + guidance_bonus_applied_bend90: float + guidance_bonus_applied_sbend: float static_safe_cache_hits: int hard_collision_cache_hits: int congestion_cache_hits: int @@ -123,6 +179,10 @@ class RouteMetrics: refinement_candidates_built: int refinement_candidates_verified: int refinement_candidates_accepted: int + pair_local_search_pairs_considered: int + pair_local_search_attempts: int + pair_local_search_accepts: int + pair_local_search_nodes_expanded: int @dataclass(frozen=True, slots=True) @@ -169,3 +229,5 @@ class RoutingRunResult: results_by_net: dict[str, RoutingResult] metrics: RouteMetrics expanded_nodes: tuple[tuple[int, int, int], ...] = () + conflict_trace: tuple[ConflictTraceEntry, ...] = () + frontier_trace: tuple[NetFrontierTrace, ...] = () diff --git a/inire/router/_astar_admission.py b/inire/router/_astar_admission.py index 19fbeda..bb07505 100644 --- a/inire/router/_astar_admission.py +++ b/inire/router/_astar_admission.py @@ -145,11 +145,14 @@ def add_node( move_type: MoveKind, cache_key: tuple, ) -> None: + frontier_trace = config.frontier_trace metrics.moves_generated += 1 metrics.total_moves_generated += 1 state = result.end_port.as_tuple() new_lower_bound_g = parent.g_cost + result.length if state in closed_set and closed_set[state] <= new_lower_bound_g + TOLERANCE_LINEAR: + if frontier_trace is not None: + frontier_trace.record("closed_set", move_type, parent.port.as_tuple(), state, result.total_dilated_bounds) metrics.pruned_closed_set += 1 metrics.total_pruned_closed_set += 1 return @@ -158,6 +161,8 @@ def add_node( end_p = result.end_port if cache_key in context.hard_collision_set: + if frontier_trace is not None: + frontier_trace.record("hard_collision", move_type, parent.port.as_tuple(), state, result.total_dilated_bounds) context.metrics.total_hard_collision_cache_hits += 1 metrics.pruned_hard_collision += 1 metrics.total_pruned_hard_collision += 1 @@ -174,29 +179,62 @@ def add_node( collision_found = ce.check_move_static(result, start_port=parent_p, end_port=end_p) if collision_found: context.hard_collision_set.add(cache_key) + if frontier_trace is not None: + frontier_trace.record("hard_collision", move_type, parent.port.as_tuple(), state, result.total_dilated_bounds) metrics.pruned_hard_collision += 1 metrics.total_pruned_hard_collision += 1 return context.static_safe_cache.add(cache_key) if config.self_collision_check and component_hits_ancestor_chain(result, parent): + if frontier_trace is not None: + frontier_trace.record("self_collision", move_type, parent.port.as_tuple(), state, result.total_dilated_bounds) return move_cost = context.cost_evaluator.score_component( result, start_port=parent_p, ) + next_seed_index = None + if ( + config.guidance_seed is not None + and parent.seed_index is not None + and parent.seed_index < len(config.guidance_seed) + and result.move_spec == config.guidance_seed[parent.seed_index] + ): + context.metrics.total_guidance_match_moves += 1 + if result.move_type == "straight": + context.metrics.total_guidance_match_moves_straight += 1 + applied_bonus = config.guidance_bonus + context.metrics.total_guidance_bonus_applied_straight += applied_bonus + elif result.move_type == "bend90": + context.metrics.total_guidance_match_moves_bend90 += 1 + applied_bonus = config.guidance_bonus + context.metrics.total_guidance_bonus_applied_bend90 += applied_bonus + else: + context.metrics.total_guidance_match_moves_sbend += 1 + applied_bonus = config.guidance_bonus + context.metrics.total_guidance_bonus_applied_sbend += applied_bonus + context.metrics.total_guidance_bonus_applied += applied_bonus + move_cost = max(0.001, move_cost - applied_bonus) + next_seed_index = parent.seed_index + 1 if config.max_cost is not None and parent.g_cost + move_cost > config.max_cost: + if frontier_trace is not None: + frontier_trace.record("cost", move_type, parent.port.as_tuple(), state, result.total_dilated_bounds) metrics.pruned_cost += 1 metrics.total_pruned_cost += 1 return if move_cost > 1e12: + if frontier_trace is not None: + frontier_trace.record("cost", move_type, parent.port.as_tuple(), state, result.total_dilated_bounds) metrics.pruned_cost += 1 metrics.total_pruned_cost += 1 return if state in closed_set and closed_set[state] <= parent.g_cost + move_cost + TOLERANCE_LINEAR: + if frontier_trace is not None: + frontier_trace.record("closed_set", move_type, parent.port.as_tuple(), state, result.total_dilated_bounds) metrics.pruned_closed_set += 1 metrics.total_pruned_closed_set += 1 return @@ -233,6 +271,8 @@ def add_node( g_cost = parent.g_cost + move_cost if state in closed_set and closed_set[state] <= g_cost + TOLERANCE_LINEAR: + if frontier_trace is not None: + frontier_trace.record("closed_set", move_type, parent.port.as_tuple(), state, result.total_dilated_bounds) metrics.pruned_closed_set += 1 metrics.total_pruned_closed_set += 1 return @@ -242,6 +282,16 @@ def add_node( target, min_bend_radius=context.min_bend_radius, ) - heapq.heappush(open_set, AStarNode(result.end_port, g_cost, h_cost, parent, result)) + heapq.heappush( + open_set, + AStarNode( + result.end_port, + g_cost, + h_cost, + parent, + result, + seed_index=next_seed_index, + ), + ) metrics.moves_added += 1 metrics.total_moves_added += 1 diff --git a/inire/router/_astar_moves.py b/inire/router/_astar_moves.py index 5eb382a..8b8a60c 100644 --- a/inire/router/_astar_moves.py +++ b/inire/router/_astar_moves.py @@ -18,6 +18,27 @@ def _quantized_lengths(values: list[float], max_reach: float) -> list[int]: return sorted((v for v in out if v > 0), reverse=True) +def _distance_to_bounds_in_heading( + current: Port, + bounds: tuple[float, float, float, float], +) -> float: + min_x, min_y, max_x, max_y = bounds + if current.r == 0: + return max(0.0, max_x - current.x) + if current.r == 90: + return max(0.0, max_y - current.y) + if current.r == 180: + return max(0.0, current.x - min_x) + return max(0.0, current.y - min_y) + + +def _should_cap_straights_to_bounds(context: AStarContext) -> bool: + return ( + not context.options.congestion.warm_start_enabled + and len(context.problem.nets) >= 8 + ) + + def _sbend_forward_span(offset: float, radius: float) -> float | None: abs_offset = abs(offset) if abs_offset <= TOLERANCE_LINEAR or radius <= 0 or abs_offset >= 2.0 * radius: @@ -211,6 +232,8 @@ def expand_moves( net_width=net_width, caller="expand_forward", ) + if _should_cap_straights_to_bounds(context): + max_reach = min(max_reach, _distance_to_bounds_in_heading(cp, context.problem.bounds)) candidate_lengths = [ search_options.min_straight_length, max_reach, diff --git a/inire/router/_astar_types.py b/inire/router/_astar_types.py index e6a1d13..83fa899 100644 --- a/inire/router/_astar_types.py +++ b/inire/router/_astar_types.py @@ -1,6 +1,6 @@ from __future__ import annotations -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import TYPE_CHECKING from inire.model import resolve_bend_geometry @@ -12,6 +12,51 @@ if TYPE_CHECKING: from inire.geometry.primitives import Port from inire.model import RoutingOptions, RoutingProblem from inire.router.cost import CostEvaluator + from inire.seeds import PathSegmentSeed + + +@dataclass(slots=True) +class FrontierTraceCollector: + hotspot_bounds: tuple[tuple[float, float, float, float], ...] + sample_limit: int = 64 + pruned_closed_set: int = 0 + pruned_hard_collision: int = 0 + pruned_self_collision: int = 0 + pruned_cost: int = 0 + samples: list[tuple[str, str, int, tuple[int, int, int], tuple[int, int, int]]] = field(default_factory=list) + + def _matching_hotspot_index(self, bounds: tuple[float, float, float, float]) -> int | None: + for idx, hotspot_bounds in enumerate(self.hotspot_bounds): + if ( + bounds[0] < hotspot_bounds[2] + and bounds[2] > hotspot_bounds[0] + and bounds[1] < hotspot_bounds[3] + and bounds[3] > hotspot_bounds[1] + ): + return idx + return None + + def record( + self, + reason: str, + move_type: str, + parent_state: tuple[int, int, int], + end_state: tuple[int, int, int], + bounds: tuple[float, float, float, float], + ) -> None: + hotspot_index = self._matching_hotspot_index(bounds) + if hotspot_index is None: + return + if reason == "closed_set": + self.pruned_closed_set += 1 + elif reason == "hard_collision": + self.pruned_hard_collision += 1 + elif reason == "self_collision": + self.pruned_self_collision += 1 + else: + self.pruned_cost += 1 + if len(self.samples) < self.sample_limit: + self.samples.append((reason, move_type, hotspot_index, parent_state, end_state)) @dataclass(frozen=True, slots=True) @@ -20,6 +65,9 @@ class SearchRunConfig: bend_physical_geometry: BendPhysicalGeometry bend_clip_margin: float | None node_limit: int + guidance_seed: tuple[PathSegmentSeed, ...] | None = None + guidance_bonus: float = 0.0 + frontier_trace: FrontierTraceCollector | None = None return_partial: bool = False store_expanded: bool = False skip_congestion: bool = False @@ -33,6 +81,9 @@ class SearchRunConfig: *, bend_collision_type: BendCollisionModel | None = None, node_limit: int | None = None, + guidance_seed: tuple[PathSegmentSeed, ...] | None = None, + guidance_bonus: float = 0.0, + frontier_trace: FrontierTraceCollector | None = None, return_partial: bool = False, store_expanded: bool = False, skip_congestion: bool = False, @@ -49,6 +100,9 @@ class SearchRunConfig: bend_physical_geometry=bend_physical_geometry, bend_clip_margin=search.bend_clip_margin, node_limit=search.node_limit if node_limit is None else node_limit, + guidance_seed=guidance_seed, + guidance_bonus=float(guidance_bonus), + frontier_trace=frontier_trace, return_partial=return_partial, store_expanded=store_expanded, skip_congestion=skip_congestion, @@ -67,6 +121,7 @@ class AStarNode: "component_result", "base_move_cost", "cache_key", + "seed_index", "congestion_resolved", ) @@ -80,6 +135,7 @@ class AStarNode: *, base_move_cost: float = 0.0, cache_key: tuple | None = None, + seed_index: int | None = None, congestion_resolved: bool = True, ) -> None: self.port = port @@ -90,6 +146,7 @@ class AStarNode: self.component_result = component_result self.base_move_cost = base_move_cost self.cache_key = cache_key + self.seed_index = seed_index self.congestion_resolved = congestion_resolved def __lt__(self, other: AStarNode) -> bool: @@ -128,6 +185,14 @@ class AStarMetrics: "total_move_cache_abs_misses", "total_move_cache_rel_hits", "total_move_cache_rel_misses", + "total_guidance_match_moves", + "total_guidance_match_moves_straight", + "total_guidance_match_moves_bend90", + "total_guidance_match_moves_sbend", + "total_guidance_bonus_applied", + "total_guidance_bonus_applied_straight", + "total_guidance_bonus_applied_bend90", + "total_guidance_bonus_applied_sbend", "total_static_safe_cache_hits", "total_hard_collision_cache_hits", "total_congestion_cache_hits", @@ -189,6 +254,10 @@ class AStarMetrics: "total_refinement_candidates_built", "total_refinement_candidates_verified", "total_refinement_candidates_accepted", + "total_pair_local_search_pairs_considered", + "total_pair_local_search_attempts", + "total_pair_local_search_accepts", + "total_pair_local_search_nodes_expanded", "last_expanded_nodes", "nodes_expanded", "moves_generated", @@ -229,6 +298,14 @@ class AStarMetrics: self.total_move_cache_abs_misses = 0 self.total_move_cache_rel_hits = 0 self.total_move_cache_rel_misses = 0 + self.total_guidance_match_moves = 0 + self.total_guidance_match_moves_straight = 0 + self.total_guidance_match_moves_bend90 = 0 + self.total_guidance_match_moves_sbend = 0 + self.total_guidance_bonus_applied = 0.0 + self.total_guidance_bonus_applied_straight = 0.0 + self.total_guidance_bonus_applied_bend90 = 0.0 + self.total_guidance_bonus_applied_sbend = 0.0 self.total_static_safe_cache_hits = 0 self.total_hard_collision_cache_hits = 0 self.total_congestion_cache_hits = 0 @@ -290,6 +367,10 @@ class AStarMetrics: self.total_refinement_candidates_built = 0 self.total_refinement_candidates_verified = 0 self.total_refinement_candidates_accepted = 0 + self.total_pair_local_search_pairs_considered = 0 + self.total_pair_local_search_attempts = 0 + self.total_pair_local_search_accepts = 0 + self.total_pair_local_search_nodes_expanded = 0 self.last_expanded_nodes: list[tuple[int, int, int]] = [] self.nodes_expanded = 0 self.moves_generated = 0 @@ -329,6 +410,14 @@ class AStarMetrics: self.total_move_cache_abs_misses = 0 self.total_move_cache_rel_hits = 0 self.total_move_cache_rel_misses = 0 + self.total_guidance_match_moves = 0 + self.total_guidance_match_moves_straight = 0 + self.total_guidance_match_moves_bend90 = 0 + self.total_guidance_match_moves_sbend = 0 + self.total_guidance_bonus_applied = 0.0 + self.total_guidance_bonus_applied_straight = 0.0 + self.total_guidance_bonus_applied_bend90 = 0.0 + self.total_guidance_bonus_applied_sbend = 0.0 self.total_static_safe_cache_hits = 0 self.total_hard_collision_cache_hits = 0 self.total_congestion_cache_hits = 0 @@ -390,6 +479,10 @@ class AStarMetrics: self.total_refinement_candidates_built = 0 self.total_refinement_candidates_verified = 0 self.total_refinement_candidates_accepted = 0 + self.total_pair_local_search_pairs_considered = 0 + self.total_pair_local_search_attempts = 0 + self.total_pair_local_search_accepts = 0 + self.total_pair_local_search_nodes_expanded = 0 def reset_per_route(self) -> None: self.nodes_expanded = 0 @@ -432,6 +525,14 @@ class AStarMetrics: move_cache_abs_misses=self.total_move_cache_abs_misses, move_cache_rel_hits=self.total_move_cache_rel_hits, move_cache_rel_misses=self.total_move_cache_rel_misses, + guidance_match_moves=self.total_guidance_match_moves, + guidance_match_moves_straight=self.total_guidance_match_moves_straight, + guidance_match_moves_bend90=self.total_guidance_match_moves_bend90, + guidance_match_moves_sbend=self.total_guidance_match_moves_sbend, + guidance_bonus_applied=self.total_guidance_bonus_applied, + guidance_bonus_applied_straight=self.total_guidance_bonus_applied_straight, + guidance_bonus_applied_bend90=self.total_guidance_bonus_applied_bend90, + guidance_bonus_applied_sbend=self.total_guidance_bonus_applied_sbend, static_safe_cache_hits=self.total_static_safe_cache_hits, hard_collision_cache_hits=self.total_hard_collision_cache_hits, congestion_cache_hits=self.total_congestion_cache_hits, @@ -493,6 +594,10 @@ class AStarMetrics: refinement_candidates_built=self.total_refinement_candidates_built, refinement_candidates_verified=self.total_refinement_candidates_verified, refinement_candidates_accepted=self.total_refinement_candidates_accepted, + pair_local_search_pairs_considered=self.total_pair_local_search_pairs_considered, + pair_local_search_attempts=self.total_pair_local_search_attempts, + pair_local_search_accepts=self.total_pair_local_search_accepts, + pair_local_search_nodes_expanded=self.total_pair_local_search_nodes_expanded, ) diff --git a/inire/router/_router.py b/inire/router/_router.py index a641c53..367e721 100644 --- a/inire/router/_router.py +++ b/inire/router/_router.py @@ -5,11 +5,23 @@ import time from dataclasses import dataclass from typing import TYPE_CHECKING -from inire.model import NetOrder, NetSpec, resolve_bend_geometry -from inire.results import RoutingOutcome, RoutingReport, RoutingResult -from inire.router._astar_types import AStarContext, AStarMetrics, SearchRunConfig +from inire.geometry.collision import RoutingWorld +from inire.model import NetOrder, NetSpec, RoutingProblem, resolve_bend_geometry +from inire.results import ( + ComponentConflictTrace, + ConflictTraceEntry, + FrontierPruneSample, + NetConflictTrace, + NetFrontierTrace, + RoutingOutcome, + RoutingReport, + RoutingResult, +) +from inire.router._astar_types import AStarContext, AStarMetrics, FrontierTraceCollector, SearchRunConfig from inire.router._search import route_astar from inire.router._seed_materialization import materialize_path_seed +from inire.router.cost import CostEvaluator +from inire.router.danger_map import DangerMap from inire.router.refiner import PathRefiner if TYPE_CHECKING: @@ -17,6 +29,7 @@ if TYPE_CHECKING: from shapely.geometry import Polygon + from inire.geometry.collision import PathVerificationDetail from inire.geometry.components import ComponentResult @@ -46,12 +59,20 @@ class _IterationReview: completed_net_ids: set[str] total_dynamic_collisions: int + +@dataclass(frozen=True, slots=True) +class _PairLocalTarget: + net_ids: tuple[str, str] + + class PathFinder: __slots__ = ( "context", "metrics", "refiner", "accumulated_expanded_nodes", + "conflict_trace", + "frontier_trace", ) def __init__( @@ -67,14 +88,23 @@ class PathFinder: self.context.cost_evaluator.danger_map.metrics = self.metrics self.refiner = PathRefiner(self.context) self.accumulated_expanded_nodes: list[tuple[int, int, int]] = [] + self.conflict_trace: list[ConflictTraceEntry] = [] + self.frontier_trace: list[NetFrontierTrace] = [] def _install_path(self, net_id: str, path: Sequence[ComponentResult]) -> None: all_geoms: list[Polygon] = [] all_dilated: list[Polygon] = [] - for result in path: + component_indexes: list[int] = [] + for component_index, result in enumerate(path): all_geoms.extend(result.collision_geometry) all_dilated.extend(result.dilated_collision_geometry) - self.context.cost_evaluator.collision_engine.add_path(net_id, all_geoms, dilated_geometry=all_dilated) + component_indexes.extend([component_index] * len(result.collision_geometry)) + self.context.cost_evaluator.collision_engine.add_path( + net_id, + all_geoms, + dilated_geometry=all_dilated, + component_indexes=component_indexes, + ) def _routing_order( self, @@ -227,6 +257,493 @@ class PathFinder: state.results = dict(state.best_results) self._replace_installed_paths(state, state.results) + def _capture_conflict_trace_entry( + self, + state: _RoutingState, + *, + stage: str, + iteration: int | None, + results: dict[str, RoutingResult], + details_by_net: dict[str, PathVerificationDetail], + review: _IterationReview, + ) -> None: + if not self.context.options.diagnostics.capture_conflict_trace: + return + + nets = [] + for net_id in state.ordered_net_ids: + result = results.get(net_id) + if result is None: + result = RoutingResult(net_id=net_id, path=(), reached_target=False) + detail = details_by_net.get(net_id) + component_conflicts = () + conflicting_net_ids = () + if detail is not None: + conflicting_net_ids = detail.conflicting_net_ids + component_conflicts = tuple( + ComponentConflictTrace( + other_net_id=other_net_id, + self_component_index=self_component_index, + other_component_index=other_component_index, + ) + for self_component_index, other_net_id, other_component_index in detail.component_conflicts + ) + nets.append( + NetConflictTrace( + net_id=net_id, + outcome=result.outcome, + reached_target=result.reached_target, + report=result.report, + conflicting_net_ids=tuple(conflicting_net_ids), + component_conflicts=component_conflicts, + ) + ) + + self.conflict_trace.append( + ConflictTraceEntry( + stage=stage, # type: ignore[arg-type] + iteration=iteration, + completed_net_ids=tuple(sorted(review.completed_net_ids)), + conflict_edges=tuple(sorted(review.conflict_edges)), + nets=tuple(nets), + ) + ) + + def _build_frontier_hotspot_bounds( + self, + state: _RoutingState, + net_id: str, + details_by_net: dict[str, PathVerificationDetail], + ) -> tuple[tuple[float, float, float, float], ...]: + result = state.results.get(net_id) + detail = details_by_net.get(net_id) + if result is None or detail is None or not result.path: + return () + + hotspot_bounds: list[tuple[float, float, float, float]] = [] + seen: set[tuple[float, float, float, float]] = set() + margin = max(5.0, self.context.cost_evaluator.collision_engine.clearance * 2.0) + + for self_component_index, other_net_id, other_component_index in detail.component_conflicts: + other_result = state.results.get(other_net_id) + if other_result is None or not other_result.path: + continue + if self_component_index >= len(result.path) or other_component_index >= len(other_result.path): + continue + left_component = result.path[self_component_index] + right_component = other_result.path[other_component_index] + overlap_found = False + for left_poly in left_component.dilated_physical_geometry: + for right_poly in right_component.dilated_physical_geometry: + if not left_poly.intersects(right_poly) or left_poly.touches(right_poly): + continue + overlap = left_poly.intersection(right_poly) + if overlap.is_empty: + continue + buffered = overlap.buffer(margin, join_style="mitre").bounds + if buffered not in seen: + seen.add(buffered) + hotspot_bounds.append(buffered) + overlap_found = True + if overlap_found: + continue + + left_bounds = left_component.total_dilated_bounds + right_bounds = right_component.total_dilated_bounds + if ( + left_bounds[0] < right_bounds[2] + and left_bounds[2] > right_bounds[0] + and left_bounds[1] < right_bounds[3] + and left_bounds[3] > right_bounds[1] + ): + buffered = ( + max(left_bounds[0], right_bounds[0]) - margin, + max(left_bounds[1], right_bounds[1]) - margin, + min(left_bounds[2], right_bounds[2]) + margin, + min(left_bounds[3], right_bounds[3]) + margin, + ) + if buffered not in seen: + seen.add(buffered) + hotspot_bounds.append(buffered) + + return tuple(hotspot_bounds) + + def _analyze_results( + self, + ordered_net_ids: Sequence[str], + results: dict[str, RoutingResult], + *, + capture_component_conflicts: bool, + count_iteration_metrics: bool, + ) -> tuple[dict[str, RoutingResult], dict[str, PathVerificationDetail], _IterationReview]: + if count_iteration_metrics: + self.metrics.total_iteration_reverify_calls += 1 + conflict_edges: set[tuple[str, str]] = set() + conflicting_nets: set[str] = set() + completed_net_ids: set[str] = set() + total_dynamic_collisions = 0 + analyzed_results = dict(results) + details_by_net: dict[str, PathVerificationDetail] = {} + + for net_id in ordered_net_ids: + result = results.get(net_id) + if not result or not result.path or not result.reached_target: + continue + + if count_iteration_metrics: + self.metrics.total_iteration_reverified_nets += 1 + detail = self.context.cost_evaluator.collision_engine.verify_path_details( + net_id, + result.path, + capture_component_conflicts=capture_component_conflicts, + ) + details_by_net[net_id] = detail + analyzed_results[net_id] = RoutingResult( + net_id=net_id, + path=result.path, + reached_target=result.reached_target, + report=detail.report, + ) + total_dynamic_collisions += detail.report.dynamic_collision_count + if analyzed_results[net_id].outcome == "completed": + completed_net_ids.add(net_id) + if not detail.conflicting_net_ids: + continue + conflicting_nets.add(net_id) + for other_net_id in detail.conflicting_net_ids: + conflicting_nets.add(other_net_id) + if other_net_id == net_id: + continue + conflict_edges.add(tuple(sorted((net_id, other_net_id)))) + + if count_iteration_metrics: + self.metrics.total_iteration_conflicting_nets += len(conflicting_nets) + self.metrics.total_iteration_conflict_edges += len(conflict_edges) + return ( + analyzed_results, + details_by_net, + _IterationReview( + conflicting_nets=conflicting_nets, + conflict_edges=conflict_edges, + completed_net_ids=completed_net_ids, + total_dynamic_collisions=total_dynamic_collisions, + ), + ) + + def _capture_frontier_trace( + self, + state: _RoutingState, + final_results: dict[str, RoutingResult], + ) -> None: + if not self.context.options.diagnostics.capture_frontier_trace: + return + + self.frontier_trace = [] + state.results = dict(final_results) + state.results, details_by_net, _ = self._analyze_results( + state.ordered_net_ids, + state.results, + capture_component_conflicts=True, + count_iteration_metrics=False, + ) + + original_metrics = self.metrics + original_context_metrics = self.context.metrics + original_engine_metrics = self.context.cost_evaluator.collision_engine.metrics + original_danger_metrics = None + if self.context.cost_evaluator.danger_map is not None: + original_danger_metrics = self.context.cost_evaluator.danger_map.metrics + + try: + for net_id in state.ordered_net_ids: + result = state.results.get(net_id) + detail = details_by_net.get(net_id) + if result is None or detail is None or not result.reached_target: + continue + if detail.report.dynamic_collision_count == 0 or not detail.component_conflicts: + continue + + hotspot_bounds = self._build_frontier_hotspot_bounds(state, net_id, details_by_net) + if not hotspot_bounds: + continue + + scratch_metrics = AStarMetrics() + self.context.metrics = scratch_metrics + self.context.cost_evaluator.collision_engine.metrics = scratch_metrics + if self.context.cost_evaluator.danger_map is not None: + self.context.cost_evaluator.danger_map.metrics = scratch_metrics + + guidance_seed = result.as_seed().segments if result.path else None + guidance_bonus = 0.0 + if guidance_seed: + guidance_bonus = max(10.0, self.context.options.objective.bend_penalty * 0.25) + collector = FrontierTraceCollector(hotspot_bounds=hotspot_bounds) + run_config = SearchRunConfig.from_options( + self.context.options, + return_partial=True, + store_expanded=False, + guidance_seed=guidance_seed, + guidance_bonus=guidance_bonus, + frontier_trace=collector, + self_collision_check=(net_id in state.needs_self_collision_check), + node_limit=self.context.options.search.node_limit, + ) + + self.context.cost_evaluator.collision_engine.remove_path(net_id) + try: + route_astar( + state.net_specs[net_id].start, + state.net_specs[net_id].target, + state.net_specs[net_id].width, + context=self.context, + metrics=scratch_metrics, + net_id=net_id, + config=run_config, + ) + finally: + if result.path: + self._install_path(net_id, result.path) + + self.frontier_trace.append( + NetFrontierTrace( + net_id=net_id, + hotspot_bounds=hotspot_bounds, + pruned_closed_set=collector.pruned_closed_set, + pruned_hard_collision=collector.pruned_hard_collision, + pruned_self_collision=collector.pruned_self_collision, + pruned_cost=collector.pruned_cost, + samples=tuple( + FrontierPruneSample( + reason=reason, # type: ignore[arg-type] + move_type=move_type, + hotspot_index=hotspot_index, + parent_state=parent_state, + end_state=end_state, + ) + for reason, move_type, hotspot_index, parent_state, end_state in collector.samples + ), + ) + ) + finally: + self.metrics = original_metrics + self.context.metrics = original_context_metrics + self.context.cost_evaluator.collision_engine.metrics = original_engine_metrics + if self.context.cost_evaluator.danger_map is not None: + self.context.cost_evaluator.danger_map.metrics = original_danger_metrics + + def _whole_set_is_better( + self, + candidate_results: dict[str, RoutingResult], + candidate_review: _IterationReview, + incumbent_results: dict[str, RoutingResult], + incumbent_review: _IterationReview, + ) -> bool: + candidate_completed = len(candidate_review.completed_net_ids) + incumbent_completed = len(incumbent_review.completed_net_ids) + if candidate_completed != incumbent_completed: + return candidate_completed > incumbent_completed + + candidate_edges = len(candidate_review.conflict_edges) + incumbent_edges = len(incumbent_review.conflict_edges) + if candidate_edges != incumbent_edges: + return candidate_edges < incumbent_edges + + if candidate_review.total_dynamic_collisions != incumbent_review.total_dynamic_collisions: + return candidate_review.total_dynamic_collisions < incumbent_review.total_dynamic_collisions + + candidate_length = sum( + result.report.total_length + for result in candidate_results.values() + if result.reached_target + ) + incumbent_length = sum( + result.report.total_length + for result in incumbent_results.values() + if result.reached_target + ) + if abs(candidate_length - incumbent_length) > 1e-6: + return candidate_length < incumbent_length + return False + + def _collect_pair_local_targets( + self, + state: _RoutingState, + results: dict[str, RoutingResult], + review: _IterationReview, + ) -> list[_PairLocalTarget]: + if not review.conflict_edges: + return [] + order_index = {net_id: idx for idx, net_id in enumerate(state.ordered_net_ids)} + seen_net_ids: set[str] = set() + targets: list[_PairLocalTarget] = [] + for left_net_id, right_net_id in sorted(review.conflict_edges): + if left_net_id in seen_net_ids or right_net_id in seen_net_ids: + return [] + left_result = results.get(left_net_id) + right_result = results.get(right_net_id) + if ( + left_result is None + or right_result is None + or not left_result.reached_target + or not right_result.reached_target + ): + continue + seen_net_ids.update((left_net_id, right_net_id)) + targets.append(_PairLocalTarget(net_ids=(left_net_id, right_net_id))) + targets.sort(key=lambda target: min(order_index[target.net_ids[0]], order_index[target.net_ids[1]])) + return targets + + def _build_pair_local_context( + self, + state: _RoutingState, + incumbent_results: dict[str, RoutingResult], + pair_net_ids: tuple[str, str], + ) -> AStarContext: + problem = self.context.problem + objective = self.context.options.objective + static_obstacles = tuple(self.context.cost_evaluator.collision_engine._static_obstacles.geometries.values()) + engine = RoutingWorld( + clearance=self.context.cost_evaluator.collision_engine.clearance, + safety_zone_radius=self.context.cost_evaluator.collision_engine.safety_zone_radius, + ) + for obstacle in static_obstacles: + engine.add_static_obstacle(obstacle) + for net_id in state.ordered_net_ids: + if net_id in pair_net_ids: + continue + result = incumbent_results.get(net_id) + if result is None or not result.path: + continue + for component in result.path: + for polygon in component.physical_geometry: + engine.add_static_obstacle(polygon) + + danger_map = DangerMap(bounds=problem.bounds) + danger_map.precompute(list(static_obstacles)) + evaluator = CostEvaluator( + engine, + danger_map, + unit_length_cost=objective.unit_length_cost, + greedy_h_weight=self.context.cost_evaluator.greedy_h_weight, + bend_penalty=objective.bend_penalty, + sbend_penalty=objective.sbend_penalty, + danger_weight=objective.danger_weight, + ) + return AStarContext( + evaluator, + RoutingProblem( + bounds=problem.bounds, + nets=tuple(state.net_specs[net_id] for net_id in state.ordered_net_ids), + static_obstacles=static_obstacles, + clearance=problem.clearance, + safety_zone_radius=problem.safety_zone_radius, + ), + self.context.options, + metrics=AStarMetrics(), + ) + + def _run_pair_local_attempt( + self, + state: _RoutingState, + incumbent_results: dict[str, RoutingResult], + pair_order: tuple[str, str], + ) -> tuple[dict[str, RoutingResult], int] | None: + local_context = self._build_pair_local_context(state, incumbent_results, pair_order) + local_results = dict(incumbent_results) + + for net_id in pair_order: + net = state.net_specs[net_id] + guidance_result = incumbent_results.get(net_id) + guidance_seed = None + guidance_bonus = 0.0 + if guidance_result and guidance_result.reached_target and guidance_result.path: + guidance_seed = guidance_result.as_seed().segments + guidance_bonus = max(10.0, self.context.options.objective.bend_penalty * 0.25) + + run_config = SearchRunConfig.from_options( + self.context.options, + return_partial=False, + skip_congestion=True, + self_collision_check=(net_id in state.needs_self_collision_check), + guidance_seed=guidance_seed, + guidance_bonus=guidance_bonus, + node_limit=self.context.options.search.node_limit, + ) + path = route_astar( + net.start, + net.target, + net.width, + context=local_context, + metrics=local_context.metrics, + net_id=net_id, + config=run_config, + ) + if not path or path[-1].end_port != net.target: + return None + + report = local_context.cost_evaluator.collision_engine.verify_path_report(net_id, path) + if not report.is_valid: + return None + local_results[net_id] = RoutingResult( + net_id=net_id, + path=tuple(path), + reached_target=True, + report=report, + ) + for component in path: + for polygon in component.physical_geometry: + local_context.cost_evaluator.collision_engine.add_static_obstacle(polygon) + local_context.clear_static_caches() + + return local_results, local_context.metrics.total_nodes_expanded + + def _run_pair_local_search(self, state: _RoutingState) -> None: + state.results, _details_by_net, review = self._analyze_results( + state.ordered_net_ids, + state.results, + capture_component_conflicts=True, + count_iteration_metrics=False, + ) + targets = self._collect_pair_local_targets(state, state.results, review) + if not targets: + return + + for target in targets[:2]: + self.metrics.total_pair_local_search_pairs_considered += 1 + incumbent_results = dict(state.results) + incumbent_review = review + accepted = False + for pair_order in (target.net_ids, target.net_ids[::-1]): + self.metrics.total_pair_local_search_attempts += 1 + candidate = self._run_pair_local_attempt(state, incumbent_results, pair_order) + if candidate is None: + continue + candidate_results, nodes_expanded = candidate + self.metrics.total_pair_local_search_nodes_expanded += nodes_expanded + self._replace_installed_paths(state, candidate_results) + candidate_results, _candidate_details_by_net, candidate_review = self._analyze_results( + state.ordered_net_ids, + candidate_results, + capture_component_conflicts=True, + count_iteration_metrics=False, + ) + if self._whole_set_is_better( + candidate_results, + candidate_review, + incumbent_results, + incumbent_review, + ): + self.metrics.total_pair_local_search_accepts += 1 + state.results = candidate_results + review = candidate_review + accepted = True + break + self._replace_installed_paths(state, incumbent_results) + + if not accepted: + state.results = incumbent_results + self._replace_installed_paths(state, incumbent_results) + def _route_net_once( self, state: _RoutingState, @@ -246,16 +763,25 @@ class PathFinder: else: coll_model, _ = resolve_bend_geometry(search) skip_congestion = False + guidance_seed = None + guidance_bonus = 0.0 if congestion.use_tiered_strategy and iteration == 0: skip_congestion = True if coll_model == "arc": coll_model = "clipped_bbox" + elif iteration > 0: + guidance_result = state.results.get(net_id) + if guidance_result and guidance_result.reached_target and guidance_result.path: + guidance_seed = guidance_result.as_seed().segments + guidance_bonus = max(10.0, self.context.options.objective.bend_penalty * 0.25) run_config = SearchRunConfig.from_options( self.context.options, bend_collision_type=coll_model, return_partial=True, store_expanded=diagnostics.capture_expanded, + guidance_seed=guidance_seed, + guidance_bonus=guidance_bonus, skip_congestion=skip_congestion, self_collision_check=(net_id in state.needs_self_collision_check), node_limit=search.node_limit, @@ -303,7 +829,6 @@ class PathFinder: congestion = self.context.options.congestion self.metrics.total_route_iterations += 1 self.metrics.reset_per_route() - if congestion.shuffle_nets and (iteration > 0 or state.initial_paths is None): iteration_seed = (congestion.seed + iteration) if congestion.seed is not None else None random.Random(iteration_seed).shuffle(state.ordered_net_ids) @@ -326,45 +851,21 @@ class PathFinder: return review def _reverify_iteration_results(self, state: _RoutingState) -> _IterationReview: - self.metrics.total_iteration_reverify_calls += 1 - conflict_edges: set[tuple[str, str]] = set() - conflicting_nets: set[str] = set() - completed_net_ids: set[str] = set() - total_dynamic_collisions = 0 - - for net_id in state.ordered_net_ids: - result = state.results.get(net_id) - if not result or not result.path or not result.reached_target: - continue - - self.metrics.total_iteration_reverified_nets += 1 - detail = self.context.cost_evaluator.collision_engine.verify_path_details(net_id, result.path) - state.results[net_id] = RoutingResult( - net_id=net_id, - path=result.path, - reached_target=result.reached_target, - report=detail.report, - ) - total_dynamic_collisions += detail.report.dynamic_collision_count - if state.results[net_id].outcome == "completed": - completed_net_ids.add(net_id) - if not detail.conflicting_net_ids: - continue - conflicting_nets.add(net_id) - for other_net_id in detail.conflicting_net_ids: - conflicting_nets.add(other_net_id) - if other_net_id == net_id: - continue - conflict_edges.add(tuple(sorted((net_id, other_net_id)))) - - self.metrics.total_iteration_conflicting_nets += len(conflicting_nets) - self.metrics.total_iteration_conflict_edges += len(conflict_edges) - return _IterationReview( - conflicting_nets=conflicting_nets, - conflict_edges=conflict_edges, - completed_net_ids=completed_net_ids, - total_dynamic_collisions=total_dynamic_collisions, + state.results, details_by_net, review = self._analyze_results( + state.ordered_net_ids, + state.results, + capture_component_conflicts=self.context.options.diagnostics.capture_conflict_trace, + count_iteration_metrics=True, ) + self._capture_conflict_trace_entry( + state, + stage="iteration", + iteration=self.metrics.total_route_iterations - 1, + results=state.results, + details_by_net=details_by_net, + review=review, + ) + return review def _run_iterations( self, @@ -428,17 +929,38 @@ class PathFinder: def _verify_results(self, state: _RoutingState) -> dict[str, RoutingResult]: final_results: dict[str, RoutingResult] = {} + details_by_net: dict[str, PathVerificationDetail] = {} for net in self.context.problem.nets: result = state.results.get(net.net_id) if not result or not result.path: final_results[net.net_id] = RoutingResult(net_id=net.net_id, path=(), reached_target=False) continue - report = self.context.cost_evaluator.collision_engine.verify_path_report(net.net_id, result.path) + detail = self.context.cost_evaluator.collision_engine.verify_path_details( + net.net_id, + result.path, + capture_component_conflicts=self.context.options.diagnostics.capture_conflict_trace, + ) + details_by_net[net.net_id] = detail final_results[net.net_id] = RoutingResult( net_id=net.net_id, path=result.path, reached_target=result.reached_target, - report=report, + report=detail.report, + ) + if self.context.options.diagnostics.capture_conflict_trace: + _, _, review = self._analyze_results( + state.ordered_net_ids, + final_results, + capture_component_conflicts=True, + count_iteration_metrics=False, + ) + self._capture_conflict_trace_entry( + state, + stage="final", + iteration=None, + results=final_results, + details_by_net=details_by_net, + review=review, ) return final_results @@ -449,6 +971,8 @@ class PathFinder: ) -> dict[str, RoutingResult]: self.context.congestion_penalty = self.context.options.congestion.base_penalty self.accumulated_expanded_nodes = [] + self.conflict_trace = [] + self.frontier_trace = [] self.metrics.reset_totals() self.metrics.reset_per_route() @@ -456,9 +980,29 @@ class PathFinder: timed_out = self._run_iterations(state, iteration_callback) self.accumulated_expanded_nodes = list(state.accumulated_expanded_nodes) self._restore_best_iteration(state) + if self.context.options.diagnostics.capture_conflict_trace: + state.results, details_by_net, review = self._analyze_results( + state.ordered_net_ids, + state.results, + capture_component_conflicts=True, + count_iteration_metrics=False, + ) + self._capture_conflict_trace_entry( + state, + stage="restored_best", + iteration=None, + results=state.results, + details_by_net=details_by_net, + review=review, + ) if timed_out: - return self._verify_results(state) + final_results = self._verify_results(state) + self._capture_frontier_trace(state, final_results) + return final_results + self._run_pair_local_search(state) self._refine_results(state) - return self._verify_results(state) + final_results = self._verify_results(state) + self._capture_frontier_trace(state, final_results) + return final_results diff --git a/inire/router/_search.py b/inire/router/_search.py index e6de987..92fc540 100644 --- a/inire/router/_search.py +++ b/inire/router/_search.py @@ -51,6 +51,7 @@ def route_astar( start, 0.0, context.cost_evaluator.h_manhattan(start, target, min_bend_radius=context.min_bend_radius), + seed_index=0 if config.guidance_seed else None, ) heapq.heappush(open_set, start_node) best_node = start_node diff --git a/inire/tests/example_scenarios.py b/inire/tests/example_scenarios.py index 9ba7a3a..3023688 100644 --- a/inire/tests/example_scenarios.py +++ b/inire/tests/example_scenarios.py @@ -15,6 +15,7 @@ from inire import ( RoutingOptions, RoutingProblem, RoutingResult, + RoutingRunResult, SearchOptions, ) from inire.geometry.collision import RoutingWorld @@ -34,6 +35,7 @@ _OBJECTIVE_FIELDS = set(ObjectiveWeights.__dataclass_fields__) ScenarioOutcome = tuple[float, int, int, int] ScenarioRun = Callable[[], ScenarioOutcome] ScenarioSnapshotRun = Callable[[], "ScenarioSnapshot"] +TraceScenarioRun = Callable[[], RoutingRunResult] @dataclass(frozen=True, slots=True) @@ -79,6 +81,19 @@ def _make_snapshot( ) +def _make_run_result( + results: dict[str, RoutingResult], + pathfinder: PathFinder, +) -> RoutingRunResult: + return RoutingRunResult( + results_by_net=results, + metrics=pathfinder.metrics.snapshot(), + expanded_nodes=tuple(pathfinder.accumulated_expanded_nodes), + conflict_trace=tuple(pathfinder.conflict_trace), + frontier_trace=tuple(pathfinder.frontier_trace), + ) + + def _sum_metrics(metrics_list: tuple[RouteMetrics, ...]) -> RouteMetrics: metric_names = RouteMetrics.__dataclass_fields__ return RouteMetrics( @@ -318,6 +333,24 @@ def run_example_05() -> ScenarioOutcome: return snapshot_example_05().as_outcome() +def trace_example_05() -> RoutingRunResult: + netlist = { + "u_turn": (Port(50, 50, 0), Port(50, 70, 180)), + "loop": (Port(100, 100, 90), Port(100, 80, 270)), + "zig_zag": (Port(20, 150, 0), Port(180, 150, 0)), + } + widths = dict.fromkeys(netlist, 2.0) + _, _, _, pathfinder = _build_routing_stack( + bounds=(0, 0, 200, 200), + netlist=netlist, + widths=widths, + evaluator_kwargs={"bend_penalty": 50.0}, + request_kwargs={"bend_radii": [20.0], "capture_conflict_trace": True, "capture_frontier_trace": True}, + ) + results = pathfinder.route_all() + return _make_run_result(results, pathfinder) + + def snapshot_example_06() -> ScenarioSnapshot: bounds = (-20, -20, 170, 170) obstacles = [ @@ -391,6 +424,14 @@ def snapshot_example_07_no_warm_start() -> ScenarioSnapshot: ) +def trace_example_07() -> RoutingRunResult: + return _trace_example_07_variant(warm_start_enabled=True) + + +def trace_example_07_no_warm_start() -> RoutingRunResult: + return _trace_example_07_variant(warm_start_enabled=False) + + def _snapshot_example_07_variant( name: str, *, @@ -454,6 +495,68 @@ def _snapshot_example_07_variant( return _make_snapshot(name, results, t1 - t0, pathfinder.metrics.snapshot()) +def _trace_example_07_variant( + *, + warm_start_enabled: bool, +) -> RoutingRunResult: + bounds = (0, 0, 1000, 1000) + obstacles = [ + box(450, 0, 550, 400), + box(450, 600, 550, 1000), + ] + num_nets = 10 + start_x = 50 + start_y_base = 500 - (num_nets * 10.0) / 2.0 + end_x = 950 + end_y_base = 100 + end_y_pitch = 800.0 / (num_nets - 1) + + netlist = {} + for index in range(num_nets): + sy = int(round(start_y_base + index * 10.0)) + ey = int(round(end_y_base + index * end_y_pitch)) + netlist[f"net_{index:02d}"] = (Port(start_x, sy, 0), Port(end_x, ey, 0)) + widths = dict.fromkeys(netlist, 2.0) + _, evaluator, metrics, pathfinder = _build_routing_stack( + bounds=bounds, + netlist=netlist, + widths=widths, + clearance=6.0, + obstacles=obstacles, + evaluator_kwargs={ + "greedy_h_weight": 1.5, + "unit_length_cost": 0.1, + "bend_penalty": 100.0, + "sbend_penalty": 400.0, + }, + request_kwargs={ + "node_limit": 2000000, + "bend_radii": [50.0], + "sbend_radii": [50.0], + "bend_clip_margin": 10.0, + "max_iterations": 15, + "base_penalty": 100.0, + "multiplier": 1.4, + "net_order": "shortest", + "capture_expanded": True, + "capture_conflict_trace": True, + "capture_frontier_trace": True, + "shuffle_nets": True, + "seed": 42, + "warm_start_enabled": warm_start_enabled, + }, + ) + + def iteration_callback(idx: int, current_results: dict[str, RoutingResult]) -> None: + _ = current_results + new_greedy = max(1.1, 1.5 - ((idx + 1) / 10.0) * 0.4) + evaluator.greedy_h_weight = new_greedy + metrics.reset_per_route() + + results = pathfinder.route_all(iteration_callback=iteration_callback) + return _make_run_result(results, pathfinder) + + def run_example_07() -> ScenarioOutcome: return snapshot_example_07().as_outcome() @@ -557,6 +660,15 @@ PERFORMANCE_SCENARIO_SNAPSHOTS: tuple[tuple[str, ScenarioSnapshotRun], ...] = ( ("example_07_large_scale_routing_no_warm_start", snapshot_example_07_no_warm_start), ) +TRACE_SCENARIO_RUNS: tuple[tuple[str, TraceScenarioRun], ...] = ( + ("example_05_orientation_stress", trace_example_05), + ("example_07_large_scale_routing", trace_example_07), +) + +TRACE_PERFORMANCE_SCENARIO_RUNS: tuple[tuple[str, TraceScenarioRun], ...] = ( + ("example_07_large_scale_routing_no_warm_start", trace_example_07_no_warm_start), +) + def capture_all_scenario_snapshots() -> tuple[ScenarioSnapshot, ...]: return tuple(run() for _, run in SCENARIO_SNAPSHOTS) diff --git a/inire/tests/test_api.py b/inire/tests/test_api.py index 024a569..0e9fdb3 100644 --- a/inire/tests/test_api.py +++ b/inire/tests/test_api.py @@ -22,8 +22,6 @@ from inire.router._astar_types import AStarContext from inire.router._router import PathFinder, _IterationReview from inire.router.cost import CostEvaluator from inire.router.danger_map import DangerMap - - def test_root_module_exports_only_stable_surface() -> None: import inire @@ -54,6 +52,8 @@ def test_route_problem_smoke() -> None: assert set(run.results_by_net) == {"net1"} assert run.results_by_net["net1"].is_valid + assert run.conflict_trace == () + assert run.frontier_trace == () def test_route_problem_supports_configs_and_debug_data() -> None: @@ -94,6 +94,14 @@ def test_route_problem_supports_configs_and_debug_data() -> None: assert run.metrics.dynamic_tree_rebuilds >= 0 assert run.metrics.visibility_corner_index_builds >= 0 assert run.metrics.visibility_builds >= 0 + assert run.metrics.guidance_match_moves >= 0 + assert run.metrics.guidance_match_moves_straight >= 0 + assert run.metrics.guidance_match_moves_bend90 >= 0 + assert run.metrics.guidance_match_moves_sbend >= 0 + assert run.metrics.guidance_bonus_applied >= 0.0 + assert run.metrics.guidance_bonus_applied_straight >= 0.0 + assert run.metrics.guidance_bonus_applied_bend90 >= 0.0 + assert run.metrics.guidance_bonus_applied_sbend >= 0.0 assert run.metrics.congestion_grid_span_cache_hits >= 0 assert run.metrics.congestion_grid_span_cache_misses >= 0 assert run.metrics.congestion_presence_cache_hits >= 0 @@ -112,6 +120,10 @@ def test_route_problem_supports_configs_and_debug_data() -> None: assert run.metrics.congestion_candidate_ids >= 0 assert run.metrics.verify_dynamic_candidate_nets >= 0 assert run.metrics.verify_path_report_calls >= 0 + assert run.metrics.pair_local_search_pairs_considered >= 0 + assert run.metrics.pair_local_search_attempts >= 0 + assert run.metrics.pair_local_search_accepts >= 0 + assert run.metrics.pair_local_search_nodes_expanded >= 0 def test_iteration_callback_observes_reverified_conflicts() -> None: @@ -141,6 +153,120 @@ def test_iteration_callback_observes_reverified_conflicts() -> None: assert results["vertical"].outcome == "colliding" +def test_capture_conflict_trace_preserves_route_outputs() -> None: + problem = RoutingProblem( + bounds=(0, 0, 100, 100), + nets=( + NetSpec("horizontal", Port(10, 50, 0), Port(90, 50, 0), width=2.0), + NetSpec("vertical", Port(50, 10, 90), Port(50, 90, 90), width=2.0), + ), + ) + base_options = RoutingOptions( + congestion=CongestionOptions(max_iterations=1, warm_start_enabled=False), + refinement=RefinementOptions(enabled=False), + ) + + run_without_trace = route(problem, options=base_options) + run_with_trace = route( + problem, + options=RoutingOptions( + congestion=base_options.congestion, + refinement=base_options.refinement, + diagnostics=DiagnosticsOptions(capture_conflict_trace=True), + ), + ) + + assert {net_id: result.outcome for net_id, result in run_without_trace.results_by_net.items()} == { + net_id: result.outcome for net_id, result in run_with_trace.results_by_net.items() + } + assert [entry.stage for entry in run_with_trace.conflict_trace] == ["iteration", "restored_best", "final"] + + +def test_capture_conflict_trace_records_component_pairs() -> None: + problem = RoutingProblem( + bounds=(0, 0, 100, 100), + nets=( + NetSpec("horizontal", Port(10, 50, 0), Port(90, 50, 0), width=2.0), + NetSpec("vertical", Port(50, 10, 90), Port(50, 90, 90), width=2.0), + ), + ) + options = RoutingOptions( + congestion=CongestionOptions(max_iterations=1, warm_start_enabled=False), + refinement=RefinementOptions(enabled=False), + diagnostics=DiagnosticsOptions(capture_conflict_trace=True), + ) + + run = route(problem, options=options) + final_entry = run.conflict_trace[-1] + trace_by_net = {net.net_id: net for net in final_entry.nets} + + assert final_entry.stage == "final" + assert final_entry.conflict_edges == (("horizontal", "vertical"),) + assert trace_by_net["horizontal"].component_conflicts[0].other_net_id == "vertical" + assert trace_by_net["horizontal"].component_conflicts[0].self_component_index == 0 + assert trace_by_net["horizontal"].component_conflicts[0].other_component_index == 0 + assert trace_by_net["vertical"].component_conflicts[0].other_net_id == "horizontal" + assert trace_by_net["vertical"].component_conflicts[0].self_component_index == 0 + assert trace_by_net["vertical"].component_conflicts[0].other_component_index == 0 + + +def test_capture_frontier_trace_preserves_route_outputs() -> None: + problem = RoutingProblem( + bounds=(0, 0, 100, 100), + nets=( + NetSpec("horizontal", Port(10, 50, 0), Port(90, 50, 0), width=2.0), + NetSpec("vertical", Port(50, 10, 90), Port(50, 90, 90), width=2.0), + ), + ) + base_options = RoutingOptions( + congestion=CongestionOptions(max_iterations=1, warm_start_enabled=False), + refinement=RefinementOptions(enabled=False), + ) + + run_without_trace = route(problem, options=base_options) + run_with_trace = route( + problem, + options=RoutingOptions( + congestion=base_options.congestion, + refinement=base_options.refinement, + diagnostics=DiagnosticsOptions(capture_frontier_trace=True), + ), + ) + + assert {net_id: result.outcome for net_id, result in run_without_trace.results_by_net.items()} == { + net_id: result.outcome for net_id, result in run_with_trace.results_by_net.items() + } + assert {trace.net_id for trace in run_with_trace.frontier_trace} == {"horizontal", "vertical"} + + +def test_capture_frontier_trace_records_prune_reasons() -> None: + problem = RoutingProblem( + bounds=(0, 0, 100, 100), + nets=( + NetSpec("horizontal", Port(10, 50, 0), Port(90, 50, 0), width=2.0), + NetSpec("vertical", Port(50, 10, 90), Port(50, 90, 90), width=2.0), + ), + ) + run = route( + problem, + options=RoutingOptions( + congestion=CongestionOptions(max_iterations=1, warm_start_enabled=False), + refinement=RefinementOptions(enabled=False), + diagnostics=DiagnosticsOptions(capture_frontier_trace=True), + ), + ) + + trace_by_net = {entry.net_id: entry for entry in run.frontier_trace} + assert trace_by_net["horizontal"].hotspot_bounds + assert ( + trace_by_net["horizontal"].pruned_closed_set + + trace_by_net["horizontal"].pruned_hard_collision + + trace_by_net["horizontal"].pruned_self_collision + + trace_by_net["horizontal"].pruned_cost + ) > 0 + assert trace_by_net["horizontal"].samples + + def test_reverify_iterations_stop_early_on_stalled_conflict_graph() -> None: problem = RoutingProblem( bounds=(0, 0, 100, 100), @@ -254,6 +380,56 @@ def test_route_all_restores_best_iteration_snapshot_on_timeout(monkeypatch: pyte assert results["netA"].outcome == "completed" +def test_capture_conflict_trace_records_restored_best_stage(monkeypatch: pytest.MonkeyPatch) -> None: + problem = RoutingProblem( + bounds=(0, 0, 100, 100), + nets=(NetSpec("netA", Port(10, 50, 0), Port(90, 50, 0), width=2.0),), + ) + options = RoutingOptions( + congestion=CongestionOptions(max_iterations=2, warm_start_enabled=False), + refinement=RefinementOptions(enabled=False), + diagnostics=DiagnosticsOptions(capture_conflict_trace=True), + ) + evaluator = CostEvaluator(RoutingWorld(clearance=2.0), DangerMap(bounds=problem.bounds)) + pathfinder = PathFinder(AStarContext(evaluator, problem, options)) + best_result = RoutingResult( + net_id="netA", + path=(Straight.generate(Port(10, 50, 0), 80.0, 2.0, dilation=1.0),), + reached_target=True, + report=RoutingReport(), + ) + worse_result = RoutingResult(net_id="netA", path=(), reached_target=False) + + def fake_run_iteration(self, state, iteration, reroute_net_ids, iteration_callback): + _ = self + _ = reroute_net_ids + _ = iteration_callback + if iteration == 0: + state.results = {"netA": best_result} + return _IterationReview( + conflicting_nets=set(), + conflict_edges=set(), + completed_net_ids={"netA"}, + total_dynamic_collisions=0, + ) + state.results = {"netA": worse_result} + return _IterationReview( + conflicting_nets=set(), + conflict_edges=set(), + completed_net_ids=set(), + total_dynamic_collisions=0, + ) + + monkeypatch.setattr(PathFinder, "_run_iteration", fake_run_iteration) + + pathfinder.route_all() + + assert [entry.stage for entry in pathfinder.conflict_trace] == [ + "restored_best", + "final", + ] + restored_entry = pathfinder.conflict_trace[0] + assert restored_entry.nets[0].outcome == "completed" def test_route_problem_locked_routes_become_static_obstacles() -> None: locked = (Straight.generate(Port(10, 50, 0), 80.0, 2.0, dilation=1.0),) problem = RoutingProblem( diff --git a/inire/tests/test_astar.py b/inire/tests/test_astar.py index 54362b9..3a0382c 100644 --- a/inire/tests/test_astar.py +++ b/inire/tests/test_astar.py @@ -2,15 +2,21 @@ import math import pytest from shapely.geometry import Polygon -from inire import RoutingProblem, RoutingOptions, RoutingResult, SearchOptions +from inire import CongestionOptions, NetSpec, RoutingProblem, RoutingOptions, RoutingResult, SearchOptions from inire.geometry.components import Bend90, Straight from inire.geometry.collision import RoutingWorld from inire.geometry.primitives import Port from inire.router._astar_types import AStarContext, AStarNode, SearchRunConfig from inire.router._astar_admission import add_node +from inire.router._astar_moves import ( + _distance_to_bounds_in_heading, + _should_cap_straights_to_bounds, +) +from inire.router._router import PathFinder, _RoutingState from inire.router._search import route_astar from inire.router.cost import CostEvaluator from inire.router.danger_map import DangerMap +from inire.seeds import StraightSeed BOUNDS = (0, -50, 150, 150) @@ -214,6 +220,84 @@ def test_astar_context_keeps_evaluator_weights_separate(basic_evaluator: CostEva assert basic_evaluator.h_manhattan(Port(0, 0, 0), Port(10, 10, 0)) > 0.0 +def test_distance_to_bounds_in_heading_is_directional() -> None: + bounds = (0, 0, 100, 200) + + assert _distance_to_bounds_in_heading(Port(20, 30, 0), bounds) == pytest.approx(80.0) + assert _distance_to_bounds_in_heading(Port(20, 30, 90), bounds) == pytest.approx(170.0) + assert _distance_to_bounds_in_heading(Port(20, 30, 180), bounds) == pytest.approx(20.0) + assert _distance_to_bounds_in_heading(Port(20, 30, 270), bounds) == pytest.approx(30.0) + + +def test_should_cap_straights_to_bounds_only_for_large_no_warm_runs(basic_evaluator: CostEvaluator) -> None: + large_context = AStarContext( + basic_evaluator, + RoutingProblem( + bounds=(0, 0, 1000, 1000), + nets=tuple( + NetSpec(f"net{i}", Port(0, i * 10, 0), Port(10, i * 10, 0), width=2.0) + for i in range(8) + ), + ), + RoutingOptions( + congestion=CongestionOptions(warm_start_enabled=False), + ), + ) + small_context = _build_context(basic_evaluator, bounds=BOUNDS) + + assert _should_cap_straights_to_bounds(large_context) + assert not _should_cap_straights_to_bounds(small_context) + + +def test_pair_local_context_clones_live_static_obstacles() -> None: + obstacle = Polygon([(20, -20), (40, -20), (40, 20), (20, 20)]) + engine = RoutingWorld(clearance=2.0) + engine.add_static_obstacle(obstacle) + danger_map = DangerMap(bounds=BOUNDS) + danger_map.precompute([obstacle]) + evaluator = CostEvaluator(engine, danger_map, bend_penalty=50.0, sbend_penalty=150.0) + finder = PathFinder( + AStarContext( + evaluator, + RoutingProblem( + bounds=BOUNDS, + nets=( + NetSpec("pair_a", Port(0, 0, 0), Port(60, 0, 0), width=2.0), + NetSpec("pair_b", Port(0, 10, 0), Port(60, 10, 0), width=2.0), + ), + ), + RoutingOptions(), + ) + ) + state = _RoutingState( + net_specs={ + "pair_a": NetSpec("pair_a", Port(0, 0, 0), Port(60, 0, 0), width=2.0), + "pair_b": NetSpec("pair_b", Port(0, 10, 0), Port(60, 10, 0), width=2.0), + }, + ordered_net_ids=["pair_a", "pair_b"], + results={}, + needs_self_collision_check=set(), + start_time=0.0, + timeout_s=1.0, + initial_paths=None, + accumulated_expanded_nodes=[], + best_results={}, + best_completed_nets=-1, + best_conflict_edges=10**9, + best_dynamic_collisions=10**9, + last_conflict_signature=(), + last_conflict_edge_count=0, + repeated_conflict_count=0, + ) + + local_context = finder._build_pair_local_context(state, {}, ("pair_a", "pair_b")) + + assert finder.context.problem.static_obstacles == () + assert len(local_context.problem.static_obstacles) == 1 + assert len(local_context.cost_evaluator.collision_engine._static_obstacles.geometries) == 1 + assert next(iter(local_context.problem.static_obstacles)).equals(obstacle) + + def test_route_astar_bend_collision_override_does_not_persist(basic_evaluator: CostEvaluator) -> None: context = _build_context(basic_evaluator, bounds=BOUNDS, bend_radii=(10.0,), bend_collision_type="arc") @@ -439,3 +523,144 @@ def test_no_dynamic_paths_skips_congestion_check(basic_evaluator: CostEvaluator) assert open_set assert context.metrics.total_congestion_check_calls == 0 assert context.metrics.total_congestion_cache_misses == 0 + + +def test_guidance_seed_matching_move_reduces_cost_and_advances_seed_index( + basic_evaluator: CostEvaluator, +) -> None: + context = _build_context(basic_evaluator, bounds=BOUNDS) + root = AStarNode(Port(0, 0, 0), 0.0, 0.0, seed_index=0) + result = Straight.generate(Port(0, 0, 0), 10.0, width=2.0, dilation=1.0) + open_set: list[AStarNode] = [] + unguided_open_set: list[AStarNode] = [] + closed_set: dict[tuple[int, int, int], float] = {} + + add_node( + root, + result, + target=Port(20, 0, 0), + net_width=2.0, + net_id="netA", + open_set=open_set, + closed_set=closed_set, + context=context, + metrics=context.metrics, + congestion_cache={}, + congestion_presence_cache={}, + congestion_candidate_precheck_cache={}, + congestion_net_envelope_cache={}, + congestion_grid_net_cache={}, + congestion_grid_span_cache={}, + config=SearchRunConfig.from_options( + context.options, + guidance_seed=(StraightSeed(length=10.0),), + guidance_bonus=5.0, + ), + move_type="straight", + cache_key=("guided",), + ) + add_node( + AStarNode(Port(0, 0, 0), 0.0, 0.0), + result, + target=Port(20, 0, 0), + net_width=2.0, + net_id="netA", + open_set=unguided_open_set, + closed_set={}, + context=context, + metrics=context.metrics, + congestion_cache={}, + congestion_presence_cache={}, + congestion_candidate_precheck_cache={}, + congestion_net_envelope_cache={}, + congestion_grid_net_cache={}, + congestion_grid_span_cache={}, + config=SearchRunConfig.from_options(context.options), + move_type="straight", + cache_key=("unguided",), + ) + + assert open_set + assert unguided_open_set + guided_node = open_set[0] + unguided_node = unguided_open_set[0] + assert guided_node.seed_index == 1 + assert guided_node.g_cost < unguided_node.g_cost + assert context.metrics.total_guidance_match_moves == 1 + assert context.metrics.total_guidance_match_moves_straight == 1 + assert context.metrics.total_guidance_match_moves_bend90 == 0 + assert context.metrics.total_guidance_match_moves_sbend == 0 + assert context.metrics.total_guidance_bonus_applied == pytest.approx(5.0) + assert context.metrics.total_guidance_bonus_applied_straight == pytest.approx(5.0) + assert context.metrics.total_guidance_bonus_applied_bend90 == pytest.approx(0.0) + assert context.metrics.total_guidance_bonus_applied_sbend == pytest.approx(0.0) + + +def test_guidance_seed_bend90_keeps_full_bonus( + basic_evaluator: CostEvaluator, +) -> None: + context = _build_context(basic_evaluator, bounds=BOUNDS) + root = AStarNode(Port(0, 0, 0), 0.0, 0.0, seed_index=0) + result = Bend90.generate(Port(0, 0, 0), 10.0, width=2.0, direction="CCW", dilation=1.0) + open_set: list[AStarNode] = [] + unguided_open_set: list[AStarNode] = [] + + add_node( + root, + result, + target=Port(10, 10, 90), + net_width=2.0, + net_id="netA", + open_set=open_set, + closed_set={}, + context=context, + metrics=context.metrics, + congestion_cache={}, + congestion_presence_cache={}, + congestion_candidate_precheck_cache={}, + congestion_net_envelope_cache={}, + congestion_grid_net_cache={}, + congestion_grid_span_cache={}, + config=SearchRunConfig.from_options( + context.options, + guidance_seed=(result.move_spec,), + guidance_bonus=5.0, + ), + move_type="bend90", + cache_key=("guided-bend90",), + ) + add_node( + AStarNode(Port(0, 0, 0), 0.0, 0.0), + result, + target=Port(10, 10, 90), + net_width=2.0, + net_id="netA", + open_set=unguided_open_set, + closed_set={}, + context=context, + metrics=context.metrics, + congestion_cache={}, + congestion_presence_cache={}, + congestion_candidate_precheck_cache={}, + congestion_net_envelope_cache={}, + congestion_grid_net_cache={}, + congestion_grid_span_cache={}, + config=SearchRunConfig.from_options(context.options), + move_type="bend90", + cache_key=("unguided-bend90",), + ) + + assert open_set + assert unguided_open_set + guided_node = open_set[0] + unguided_node = unguided_open_set[0] + assert guided_node.seed_index == 1 + assert unguided_node.g_cost - guided_node.g_cost == pytest.approx(5.0) + assert context.metrics.total_guidance_match_moves == 1 + assert context.metrics.total_guidance_match_moves_straight == 0 + assert context.metrics.total_guidance_match_moves_bend90 == 1 + assert context.metrics.total_guidance_match_moves_sbend == 0 + assert context.metrics.total_guidance_bonus_applied == pytest.approx(5.0) + assert context.metrics.total_guidance_bonus_applied_straight == pytest.approx(0.0) + assert context.metrics.total_guidance_bonus_applied_bend90 == pytest.approx(5.0) + assert context.metrics.total_guidance_bonus_applied_sbend == pytest.approx(0.0) diff --git a/inire/tests/test_collision.py b/inire/tests/test_collision.py index 4fd4033..e016061 100644 --- a/inire/tests/test_collision.py +++ b/inire/tests/test_collision.py @@ -169,6 +169,67 @@ def test_verify_path_details_returns_conflicting_net_ids() -> None: assert detail.conflicting_net_ids == ("netB",) +def test_verify_path_details_reports_component_conflicts() -> None: + engine = RoutingWorld(clearance=2.0) + engine.metrics = AStarMetrics() + path_a = [Straight.generate(Port(0, 0, 0), 20.0, width=2.0, dilation=1.0)] + path_b = [ + Straight.generate(Port(100, 0, 0), 10.0, width=2.0, dilation=1.0), + Straight.generate(Port(0, 0, 0), 20.0, width=2.0, dilation=1.0), + ] + + engine.add_path( + "netB", + [poly for component in path_b for poly in component.collision_geometry], + dilated_geometry=[poly for component in path_b for poly in component.dilated_collision_geometry], + component_indexes=[0] * len(path_b[0].collision_geometry) + [1] * len(path_b[1].collision_geometry), + ) + + detail = engine.verify_path_details("netA", path_a, capture_component_conflicts=True) + + assert detail.conflicting_net_ids == ("netB",) + assert detail.component_conflicts == ((0, "netB", 1),) + + +def test_verify_path_details_deduplicates_component_conflicts() -> None: + engine = RoutingWorld(clearance=2.0) + engine.metrics = AStarMetrics() + query_component = ComponentResult( + start_port=Port(0, 0, 0), + collision_geometry=[box(0, 0, 10, 10), box(12, 0, 22, 10)], + end_port=Port(22, 0, 0), + length=22.0, + move_type="straight", + move_spec=StraightSeed(22.0), + physical_geometry=[box(0, 0, 10, 10), box(12, 0, 22, 10)], + dilated_collision_geometry=[box(0, 0, 10, 10), box(12, 0, 22, 10)], + dilated_physical_geometry=[box(0, 0, 10, 10), box(12, 0, 22, 10)], + ) + blocker_component = ComponentResult( + start_port=Port(0, 0, 0), + collision_geometry=[box(5, 0, 17, 10)], + end_port=Port(17, 0, 0), + length=12.0, + move_type="straight", + move_spec=StraightSeed(12.0), + physical_geometry=[box(5, 0, 17, 10)], + dilated_collision_geometry=[box(5, 0, 17, 10)], + dilated_physical_geometry=[box(5, 0, 17, 10)], + ) + + engine.add_path( + "netB", + blocker_component.collision_geometry, + dilated_geometry=blocker_component.dilated_collision_geometry, + component_indexes=[0], + ) + + detail = engine.verify_path_details("netA", [query_component], capture_component_conflicts=True) + + assert detail.conflicting_net_ids == ("netB",) + assert detail.component_conflicts == ((0, "netB", 0),) + + def test_remove_path_clears_dynamic_path() -> None: engine = RoutingWorld(clearance=2.0) path = [Straight.generate(Port(0, 0, 0), 20.0, width=2.0, dilation=1.0)] diff --git a/inire/tests/test_example_regressions.py b/inire/tests/test_example_regressions.py index e6dd3b6..43256da 100644 --- a/inire/tests/test_example_regressions.py +++ b/inire/tests/test_example_regressions.py @@ -14,7 +14,15 @@ from inire import ( ) from inire.router._stack import build_routing_stack from inire.seeds import Bend90Seed, PathSeed, StraightSeed -from inire.tests.example_scenarios import SCENARIOS, _build_evaluator, _build_pathfinder, _net_specs, AStarMetrics, snapshot_example_05 +from inire.tests.example_scenarios import ( + SCENARIOS, + _build_evaluator, + _build_pathfinder, + _net_specs, + AStarMetrics, + snapshot_example_05, + snapshot_example_07_no_warm_start, +) EXPECTED_OUTCOMES = { @@ -43,6 +51,14 @@ def test_example_05_avoids_dynamic_tree_rebuilds() -> None: assert snapshot.metrics.dynamic_tree_rebuilds == 0 +def test_example_07_no_warm_start_canary_improves_validity() -> None: + snapshot = snapshot_example_07_no_warm_start() + + assert snapshot.total_results == 10 + assert snapshot.reached_targets == 10 + assert snapshot.valid_results == 10 + + def test_example_06_clipped_bbox_margin_restores_legacy_seed() -> None: bounds = (-20, -20, 170, 170) obstacles = ( diff --git a/inire/tests/test_performance_reporting.py b/inire/tests/test_performance_reporting.py index 4a2d1be..062747d 100644 --- a/inire/tests/test_performance_reporting.py +++ b/inire/tests/test_performance_reporting.py @@ -19,6 +19,14 @@ def test_snapshot_example_01_exposes_metrics() -> None: assert snapshot.metrics.score_component_calls >= 0 assert snapshot.metrics.danger_map_lookup_calls >= 0 assert snapshot.metrics.move_cache_abs_misses >= 0 + assert snapshot.metrics.guidance_match_moves >= 0 + assert snapshot.metrics.guidance_match_moves_straight >= 0 + assert snapshot.metrics.guidance_match_moves_bend90 >= 0 + assert snapshot.metrics.guidance_match_moves_sbend >= 0 + assert snapshot.metrics.guidance_bonus_applied >= 0.0 + assert snapshot.metrics.guidance_bonus_applied_straight >= 0.0 + assert snapshot.metrics.guidance_bonus_applied_bend90 >= 0.0 + assert snapshot.metrics.guidance_bonus_applied_sbend >= 0.0 assert snapshot.metrics.ray_cast_calls >= 0 assert snapshot.metrics.ray_cast_calls_expand_forward >= 0 assert snapshot.metrics.dynamic_tree_rebuilds >= 0 @@ -36,6 +44,10 @@ def test_snapshot_example_01_exposes_metrics() -> None: assert snapshot.metrics.congestion_candidate_ids >= 0 assert snapshot.metrics.verify_dynamic_candidate_nets >= 0 assert snapshot.metrics.refinement_candidates_verified >= 0 + assert snapshot.metrics.pair_local_search_pairs_considered >= 0 + assert snapshot.metrics.pair_local_search_attempts >= 0 + assert snapshot.metrics.pair_local_search_accepts >= 0 + assert snapshot.metrics.pair_local_search_nodes_expanded >= 0 def test_record_performance_baseline_script_writes_selected_scenario(tmp_path: Path) -> None: @@ -195,3 +207,70 @@ def test_diff_performance_baseline_script_renders_current_metrics_for_added_scen report = output_path.read_text() assert "| example_07_large_scale_routing_no_warm_start | duration_s | - |" in report assert "| example_07_large_scale_routing_no_warm_start | nodes_expanded | - |" in report + + +def test_record_conflict_trace_script_writes_selected_scenario(tmp_path: Path) -> None: + repo_root = Path(__file__).resolve().parents[2] + script_path = repo_root / "scripts" / "record_conflict_trace.py" + + subprocess.run( + [ + sys.executable, + str(script_path), + "--output-dir", + str(tmp_path), + "--scenario", + "example_05_orientation_stress", + ], + check=True, + ) + + payload = json.loads((tmp_path / "conflict_trace.json").read_text()) + assert payload["generated_at"] + assert payload["generator"] == "scripts/record_conflict_trace.py" + assert [entry["name"] for entry in payload["scenarios"]] == ["example_05_orientation_stress"] + assert (tmp_path / "conflict_trace.md").exists() + + +def test_record_conflict_trace_script_supports_performance_only_scenario(tmp_path: Path) -> None: + repo_root = Path(__file__).resolve().parents[2] + script_path = repo_root / "scripts" / "record_conflict_trace.py" + + subprocess.run( + [ + sys.executable, + str(script_path), + "--output-dir", + str(tmp_path), + "--include-performance-only", + "--scenario", + "example_07_large_scale_routing_no_warm_start", + ], + check=True, + ) + + payload = json.loads((tmp_path / "conflict_trace.json").read_text()) + assert [entry["name"] for entry in payload["scenarios"]] == ["example_07_large_scale_routing_no_warm_start"] + + +def test_record_frontier_trace_script_writes_selected_scenario(tmp_path: Path) -> None: + repo_root = Path(__file__).resolve().parents[2] + script_path = repo_root / "scripts" / "record_frontier_trace.py" + + subprocess.run( + [ + sys.executable, + str(script_path), + "--output-dir", + str(tmp_path), + "--scenario", + "example_05_orientation_stress", + ], + check=True, + ) + + payload = json.loads((tmp_path / "frontier_trace.json").read_text()) + assert payload["generated_at"] + assert payload["generator"] == "scripts/record_frontier_trace.py" + assert [entry["name"] for entry in payload["scenarios"]] == ["example_05_orientation_stress"] + assert (tmp_path / "frontier_trace.md").exists() diff --git a/scripts/record_conflict_trace.py b/scripts/record_conflict_trace.py new file mode 100644 index 0000000..2baaf9c --- /dev/null +++ b/scripts/record_conflict_trace.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +from collections import Counter +from dataclasses import asdict +from datetime import datetime +from pathlib import Path + +from inire.results import RoutingRunResult +from inire.tests.example_scenarios import TRACE_PERFORMANCE_SCENARIO_RUNS, TRACE_SCENARIO_RUNS + + +def _trace_registry(include_performance_only: bool) -> tuple[tuple[str, object], ...]: + if include_performance_only: + return TRACE_SCENARIO_RUNS + TRACE_PERFORMANCE_SCENARIO_RUNS + return TRACE_SCENARIO_RUNS + + +def _selected_runs( + selected_scenarios: tuple[str, ...] | None, + *, + include_performance_only: bool, +) -> tuple[tuple[str, object], ...]: + if selected_scenarios is None: + return (("example_07_large_scale_routing_no_warm_start", dict(TRACE_PERFORMANCE_SCENARIO_RUNS)["example_07_large_scale_routing_no_warm_start"]),) + + registry = dict(TRACE_SCENARIO_RUNS + TRACE_PERFORMANCE_SCENARIO_RUNS) + allowed_standard = dict(_trace_registry(include_performance_only)) + runs = [] + for name in selected_scenarios: + if name in allowed_standard: + runs.append((name, allowed_standard[name])) + continue + if name in registry: + runs.append((name, registry[name])) + continue + valid = ", ".join(sorted(registry)) + raise SystemExit(f"Unknown trace scenario: {name}. Valid scenarios: {valid}") + return tuple(runs) + + +def _result_summary(run: RoutingRunResult) -> dict[str, object]: + return { + "total_results": len(run.results_by_net), + "valid_results": sum(1 for result in run.results_by_net.values() if result.is_valid), + "reached_targets": sum(1 for result in run.results_by_net.values() if result.reached_target), + "results_by_net": { + net_id: { + "outcome": result.outcome, + "reached_target": result.reached_target, + "report": asdict(result.report), + } + for net_id, result in run.results_by_net.items() + }, + } + + +def _build_payload( + selected_scenarios: tuple[str, ...] | None, + *, + include_performance_only: bool, +) -> dict[str, object]: + scenarios = [] + for name, run in _selected_runs(selected_scenarios, include_performance_only=include_performance_only): + result = run() + scenarios.append( + { + "name": name, + "summary": _result_summary(result), + "metrics": asdict(result.metrics), + "conflict_trace": [asdict(entry) for entry in result.conflict_trace], + } + ) + return { + "generated_at": datetime.now().astimezone().isoformat(timespec="seconds"), + "generator": "scripts/record_conflict_trace.py", + "scenarios": scenarios, + } + + +def _count_stage_nets(entry: dict[str, object]) -> int: + return sum( + 1 + for net in entry["nets"] + if net["report"]["dynamic_collision_count"] > 0 + ) + + +def _canonical_component_pair( + net_id: str, + self_component_index: int, + other_net_id: str, + other_component_index: int, +) -> tuple[tuple[str, int], tuple[str, int]]: + left = (net_id, self_component_index) + right = (other_net_id, other_component_index) + if left <= right: + return (left, right) + return (right, left) + + +def _render_markdown(payload: dict[str, object]) -> str: + lines = [ + "# Conflict Trace", + "", + f"Generated at {payload['generated_at']} by `{payload['generator']}`.", + "", + ] + + for scenario in payload["scenarios"]: + lines.extend( + [ + f"## {scenario['name']}", + "", + f"Results: {scenario['summary']['valid_results']} valid / " + f"{scenario['summary']['reached_targets']} reached / " + f"{scenario['summary']['total_results']} total.", + "", + "| Stage | Iteration | Conflicting Nets | Conflict Edges | Completed Nets |", + "| :-- | --: | --: | --: | --: |", + ] + ) + + net_stage_counts: Counter[str] = Counter() + edge_counts: Counter[tuple[str, str]] = Counter() + component_pair_counts: Counter[tuple[tuple[str, int], tuple[str, int]]] = Counter() + trace_entries = scenario["conflict_trace"] + for entry in trace_entries: + lines.append( + "| " + f"{entry['stage']} | " + f"{'' if entry['iteration'] is None else entry['iteration']} | " + f"{_count_stage_nets(entry)} | " + f"{len(entry['conflict_edges'])} | " + f"{len(entry['completed_net_ids'])} |" + ) + seen_component_pairs: set[tuple[tuple[str, int], tuple[str, int]]] = set() + for edge in entry["conflict_edges"]: + edge_counts[tuple(edge)] += 1 + for net in entry["nets"]: + if net["report"]["dynamic_collision_count"] > 0: + net_stage_counts[net["net_id"]] += 1 + for component_conflict in net["component_conflicts"]: + pair = _canonical_component_pair( + net["net_id"], + component_conflict["self_component_index"], + component_conflict["other_net_id"], + component_conflict["other_component_index"], + ) + seen_component_pairs.add(pair) + for pair in seen_component_pairs: + component_pair_counts[pair] += 1 + + lines.extend(["", "Top nets by traced dynamic-collision stages:", ""]) + if net_stage_counts: + for net_id, count in net_stage_counts.most_common(10): + lines.append(f"- `{net_id}`: {count}") + else: + lines.append("- None") + + lines.extend(["", "Top net pairs by frequency:", ""]) + if edge_counts: + for (left, right), count in edge_counts.most_common(10): + lines.append(f"- `{left}` <-> `{right}`: {count}") + else: + lines.append("- None") + + lines.extend(["", "Top component pairs by frequency:", ""]) + if component_pair_counts: + for pair, count in component_pair_counts.most_common(10): + (left_net, left_index), (right_net, right_index) = pair + lines.append(f"- `{left_net}[{left_index}]` <-> `{right_net}[{right_index}]`: {count}") + else: + lines.append("- None") + + lines.append("") + + return "\n".join(lines) + + +def main() -> None: + parser = argparse.ArgumentParser(description="Record conflict-trace artifacts for selected trace scenarios.") + parser.add_argument( + "--scenario", + action="append", + dest="scenarios", + default=[], + help="Optional trace scenario name to include. May be passed more than once.", + ) + parser.add_argument( + "--include-performance-only", + action="store_true", + help="Include performance-only trace scenarios when selecting from the standard registry.", + ) + parser.add_argument( + "--output-dir", + type=Path, + default=None, + help="Directory to write conflict_trace.json and conflict_trace.md into. Defaults to /docs.", + ) + args = parser.parse_args() + + repo_root = Path(__file__).resolve().parents[1] + output_dir = repo_root / "docs" if args.output_dir is None else args.output_dir.resolve() + output_dir.mkdir(exist_ok=True) + + selected = tuple(args.scenarios) if args.scenarios else None + payload = _build_payload(selected, include_performance_only=args.include_performance_only) + json_path = output_dir / "conflict_trace.json" + markdown_path = output_dir / "conflict_trace.md" + + json_path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n") + markdown_path.write_text(_render_markdown(payload) + "\n") + + if json_path.is_relative_to(repo_root): + print(f"Wrote {json_path.relative_to(repo_root)}") + else: + print(f"Wrote {json_path}") + if markdown_path.is_relative_to(repo_root): + print(f"Wrote {markdown_path.relative_to(repo_root)}") + else: + print(f"Wrote {markdown_path}") + + +if __name__ == "__main__": + main() diff --git a/scripts/record_frontier_trace.py b/scripts/record_frontier_trace.py new file mode 100644 index 0000000..e101c90 --- /dev/null +++ b/scripts/record_frontier_trace.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +from collections import Counter +from dataclasses import asdict +from datetime import datetime +from pathlib import Path + +from inire.tests.example_scenarios import TRACE_PERFORMANCE_SCENARIO_RUNS, TRACE_SCENARIO_RUNS + + +def _trace_registry(include_performance_only: bool) -> tuple[tuple[str, object], ...]: + if include_performance_only: + return TRACE_SCENARIO_RUNS + TRACE_PERFORMANCE_SCENARIO_RUNS + return TRACE_SCENARIO_RUNS + + +def _selected_runs( + selected_scenarios: tuple[str, ...] | None, + *, + include_performance_only: bool, +) -> tuple[tuple[str, object], ...]: + if selected_scenarios is None: + default_registry = dict(TRACE_PERFORMANCE_SCENARIO_RUNS) + return (("example_07_large_scale_routing_no_warm_start", default_registry["example_07_large_scale_routing_no_warm_start"]),) + + registry = dict(TRACE_SCENARIO_RUNS + TRACE_PERFORMANCE_SCENARIO_RUNS) + allowed_standard = dict(_trace_registry(include_performance_only)) + runs = [] + for name in selected_scenarios: + if name in allowed_standard: + runs.append((name, allowed_standard[name])) + continue + if name in registry: + runs.append((name, registry[name])) + continue + valid = ", ".join(sorted(registry)) + raise SystemExit(f"Unknown trace scenario: {name}. Valid scenarios: {valid}") + return tuple(runs) + + +def _build_payload( + selected_scenarios: tuple[str, ...] | None, + *, + include_performance_only: bool, +) -> dict[str, object]: + scenarios = [] + for name, run in _selected_runs(selected_scenarios, include_performance_only=include_performance_only): + result = run() + scenarios.append( + { + "name": name, + "summary": { + "total_results": len(result.results_by_net), + "valid_results": sum(1 for entry in result.results_by_net.values() if entry.is_valid), + "reached_targets": sum(1 for entry in result.results_by_net.values() if entry.reached_target), + }, + "metrics": asdict(result.metrics), + "frontier_trace": [asdict(entry) for entry in result.frontier_trace], + } + ) + return { + "generated_at": datetime.now().astimezone().isoformat(timespec="seconds"), + "generator": "scripts/record_frontier_trace.py", + "scenarios": scenarios, + } + + +def _render_markdown(payload: dict[str, object]) -> str: + lines = [ + "# Frontier Trace", + "", + f"Generated at {payload['generated_at']} by `{payload['generator']}`.", + "", + ] + + for scenario in payload["scenarios"]: + lines.extend( + [ + f"## {scenario['name']}", + "", + f"Results: {scenario['summary']['valid_results']} valid / " + f"{scenario['summary']['reached_targets']} reached / " + f"{scenario['summary']['total_results']} total.", + "", + "| Net | Hotspots | Closed-Set | Hard Collision | Self Collision | Cost | Samples |", + "| :-- | --: | --: | --: | --: | --: | --: |", + ] + ) + + reason_counts: Counter[str] = Counter() + hotspot_counts: Counter[tuple[str, int]] = Counter() + for net_trace in scenario["frontier_trace"]: + sample_count = len(net_trace["samples"]) + lines.append( + "| " + f"{net_trace['net_id']} | " + f"{len(net_trace['hotspot_bounds'])} | " + f"{net_trace['pruned_closed_set']} | " + f"{net_trace['pruned_hard_collision']} | " + f"{net_trace['pruned_self_collision']} | " + f"{net_trace['pruned_cost']} | " + f"{sample_count} |" + ) + reason_counts["closed_set"] += net_trace["pruned_closed_set"] + reason_counts["hard_collision"] += net_trace["pruned_hard_collision"] + reason_counts["self_collision"] += net_trace["pruned_self_collision"] + reason_counts["cost"] += net_trace["pruned_cost"] + for sample in net_trace["samples"]: + hotspot_counts[(net_trace["net_id"], sample["hotspot_index"])] += 1 + + lines.extend(["", "Prune totals by reason:", ""]) + if reason_counts: + for reason, count in reason_counts.most_common(): + lines.append(f"- `{reason}`: {count}") + else: + lines.append("- None") + + lines.extend(["", "Top traced hotspots by sample count:", ""]) + if hotspot_counts: + for (net_id, hotspot_index), count in hotspot_counts.most_common(10): + lines.append(f"- `{net_id}` hotspot `{hotspot_index}`: {count}") + else: + lines.append("- None") + + lines.extend(["", "Per-net sampled reason/move breakdown:", ""]) + if scenario["frontier_trace"]: + for net_trace in scenario["frontier_trace"]: + reason_move_counts: Counter[tuple[str, str]] = Counter() + hotspot_sample_counts: Counter[int] = Counter() + for sample in net_trace["samples"]: + reason_move_counts[(sample["reason"], sample["move_type"])] += 1 + hotspot_sample_counts[sample["hotspot_index"]] += 1 + lines.append(f"- `{net_trace['net_id']}`") + if reason_move_counts: + top_pairs = ", ".join( + f"`{reason}` x `{move}` = {count}" + for (reason, move), count in reason_move_counts.most_common(3) + ) + lines.append(f" sampled reasons: {top_pairs}") + else: + lines.append(" sampled reasons: none") + if hotspot_sample_counts: + top_hotspots = ", ".join( + f"`{hotspot}` = {count}" for hotspot, count in hotspot_sample_counts.most_common(3) + ) + lines.append(f" hotspot samples: {top_hotspots}") + else: + lines.append(" hotspot samples: none") + else: + lines.append("- None") + + lines.append("") + + return "\n".join(lines) + + +def main() -> None: + parser = argparse.ArgumentParser(description="Record frontier-trace artifacts for selected trace scenarios.") + parser.add_argument( + "--scenario", + action="append", + dest="scenarios", + default=[], + help="Optional trace scenario name to include. May be passed more than once.", + ) + parser.add_argument( + "--include-performance-only", + action="store_true", + help="Include performance-only trace scenarios when selecting from the standard registry.", + ) + parser.add_argument( + "--output-dir", + type=Path, + default=None, + help="Directory to write frontier_trace.json and frontier_trace.md into. Defaults to /docs.", + ) + args = parser.parse_args() + + repo_root = Path(__file__).resolve().parents[1] + output_dir = repo_root / "docs" if args.output_dir is None else args.output_dir.resolve() + output_dir.mkdir(exist_ok=True) + + selected = tuple(args.scenarios) if args.scenarios else None + payload = _build_payload(selected, include_performance_only=args.include_performance_only) + json_path = output_dir / "frontier_trace.json" + markdown_path = output_dir / "frontier_trace.md" + + json_path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n") + markdown_path.write_text(_render_markdown(payload) + "\n") + + if json_path.is_relative_to(repo_root): + print(f"Wrote {json_path.relative_to(repo_root)}") + else: + print(f"Wrote {json_path}") + if markdown_path.is_relative_to(repo_root): + print(f"Wrote {markdown_path.relative_to(repo_root)}") + else: + print(f"Wrote {markdown_path}") + + +if __name__ == "__main__": + main()