diff --git a/DOCS.md b/DOCS.md index 75a7cba..d70cd8c 100644 --- a/DOCS.md +++ b/DOCS.md @@ -130,8 +130,6 @@ Use `RoutingProblem.initial_paths` to provide semantic per-net seeds. Seeds are | `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. | -| `capture_iteration_trace` | `False` | Capture per-iteration and per-net route-attempt attribution for negotiated-congestion diagnosis. | -| `capture_pre_pair_frontier_trace` | `False` | Capture the final unresolved pre-pair-local subset iteration plus hotspot-adjacent frontier prunes for the routed nets in that basin. | ## 7. Conflict Trace @@ -188,60 +186,7 @@ Use `scripts/record_frontier_trace.py` to capture JSON and Markdown frontier-pru 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. Pre-Pair Frontier Trace - -`RoutingRunResult.pre_pair_frontier_trace` is either a single immutable trace entry or `None`. It is populated only when `RoutingOptions.diagnostics.capture_pre_pair_frontier_trace=True`. - -Trace types: - -- `PrePairFrontierTraceEntry` - - `iteration`: The final unresolved subset-reroute iteration immediately before pair-local handoff - - `routed_net_ids`: Nets rerouted in that iteration, in routing order - - `conflict_edges`: Dynamic conflict edges reported for that unresolved basin - - `nets`: Per-net attempt attribution plus hotspot-adjacent frontier rerun data -- `PrePairNetTrace` - - `net_id` - - `nodes_expanded` - - `congestion_check_calls` - - `pruned_closed_set` - - `pruned_cost` - - `pruned_hard_collision` - - `guidance_seed_present` - - `frontier`: A `NetFrontierTrace` captured against the restored best unresolved state - -Use `scripts/record_pre_pair_frontier_trace.py` to capture JSON and Markdown artifacts. Its default comparison target is the solved seed-42 no-warm canary versus the heavier seed-43 no-warm canary. - -## 10. Iteration Trace - -`RoutingRunResult.iteration_trace` is an immutable tuple of negotiated-congestion iteration summaries. It is empty unless `RoutingOptions.diagnostics.capture_iteration_trace=True`. - -Trace types: - -- `IterationTraceEntry` - - `iteration` - - `congestion_penalty`: Penalty in effect for that iteration - - `routed_net_ids`: Nets rerouted during that iteration, in routing order - - `completed_nets` - - `conflict_edges` - - `total_dynamic_collisions` - - `nodes_expanded` - - `congestion_check_calls` - - `congestion_candidate_ids` - - `congestion_exact_pair_checks` - - `net_attempts`: Per-net attribution for that iteration -- `IterationNetAttemptTrace` - - `net_id` - - `reached_target` - - `nodes_expanded` - - `congestion_check_calls` - - `pruned_closed_set` - - `pruned_cost` - - `pruned_hard_collision` - - `guidance_seed_present` - -Use `scripts/record_iteration_trace.py` to capture JSON and Markdown iteration-attribution artifacts. Its default comparison target is the solved seed-42 no-warm canary versus the pathological seed-43 no-warm canary. - -## 11. RouteMetrics +## 9. RouteMetrics `RoutingRunResult.metrics` is an immutable per-run snapshot. @@ -321,15 +266,13 @@ Use `scripts/record_iteration_trace.py` to capture JSON and Markdown iteration-a - `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. -- `late_phase_capped_nets`: Number of late all-reached heavy-net reroutes run under the bounded node-limit cap before pair-local handoff. -- `late_phase_capped_fallbacks`: Number of those capped late-phase reroutes that fell back to the incumbent reached-target path instead of replacing it. ## 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. Use `scripts/record_conflict_trace.py` for opt-in conflict-hotspot traces, `scripts/record_frontier_trace.py` for hotspot-adjacent prune traces, `scripts/record_pre_pair_frontier_trace.py` for the final unresolved pre-pair basin, `scripts/record_iteration_trace.py` for per-iteration negotiated-congestion attribution, and `scripts/characterize_pair_local_search.py` to sweep example_07-style no-warm runs for pair-local repair behavior. 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, `scripts/record_frontier_trace.py` for hotspot-adjacent prune traces, and `scripts/characterize_pair_local_search.py` to sweep example_07-style no-warm runs for pair-local repair behavior. The counter baseline is currently observational and is not enforced as a CI gate. ## 11. Tuning Notes diff --git a/docs/iteration_trace.json b/docs/iteration_trace.json deleted file mode 100644 index 2bebaa6..0000000 --- a/docs/iteration_trace.json +++ /dev/null @@ -1,1395 +0,0 @@ -{ - "generated_at": "2026-04-02T18:51:01-07:00", - "generator": "scripts/record_iteration_trace.py", - "scenarios": [ - { - "iteration_trace": [ - { - "completed_nets": 1, - "conflict_edges": 16, - "congestion_candidate_ids": 0, - "congestion_check_calls": 0, - "congestion_exact_pair_checks": 0, - "congestion_penalty": 100.0, - "iteration": 0, - "net_attempts": [ - { - "congestion_check_calls": 0, - "guidance_seed_present": false, - "net_id": "net_08", - "nodes_expanded": 106, - "pruned_closed_set": 58, - "pruned_cost": 2, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 0, - "guidance_seed_present": false, - "net_id": "net_06", - "nodes_expanded": 12, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 0, - "guidance_seed_present": false, - "net_id": "net_03", - "nodes_expanded": 12, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 0, - "guidance_seed_present": false, - "net_id": "net_00", - "nodes_expanded": 158, - "pruned_closed_set": 72, - "pruned_cost": 74, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 0, - "guidance_seed_present": false, - "net_id": "net_07", - "nodes_expanded": 14, - "pruned_closed_set": 4, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 0, - "guidance_seed_present": false, - "net_id": "net_01", - "nodes_expanded": 38, - "pruned_closed_set": 14, - "pruned_cost": 2, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 0, - "guidance_seed_present": false, - "net_id": "net_09", - "nodes_expanded": 209, - "pruned_closed_set": 93, - "pruned_cost": 99, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 0, - "guidance_seed_present": false, - "net_id": "net_02", - "nodes_expanded": 14, - "pruned_closed_set": 3, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 0, - "guidance_seed_present": false, - "net_id": "net_04", - "nodes_expanded": 4, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 0, - "guidance_seed_present": false, - "net_id": "net_05", - "nodes_expanded": 4, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - } - ], - "nodes_expanded": 571, - "routed_net_ids": [ - "net_08", - "net_06", - "net_03", - "net_00", - "net_07", - "net_01", - "net_09", - "net_02", - "net_04", - "net_05" - ], - "total_dynamic_collisions": 50 - }, - { - "completed_nets": 2, - "conflict_edges": 12, - "congestion_candidate_ids": 2378, - "congestion_check_calls": 974, - "congestion_exact_pair_checks": 1998, - "congestion_penalty": 140.0, - "iteration": 1, - "net_attempts": [ - { - "congestion_check_calls": 20, - "guidance_seed_present": true, - "net_id": "net_04", - "nodes_expanded": 3, - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 317, - "guidance_seed_present": true, - "net_id": "net_06", - "nodes_expanded": 81, - "pruned_closed_set": 14, - "pruned_cost": 22, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 45, - "guidance_seed_present": true, - "net_id": "net_01", - "nodes_expanded": 8, - "pruned_closed_set": 2, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 31, - "guidance_seed_present": true, - "net_id": "net_09", - "nodes_expanded": 6, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 20, - "guidance_seed_present": true, - "net_id": "net_05", - "nodes_expanded": 3, - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 36, - "guidance_seed_present": true, - "net_id": "net_02", - "nodes_expanded": 8, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 33, - "guidance_seed_present": true, - "net_id": "net_00", - "nodes_expanded": 7, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 372, - "guidance_seed_present": true, - "net_id": "net_03", - "nodes_expanded": 117, - "pruned_closed_set": 19, - "pruned_cost": 47, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 54, - "guidance_seed_present": true, - "net_id": "net_07", - "nodes_expanded": 11, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 46, - "guidance_seed_present": true, - "net_id": "net_08", - "nodes_expanded": 9, - "pruned_closed_set": 2, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - } - ], - "nodes_expanded": 253, - "routed_net_ids": [ - "net_04", - "net_06", - "net_01", - "net_09", - "net_05", - "net_02", - "net_00", - "net_03", - "net_07", - "net_08" - ], - "total_dynamic_collisions": 54 - }, - { - "completed_nets": 4, - "conflict_edges": 5, - "congestion_candidate_ids": 1928, - "congestion_check_calls": 993, - "congestion_exact_pair_checks": 1571, - "congestion_penalty": 196.0, - "iteration": 2, - "net_attempts": [ - { - "congestion_check_calls": 20, - "guidance_seed_present": true, - "net_id": "net_05", - "nodes_expanded": 3, - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 399, - "guidance_seed_present": true, - "net_id": "net_02", - "nodes_expanded": 120, - "pruned_closed_set": 31, - "pruned_cost": 47, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 20, - "guidance_seed_present": true, - "net_id": "net_04", - "nodes_expanded": 3, - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 275, - "guidance_seed_present": true, - "net_id": "net_01", - "nodes_expanded": 63, - "pruned_closed_set": 15, - "pruned_cost": 3, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 68, - "guidance_seed_present": true, - "net_id": "net_08", - "nodes_expanded": 16, - "pruned_closed_set": 6, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 59, - "guidance_seed_present": true, - "net_id": "net_09", - "nodes_expanded": 16, - "pruned_closed_set": 4, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 31, - "guidance_seed_present": true, - "net_id": "net_03", - "nodes_expanded": 6, - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 38, - "guidance_seed_present": true, - "net_id": "net_06", - "nodes_expanded": 8, - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 29, - "guidance_seed_present": true, - "net_id": "net_07", - "nodes_expanded": 6, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 54, - "guidance_seed_present": true, - "net_id": "net_00", - "nodes_expanded": 12, - "pruned_closed_set": 2, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - } - ], - "nodes_expanded": 253, - "routed_net_ids": [ - "net_05", - "net_02", - "net_04", - "net_01", - "net_08", - "net_09", - "net_03", - "net_06", - "net_07", - "net_00" - ], - "total_dynamic_collisions": 22 - }, - { - "completed_nets": 6, - "conflict_edges": 2, - "congestion_candidate_ids": 852, - "congestion_check_calls": 437, - "congestion_exact_pair_checks": 698, - "congestion_penalty": 274.4, - "iteration": 3, - "net_attempts": [ - { - "congestion_check_calls": 79, - "guidance_seed_present": true, - "net_id": "net_02", - "nodes_expanded": 15, - "pruned_closed_set": 4, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 68, - "guidance_seed_present": true, - "net_id": "net_01", - "nodes_expanded": 20, - "pruned_closed_set": 3, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 34, - "guidance_seed_present": true, - "net_id": "net_09", - "nodes_expanded": 11, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 73, - "guidance_seed_present": true, - "net_id": "net_00", - "nodes_expanded": 14, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 30, - "guidance_seed_present": true, - "net_id": "net_07", - "nodes_expanded": 7, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 20, - "guidance_seed_present": true, - "net_id": "net_05", - "nodes_expanded": 3, - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 20, - "guidance_seed_present": true, - "net_id": "net_04", - "nodes_expanded": 3, - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 35, - "guidance_seed_present": true, - "net_id": "net_06", - "nodes_expanded": 8, - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 22, - "guidance_seed_present": true, - "net_id": "net_03", - "nodes_expanded": 6, - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 56, - "guidance_seed_present": true, - "net_id": "net_08", - "nodes_expanded": 13, - "pruned_closed_set": 4, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - } - ], - "nodes_expanded": 100, - "routed_net_ids": [ - "net_02", - "net_01", - "net_09", - "net_00", - "net_07", - "net_05", - "net_04", - "net_06", - "net_03", - "net_08" - ], - "total_dynamic_collisions": 10 - }, - { - "completed_nets": 6, - "conflict_edges": 2, - "congestion_candidate_ids": 627, - "congestion_check_calls": 332, - "congestion_exact_pair_checks": 513, - "congestion_penalty": 384.15999999999997, - "iteration": 4, - "net_attempts": [ - { - "congestion_check_calls": 30, - "guidance_seed_present": true, - "net_id": "net_07", - "nodes_expanded": 7, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 179, - "guidance_seed_present": true, - "net_id": "net_06", - "nodes_expanded": 46, - "pruned_closed_set": 7, - "pruned_cost": 15, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 43, - "guidance_seed_present": true, - "net_id": "net_00", - "nodes_expanded": 10, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 80, - "guidance_seed_present": true, - "net_id": "net_01", - "nodes_expanded": 18, - "pruned_closed_set": 3, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - } - ], - "nodes_expanded": 81, - "routed_net_ids": [ - "net_07", - "net_06", - "net_00", - "net_01" - ], - "total_dynamic_collisions": 10 - } - ], - "metrics": { - "congestion_cache_hits": 31, - "congestion_cache_misses": 2736, - "congestion_candidate_ids": 5785, - "congestion_candidate_nets": 6163, - "congestion_candidate_precheck_hits": 1383, - "congestion_candidate_precheck_misses": 1418, - "congestion_candidate_precheck_skips": 34, - "congestion_check_calls": 2736, - "congestion_exact_pair_checks": 4780, - "congestion_grid_net_cache_hits": 1356, - "congestion_grid_net_cache_misses": 2608, - "congestion_grid_span_cache_hits": 1247, - "congestion_grid_span_cache_misses": 1308, - "congestion_lazy_requeues": 0, - "congestion_lazy_resolutions": 0, - "congestion_net_envelope_cache_hits": 1452, - "congestion_net_envelope_cache_misses": 2720, - "congestion_presence_cache_hits": 1541, - "congestion_presence_cache_misses": 1642, - "congestion_presence_skips": 382, - "danger_map_cache_hits": 11547, - "danger_map_cache_misses": 6063, - "danger_map_lookup_calls": 17610, - "danger_map_query_calls": 6063, - "danger_map_total_ns": 171226180, - "dynamic_grid_rebuilds": 0, - "dynamic_path_objects_added": 399, - "dynamic_path_objects_removed": 351, - "dynamic_tree_rebuilds": 0, - "guidance_bonus_applied": 6750.0, - "guidance_bonus_applied_bend90": 2250.0, - "guidance_bonus_applied_sbend": 375.0, - "guidance_bonus_applied_straight": 4125.0, - "guidance_match_moves": 108, - "guidance_match_moves_bend90": 36, - "guidance_match_moves_sbend": 6, - "guidance_match_moves_straight": 66, - "hard_collision_cache_hits": 0, - "iteration_conflict_edges": 37, - "iteration_conflicting_nets": 32, - "iteration_reverified_nets": 50, - "iteration_reverify_calls": 5, - "late_phase_capped_fallbacks": 0, - "late_phase_capped_nets": 0, - "move_cache_abs_hits": 1200, - "move_cache_abs_misses": 5338, - "move_cache_rel_hits": 4768, - "move_cache_rel_misses": 570, - "moves_added": 5853, - "moves_generated": 6538, - "nets_carried_forward": 6, - "nets_reached_target": 44, - "nets_routed": 44, - "nodes_expanded": 1258, - "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": 374, - "pruned_cost": 311, - "pruned_hard_collision": 0, - "ray_cast_calls": 4310, - "ray_cast_calls_expand_forward": 1214, - "ray_cast_calls_expand_snap": 39, - "ray_cast_calls_other": 0, - "ray_cast_calls_straight_static": 3051, - "ray_cast_calls_visibility_build": 0, - "ray_cast_calls_visibility_query": 0, - "ray_cast_calls_visibility_tangent": 6, - "ray_cast_candidate_bounds": 159, - "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": 5, - "score_component_calls": 6181, - "score_component_total_ns": 191650546, - "static_net_tree_rebuilds": 1, - "static_raw_tree_rebuilds": 1, - "static_safe_cache_hits": 1170, - "static_tree_rebuilds": 1, - "timeout_events": 0, - "verify_dynamic_candidate_nets": 1822, - "verify_dynamic_exact_pair_checks": 504, - "verify_path_report_calls": 164, - "verify_static_buffer_ops": 779, - "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": 1214, - "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 - } - }, - { - "iteration_trace": [ - { - "completed_nets": 1, - "conflict_edges": 16, - "congestion_candidate_ids": 0, - "congestion_check_calls": 0, - "congestion_exact_pair_checks": 0, - "congestion_penalty": 100.0, - "iteration": 0, - "net_attempts": [ - { - "congestion_check_calls": 0, - "guidance_seed_present": false, - "net_id": "net_00", - "nodes_expanded": 158, - "pruned_closed_set": 72, - "pruned_cost": 74, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 0, - "guidance_seed_present": false, - "net_id": "net_05", - "nodes_expanded": 4, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 0, - "guidance_seed_present": false, - "net_id": "net_07", - "nodes_expanded": 14, - "pruned_closed_set": 4, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 0, - "guidance_seed_present": false, - "net_id": "net_01", - "nodes_expanded": 38, - "pruned_closed_set": 14, - "pruned_cost": 2, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 0, - "guidance_seed_present": false, - "net_id": "net_09", - "nodes_expanded": 209, - "pruned_closed_set": 93, - "pruned_cost": 99, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 0, - "guidance_seed_present": false, - "net_id": "net_08", - "nodes_expanded": 106, - "pruned_closed_set": 58, - "pruned_cost": 2, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 0, - "guidance_seed_present": false, - "net_id": "net_06", - "nodes_expanded": 12, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 0, - "guidance_seed_present": false, - "net_id": "net_03", - "nodes_expanded": 12, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 0, - "guidance_seed_present": false, - "net_id": "net_02", - "nodes_expanded": 14, - "pruned_closed_set": 3, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 0, - "guidance_seed_present": false, - "net_id": "net_04", - "nodes_expanded": 4, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - } - ], - "nodes_expanded": 571, - "routed_net_ids": [ - "net_00", - "net_05", - "net_07", - "net_01", - "net_09", - "net_08", - "net_06", - "net_03", - "net_02", - "net_04" - ], - "total_dynamic_collisions": 50 - }, - { - "completed_nets": 1, - "conflict_edges": 13, - "congestion_candidate_ids": 2562, - "congestion_check_calls": 961, - "congestion_exact_pair_checks": 2032, - "congestion_penalty": 140.0, - "iteration": 1, - "net_attempts": [ - { - "congestion_check_calls": 31, - "guidance_seed_present": true, - "net_id": "net_09", - "nodes_expanded": 6, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 46, - "guidance_seed_present": true, - "net_id": "net_08", - "nodes_expanded": 9, - "pruned_closed_set": 2, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 32, - "guidance_seed_present": true, - "net_id": "net_00", - "nodes_expanded": 6, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 259, - "guidance_seed_present": true, - "net_id": "net_07", - "nodes_expanded": 86, - "pruned_closed_set": 19, - "pruned_cost": 44, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 20, - "guidance_seed_present": true, - "net_id": "net_04", - "nodes_expanded": 3, - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 43, - "guidance_seed_present": true, - "net_id": "net_01", - "nodes_expanded": 7, - "pruned_closed_set": 2, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 372, - "guidance_seed_present": true, - "net_id": "net_03", - "nodes_expanded": 117, - "pruned_closed_set": 19, - "pruned_cost": 47, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 27, - "guidance_seed_present": true, - "net_id": "net_05", - "nodes_expanded": 5, - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 84, - "guidance_seed_present": true, - "net_id": "net_02", - "nodes_expanded": 20, - "pruned_closed_set": 2, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 47, - "guidance_seed_present": true, - "net_id": "net_06", - "nodes_expanded": 10, - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - } - ], - "nodes_expanded": 269, - "routed_net_ids": [ - "net_09", - "net_08", - "net_00", - "net_07", - "net_04", - "net_01", - "net_03", - "net_05", - "net_02", - "net_06" - ], - "total_dynamic_collisions": 53 - }, - { - "completed_nets": 4, - "conflict_edges": 3, - "congestion_candidate_ids": 1610, - "congestion_check_calls": 643, - "congestion_exact_pair_checks": 1224, - "congestion_penalty": 196.0, - "iteration": 2, - "net_attempts": [ - { - "congestion_check_calls": 121, - "guidance_seed_present": true, - "net_id": "net_08", - "nodes_expanded": 26, - "pruned_closed_set": 9, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 50, - "guidance_seed_present": true, - "net_id": "net_07", - "nodes_expanded": 10, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 92, - "guidance_seed_present": true, - "net_id": "net_01", - "nodes_expanded": 20, - "pruned_closed_set": 7, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 121, - "guidance_seed_present": true, - "net_id": "net_06", - "nodes_expanded": 29, - "pruned_closed_set": 0, - "pruned_cost": 5, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 58, - "guidance_seed_present": true, - "net_id": "net_02", - "nodes_expanded": 13, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 72, - "guidance_seed_present": true, - "net_id": "net_09", - "nodes_expanded": 15, - "pruned_closed_set": 4, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 50, - "guidance_seed_present": true, - "net_id": "net_00", - "nodes_expanded": 13, - "pruned_closed_set": 3, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 20, - "guidance_seed_present": true, - "net_id": "net_05", - "nodes_expanded": 3, - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 39, - "guidance_seed_present": true, - "net_id": "net_03", - "nodes_expanded": 8, - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 20, - "guidance_seed_present": true, - "net_id": "net_04", - "nodes_expanded": 3, - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - } - ], - "nodes_expanded": 140, - "routed_net_ids": [ - "net_08", - "net_07", - "net_01", - "net_06", - "net_02", - "net_09", - "net_00", - "net_05", - "net_03", - "net_04" - ], - "total_dynamic_collisions": 15 - }, - { - "completed_nets": 4, - "conflict_edges": 3, - "congestion_candidate_ids": 557, - "congestion_check_calls": 250, - "congestion_exact_pair_checks": 428, - "congestion_penalty": 274.4, - "iteration": 3, - "net_attempts": [ - { - "congestion_check_calls": 36, - "guidance_seed_present": true, - "net_id": "net_03", - "nodes_expanded": 8, - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 30, - "guidance_seed_present": true, - "net_id": "net_07", - "nodes_expanded": 7, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 30, - "guidance_seed_present": true, - "net_id": "net_02", - "nodes_expanded": 7, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 49, - "guidance_seed_present": true, - "net_id": "net_09", - "nodes_expanded": 11, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 70, - "guidance_seed_present": true, - "net_id": "net_08", - "nodes_expanded": 13, - "pruned_closed_set": 4, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 35, - "guidance_seed_present": true, - "net_id": "net_06", - "nodes_expanded": 8, - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - } - ], - "nodes_expanded": 54, - "routed_net_ids": [ - "net_03", - "net_07", - "net_02", - "net_09", - "net_08", - "net_06" - ], - "total_dynamic_collisions": 15 - }, - { - "completed_nets": 6, - "conflict_edges": 2, - "congestion_candidate_ids": 1025, - "congestion_check_calls": 505, - "congestion_exact_pair_checks": 829, - "congestion_penalty": 384.15999999999997, - "iteration": 4, - "net_attempts": [ - { - "congestion_check_calls": 30, - "guidance_seed_present": true, - "net_id": "net_07", - "nodes_expanded": 7, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 32, - "guidance_seed_present": true, - "net_id": "net_02", - "nodes_expanded": 8, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 179, - "guidance_seed_present": true, - "net_id": "net_06", - "nodes_expanded": 46, - "pruned_closed_set": 7, - "pruned_cost": 15, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 192, - "guidance_seed_present": true, - "net_id": "net_03", - "nodes_expanded": 54, - "pruned_closed_set": 7, - "pruned_cost": 21, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 26, - "guidance_seed_present": true, - "net_id": "net_09", - "nodes_expanded": 9, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 46, - "guidance_seed_present": true, - "net_id": "net_08", - "nodes_expanded": 12, - "pruned_closed_set": 4, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - } - ], - "nodes_expanded": 136, - "routed_net_ids": [ - "net_07", - "net_02", - "net_06", - "net_03", - "net_09", - "net_08" - ], - "total_dynamic_collisions": 10 - }, - { - "completed_nets": 6, - "conflict_edges": 2, - "congestion_candidate_ids": 419, - "congestion_check_calls": 171, - "congestion_exact_pair_checks": 287, - "congestion_penalty": 537.824, - "iteration": 5, - "net_attempts": [ - { - "congestion_check_calls": 85, - "guidance_seed_present": true, - "net_id": "net_07", - "nodes_expanded": 16, - "pruned_closed_set": 3, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 86, - "guidance_seed_present": true, - "net_id": "net_02", - "nodes_expanded": 17, - "pruned_closed_set": 4, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 0, - "guidance_seed_present": true, - "net_id": "net_06", - "nodes_expanded": 0, - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - }, - { - "congestion_check_calls": 0, - "guidance_seed_present": true, - "net_id": "net_03", - "nodes_expanded": 0, - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "reached_target": true - } - ], - "nodes_expanded": 33, - "routed_net_ids": [ - "net_07", - "net_02", - "net_06", - "net_03" - ], - "total_dynamic_collisions": 10 - } - ], - "metrics": { - "congestion_cache_hits": 8, - "congestion_cache_misses": 2530, - "congestion_candidate_ids": 6173, - "congestion_candidate_nets": 5869, - "congestion_candidate_precheck_hits": 1152, - "congestion_candidate_precheck_misses": 1460, - "congestion_candidate_precheck_skips": 74, - "congestion_check_calls": 2530, - "congestion_exact_pair_checks": 4800, - "congestion_grid_net_cache_hits": 1192, - "congestion_grid_net_cache_misses": 2676, - "congestion_grid_span_cache_hits": 1065, - "congestion_grid_span_cache_misses": 1366, - "congestion_lazy_requeues": 0, - "congestion_lazy_resolutions": 0, - "congestion_net_envelope_cache_hits": 1234, - "congestion_net_envelope_cache_misses": 2769, - "congestion_presence_cache_hits": 1302, - "congestion_presence_cache_misses": 1664, - "congestion_presence_skips": 354, - "danger_map_cache_hits": 11485, - "danger_map_cache_misses": 5474, - "danger_map_lookup_calls": 16959, - "danger_map_query_calls": 5474, - "danger_map_total_ns": 145721703, - "dynamic_grid_rebuilds": 0, - "dynamic_path_objects_added": 397, - "dynamic_path_objects_removed": 350, - "dynamic_tree_rebuilds": 0, - "guidance_bonus_applied": 7562.5, - "guidance_bonus_applied_bend90": 2937.5, - "guidance_bonus_applied_sbend": 250.0, - "guidance_bonus_applied_straight": 4375.0, - "guidance_match_moves": 121, - "guidance_match_moves_bend90": 47, - "guidance_match_moves_sbend": 4, - "guidance_match_moves_straight": 70, - "hard_collision_cache_hits": 0, - "iteration_conflict_edges": 39, - "iteration_conflicting_nets": 39, - "iteration_reverified_nets": 60, - "iteration_reverify_calls": 6, - "late_phase_capped_fallbacks": 2, - "late_phase_capped_nets": 2, - "move_cache_abs_hits": 1304, - "move_cache_abs_misses": 4997, - "move_cache_rel_hits": 4419, - "move_cache_rel_misses": 578, - "moves_added": 5638, - "moves_generated": 6301, - "nets_carried_forward": 14, - "nets_reached_target": 44, - "nets_routed": 46, - "nodes_expanded": 1203, - "pair_local_search_accepts": 2, - "pair_local_search_attempts": 3, - "pair_local_search_nodes_expanded": 39, - "pair_local_search_pairs_considered": 2, - "path_cost_calls": 0, - "pruned_closed_set": 354, - "pruned_cost": 309, - "pruned_hard_collision": 0, - "ray_cast_calls": 4059, - "ray_cast_calls_expand_forward": 1159, - "ray_cast_calls_expand_snap": 13, - "ray_cast_calls_other": 0, - "ray_cast_calls_straight_static": 2881, - "ray_cast_calls_visibility_build": 0, - "ray_cast_calls_visibility_query": 0, - "ray_cast_calls_visibility_tangent": 6, - "ray_cast_candidate_bounds": 170, - "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": 5962, - "score_component_total_ns": 164785883, - "static_net_tree_rebuilds": 1, - "static_raw_tree_rebuilds": 1, - "static_safe_cache_hits": 1276, - "static_tree_rebuilds": 1, - "timeout_events": 0, - "verify_dynamic_candidate_nets": 1884, - "verify_dynamic_exact_pair_checks": 557, - "verify_path_report_calls": 174, - "verify_static_buffer_ops": 805, - "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": 1159, - "warm_start_paths_built": 0, - "warm_start_paths_used": 0 - }, - "name": "example_07_large_scale_routing_no_warm_start_seed43", - "summary": { - "reached_targets": 10, - "total_results": 10, - "valid_results": 10 - } - } - ] -} diff --git a/docs/iteration_trace.md b/docs/iteration_trace.md deleted file mode 100644 index 54e4967..0000000 --- a/docs/iteration_trace.md +++ /dev/null @@ -1,81 +0,0 @@ -# Iteration Trace - -Generated at 2026-04-02T18:51:01-07:00 by `scripts/record_iteration_trace.py`. - -## example_07_large_scale_routing_no_warm_start - -Results: 10 valid / 10 reached / 10 total. - -| Iteration | Penalty | Routed Nets | Completed | Conflict Edges | Dynamic Collisions | Nodes | Congestion Checks | Candidate Ids | Exact Pairs | -| --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | -| 0 | 100.0 | 10 | 1 | 16 | 50 | 571 | 0 | 0 | 0 | -| 1 | 140.0 | 10 | 2 | 12 | 54 | 253 | 974 | 2378 | 1998 | -| 2 | 196.0 | 10 | 4 | 5 | 22 | 253 | 993 | 1928 | 1571 | -| 3 | 274.4 | 10 | 6 | 2 | 10 | 100 | 437 | 852 | 698 | -| 4 | 384.2 | 4 | 6 | 2 | 10 | 81 | 332 | 627 | 513 | - -Top nets by iteration-attributed nodes expanded: - -- `net_09`: 242 -- `net_00`: 201 -- `net_02`: 157 -- `net_06`: 155 -- `net_01`: 147 -- `net_08`: 144 -- `net_03`: 141 -- `net_07`: 45 -- `net_04`: 13 -- `net_05`: 13 - -Top nets by iteration-attributed congestion checks: - -- `net_06`: 569 -- `net_02`: 514 -- `net_01`: 468 -- `net_03`: 425 -- `net_00`: 203 -- `net_08`: 170 -- `net_07`: 143 -- `net_09`: 124 -- `net_04`: 60 -- `net_05`: 60 - -## example_07_large_scale_routing_no_warm_start_seed43 - -Results: 10 valid / 10 reached / 10 total. - -| Iteration | Penalty | Routed Nets | Completed | Conflict Edges | Dynamic Collisions | Nodes | Congestion Checks | Candidate Ids | Exact Pairs | -| --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | -| 0 | 100.0 | 10 | 1 | 16 | 50 | 571 | 0 | 0 | 0 | -| 1 | 140.0 | 10 | 1 | 13 | 53 | 269 | 961 | 2562 | 2032 | -| 2 | 196.0 | 10 | 4 | 3 | 15 | 140 | 643 | 1610 | 1224 | -| 3 | 274.4 | 6 | 4 | 3 | 15 | 54 | 250 | 557 | 428 | -| 4 | 384.2 | 6 | 6 | 2 | 10 | 136 | 505 | 1025 | 829 | -| 5 | 537.8 | 4 | 6 | 2 | 10 | 33 | 171 | 419 | 287 | - -Top nets by iteration-attributed nodes expanded: - -- `net_09`: 250 -- `net_03`: 199 -- `net_00`: 177 -- `net_08`: 166 -- `net_07`: 140 -- `net_06`: 105 -- `net_02`: 79 -- `net_01`: 65 -- `net_05`: 12 -- `net_04`: 10 - -Top nets by iteration-attributed congestion checks: - -- `net_03`: 639 -- `net_07`: 454 -- `net_06`: 382 -- `net_02`: 290 -- `net_08`: 283 -- `net_09`: 178 -- `net_01`: 135 -- `net_00`: 82 -- `net_05`: 47 -- `net_04`: 40 - diff --git a/docs/optimization_pass_01_log.md b/docs/optimization_pass_01_log.md index 12ffdb6..2f5c5e7 100644 --- a/docs/optimization_pass_01_log.md +++ b/docs/optimization_pass_01_log.md @@ -3629,113 +3629,3 @@ Findings: | 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 | - | - -## Step 64 seed-43 iteration-trace diagnosis - -Measured on 2026-04-02T16:11:39-07:00. - -Findings: - -- Added `capture_iteration_trace` plus `scripts/record_iteration_trace.py` and tracked the first `seed 42` vs `seed 43` no-warm comparison in `docs/iteration_trace.json` and `docs/iteration_trace.md`. -- The pathological `seed 43` basin is not front-loaded. It matches the solved `seed 42` path through iteration `5`, then falls into three extra iterations with only `4` completed nets and `4` conflict edges. -- The late blowup is concentrated in two nets, not the whole routing set: `net_06` contributes `31604` attributed nodes and `83752` congestion checks, while `net_03` contributes `27532` nodes and `75019` congestion checks. -- This points the next optimization work at late-iteration reroute behavior for a small subset of nets rather than another global congestion or pair-local-search change. - -## Step 65 stop after fully reached two-edge plateau - -Measured on 2026-04-02T16:21:02-07:00. - -Findings: - -- Added a narrow late-iteration stop rule: once every net already reaches target and the best snapshot is down to the final `<=2` dynamic-conflict-edge basin, stop after the first no-improvement iteration and hand off to bounded pair-local repair. -- The solved seed-42 no-warm canary improved from `6` to `5` negotiated-congestion iterations and dropped from about `1764` to `1303` nodes and from `4625` to `2921` congestion checks, while staying `10/10/10`. -- The former seed-43 pathological basin collapsed from about `50s`, `61259` nodes, and `165223` congestion checks to about `2.53s`, `1691` nodes, and `4330` congestion checks, still finishing `10/10/10`. -- Guardrails held unchanged: warmed `example_07` stayed `10/10/10`, and `example_05_orientation_stress` stayed `3/3/3`. - -## Step 66 reroute only current conflict nets in late all-reached phase - -Measured on 2026-04-02T16:46:00-07:00. - -Findings: - -- Once all nets already reach target and the live conflict graph is down to `<=3` edges, the next negotiated iteration now reroutes only the currently conflicting nets instead of all nets. -- The solved seed-42 no-warm canary stayed `10/10/10` and improved from `50` routed nets / `1303` nodes / `2921` congestion checks to `44` routed nets / `1258` nodes / `2736` congestion checks. -- The seed-43 no-warm canary stayed `10/10/10` and improved from `60` routed nets / `1691` nodes / `4330` congestion checks to `46` routed nets / `1582` nodes / `3881` congestion checks. -- Guardrails held: warmed `example_07` stayed `10/10/10`, and `example_05_orientation_stress` stayed `3/3/3` while trimming slightly to `5` routed nets, `297` nodes, and `146` congestion checks. - -## Step 67 route lighter late conflict nets first - -Measured on 2026-04-02T17:16:54-07:00. - -Findings: - -- Kept the late all-reached conflict-set reroute, but now order those subset reroutes by the previous iteration's attributed work ascending so the lighter partner nets settle first and the heavier nets route later against a more stable late-phase context. -- The solved seed-42 no-warm canary stayed effectively flat at `10/10/10`, `44` routed nets, `1258` nodes, and `2736` congestion checks. -- The seed-43 no-warm canary stayed `10/10/10` and improved slightly from `1582` nodes / `3881` congestion checks to `1576` nodes / `3836` congestion checks. -- The remaining late-phase hotspot is still concentrated in `net_03` and `net_06`, especially the final 4-net iteration in the seed-43 trace. - -## Step 68 pre-pair frontier diagnostics kept, scratch reroute rejected - -Measured on 2026-04-02T17:57:48-07:00. - -Findings: - -- Kept a new opt-in `capture_pre_pair_frontier_trace` surface plus `scripts/record_pre_pair_frontier_trace.py`, and tracked the first seed-42 vs seed-43 artifacts in `docs/pre_pair_frontier_trace.json` and `docs/pre_pair_frontier_trace.md`. -- The final unresolved subset iteration is now explicit: seed `42` captures iteration `4` with routed nets `net_07`, `net_06`, `net_00`, `net_01`; seed `43` captures iteration `5` with routed nets `net_07`, `net_02`, `net_06`, `net_03`. -- The seed-43 heavy-net concentration is confirmed by the new trace: `net_03` and `net_06` account for most of the last unresolved iteration's work, and the hotspot-adjacent sampled prunes in that basin are closed-set dominated rather than hard-collision dominated. -- I also measured a bounded pre-pair scratch reroute for the two heaviest traced nets, but rejected it: it added runtime, produced `0` accepted repairs, and left the solved canaries at the same `1258 / 2736` and `1576 / 3836` node/check totals after revert. - -## Step 69 cap heavy late-phase reroutes with incumbent fallback - -Measured on 2026-04-02T18:20:00-07:00. - -Findings: - -- In the final all-reached 4-net subset iteration, the router now caps only the heavy reroute endpoints whose previous-iteration attributed work is already pathological, and falls back to their incumbent reached-target paths if the capped reroute does not finish cleanly. -- The solved seed-42 no-warm canary stays flat at `10/10/10`, `44` routed nets, `1258` nodes, and `2736` congestion checks, with `late_phase_capped_nets=0`. -- The heavier seed-43 no-warm canary stays `10/10/10` and improves from `1576` nodes / `3836` congestion checks to `1459` nodes / `3455` congestion checks, with `late_phase_capped_nets=2` and `late_phase_capped_fallbacks=2`. -- Guardrails held: warmed `example_07` stayed `10/10/10`, and `example_05_orientation_stress` stayed `3/3/3` with no late-phase capping activity. - -## Step 70 tighten late-phase cap from 128 to 64 - -Measured on 2026-04-02T18:33:00-07:00. - -Findings: - -- Tightened the bounded heavy-net late-phase reroute cap from `128` nodes to `64`, keeping the same incumbent fallback behavior and the same heavy-net selection rule. -- The solved seed-42 no-warm canary stays flat at `10/10/10`, `44` routed nets, `1258` nodes, and `2736` congestion checks, with `late_phase_capped_nets=0`. -- The heavier seed-43 no-warm canary stays `10/10/10` and improves again from `1459` nodes / `3455` congestion checks to `1331` nodes / `3012` congestion checks, still with `late_phase_capped_nets=2` and `late_phase_capped_fallbacks=2`. -- Guardrails held: warmed `example_07` stayed `10/10/10`, and `example_05_orientation_stress` stayed `3/3/3` with no late-phase capping activity. - -## Step 71 tighten late-phase cap from 64 to 32 after cap sweep - -Measured on 2026-04-02T18:43:00-07:00. - -Findings: - -- Ran a cap sweep across `32`, `48`, `64`, `96`, `128`, and uncapped behavior for the two no-warm seeds. The winner was `32`: it preserved both `10/10/10` canaries and gave the best seed-43 node/check totals while leaving seed-42 flat. -- Landed that tighter cap in the router. -- The solved seed-42 no-warm canary stays flat at `10/10/10`, `44` routed nets, `1258` nodes, and `2736` congestion checks, with `late_phase_capped_nets=0`. -- The heavier seed-43 no-warm canary stays `10/10/10` and improves again from `1331` nodes / `3012` congestion checks to `1267` nodes / `2813` congestion checks, still with `late_phase_capped_nets=2` and `late_phase_capped_fallbacks=2`. - -## Step 72 tighten late-phase cap from 32 to 1 after floor sweep - -Measured on 2026-04-02T18:45:00-07:00. - -Findings: - -- Extended the cap sweep below `32` and found the same pattern continued all the way down to `1`: seed-42 stayed flat because the cap never fires there, while seed-43 kept getting cheaper and still converged through the same incumbent-fallback path. -- Landed the tightest safe setting, `1`, so late pathological reroutes now act as a minimal probe before immediately falling back to the incumbent reached-target path if they do not finish cleanly. -- The solved seed-42 no-warm canary stays flat at `10/10/10`, `44` routed nets, `1258` nodes, and `2736` congestion checks, with `late_phase_capped_nets=0`. -- The heavier seed-43 no-warm canary stays `10/10/10` and improves again from `1267` nodes / `2813` congestion checks to `1205` nodes / `2548` congestion checks, still with `late_phase_capped_nets=2` and `late_phase_capped_fallbacks=2`. - -## Step 73 skip capped late-phase reroutes and carry the incumbent directly - -Measured on 2026-04-02T18:52:00-07:00. - -Findings: - -- Characterization showed the two capped late seed-43 reroutes were pure churn even at a `1`-node cap: they always fell back to the incumbent reached-target path and pair-local repair still resolved the final pairs. -- Moved that behavior into `_route_net_once()` directly: when a late heavy reroute is already capped and has a reached-target incumbent fallback, the router now reinstalls the incumbent immediately instead of calling `route_astar()` for a doomed probe. -- The solved seed-42 no-warm canary stays flat at `10/10/10`, `44` routed nets, `1258` nodes, and `2736` congestion checks, with `late_phase_capped_nets=0`. -- The heavier seed-43 no-warm canary stays `10/10/10` and improves again from `1205` nodes / `2548` congestion checks to `1203` nodes / `2530` congestion checks, still with `late_phase_capped_nets=2` and `late_phase_capped_fallbacks=2`. diff --git a/docs/performance.md b/docs/performance.md index 3e801d9..7085214 100644 --- a/docs/performance.md +++ b/docs/performance.md @@ -5,23 +5,37 @@ 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. + +Use `scripts/characterize_pair_local_search.py` when you want a small parameter sweep over example_07-style no-warm runs instead of a single canary reading. +The current tracked sweep output lives in `docs/pair_local_characterization.json` and `docs/pair_local_characterization.md`. + | 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.0037 | 1 | 1 | 1 | 1 | 1 | 2 | 10 | 11 | 7 | 0 | 0 | 0 | 5 | -| example_02_congestion_resolution | 0.3361 | 3 | 3 | 3 | 1 | 3 | 366 | 1164 | 1413 | 668 | 0 | 0 | 0 | 41 | -| example_03_locked_paths | 0.1877 | 2 | 2 | 2 | 2 | 2 | 191 | 657 | 904 | 307 | 0 | 0 | 0 | 18 | -| example_04_sbends_and_radii | 0.0269 | 2 | 2 | 2 | 1 | 2 | 15 | 70 | 123 | 65 | 0 | 0 | 0 | 10 | -| example_05_orientation_stress | 0.2311 | 3 | 3 | 3 | 2 | 5 | 297 | 1274 | 1680 | 689 | 0 | 0 | 146 | 20 | -| example_06_bend_collision_models | 0.1988 | 3 | 3 | 3 | 3 | 3 | 240 | 682 | 1026 | 629 | 0 | 0 | 0 | 15 | -| example_07_large_scale_routing | 0.2088 | 10 | 10 | 10 | 1 | 10 | 78 | 383 | 372 | 227 | 0 | 0 | 0 | 50 | -| example_08_custom_bend_geometry | 0.0177 | 2 | 2 | 2 | 2 | 2 | 18 | 56 | 78 | 56 | 0 | 0 | 0 | 10 | -| example_09_unroutable_best_effort | 0.0057 | 1 | 0 | 0 | 1 | 1 | 3 | 13 | 16 | 10 | 0 | 0 | 0 | 1 | +| 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 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` + +The latest tracked characterization sweep confirms there is no smaller stable pair-local smoke case under the `<=1.0s` rule, so the 10-net no-warm-start canary remains the primary regression target for this behavior. + 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, 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, late_phase_capped_nets, late_phase_capped_fallbacks +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 952600d..a0477d3 100644 --- a/docs/performance_baseline.json +++ b/docs/performance_baseline.json @@ -3,7 +3,7 @@ "generator": "scripts/record_performance_baseline.py", "scenarios": [ { - "duration_s": 0.003715757979080081, + "duration_s": 0.003964120987802744, "metrics": { "congestion_cache_hits": 0, "congestion_cache_misses": 0, @@ -47,8 +47,6 @@ "iteration_conflicting_nets": 0, "iteration_reverified_nets": 1, "iteration_reverify_calls": 1, - "late_phase_capped_fallbacks": 0, - "late_phase_capped_nets": 0, "move_cache_abs_hits": 1, "move_cache_abs_misses": 10, "move_cache_rel_hits": 0, @@ -87,7 +85,7 @@ "refinement_windows_considered": 0, "route_iterations": 1, "score_component_calls": 11, - "score_component_total_ns": 16864, + "score_component_total_ns": 18064, "static_net_tree_rebuilds": 1, "static_raw_tree_rebuilds": 0, "static_safe_cache_hits": 1, @@ -95,7 +93,7 @@ "timeout_events": 0, "verify_dynamic_candidate_nets": 0, "verify_dynamic_exact_pair_checks": 0, - "verify_path_report_calls": 5, + "verify_path_report_calls": 4, "verify_static_buffer_ops": 0, "visibility_builds": 0, "visibility_corner_hits_exact": 0, @@ -117,7 +115,7 @@ "valid_results": 1 }, { - "duration_s": 0.33605348505079746, + "duration_s": 0.3377689190674573, "metrics": { "congestion_cache_hits": 0, "congestion_cache_misses": 0, @@ -161,8 +159,6 @@ "iteration_conflicting_nets": 0, "iteration_reverified_nets": 3, "iteration_reverify_calls": 1, - "late_phase_capped_fallbacks": 0, - "late_phase_capped_nets": 0, "move_cache_abs_hits": 12, "move_cache_abs_misses": 1401, "move_cache_rel_hits": 1293, @@ -201,15 +197,15 @@ "refinement_windows_considered": 10, "route_iterations": 1, "score_component_calls": 976, - "score_component_total_ns": 1109505, + "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": 92, - "verify_dynamic_exact_pair_checks": 90, - "verify_path_report_calls": 41, + "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, @@ -231,7 +227,7 @@ "valid_results": 3 }, { - "duration_s": 0.18771230895072222, + "duration_s": 0.1929313091095537, "metrics": { "congestion_cache_hits": 0, "congestion_cache_misses": 0, @@ -275,8 +271,6 @@ "iteration_conflicting_nets": 0, "iteration_reverified_nets": 2, "iteration_reverify_calls": 2, - "late_phase_capped_fallbacks": 0, - "late_phase_capped_nets": 0, "move_cache_abs_hits": 1, "move_cache_abs_misses": 903, "move_cache_rel_hits": 821, @@ -315,16 +309,16 @@ "refinement_windows_considered": 2, "route_iterations": 2, "score_component_calls": 504, - "score_component_total_ns": 546567, + "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": 10, - "verify_dynamic_exact_pair_checks": 10, - "verify_path_report_calls": 18, - "verify_static_buffer_ops": 90, + "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, @@ -345,7 +339,7 @@ "valid_results": 2 }, { - "duration_s": 0.026945222169160843, + "duration_s": 0.02791503700427711, "metrics": { "congestion_cache_hits": 0, "congestion_cache_misses": 0, @@ -389,8 +383,6 @@ "iteration_conflicting_nets": 0, "iteration_reverified_nets": 2, "iteration_reverify_calls": 1, - "late_phase_capped_fallbacks": 0, - "late_phase_capped_nets": 0, "move_cache_abs_hits": 1, "move_cache_abs_misses": 122, "move_cache_rel_hits": 80, @@ -429,15 +421,15 @@ "refinement_windows_considered": 0, "route_iterations": 1, "score_component_calls": 90, - "score_component_total_ns": 97710, + "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": 12, + "verify_dynamic_candidate_nets": 9, "verify_dynamic_exact_pair_checks": 0, - "verify_path_report_calls": 10, + "verify_path_report_calls": 8, "verify_static_buffer_ops": 0, "visibility_builds": 0, "visibility_corner_hits_exact": 0, @@ -459,75 +451,73 @@ "valid_results": 2 }, { - "duration_s": 0.23108969815075397, + "duration_s": 0.23665715800598264, "metrics": { - "congestion_cache_hits": 3, - "congestion_cache_misses": 146, + "congestion_cache_hits": 4, + "congestion_cache_misses": 149, "congestion_candidate_ids": 32, "congestion_candidate_nets": 23, - "congestion_candidate_precheck_hits": 129, - "congestion_candidate_precheck_misses": 20, + "congestion_candidate_precheck_hits": 131, + "congestion_candidate_precheck_misses": 22, "congestion_candidate_precheck_skips": 0, - "congestion_check_calls": 146, + "congestion_check_calls": 149, "congestion_exact_pair_checks": 30, "congestion_grid_net_cache_hits": 16, - "congestion_grid_net_cache_misses": 26, + "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": 127, - "congestion_net_envelope_cache_misses": 39, - "congestion_presence_cache_hits": 196, - "congestion_presence_cache_misses": 27, - "congestion_presence_skips": 74, + "congestion_net_envelope_cache_hits": 128, + "congestion_net_envelope_cache_misses": 43, + "congestion_presence_cache_hits": 200, + "congestion_presence_cache_misses": 30, + "congestion_presence_skips": 77, "danger_map_cache_hits": 0, "danger_map_cache_misses": 0, "danger_map_lookup_calls": 0, "danger_map_query_calls": 0, "danger_map_total_ns": 0, "dynamic_grid_rebuilds": 0, - "dynamic_path_objects_added": 48, - "dynamic_path_objects_removed": 36, + "dynamic_path_objects_added": 49, + "dynamic_path_objects_removed": 37, "dynamic_tree_rebuilds": 0, - "guidance_bonus_applied": 562.5, + "guidance_bonus_applied": 687.5, "guidance_bonus_applied_bend90": 500.0, "guidance_bonus_applied_sbend": 0.0, - "guidance_bonus_applied_straight": 62.5, - "guidance_match_moves": 9, + "guidance_bonus_applied_straight": 187.5, + "guidance_match_moves": 11, "guidance_match_moves_bend90": 8, "guidance_match_moves_sbend": 0, - "guidance_match_moves_straight": 1, + "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, - "late_phase_capped_fallbacks": 0, - "late_phase_capped_nets": 0, - "move_cache_abs_hits": 374, + "move_cache_abs_hits": 385, "move_cache_abs_misses": 1306, "move_cache_rel_hits": 1204, "move_cache_rel_misses": 102, - "moves_added": 689, - "moves_generated": 1680, - "nets_carried_forward": 1, - "nets_reached_target": 5, - "nets_routed": 5, - "nodes_expanded": 297, + "moves_added": 696, + "moves_generated": 1691, + "nets_carried_forward": 0, + "nets_reached_target": 6, + "nets_routed": 6, + "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": 159, - "pruned_cost": 533, + "pruned_cost": 537, "pruned_hard_collision": 14, - "ray_cast_calls": 1274, - "ray_cast_calls_expand_forward": 292, - "ray_cast_calls_expand_snap": 2, + "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": 971, + "ray_cast_calls_straight_static": 979, "ray_cast_calls_visibility_build": 0, "ray_cast_calls_visibility_query": 0, "ray_cast_calls_visibility_tangent": 9, @@ -542,16 +532,16 @@ "refinement_static_bounds_checked": 0, "refinement_windows_considered": 0, "route_iterations": 2, - "score_component_calls": 1234, - "score_component_total_ns": 1223569, + "score_component_calls": 1245, + "score_component_total_ns": 1260961, "static_net_tree_rebuilds": 3, "static_raw_tree_rebuilds": 0, - "static_safe_cache_hits": 8, + "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": 20, + "verify_path_report_calls": 18, "verify_static_buffer_ops": 0, "visibility_builds": 0, "visibility_corner_hits_exact": 0, @@ -563,7 +553,7 @@ "visibility_point_queries": 0, "visibility_tangent_candidate_corner_checks": 70, "visibility_tangent_candidate_ray_tests": 9, - "visibility_tangent_candidate_scans": 292, + "visibility_tangent_candidate_scans": 293, "warm_start_paths_built": 2, "warm_start_paths_used": 2 }, @@ -573,7 +563,7 @@ "valid_results": 3 }, { - "duration_s": 0.19879506202414632, + "duration_s": 0.19982667709700763, "metrics": { "congestion_cache_hits": 0, "congestion_cache_misses": 0, @@ -599,7 +589,7 @@ "danger_map_cache_misses": 731, "danger_map_lookup_calls": 1914, "danger_map_query_calls": 731, - "danger_map_total_ns": 19050142, + "danger_map_total_ns": 18959782, "dynamic_grid_rebuilds": 0, "dynamic_path_objects_added": 54, "dynamic_path_objects_removed": 36, @@ -617,8 +607,6 @@ "iteration_conflicting_nets": 0, "iteration_reverified_nets": 3, "iteration_reverify_calls": 3, - "late_phase_capped_fallbacks": 0, - "late_phase_capped_nets": 0, "move_cache_abs_hits": 186, "move_cache_abs_misses": 840, "move_cache_rel_hits": 702, @@ -657,7 +645,7 @@ "refinement_windows_considered": 0, "route_iterations": 3, "score_component_calls": 842, - "score_component_total_ns": 21353240, + "score_component_total_ns": 21338709, "static_net_tree_rebuilds": 3, "static_raw_tree_rebuilds": 3, "static_safe_cache_hits": 141, @@ -665,8 +653,8 @@ "timeout_events": 0, "verify_dynamic_candidate_nets": 0, "verify_dynamic_exact_pair_checks": 0, - "verify_path_report_calls": 15, - "verify_static_buffer_ops": 90, + "verify_path_report_calls": 12, + "verify_static_buffer_ops": 72, "visibility_builds": 0, "visibility_corner_hits_exact": 0, "visibility_corner_index_builds": 3, @@ -687,7 +675,7 @@ "valid_results": 3 }, { - "duration_s": 0.20880168909206986, + "duration_s": 0.20046633295714855, "metrics": { "congestion_cache_hits": 0, "congestion_cache_misses": 0, @@ -713,7 +701,7 @@ "danger_map_cache_misses": 448, "danger_map_lookup_calls": 681, "danger_map_query_calls": 448, - "danger_map_total_ns": 11025527, + "danger_map_total_ns": 11017087, "dynamic_grid_rebuilds": 0, "dynamic_path_objects_added": 132, "dynamic_path_objects_removed": 88, @@ -731,8 +719,6 @@ "iteration_conflicting_nets": 0, "iteration_reverified_nets": 10, "iteration_reverify_calls": 1, - "late_phase_capped_fallbacks": 0, - "late_phase_capped_nets": 0, "move_cache_abs_hits": 6, "move_cache_abs_misses": 366, "move_cache_rel_hits": 275, @@ -771,16 +757,16 @@ "refinement_windows_considered": 0, "route_iterations": 1, "score_component_calls": 291, - "score_component_total_ns": 11875928, + "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": 476, - "verify_dynamic_exact_pair_checks": 72, - "verify_path_report_calls": 50, - "verify_static_buffer_ops": 220, + "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, @@ -801,7 +787,7 @@ "valid_results": 10 }, { - "duration_s": 0.017696003895252943, + "duration_s": 0.01759456400759518, "metrics": { "congestion_cache_hits": 0, "congestion_cache_misses": 0, @@ -845,8 +831,6 @@ "iteration_conflicting_nets": 0, "iteration_reverified_nets": 2, "iteration_reverify_calls": 2, - "late_phase_capped_fallbacks": 0, - "late_phase_capped_nets": 0, "move_cache_abs_hits": 2, "move_cache_abs_misses": 76, "move_cache_rel_hits": 32, @@ -885,7 +869,7 @@ "refinement_windows_considered": 0, "route_iterations": 2, "score_component_calls": 72, - "score_component_total_ns": 87742, + "score_component_total_ns": 85864, "static_net_tree_rebuilds": 2, "static_raw_tree_rebuilds": 0, "static_safe_cache_hits": 2, @@ -893,7 +877,7 @@ "timeout_events": 0, "verify_dynamic_candidate_nets": 0, "verify_dynamic_exact_pair_checks": 0, - "verify_path_report_calls": 10, + "verify_path_report_calls": 8, "verify_static_buffer_ops": 0, "visibility_builds": 0, "visibility_corner_hits_exact": 0, @@ -915,7 +899,7 @@ "valid_results": 2 }, { - "duration_s": 0.005660973023623228, + "duration_s": 0.005838233977556229, "metrics": { "congestion_cache_hits": 0, "congestion_cache_misses": 0, @@ -941,7 +925,7 @@ "danger_map_cache_misses": 20, "danger_map_lookup_calls": 30, "danger_map_query_calls": 20, - "danger_map_total_ns": 515133, + "danger_map_total_ns": 523870, "dynamic_grid_rebuilds": 0, "dynamic_path_objects_added": 2, "dynamic_path_objects_removed": 1, @@ -959,8 +943,6 @@ "iteration_conflicting_nets": 0, "iteration_reverified_nets": 0, "iteration_reverify_calls": 1, - "late_phase_capped_fallbacks": 0, - "late_phase_capped_nets": 0, "move_cache_abs_hits": 0, "move_cache_abs_misses": 16, "move_cache_rel_hits": 2, @@ -999,7 +981,7 @@ "refinement_windows_considered": 0, "route_iterations": 1, "score_component_calls": 14, - "score_component_total_ns": 554809, + "score_component_total_ns": 563611, "static_net_tree_rebuilds": 1, "static_raw_tree_rebuilds": 1, "static_safe_cache_hits": 0, diff --git a/docs/pre_pair_frontier_trace.json b/docs/pre_pair_frontier_trace.json deleted file mode 100644 index 42d83c6..0000000 --- a/docs/pre_pair_frontier_trace.json +++ /dev/null @@ -1,1005 +0,0 @@ -{ - "generated_at": "2026-04-02T18:51:01-07:00", - "generator": "scripts/record_pre_pair_frontier_trace.py", - "scenarios": [ - { - "metrics": { - "congestion_cache_hits": 31, - "congestion_cache_misses": 2736, - "congestion_candidate_ids": 5785, - "congestion_candidate_nets": 6163, - "congestion_candidate_precheck_hits": 1383, - "congestion_candidate_precheck_misses": 1418, - "congestion_candidate_precheck_skips": 34, - "congestion_check_calls": 2736, - "congestion_exact_pair_checks": 4780, - "congestion_grid_net_cache_hits": 1356, - "congestion_grid_net_cache_misses": 2608, - "congestion_grid_span_cache_hits": 1247, - "congestion_grid_span_cache_misses": 1308, - "congestion_lazy_requeues": 0, - "congestion_lazy_resolutions": 0, - "congestion_net_envelope_cache_hits": 1452, - "congestion_net_envelope_cache_misses": 2720, - "congestion_presence_cache_hits": 1541, - "congestion_presence_cache_misses": 1642, - "congestion_presence_skips": 382, - "danger_map_cache_hits": 11547, - "danger_map_cache_misses": 6063, - "danger_map_lookup_calls": 17610, - "danger_map_query_calls": 6063, - "danger_map_total_ns": 171779571, - "dynamic_grid_rebuilds": 0, - "dynamic_path_objects_added": 399, - "dynamic_path_objects_removed": 351, - "dynamic_tree_rebuilds": 0, - "guidance_bonus_applied": 6750.0, - "guidance_bonus_applied_bend90": 2250.0, - "guidance_bonus_applied_sbend": 375.0, - "guidance_bonus_applied_straight": 4125.0, - "guidance_match_moves": 108, - "guidance_match_moves_bend90": 36, - "guidance_match_moves_sbend": 6, - "guidance_match_moves_straight": 66, - "hard_collision_cache_hits": 0, - "iteration_conflict_edges": 37, - "iteration_conflicting_nets": 32, - "iteration_reverified_nets": 50, - "iteration_reverify_calls": 5, - "late_phase_capped_fallbacks": 0, - "late_phase_capped_nets": 0, - "move_cache_abs_hits": 1200, - "move_cache_abs_misses": 5338, - "move_cache_rel_hits": 4768, - "move_cache_rel_misses": 570, - "moves_added": 5853, - "moves_generated": 6538, - "nets_carried_forward": 6, - "nets_reached_target": 44, - "nets_routed": 44, - "nodes_expanded": 1258, - "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": 374, - "pruned_cost": 311, - "pruned_hard_collision": 0, - "ray_cast_calls": 4310, - "ray_cast_calls_expand_forward": 1214, - "ray_cast_calls_expand_snap": 39, - "ray_cast_calls_other": 0, - "ray_cast_calls_straight_static": 3051, - "ray_cast_calls_visibility_build": 0, - "ray_cast_calls_visibility_query": 0, - "ray_cast_calls_visibility_tangent": 6, - "ray_cast_candidate_bounds": 159, - "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": 5, - "score_component_calls": 6181, - "score_component_total_ns": 192508906, - "static_net_tree_rebuilds": 1, - "static_raw_tree_rebuilds": 1, - "static_safe_cache_hits": 1170, - "static_tree_rebuilds": 1, - "timeout_events": 0, - "verify_dynamic_candidate_nets": 1822, - "verify_dynamic_exact_pair_checks": 504, - "verify_path_report_calls": 164, - "verify_static_buffer_ops": 779, - "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": 1214, - "warm_start_paths_built": 0, - "warm_start_paths_used": 0 - }, - "name": "example_07_large_scale_routing_no_warm_start", - "pre_pair_frontier_trace": { - "conflict_edges": [ - [ - "net_01", - "net_02" - ], - [ - "net_06", - "net_07" - ] - ], - "iteration": 4, - "nets": [ - { - "congestion_check_calls": 30, - "frontier": { - "hotspot_bounds": [ - [ - 827.6047906391041, - 482.9684848604278, - 917.390687834262, - 572.0 - ], - [ - 884.0, - 555.0, - 916.0, - 598.0 - ] - ], - "net_id": "net_07", - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "pruned_self_collision": 0, - "samples": [] - }, - "guidance_seed_present": true, - "net_id": "net_07", - "nodes_expanded": 7, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0 - }, - { - "congestion_check_calls": 179, - "frontier": { - "hotspot_bounds": [ - [ - 826.3396407947606, - 482.8851198636423, - 917.390687834262, - 572.0 - ], - [ - 884.0, - 545.0, - 916.2379632934325, - 582.0 - ], - [ - 883.7620367065675, - 571.0, - 916.0, - 652.2850117535756 - ] - ], - "net_id": "net_06", - "pruned_closed_set": 5, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "pruned_self_collision": 0, - "samples": [ - { - "end_state": [ - 900, - 510, - 0 - ], - "hotspot_index": 0, - "move_type": "straight", - "parent_state": [ - 525, - 510, - 0 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 850, - 510, - 0 - ], - "hotspot_index": 0, - "move_type": "straight", - "parent_state": [ - 525, - 510, - 0 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 900, - 510, - 0 - ], - "hotspot_index": 0, - "move_type": "straight", - "parent_state": [ - 763, - 510, - 0 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 850, - 510, - 0 - ], - "hotspot_index": 0, - "move_type": "straight", - "parent_state": [ - 763, - 510, - 0 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 900, - 510, - 0 - ], - "hotspot_index": 0, - "move_type": "straight", - "parent_state": [ - 881, - 510, - 0 - ], - "reason": "closed_set" - } - ] - }, - "guidance_seed_present": true, - "net_id": "net_06", - "nodes_expanded": 46, - "pruned_closed_set": 7, - "pruned_cost": 15, - "pruned_hard_collision": 0 - }, - { - "congestion_check_calls": 43, - "frontier": { - "hotspot_bounds": [ - [ - 506.3396407947511, - 398.0, - 597.3906878342618, - 487.1148801363561 - ], - [ - 564.0, - 224.0, - 596.0, - 415.0 - ] - ], - "net_id": "net_00", - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "pruned_self_collision": 0, - "samples": [] - }, - "guidance_seed_present": true, - "net_id": "net_00", - "nodes_expanded": 10, - "pruned_closed_set": 1, - "pruned_cost": 0, - "pruned_hard_collision": 0 - }, - { - "congestion_check_calls": 80, - "frontier": { - "hotspot_bounds": [ - [ - 506.3396407947511, - 398.0, - 597.3906878342618, - 487.1148801363561 - ], - [ - 564.0, - 388.0, - 596.2379632934325, - 425.0 - ], - [ - 563.7620367065675, - 169.71498824645388, - 596.0, - 251.0 - ] - ], - "net_id": "net_01", - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "pruned_self_collision": 0, - "samples": [] - }, - "guidance_seed_present": true, - "net_id": "net_01", - "nodes_expanded": 18, - "pruned_closed_set": 3, - "pruned_cost": 0, - "pruned_hard_collision": 0 - } - ], - "routed_net_ids": [ - "net_07", - "net_06", - "net_00", - "net_01" - ] - }, - "summary": { - "reached_targets": 10, - "total_results": 10, - "valid_results": 10 - } - }, - { - "metrics": { - "congestion_cache_hits": 8, - "congestion_cache_misses": 2530, - "congestion_candidate_ids": 6173, - "congestion_candidate_nets": 5869, - "congestion_candidate_precheck_hits": 1152, - "congestion_candidate_precheck_misses": 1460, - "congestion_candidate_precheck_skips": 74, - "congestion_check_calls": 2530, - "congestion_exact_pair_checks": 4800, - "congestion_grid_net_cache_hits": 1192, - "congestion_grid_net_cache_misses": 2676, - "congestion_grid_span_cache_hits": 1065, - "congestion_grid_span_cache_misses": 1366, - "congestion_lazy_requeues": 0, - "congestion_lazy_resolutions": 0, - "congestion_net_envelope_cache_hits": 1234, - "congestion_net_envelope_cache_misses": 2769, - "congestion_presence_cache_hits": 1302, - "congestion_presence_cache_misses": 1664, - "congestion_presence_skips": 354, - "danger_map_cache_hits": 11485, - "danger_map_cache_misses": 5474, - "danger_map_lookup_calls": 16959, - "danger_map_query_calls": 5474, - "danger_map_total_ns": 143896014, - "dynamic_grid_rebuilds": 0, - "dynamic_path_objects_added": 397, - "dynamic_path_objects_removed": 350, - "dynamic_tree_rebuilds": 0, - "guidance_bonus_applied": 7562.5, - "guidance_bonus_applied_bend90": 2937.5, - "guidance_bonus_applied_sbend": 250.0, - "guidance_bonus_applied_straight": 4375.0, - "guidance_match_moves": 121, - "guidance_match_moves_bend90": 47, - "guidance_match_moves_sbend": 4, - "guidance_match_moves_straight": 70, - "hard_collision_cache_hits": 0, - "iteration_conflict_edges": 39, - "iteration_conflicting_nets": 39, - "iteration_reverified_nets": 60, - "iteration_reverify_calls": 6, - "late_phase_capped_fallbacks": 2, - "late_phase_capped_nets": 2, - "move_cache_abs_hits": 1304, - "move_cache_abs_misses": 4997, - "move_cache_rel_hits": 4419, - "move_cache_rel_misses": 578, - "moves_added": 5638, - "moves_generated": 6301, - "nets_carried_forward": 14, - "nets_reached_target": 44, - "nets_routed": 46, - "nodes_expanded": 1203, - "pair_local_search_accepts": 2, - "pair_local_search_attempts": 3, - "pair_local_search_nodes_expanded": 39, - "pair_local_search_pairs_considered": 2, - "path_cost_calls": 0, - "pruned_closed_set": 354, - "pruned_cost": 309, - "pruned_hard_collision": 0, - "ray_cast_calls": 4059, - "ray_cast_calls_expand_forward": 1159, - "ray_cast_calls_expand_snap": 13, - "ray_cast_calls_other": 0, - "ray_cast_calls_straight_static": 2881, - "ray_cast_calls_visibility_build": 0, - "ray_cast_calls_visibility_query": 0, - "ray_cast_calls_visibility_tangent": 6, - "ray_cast_candidate_bounds": 170, - "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": 5962, - "score_component_total_ns": 163113517, - "static_net_tree_rebuilds": 1, - "static_raw_tree_rebuilds": 1, - "static_safe_cache_hits": 1276, - "static_tree_rebuilds": 1, - "timeout_events": 0, - "verify_dynamic_candidate_nets": 1884, - "verify_dynamic_exact_pair_checks": 557, - "verify_path_report_calls": 174, - "verify_static_buffer_ops": 805, - "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": 1159, - "warm_start_paths_built": 0, - "warm_start_paths_used": 0 - }, - "name": "example_07_large_scale_routing_no_warm_start_seed43", - "pre_pair_frontier_trace": { - "conflict_edges": [ - [ - "net_02", - "net_03" - ], - [ - "net_06", - "net_07" - ] - ], - "iteration": 5, - "nets": [ - { - "congestion_check_calls": 85, - "frontier": { - "hotspot_bounds": [ - [ - 827.6047906391041, - 482.9684848604278, - 917.390687834262, - 572.0 - ], - [ - 884.0, - 555.0, - 916.0, - 598.0 - ] - ], - "net_id": "net_07", - "pruned_closed_set": 2, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "pruned_self_collision": 0, - "samples": [ - { - "end_state": [ - 850, - 520, - 0 - ], - "hotspot_index": 0, - "move_type": "straight", - "parent_state": [ - 525, - 520, - 0 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 850, - 520, - 0 - ], - "hotspot_index": 0, - "move_type": "straight", - "parent_state": [ - 763, - 520, - 0 - ], - "reason": "closed_set" - } - ] - }, - "guidance_seed_present": true, - "net_id": "net_07", - "nodes_expanded": 16, - "pruned_closed_set": 3, - "pruned_cost": 0, - "pruned_hard_collision": 0 - }, - { - "congestion_check_calls": 86, - "frontier": { - "hotspot_bounds": [ - [ - 826.3396407947511, - 418.0, - 917.3906878342618, - 507.1148801363561 - ], - [ - 884.0, - 402.0, - 916.0, - 435.0 - ] - ], - "net_id": "net_02", - "pruned_closed_set": 3, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "pruned_self_collision": 0, - "samples": [ - { - "end_state": [ - 850, - 470, - 0 - ], - "hotspot_index": 0, - "move_type": "straight", - "parent_state": [ - 525, - 470, - 0 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 850, - 470, - 0 - ], - "hotspot_index": 0, - "move_type": "straight", - "parent_state": [ - 763, - 470, - 0 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 900, - 470, - 0 - ], - "hotspot_index": 0, - "move_type": "straight", - "parent_state": [ - 881, - 470, - 0 - ], - "reason": "closed_set" - } - ] - }, - "guidance_seed_present": true, - "net_id": "net_02", - "nodes_expanded": 17, - "pruned_closed_set": 4, - "pruned_cost": 0, - "pruned_hard_collision": 0 - }, - { - "congestion_check_calls": 0, - "frontier": { - "hotspot_bounds": [ - [ - 826.3396407947606, - 482.8851198636423, - 917.390687834262, - 572.0 - ], - [ - 884.0, - 545.0, - 916.2379632934325, - 582.0 - ], - [ - 883.7620367065675, - 571.0, - 916.0, - 652.2850117535756 - ] - ], - "net_id": "net_06", - "pruned_closed_set": 8, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "pruned_self_collision": 0, - "samples": [ - { - "end_state": [ - 850, - 510, - 0 - ], - "hotspot_index": 0, - "move_type": "straight", - "parent_state": [ - 525, - 510, - 0 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 850, - 510, - 0 - ], - "hotspot_index": 0, - "move_type": "straight", - "parent_state": [ - 763, - 510, - 0 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 900, - 510, - 0 - ], - "hotspot_index": 0, - "move_type": "straight", - "parent_state": [ - 881, - 510, - 0 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 900, - 633, - 270 - ], - "hotspot_index": 2, - "move_type": "straight", - "parent_state": [ - 900, - 638, - 270 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 895, - 633, - 270 - ], - "hotspot_index": 2, - "move_type": "straight", - "parent_state": [ - 895, - 638, - 270 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 880, - 633, - 270 - ], - "hotspot_index": 2, - "move_type": "straight", - "parent_state": [ - 880, - 638, - 270 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 900, - 633, - 270 - ], - "hotspot_index": 2, - "move_type": "straight", - "parent_state": [ - 900, - 827, - 270 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 900, - 633, - 270 - ], - "hotspot_index": 2, - "move_type": "straight", - "parent_state": [ - 900, - 832, - 270 - ], - "reason": "closed_set" - } - ] - }, - "guidance_seed_present": true, - "net_id": "net_06", - "nodes_expanded": 0, - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0 - }, - { - "congestion_check_calls": 0, - "frontier": { - "hotspot_bounds": [ - [ - 826.3396407947511, - 418.0, - 917.3906878342618, - 507.1148801363561 - ], - [ - 884.0, - 408.0, - 916.2379632934325, - 445.0 - ], - [ - 883.7620367065675, - 347.71498824645397, - 916.0, - 429.0 - ] - ], - "net_id": "net_03", - "pruned_closed_set": 12, - "pruned_cost": 0, - "pruned_hard_collision": 0, - "pruned_self_collision": 0, - "samples": [ - { - "end_state": [ - 850, - 480, - 0 - ], - "hotspot_index": 0, - "move_type": "straight", - "parent_state": [ - 525, - 480, - 0 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 850, - 480, - 0 - ], - "hotspot_index": 0, - "move_type": "straight", - "parent_state": [ - 763, - 480, - 0 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 900, - 367, - 90 - ], - "hotspot_index": 2, - "move_type": "straight", - "parent_state": [ - 900, - 362, - 90 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 895, - 367, - 90 - ], - "hotspot_index": 2, - "move_type": "straight", - "parent_state": [ - 895, - 362, - 90 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 880, - 367, - 90 - ], - "hotspot_index": 2, - "move_type": "straight", - "parent_state": [ - 880, - 362, - 90 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 896, - 367, - 90 - ], - "hotspot_index": 2, - "move_type": "straight", - "parent_state": [ - 896, - 362, - 90 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 891, - 367, - 90 - ], - "hotspot_index": 2, - "move_type": "straight", - "parent_state": [ - 891, - 362, - 90 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 886, - 367, - 90 - ], - "hotspot_index": 2, - "move_type": "straight", - "parent_state": [ - 886, - 362, - 90 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 882, - 367, - 90 - ], - "hotspot_index": 2, - "move_type": "straight", - "parent_state": [ - 882, - 362, - 90 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 900, - 367, - 90 - ], - "hotspot_index": 2, - "move_type": "straight", - "parent_state": [ - 900, - 163, - 90 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 900, - 367, - 90 - ], - "hotspot_index": 2, - "move_type": "straight", - "parent_state": [ - 900, - 158, - 90 - ], - "reason": "closed_set" - }, - { - "end_state": [ - 900, - 367, - 90 - ], - "hotspot_index": 2, - "move_type": "straight", - "parent_state": [ - 900, - 267, - 90 - ], - "reason": "closed_set" - } - ] - }, - "guidance_seed_present": true, - "net_id": "net_03", - "nodes_expanded": 0, - "pruned_closed_set": 0, - "pruned_cost": 0, - "pruned_hard_collision": 0 - } - ], - "routed_net_ids": [ - "net_07", - "net_02", - "net_06", - "net_03" - ] - }, - "summary": { - "reached_targets": 10, - "total_results": 10, - "valid_results": 10 - } - } - ] -} diff --git a/docs/pre_pair_frontier_trace.md b/docs/pre_pair_frontier_trace.md deleted file mode 100644 index a760f82..0000000 --- a/docs/pre_pair_frontier_trace.md +++ /dev/null @@ -1,48 +0,0 @@ -# Pre-Pair Frontier Trace - -Generated at 2026-04-02T18:51:01-07:00 by `scripts/record_pre_pair_frontier_trace.py`. - -## example_07_large_scale_routing_no_warm_start - -Results: 10 valid / 10 reached / 10 total. - -Captured iteration: `4` - -Conflict edges: `(('net_01', 'net_02'), ('net_06', 'net_07'))` - -| Net | Nodes | Checks | Closed-Set | Cost | Hard Collision | Guidance Seed | Frontier Samples | -| :-- | --: | --: | --: | --: | --: | :--: | --: | -| net_07 | 7 | 30 | 1 | 0 | 0 | yes | 0 | -| net_06 | 46 | 179 | 7 | 15 | 0 | yes | 5 | -| net_00 | 10 | 43 | 1 | 0 | 0 | yes | 0 | -| net_01 | 18 | 80 | 3 | 0 | 0 | yes | 0 | - -Frontier prune totals by reason: - -- `closed_set`: 5 -- `hard_collision`: 0 -- `self_collision`: 0 -- `cost`: 0 - -## example_07_large_scale_routing_no_warm_start_seed43 - -Results: 10 valid / 10 reached / 10 total. - -Captured iteration: `5` - -Conflict edges: `(('net_02', 'net_03'), ('net_06', 'net_07'))` - -| Net | Nodes | Checks | Closed-Set | Cost | Hard Collision | Guidance Seed | Frontier Samples | -| :-- | --: | --: | --: | --: | --: | :--: | --: | -| net_07 | 16 | 85 | 3 | 0 | 0 | yes | 2 | -| net_02 | 17 | 86 | 4 | 0 | 0 | yes | 3 | -| net_06 | 0 | 0 | 0 | 0 | 0 | yes | 8 | -| net_03 | 0 | 0 | 0 | 0 | 0 | yes | 12 | - -Frontier prune totals by reason: - -- `closed_set`: 25 -- `hard_collision`: 0 -- `self_collision`: 0 -- `cost`: 0 - diff --git a/inire/__init__.py b/inire/__init__.py index a53b46a..9c63046 100644 --- a/inire/__init__.py +++ b/inire/__init__.py @@ -18,12 +18,8 @@ from .results import ( # noqa: PLC0414 ComponentConflictTrace as ComponentConflictTrace, ConflictTraceEntry as ConflictTraceEntry, FrontierPruneSample as FrontierPruneSample, - IterationNetAttemptTrace as IterationNetAttemptTrace, - IterationTraceEntry as IterationTraceEntry, NetConflictTrace as NetConflictTrace, NetFrontierTrace as NetFrontierTrace, - PrePairFrontierTraceEntry as PrePairFrontierTraceEntry, - PrePairNetTrace as PrePairNetTrace, RoutingResult as RoutingResult, RoutingRunResult as RoutingRunResult, ) @@ -51,8 +47,6 @@ def route( expanded_nodes=tuple(finder.accumulated_expanded_nodes), conflict_trace=tuple(finder.conflict_trace), frontier_trace=tuple(finder.frontier_trace), - pre_pair_frontier_trace=finder.pre_pair_frontier_trace, - iteration_trace=tuple(finder.iteration_trace), ) __all__ = [ @@ -68,10 +62,6 @@ __all__ = [ "PathSeed", "Port", "FrontierPruneSample", - "IterationNetAttemptTrace", - "IterationTraceEntry", - "PrePairFrontierTraceEntry", - "PrePairNetTrace", "RefinementOptions", "RoutingOptions", "RoutingProblem", diff --git a/inire/model.py b/inire/model.py index f9c8020..5860102 100644 --- a/inire/model.py +++ b/inire/model.py @@ -107,8 +107,6 @@ class DiagnosticsOptions: capture_expanded: bool = False capture_conflict_trace: bool = False capture_frontier_trace: bool = False - capture_iteration_trace: bool = False - capture_pre_pair_frontier_trace: bool = False @dataclass(frozen=True, slots=True) diff --git a/inire/results.py b/inire/results.py index d273756..88dd3a3 100644 --- a/inire/results.py +++ b/inire/results.py @@ -78,53 +78,6 @@ class NetFrontierTrace: samples: tuple[FrontierPruneSample, ...] = () -@dataclass(frozen=True, slots=True) -class PrePairNetTrace: - net_id: str - nodes_expanded: int - congestion_check_calls: int - pruned_closed_set: int - pruned_cost: int - pruned_hard_collision: int - guidance_seed_present: bool - frontier: NetFrontierTrace - - -@dataclass(frozen=True, slots=True) -class PrePairFrontierTraceEntry: - iteration: int - routed_net_ids: tuple[str, ...] - conflict_edges: tuple[tuple[str, str], ...] - nets: tuple[PrePairNetTrace, ...] - - -@dataclass(frozen=True, slots=True) -class IterationNetAttemptTrace: - net_id: str - reached_target: bool - nodes_expanded: int - congestion_check_calls: int - pruned_closed_set: int - pruned_cost: int - pruned_hard_collision: int - guidance_seed_present: bool - - -@dataclass(frozen=True, slots=True) -class IterationTraceEntry: - iteration: int - congestion_penalty: float - routed_net_ids: tuple[str, ...] - completed_nets: int - conflict_edges: int - total_dynamic_collisions: int - nodes_expanded: int - congestion_check_calls: int - congestion_candidate_ids: int - congestion_exact_pair_checks: int - net_attempts: tuple[IterationNetAttemptTrace, ...] = () - - @dataclass(frozen=True, slots=True) class RouteMetrics: nodes_expanded: int @@ -230,8 +183,6 @@ class RouteMetrics: pair_local_search_attempts: int pair_local_search_accepts: int pair_local_search_nodes_expanded: int - late_phase_capped_nets: int - late_phase_capped_fallbacks: int @dataclass(frozen=True, slots=True) @@ -280,5 +231,3 @@ class RoutingRunResult: expanded_nodes: tuple[tuple[int, int, int], ...] = () conflict_trace: tuple[ConflictTraceEntry, ...] = () frontier_trace: tuple[NetFrontierTrace, ...] = () - pre_pair_frontier_trace: PrePairFrontierTraceEntry | None = None - iteration_trace: tuple[IterationTraceEntry, ...] = () diff --git a/inire/router/_astar_types.py b/inire/router/_astar_types.py index 1c3b7a1..83fa899 100644 --- a/inire/router/_astar_types.py +++ b/inire/router/_astar_types.py @@ -258,8 +258,6 @@ class AStarMetrics: "total_pair_local_search_attempts", "total_pair_local_search_accepts", "total_pair_local_search_nodes_expanded", - "total_late_phase_capped_nets", - "total_late_phase_capped_fallbacks", "last_expanded_nodes", "nodes_expanded", "moves_generated", @@ -373,8 +371,6 @@ class AStarMetrics: self.total_pair_local_search_attempts = 0 self.total_pair_local_search_accepts = 0 self.total_pair_local_search_nodes_expanded = 0 - self.total_late_phase_capped_nets = 0 - self.total_late_phase_capped_fallbacks = 0 self.last_expanded_nodes: list[tuple[int, int, int]] = [] self.nodes_expanded = 0 self.moves_generated = 0 @@ -487,8 +483,6 @@ class AStarMetrics: self.total_pair_local_search_attempts = 0 self.total_pair_local_search_accepts = 0 self.total_pair_local_search_nodes_expanded = 0 - self.total_late_phase_capped_nets = 0 - self.total_late_phase_capped_fallbacks = 0 def reset_per_route(self) -> None: self.nodes_expanded = 0 @@ -604,8 +598,6 @@ class AStarMetrics: 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, - late_phase_capped_nets=self.total_late_phase_capped_nets, - late_phase_capped_fallbacks=self.total_late_phase_capped_fallbacks, ) diff --git a/inire/router/_router.py b/inire/router/_router.py index bc8aa97..367e721 100644 --- a/inire/router/_router.py +++ b/inire/router/_router.py @@ -11,12 +11,8 @@ from inire.results import ( ComponentConflictTrace, ConflictTraceEntry, FrontierPruneSample, - IterationNetAttemptTrace, - IterationTraceEntry, NetConflictTrace, NetFrontierTrace, - PrePairFrontierTraceEntry, - PrePairNetTrace, RoutingOutcome, RoutingReport, RoutingResult, @@ -54,9 +50,6 @@ class _RoutingState: last_conflict_signature: tuple[tuple[str, str], ...] last_conflict_edge_count: int repeated_conflict_count: int - pair_local_plateau_count: int - recent_attempt_work: dict[str, int] - pre_pair_candidate: _PrePairCandidate | None @dataclass(slots=True) @@ -72,30 +65,6 @@ class _PairLocalTarget: net_ids: tuple[str, str] -@dataclass(frozen=True, slots=True) -class _PrePairCandidate: - iteration: int - routed_net_ids: tuple[str, ...] - conflict_edges: tuple[tuple[str, str], ...] - net_attempts: tuple[IterationNetAttemptTrace, ...] - - -_ITERATION_TRACE_TOTALS = ( - "nodes_expanded", - "congestion_check_calls", - "congestion_candidate_ids", - "congestion_exact_pair_checks", -) - -_ATTEMPT_TRACE_TOTALS = ( - "nodes_expanded", - "congestion_check_calls", - "pruned_closed_set", - "pruned_cost", - "pruned_hard_collision", -) - - class PathFinder: __slots__ = ( "context", @@ -104,8 +73,6 @@ class PathFinder: "accumulated_expanded_nodes", "conflict_trace", "frontier_trace", - "pre_pair_frontier_trace", - "iteration_trace", ) def __init__( @@ -123,48 +90,6 @@ class PathFinder: self.accumulated_expanded_nodes: list[tuple[int, int, int]] = [] self.conflict_trace: list[ConflictTraceEntry] = [] self.frontier_trace: list[NetFrontierTrace] = [] - self.pre_pair_frontier_trace: PrePairFrontierTraceEntry | None = None - self.iteration_trace: list[IterationTraceEntry] = [] - - def _metric_total(self, metric_name: str) -> int: - return int(getattr(self.metrics, f"total_{metric_name}")) - - def _capture_metric_totals(self, metric_names: tuple[str, ...]) -> dict[str, int]: - return {metric_name: self._metric_total(metric_name) for metric_name in metric_names} - - def _metric_deltas(self, before: dict[str, int], after: dict[str, int]) -> dict[str, int]: - return {metric_name: after[metric_name] - before[metric_name] for metric_name in before} - - def _results_all_reached_target(self, state: _RoutingState) -> bool: - return ( - len(state.results) == len(state.ordered_net_ids) - and all(result.reached_target for result in state.results.values()) - ) - - def _has_incumbent_fallback(self, result: RoutingResult | None) -> bool: - return bool(result and result.reached_target and result.path) - - def _restore_incumbent_fallback( - self, - net_id: str, - result: RoutingResult, - guidance_seed_present: bool, - ) -> tuple[RoutingResult, bool]: - self.metrics.total_late_phase_capped_fallbacks += 1 - self._install_path(net_id, result.path) - return result, guidance_seed_present - - def _guidance_for_result( - self, - result: RoutingResult | None, - ) -> tuple[Sequence[ComponentResult] | None, float, bool]: - if result is None or not result.reached_target or not result.path: - return None, 0.0, False - return ( - result.as_seed().segments, - max(10.0, self.context.options.objective.bend_penalty * 0.25), - True, - ) def _install_path(self, net_id: str, path: Sequence[ComponentResult]) -> None: all_geoms: list[Polygon] = [] @@ -263,9 +188,6 @@ class PathFinder: last_conflict_signature=(), last_conflict_edge_count=0, repeated_conflict_count=0, - pair_local_plateau_count=0, - recent_attempt_work={}, - pre_pair_candidate=None, ) if state.initial_paths is None and congestion.warm_start_enabled: state.initial_paths = self._build_greedy_warm_start_paths(net_specs, congestion.net_order) @@ -303,38 +225,6 @@ class PathFinder: if result and result.path: self._install_path(net_id, result.path) - def _analyze_restored_best( - self, - state: _RoutingState, - ) -> tuple[dict[str, PathVerificationDetail], _IterationReview]: - capture_component_conflicts = ( - self.context.options.diagnostics.capture_conflict_trace - or self.context.options.diagnostics.capture_pre_pair_frontier_trace - ) - state.results, details_by_net, review = self._analyze_results( - state.ordered_net_ids, - state.results, - capture_component_conflicts=capture_component_conflicts, - count_iteration_metrics=False, - ) - if self.context.options.diagnostics.capture_conflict_trace: - self._capture_conflict_trace_entry( - state, - stage="restored_best", - iteration=None, - results=state.results, - details_by_net=details_by_net, - review=review, - ) - if self.context.options.diagnostics.capture_pre_pair_frontier_trace: - self.pre_pair_frontier_trace = self._materialize_pre_pair_frontier_trace( - state, - state.results, - details_by_net, - review, - ) - return details_by_net, review - def _update_best_iteration(self, state: _RoutingState, review: _IterationReview) -> bool: completed_nets = len(review.completed_net_ids) conflict_edges = len(review.conflict_edges) @@ -478,93 +368,6 @@ class PathFinder: return tuple(hotspot_bounds) - def _capture_single_frontier_trace( - self, - state: _RoutingState, - net_id: str, - result: RoutingResult, - hotspot_bounds: tuple[tuple[float, float, float, float], ...], - ) -> NetFrontierTrace: - if not hotspot_bounds: - return NetFrontierTrace( - net_id=net_id, - hotspot_bounds=(), - pruned_closed_set=0, - pruned_hard_collision=0, - pruned_self_collision=0, - pruned_cost=0, - ) - - 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: - 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) - - return 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 _analyze_results( self, ordered_net_ids: Sequence[str], @@ -643,26 +446,90 @@ class PathFinder: capture_component_conflicts=True, count_iteration_metrics=False, ) - 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 + 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 - self.frontier_trace.append( - self._capture_single_frontier_trace( - state, - net_id, - result, - hotspot_bounds, + 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, @@ -698,9 +565,6 @@ class PathFinder: return candidate_length < incumbent_length return False - def _pair_local_attempt_orders(self, target: _PairLocalTarget) -> tuple[tuple[str, str], tuple[str, str]]: - return target.net_ids, target.net_ids[::-1] - def _collect_pair_local_targets( self, state: _RoutingState, @@ -778,141 +642,6 @@ class PathFinder: metrics=AStarMetrics(), ) - def _materialize_pre_pair_frontier_trace( - self, - state: _RoutingState, - results: dict[str, RoutingResult], - details_by_net: dict[str, PathVerificationDetail], - review: _IterationReview, - ) -> PrePairFrontierTraceEntry | None: - candidate = state.pre_pair_candidate - if candidate is None: - return None - - result_by_net = dict(results) - detail_by_net = dict(details_by_net) - nets: list[PrePairNetTrace] = [] - attempt_by_net = {attempt.net_id: attempt for attempt in candidate.net_attempts} - for net_id in candidate.routed_net_ids: - attempt = attempt_by_net.get(net_id) - result = result_by_net.get(net_id) - detail = detail_by_net.get(net_id) - if attempt is None or result is None or detail is None or not result.reached_target: - continue - hotspot_bounds = self._build_frontier_hotspot_bounds(state, net_id, detail_by_net) - nets.append( - PrePairNetTrace( - net_id=net_id, - nodes_expanded=attempt.nodes_expanded, - congestion_check_calls=attempt.congestion_check_calls, - pruned_closed_set=attempt.pruned_closed_set, - pruned_cost=attempt.pruned_cost, - pruned_hard_collision=attempt.pruned_hard_collision, - guidance_seed_present=attempt.guidance_seed_present, - frontier=self._capture_single_frontier_trace(state, net_id, result, hotspot_bounds), - ) - ) - - if not nets: - return None - return PrePairFrontierTraceEntry( - iteration=candidate.iteration, - routed_net_ids=candidate.routed_net_ids, - conflict_edges=candidate.conflict_edges, - nets=tuple(nets), - ) - - def _build_iteration_reroute_plan( - self, - state: _RoutingState, - reroute_net_ids: set[str], - ) -> tuple[list[str], set[str]]: - routed_net_ids = [net_id for net_id in state.ordered_net_ids if net_id in reroute_net_ids] - capped_net_ids: set[str] = set() - if len(reroute_net_ids) >= len(state.ordered_net_ids) or not state.recent_attempt_work: - return routed_net_ids, capped_net_ids - - order_index = {net_id: idx for idx, net_id in enumerate(state.ordered_net_ids)} - routed_net_ids.sort(key=lambda net_id: (state.recent_attempt_work.get(net_id, 0), order_index[net_id])) - if ( - len(routed_net_ids) == 4 - and state.best_conflict_edges <= 2 - and self._results_all_reached_target(state) - ): - heavy_net_ids = sorted( - routed_net_ids, - key=lambda net_id: (-state.recent_attempt_work.get(net_id, 0), order_index[net_id]), - )[:2] - capped_net_ids = { - net_id for net_id in heavy_net_ids if state.recent_attempt_work.get(net_id, 0) >= 200 - } - return routed_net_ids, capped_net_ids - - def _update_pre_pair_candidate( - self, - state: _RoutingState, - *, - iteration: int, - reroute_net_ids: set[str], - routed_net_ids: list[str], - attempt_traces: list[IterationNetAttemptTrace], - review: _IterationReview, - ) -> None: - if self._results_all_reached_target(state) and len(reroute_net_ids) < len(state.ordered_net_ids) and review.conflict_edges: - state.pre_pair_candidate = _PrePairCandidate( - iteration=iteration, - routed_net_ids=tuple(routed_net_ids), - conflict_edges=tuple(sorted(review.conflict_edges)), - net_attempts=tuple(attempt_traces), - ) - return - state.pre_pair_candidate = None - - def _next_reroute_net_ids( - self, - state: _RoutingState, - review: _IterationReview, - ) -> set[str]: - if self._results_all_reached_target(state) and 0 < len(review.conflict_edges) <= 3: - return set(review.conflicting_nets) - return set(state.ordered_net_ids) - - def _should_stop_for_pair_local_plateau( - self, - state: _RoutingState, - *, - improved: bool, - ) -> bool: - if improved: - state.pair_local_plateau_count = 0 - return False - if self._results_all_reached_target(state) and state.best_conflict_edges <= 2: - # Once the run is fully reached-target and already in the final <=2-edge - # basin, another non-improving negotiated iteration is just churn before - # the bounded pair-local repair. - state.pair_local_plateau_count += 1 - return state.pair_local_plateau_count >= 1 - state.pair_local_plateau_count = 0 - return False - - def _update_repeated_conflict_state( - self, - state: _RoutingState, - review: _IterationReview, - ) -> bool: - current_signature = tuple(sorted(review.conflict_edges)) - repeated = ( - bool(current_signature) - and ( - current_signature == state.last_conflict_signature - or len(current_signature) == state.last_conflict_edge_count - ) - ) - state.repeated_conflict_count = state.repeated_conflict_count + 1 if repeated else 0 - state.last_conflict_signature = current_signature - state.last_conflict_edge_count = len(current_signature) - return state.repeated_conflict_count >= 2 - def _run_pair_local_attempt( self, state: _RoutingState, @@ -924,7 +653,12 @@ class PathFinder: for net_id in pair_order: net = state.net_specs[net_id] - guidance_seed, guidance_bonus, _ = self._guidance_for_result(incumbent_results.get(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, @@ -963,62 +697,6 @@ class PathFinder: return local_results, local_context.metrics.total_nodes_expanded - def _apply_pair_local_candidate( - self, - state: _RoutingState, - candidate_results: dict[str, RoutingResult], - incumbent_results: dict[str, RoutingResult], - incumbent_review: _IterationReview, - ) -> tuple[bool, _IterationReview]: - 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 - return True, candidate_review - - self._replace_installed_paths(state, incumbent_results) - return False, incumbent_review - - def _run_pair_local_target( - self, - state: _RoutingState, - target: _PairLocalTarget, - review: _IterationReview, - ) -> _IterationReview: - incumbent_results = dict(state.results) - incumbent_review = review - self.metrics.total_pair_local_search_pairs_considered += 1 - for pair_order in self._pair_local_attempt_orders(target): - 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 - accepted, next_review = self._apply_pair_local_candidate( - state, - candidate_results, - incumbent_results, - incumbent_review, - ) - if accepted: - return next_review - - state.results = incumbent_results - self._replace_installed_paths(state, incumbent_results) - return incumbent_review - def _run_pair_local_search(self, state: _RoutingState) -> None: state.results, _details_by_net, review = self._analyze_results( state.ordered_net_ids, @@ -1031,26 +709,53 @@ class PathFinder: return for target in targets[:2]: - review = self._run_pair_local_target(state, target, review) + 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, iteration: int, net_id: str, - *, - node_limit_override: int | None = None, - incumbent_fallback: RoutingResult | None = None, - ) -> tuple[RoutingResult, bool]: + ) -> RoutingResult: search = self.context.options.search congestion = self.context.options.congestion diagnostics = self.context.options.diagnostics net = state.net_specs[net_id] self.metrics.total_nets_routed += 1 - if node_limit_override is not None: - self.metrics.total_late_phase_capped_nets += 1 self.context.cost_evaluator.collision_engine.remove_path(net_id) - guidance_seed_present = False if iteration == 0 and state.initial_paths and net_id in state.initial_paths: self.metrics.total_warm_start_paths_used += 1 @@ -1058,18 +763,17 @@ class PathFinder: else: coll_model, _ = resolve_bend_geometry(search) skip_congestion = False - guidance_seed, guidance_bonus, guidance_seed_present = (None, 0.0, 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_seed, guidance_bonus, guidance_seed_present = self._guidance_for_result( - state.results.get(net_id) - ) - - if node_limit_override is not None and self._has_incumbent_fallback(incumbent_fallback): - return self._restore_incumbent_fallback(net_id, incumbent_fallback, guidance_seed_present) + 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, @@ -1080,7 +784,7 @@ class PathFinder: guidance_bonus=guidance_bonus, skip_congestion=skip_congestion, self_collision_check=(net_id in state.needs_self_collision_check), - node_limit=search.node_limit if node_limit_override is None else node_limit_override, + node_limit=search.node_limit, ) path = route_astar( net.start, @@ -1096,13 +800,9 @@ class PathFinder: state.accumulated_expanded_nodes.extend(self.metrics.last_expanded_nodes) if not path: - if self._has_incumbent_fallback(incumbent_fallback): - return self._restore_incumbent_fallback(net_id, incumbent_fallback, guidance_seed_present) - return RoutingResult(net_id=net_id, path=(), reached_target=False), guidance_seed_present + return RoutingResult(net_id=net_id, path=(), reached_target=False) reached_target = path[-1].end_port == net.target - if not reached_target and self._has_incumbent_fallback(incumbent_fallback): - return self._restore_incumbent_fallback(net_id, incumbent_fallback, guidance_seed_present) if reached_target: self.metrics.total_nets_reached_target += 1 report = None @@ -1112,14 +812,11 @@ class PathFinder: if report.self_collision_count > 0: state.needs_self_collision_check.add(net_id) - return ( - RoutingResult( - net_id=net_id, - path=tuple(path), - reached_target=reached_target, - report=RoutingReport() if report is None else report, - ), - guidance_seed_present, + return RoutingResult( + net_id=net_id, + path=tuple(path), + reached_target=reached_target, + report=RoutingReport() if report is None else report, ) def _run_iteration( @@ -1130,85 +827,24 @@ class PathFinder: iteration_callback: Callable[[int, dict[str, RoutingResult]], None] | None, ) -> _IterationReview | None: congestion = self.context.options.congestion - diagnostics = self.context.options.diagnostics 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) - iteration_penalty = self.context.congestion_penalty - routed_net_ids, capped_net_ids = self._build_iteration_reroute_plan(state, reroute_net_ids) + routed_net_ids = [net_id for net_id in state.ordered_net_ids if net_id in reroute_net_ids] self.metrics.total_nets_carried_forward += len(state.ordered_net_ids) - len(routed_net_ids) - iteration_before = {} - attempt_traces: list[IterationNetAttemptTrace] = [] - attempt_work: dict[str, int] = {} - if diagnostics.capture_iteration_trace: - iteration_before = self._capture_metric_totals(_ITERATION_TRACE_TOTALS) for net_id in routed_net_ids: if time.monotonic() - state.start_time > state.timeout_s: self.metrics.total_timeout_events += 1 return None - attempt_before = self._capture_metric_totals(_ATTEMPT_TRACE_TOTALS) - node_limit_override = None - incumbent_fallback = None - if net_id in capped_net_ids: - node_limit_override = min(self.context.options.search.node_limit, 1) - incumbent_fallback = state.results.get(net_id) - result, guidance_seed_present = self._route_net_once( - state, - iteration, - net_id, - node_limit_override=node_limit_override, - incumbent_fallback=incumbent_fallback, - ) + result = self._route_net_once(state, iteration, net_id) state.results[net_id] = result - attempt_after = self._capture_metric_totals(_ATTEMPT_TRACE_TOTALS) - deltas = self._metric_deltas(attempt_before, attempt_after) - attempt_work[net_id] = deltas["nodes_expanded"] + deltas["congestion_check_calls"] - attempt_traces.append( - IterationNetAttemptTrace( - net_id=net_id, - reached_target=result.reached_target, - nodes_expanded=deltas["nodes_expanded"], - congestion_check_calls=deltas["congestion_check_calls"], - pruned_closed_set=deltas["pruned_closed_set"], - pruned_cost=deltas["pruned_cost"], - pruned_hard_collision=deltas["pruned_hard_collision"], - guidance_seed_present=guidance_seed_present, - ) - ) - state.recent_attempt_work = attempt_work review = self._reverify_iteration_results(state) - self._update_pre_pair_candidate( - state, - iteration=iteration, - reroute_net_ids=reroute_net_ids, - routed_net_ids=routed_net_ids, - attempt_traces=attempt_traces, - review=review, - ) - if diagnostics.capture_iteration_trace: - iteration_after = self._capture_metric_totals(_ITERATION_TRACE_TOTALS) - deltas = self._metric_deltas(iteration_before, iteration_after) - self.iteration_trace.append( - IterationTraceEntry( - iteration=iteration, - congestion_penalty=iteration_penalty, - routed_net_ids=tuple(routed_net_ids), - completed_nets=len(review.completed_net_ids), - conflict_edges=len(review.conflict_edges), - total_dynamic_collisions=review.total_dynamic_collisions, - nodes_expanded=deltas["nodes_expanded"], - congestion_check_calls=deltas["congestion_check_calls"], - congestion_candidate_ids=deltas["congestion_candidate_ids"], - congestion_exact_pair_checks=deltas["congestion_exact_pair_checks"], - net_attempts=tuple(attempt_traces), - ) - ) if iteration_callback: iteration_callback(iteration, state.results) @@ -1237,28 +873,34 @@ class PathFinder: iteration_callback: Callable[[int, dict[str, RoutingResult]], None] | None, ) -> bool: congestion = self.context.options.congestion - reroute_net_ids = set(state.ordered_net_ids) for iteration in range(congestion.max_iterations): review = self._run_iteration( state, iteration, - reroute_net_ids, + set(state.ordered_net_ids), iteration_callback, ) if review is None: return True - improved = self._update_best_iteration(state, review) + self._update_best_iteration(state, review) if not any( result.outcome in {"colliding", "partial", "unroutable"} for result in state.results.values() ): return False - reroute_net_ids = self._next_reroute_net_ids(state, review) - if self._should_stop_for_pair_local_plateau(state, improved=improved): - return False - - if self._update_repeated_conflict_state(state, review): + current_signature = tuple(sorted(review.conflict_edges)) + repeated = ( + bool(current_signature) + and ( + current_signature == state.last_conflict_signature + or len(current_signature) == state.last_conflict_edge_count + ) + ) + state.repeated_conflict_count = state.repeated_conflict_count + 1 if repeated else 0 + state.last_conflict_signature = current_signature + state.last_conflict_edge_count = len(current_signature) + if state.repeated_conflict_count >= 2: return False self.context.congestion_penalty *= congestion.multiplier return False @@ -1331,8 +973,6 @@ class PathFinder: self.accumulated_expanded_nodes = [] self.conflict_trace = [] self.frontier_trace = [] - self.pre_pair_frontier_trace = None - self.iteration_trace = [] self.metrics.reset_totals() self.metrics.reset_per_route() @@ -1340,7 +980,21 @@ class PathFinder: timed_out = self._run_iterations(state, iteration_callback) self.accumulated_expanded_nodes = list(state.accumulated_expanded_nodes) self._restore_best_iteration(state) - self._analyze_restored_best(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: final_results = self._verify_results(state) diff --git a/inire/tests/example_scenarios.py b/inire/tests/example_scenarios.py index e60fa34..c7feb84 100644 --- a/inire/tests/example_scenarios.py +++ b/inire/tests/example_scenarios.py @@ -91,8 +91,6 @@ def _make_run_result( expanded_nodes=tuple(pathfinder.accumulated_expanded_nodes), conflict_trace=tuple(pathfinder.conflict_trace), frontier_trace=tuple(pathfinder.frontier_trace), - pre_pair_frontier_trace=pathfinder.pre_pair_frontier_trace, - iteration_trace=tuple(pathfinder.iteration_trace), ) @@ -426,14 +424,6 @@ def snapshot_example_07_no_warm_start() -> ScenarioSnapshot: ) -def snapshot_example_07_no_warm_start_seed43() -> ScenarioSnapshot: - return _snapshot_example_07_variant( - "example_07_large_scale_routing_no_warm_start_seed43", - warm_start_enabled=False, - seed=43, - ) - - def trace_example_07() -> RoutingRunResult: return _trace_example_07_variant(warm_start_enabled=True) @@ -442,10 +432,6 @@ def trace_example_07_no_warm_start() -> RoutingRunResult: return _trace_example_07_variant(warm_start_enabled=False) -def trace_example_07_no_warm_start_seed43() -> RoutingRunResult: - return _trace_example_07_variant(warm_start_enabled=False, seed=43) - - def _build_example_07_variant_stack( *, num_nets: int, @@ -453,8 +439,6 @@ def _build_example_07_variant_stack( warm_start_enabled: bool, capture_conflict_trace: bool = False, capture_frontier_trace: bool = False, - capture_iteration_trace: bool = False, - capture_pre_pair_frontier_trace: bool = False, ) -> tuple[CostEvaluator, AStarMetrics, PathFinder]: bounds = (0, 0, 1000, 1000) obstacles = [ @@ -497,8 +481,6 @@ def _build_example_07_variant_stack( "capture_expanded": True, "capture_conflict_trace": capture_conflict_trace, "capture_frontier_trace": capture_frontier_trace, - "capture_iteration_trace": capture_iteration_trace, - "capture_pre_pair_frontier_trace": capture_pre_pair_frontier_trace, "shuffle_nets": True, "seed": seed, "warm_start_enabled": warm_start_enabled, @@ -514,8 +496,6 @@ def _run_example_07_variant( warm_start_enabled: bool, capture_conflict_trace: bool = False, capture_frontier_trace: bool = False, - capture_iteration_trace: bool = False, - capture_pre_pair_frontier_trace: bool = False, ) -> RoutingRunResult: evaluator, metrics, pathfinder = _build_example_07_variant_stack( num_nets=num_nets, @@ -523,8 +503,6 @@ def _run_example_07_variant( warm_start_enabled=warm_start_enabled, capture_conflict_trace=capture_conflict_trace, capture_frontier_trace=capture_frontier_trace, - capture_iteration_trace=capture_iteration_trace, - capture_pre_pair_frontier_trace=capture_pre_pair_frontier_trace, ) def iteration_callback(idx: int, current_results: dict[str, RoutingResult]) -> None: @@ -541,12 +519,11 @@ def _snapshot_example_07_variant( name: str, *, warm_start_enabled: bool, - seed: int = 42, ) -> ScenarioSnapshot: t0 = perf_counter() run = _run_example_07_variant( num_nets=10, - seed=seed, + seed=42, warm_start_enabled=warm_start_enabled, ) t1 = perf_counter() @@ -556,16 +533,13 @@ def _snapshot_example_07_variant( def _trace_example_07_variant( *, warm_start_enabled: bool, - seed: int = 42, ) -> RoutingRunResult: return _run_example_07_variant( num_nets=10, - seed=seed, + seed=42, warm_start_enabled=warm_start_enabled, capture_conflict_trace=True, capture_frontier_trace=True, - capture_iteration_trace=True, - capture_pre_pair_frontier_trace=True, ) @@ -670,7 +644,6 @@ SCENARIO_SNAPSHOTS: tuple[tuple[str, ScenarioSnapshotRun], ...] = ( PERFORMANCE_SCENARIO_SNAPSHOTS: tuple[tuple[str, ScenarioSnapshotRun], ...] = ( ("example_07_large_scale_routing_no_warm_start", snapshot_example_07_no_warm_start), - ("example_07_large_scale_routing_no_warm_start_seed43", snapshot_example_07_no_warm_start_seed43), ) TRACE_SCENARIO_RUNS: tuple[tuple[str, TraceScenarioRun], ...] = ( @@ -680,7 +653,6 @@ TRACE_SCENARIO_RUNS: tuple[tuple[str, TraceScenarioRun], ...] = ( TRACE_PERFORMANCE_SCENARIO_RUNS: tuple[tuple[str, TraceScenarioRun], ...] = ( ("example_07_large_scale_routing_no_warm_start", trace_example_07_no_warm_start), - ("example_07_large_scale_routing_no_warm_start_seed43", trace_example_07_no_warm_start_seed43), ) diff --git a/inire/tests/test_api.py b/inire/tests/test_api.py index 926859c..0e9fdb3 100644 --- a/inire/tests/test_api.py +++ b/inire/tests/test_api.py @@ -3,7 +3,6 @@ import importlib import pytest from shapely.geometry import box -import inire.router._router as router_module from inire import ( CongestionOptions, DiagnosticsOptions, @@ -55,8 +54,6 @@ def test_route_problem_smoke() -> None: assert run.results_by_net["net1"].is_valid assert run.conflict_trace == () assert run.frontier_trace == () - assert run.pre_pair_frontier_trace is None - assert run.iteration_trace == () def test_route_problem_supports_configs_and_debug_data() -> None: @@ -127,8 +124,6 @@ def test_route_problem_supports_configs_and_debug_data() -> None: 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 - assert run.metrics.late_phase_capped_nets >= 0 - assert run.metrics.late_phase_capped_fallbacks >= 0 def test_iteration_callback_observes_reverified_conflicts() -> None: @@ -187,72 +182,6 @@ def test_capture_conflict_trace_preserves_route_outputs() -> None: assert [entry.stage for entry in run_with_trace.conflict_trace] == ["iteration", "restored_best", "final"] -def test_capture_iteration_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_iteration_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 len(run_with_trace.iteration_trace) == 1 - - -def test_capture_iteration_trace_records_iteration_and_attempt_deltas() -> 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_iteration_trace=True), - ), - ) - - entry = run.iteration_trace[0] - assert entry.iteration == 0 - assert entry.congestion_penalty == 100.0 - assert entry.routed_net_ids == ("horizontal", "vertical") - assert entry.completed_nets == 0 - assert entry.conflict_edges == 1 - assert entry.total_dynamic_collisions >= 2 - assert entry.nodes_expanded >= 0 - assert entry.congestion_check_calls >= 0 - assert entry.congestion_candidate_ids >= 0 - assert entry.congestion_exact_pair_checks >= 0 - assert len(entry.net_attempts) == 2 - assert [attempt.net_id for attempt in entry.net_attempts] == ["horizontal", "vertical"] - assert all(attempt.nodes_expanded >= 0 for attempt in entry.net_attempts) - assert all(attempt.congestion_check_calls >= 0 for attempt in entry.net_attempts) - assert all(not attempt.guidance_seed_present for attempt in entry.net_attempts) - assert sum(attempt.nodes_expanded for attempt in entry.net_attempts) == entry.nodes_expanded - assert sum(attempt.congestion_check_calls for attempt in entry.net_attempts) == entry.congestion_check_calls - - def test_capture_conflict_trace_records_component_pairs() -> None: problem = RoutingProblem( bounds=(0, 0, 100, 100), @@ -310,35 +239,6 @@ def test_capture_frontier_trace_preserves_route_outputs() -> None: assert {trace.net_id for trace in run_with_trace.frontier_trace} == {"horizontal", "vertical"} -def test_capture_pre_pair_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_pre_pair_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 run_with_trace.pre_pair_frontier_trace is None - - def test_capture_frontier_trace_records_prune_reasons() -> None: problem = RoutingProblem( bounds=(0, 0, 100, 100), @@ -385,220 +285,6 @@ def test_reverify_iterations_stop_early_on_stalled_conflict_graph() -> None: assert run.metrics.route_iterations < 10 -def test_reverify_iterations_limit_late_reroutes_to_conflicting_nets(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), - NetSpec("netB", Port(50, 10, 90), Port(50, 90, 90), width=2.0), - NetSpec("netC", Port(10, 20, 0), Port(90, 20, 0), width=2.0), - ), - ) - options = RoutingOptions( - congestion=CongestionOptions(max_iterations=10, warm_start_enabled=False), - refinement=RefinementOptions(enabled=False), - ) - evaluator = CostEvaluator(RoutingWorld(clearance=2.0), DangerMap(bounds=problem.bounds)) - pathfinder = PathFinder(AStarContext(evaluator, problem, options)) - colliding_a = RoutingResult( - net_id="netA", - path=(Straight.generate(Port(10, 50, 0), 80.0, 2.0, dilation=1.0),), - reached_target=True, - report=RoutingReport(dynamic_collision_count=1, total_length=80.0), - ) - colliding_b = RoutingResult( - net_id="netB", - path=(Straight.generate(Port(50, 10, 90), 80.0, 2.0, dilation=1.0),), - reached_target=True, - report=RoutingReport(dynamic_collision_count=1, total_length=80.0), - ) - completed_c = RoutingResult( - net_id="netC", - path=(Straight.generate(Port(10, 20, 0), 80.0, 2.0, dilation=1.0),), - reached_target=True, - report=RoutingReport(total_length=80.0), - ) - iterations_seen: list[int] = [] - reroute_sets: list[set[str]] = [] - - def fake_run_iteration(self, state, iteration, reroute_net_ids, iteration_callback): - _ = self - _ = iteration_callback - iterations_seen.append(iteration) - reroute_sets.append(set(reroute_net_ids)) - state.results = {"netA": colliding_a, "netB": colliding_b, "netC": completed_c} - return _IterationReview( - conflicting_nets={"netA", "netB"}, - conflict_edges={("netA", "netB")}, - completed_net_ids={"netC"}, - total_dynamic_collisions=2, - ) - - monkeypatch.setattr(PathFinder, "_run_iteration", fake_run_iteration) - monkeypatch.setattr(PathFinder, "_verify_results", lambda self, state: dict(state.results)) - monkeypatch.setattr(PathFinder, "_run_pair_local_search", lambda self, state: None) - - results = pathfinder.route_all() - - assert iterations_seen == [0, 1] - assert reroute_sets == [{"netA", "netB", "netC"}, {"netA", "netB"}] - assert results["netA"].outcome == "colliding" - assert results["netB"].outcome == "colliding" - assert results["netC"].reached_target - - -def test_run_iteration_orders_subset_reroutes_by_recent_work(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), - NetSpec("netB", Port(50, 10, 90), Port(50, 90, 90), width=2.0), - NetSpec("netC", Port(10, 20, 0), Port(90, 20, 0), width=2.0), - ), - ) - options = RoutingOptions( - congestion=CongestionOptions(max_iterations=2, warm_start_enabled=False, shuffle_nets=False), - refinement=RefinementOptions(enabled=False), - ) - evaluator = CostEvaluator(RoutingWorld(clearance=2.0), DangerMap(bounds=problem.bounds)) - pathfinder = PathFinder(AStarContext(evaluator, problem, options)) - state = pathfinder._prepare_state() - state.recent_attempt_work = {"netA": 200, "netB": 20} - route_order: list[str] = [] - - def fake_route_net_once(self, state, iteration, net_id, *, node_limit_override=None, incumbent_fallback=None): - _ = self - _ = state - _ = iteration - route_order.append(net_id) - assert node_limit_override is None - assert incumbent_fallback is None - return RoutingResult(net_id=net_id, path=(), reached_target=False), False - - def fake_reverify(self, state): - _ = self - _ = state - return _IterationReview( - conflicting_nets={"netA", "netB"}, - conflict_edges={("netA", "netB")}, - completed_net_ids=set(), - total_dynamic_collisions=2, - ) - - monkeypatch.setattr(PathFinder, "_route_net_once", fake_route_net_once) - monkeypatch.setattr(PathFinder, "_reverify_iteration_results", fake_reverify) - - review = pathfinder._run_iteration(state, 1, {"netA", "netB"}, None) - - assert review is not None - assert route_order == ["netB", "netA"] - - -def test_run_iteration_caps_two_heaviest_late_phase_nets(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), - NetSpec("netB", Port(50, 10, 90), Port(50, 90, 90), width=2.0), - NetSpec("netC", Port(10, 20, 0), Port(90, 20, 0), width=2.0), - NetSpec("netD", Port(10, 80, 0), Port(90, 80, 0), width=2.0), - NetSpec("netE", Port(10, 65, 0), Port(90, 65, 0), width=2.0), - ), - ) - options = RoutingOptions( - objective=ObjectiveWeights(bend_penalty=100.0), - congestion=CongestionOptions(max_iterations=2, warm_start_enabled=False, shuffle_nets=False), - refinement=RefinementOptions(enabled=False), - ) - evaluator = CostEvaluator(RoutingWorld(clearance=2.0), DangerMap(bounds=problem.bounds)) - pathfinder = PathFinder(AStarContext(evaluator, problem, options)) - state = pathfinder._prepare_state() - state.results = { - net_id: RoutingResult(net_id=net_id, path=(Straight.generate(spec.start, 80.0, 2.0, dilation=1.0),), reached_target=True) - for net_id, spec in state.net_specs.items() - } - state.best_conflict_edges = 2 - state.recent_attempt_work = {"netA": 20, "netB": 40, "netC": 400, "netD": 220} - incumbents = dict(state.results) - caps_by_net: dict[str, tuple[int | None, RoutingResult | None]] = {} - - def fake_route_net_once(self, state, iteration, net_id, *, node_limit_override=None, incumbent_fallback=None): - _ = self - _ = state - _ = iteration - caps_by_net[net_id] = (node_limit_override, incumbent_fallback) - return RoutingResult(net_id=net_id, path=(), reached_target=False), False - - def fake_reverify(self, state): - _ = self - _ = state - return _IterationReview( - conflicting_nets={"netA", "netB", "netC", "netD"}, - conflict_edges={("netA", "netB"), ("netC", "netD")}, - completed_net_ids=set(), - total_dynamic_collisions=2, - ) - - monkeypatch.setattr(PathFinder, "_route_net_once", fake_route_net_once) - monkeypatch.setattr(PathFinder, "_reverify_iteration_results", fake_reverify) - - review = pathfinder._run_iteration(state, 1, {"netA", "netB", "netC", "netD"}, None) - - assert review is not None - assert caps_by_net["netA"] == (None, None) - assert caps_by_net["netB"] == (None, None) - assert caps_by_net["netC"][0] == 1 - assert caps_by_net["netD"][0] == 1 - assert caps_by_net["netC"][1] is incumbents["netC"] - assert caps_by_net["netD"][1] is incumbents["netD"] - - -def test_route_net_once_skips_search_for_capped_incumbent_fallback(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( - objective=ObjectiveWeights(bend_penalty=100.0), - congestion=CongestionOptions(max_iterations=2, warm_start_enabled=False), - refinement=RefinementOptions(enabled=False), - ) - evaluator = CostEvaluator(RoutingWorld(clearance=2.0), DangerMap(bounds=problem.bounds)) - pathfinder = PathFinder(AStarContext(evaluator, problem, options)) - state = pathfinder._prepare_state() - incumbent = RoutingResult( - net_id="netA", - path=(Straight.generate(problem.nets[0].start, 80.0, 2.0, dilation=1.0),), - reached_target=True, - ) - state.results["netA"] = incumbent - installed: list[tuple[str, tuple[object, ...]]] = [] - - def fail_route_astar(*args, **kwargs): - raise AssertionError("route_astar should not run for capped incumbent fallback") - - def record_install(self, net_id, path): - _ = self - installed.append((net_id, tuple(path))) - - monkeypatch.setattr(router_module, "route_astar", fail_route_astar) - monkeypatch.setattr(PathFinder, "_install_path", record_install) - - result, guidance_seed_present = pathfinder._route_net_once( - state, - 1, - "netA", - node_limit_override=1, - incumbent_fallback=incumbent, - ) - - assert result is incumbent - assert guidance_seed_present is True - assert installed == [("netA", incumbent.path)] - assert pathfinder.metrics.total_late_phase_capped_nets == 1 - assert pathfinder.metrics.total_late_phase_capped_fallbacks == 1 - - def test_route_all_restores_best_iteration_snapshot(monkeypatch: pytest.MonkeyPatch) -> None: problem = RoutingProblem( bounds=(0, 0, 100, 100), diff --git a/inire/tests/test_astar.py b/inire/tests/test_astar.py index 445991f..3a0382c 100644 --- a/inire/tests/test_astar.py +++ b/inire/tests/test_astar.py @@ -288,9 +288,6 @@ def test_pair_local_context_clones_live_static_obstacles() -> None: last_conflict_signature=(), last_conflict_edge_count=0, repeated_conflict_count=0, - pair_local_plateau_count=0, - recent_attempt_work={}, - pre_pair_candidate=None, ) local_context = finder._build_pair_local_context(state, {}, ("pair_a", "pair_b")) diff --git a/inire/tests/test_example_performance.py b/inire/tests/test_example_performance.py index adf7bfb..d572228 100644 --- a/inire/tests/test_example_performance.py +++ b/inire/tests/test_example_performance.py @@ -6,13 +6,7 @@ from typing import TYPE_CHECKING import pytest -from inire.tests.example_scenarios import ( - SCENARIOS, - ScenarioOutcome, - snapshot_example_07_no_warm_start, - snapshot_example_07_no_warm_start_seed43, - trace_example_07_no_warm_start_seed43, -) +from inire.tests.example_scenarios import SCENARIOS, ScenarioOutcome, snapshot_example_07_no_warm_start if TYPE_CHECKING: from collections.abc import Callable @@ -22,7 +16,6 @@ RUN_PERFORMANCE = os.environ.get("INIRE_RUN_PERFORMANCE") == "1" PERFORMANCE_REPEATS = 3 REGRESSION_FACTOR = 1.5 NO_WARM_START_REGRESSION_SECONDS = 15.0 -NO_WARM_START_SEED43_REGRESSION_SECONDS = 20.0 # Baselines are measured from clean 6a28dcf-style runs without plotting. BASELINE_SECONDS = { @@ -92,32 +85,3 @@ def test_example_07_no_warm_start_runtime_regression() -> None: f"{snapshot.duration_s:.4f}s exceeded guardrail " f"{NO_WARM_START_REGRESSION_SECONDS:.1f}s" ) - - -@pytest.mark.performance -@pytest.mark.skipif(not RUN_PERFORMANCE, reason="set INIRE_RUN_PERFORMANCE=1 to run runtime regression checks") -def test_example_07_no_warm_start_seed43_runtime_regression() -> None: - snapshot = snapshot_example_07_no_warm_start_seed43() - run = trace_example_07_no_warm_start_seed43() - - assert snapshot.total_results == 10 - assert snapshot.valid_results == 10 - assert snapshot.reached_targets == 10 - assert snapshot.metrics.warm_start_paths_built == 0 - assert snapshot.metrics.warm_start_paths_used == 0 - assert snapshot.metrics.pair_local_search_pairs_considered >= 1 - assert snapshot.metrics.pair_local_search_accepts >= 1 - assert snapshot.duration_s <= NO_WARM_START_SEED43_REGRESSION_SECONDS, ( - "example_07_large_scale_routing_no_warm_start_seed43 runtime " - f"{snapshot.duration_s:.4f}s exceeded guardrail " - f"{NO_WARM_START_SEED43_REGRESSION_SECONDS:.1f}s" - ) - - assert run.iteration_trace - assert len(run.results_by_net) == 10 - assert sum(result.is_valid for result in run.results_by_net.values()) == 10 - assert sum(result.reached_target for result in run.results_by_net.values()) == 10 - assert run.metrics.warm_start_paths_built == 0 - assert run.metrics.warm_start_paths_used == 0 - assert run.metrics.pair_local_search_pairs_considered >= 1 - assert run.metrics.pair_local_search_accepts >= 1 diff --git a/inire/tests/test_example_regressions.py b/inire/tests/test_example_regressions.py index c7a10d9..a7c3b73 100644 --- a/inire/tests/test_example_regressions.py +++ b/inire/tests/test_example_regressions.py @@ -75,7 +75,6 @@ def test_example_07_no_warm_start_trace_finishes_without_conflict_edges() -> Non assert sum(result.reached_target for result in run.results_by_net.values()) == 10 assert run.metrics.pair_local_search_pairs_considered >= 1 assert run.metrics.pair_local_search_accepts >= 1 - assert run.pre_pair_frontier_trace is not None final_entry = run.conflict_trace[-1] assert final_entry.stage == "final" diff --git a/inire/tests/test_performance_reporting.py b/inire/tests/test_performance_reporting.py index 9ded29a..b594308 100644 --- a/inire/tests/test_performance_reporting.py +++ b/inire/tests/test_performance_reporting.py @@ -48,8 +48,6 @@ def test_snapshot_example_01_exposes_metrics() -> None: 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 - assert snapshot.metrics.late_phase_capped_nets >= 0 - assert snapshot.metrics.late_phase_capped_fallbacks >= 0 def test_record_performance_baseline_script_writes_selected_scenario(tmp_path: Path) -> None: @@ -278,55 +276,6 @@ def test_record_frontier_trace_script_writes_selected_scenario(tmp_path: Path) - assert (tmp_path / "frontier_trace.md").exists() -def test_record_iteration_trace_script_writes_selected_scenario(tmp_path: Path) -> None: - repo_root = Path(__file__).resolve().parents[2] - script_path = repo_root / "scripts" / "record_iteration_trace.py" - - subprocess.run( - [ - sys.executable, - str(script_path), - "--include-performance-only", - "--scenario", - "example_07_large_scale_routing_no_warm_start", - "--output-dir", - str(tmp_path), - ], - check=True, - ) - - payload = json.loads((tmp_path / "iteration_trace.json").read_text()) - assert payload["generated_at"] - assert payload["generator"] == "scripts/record_iteration_trace.py" - assert [entry["name"] for entry in payload["scenarios"]] == ["example_07_large_scale_routing_no_warm_start"] - assert (tmp_path / "iteration_trace.md").exists() - - -def test_record_pre_pair_frontier_trace_script_writes_selected_scenario(tmp_path: Path) -> None: - repo_root = Path(__file__).resolve().parents[2] - script_path = repo_root / "scripts" / "record_pre_pair_frontier_trace.py" - - subprocess.run( - [ - sys.executable, - str(script_path), - "--include-performance-only", - "--scenario", - "example_07_large_scale_routing_no_warm_start", - "--output-dir", - str(tmp_path), - ], - check=True, - ) - - payload = json.loads((tmp_path / "pre_pair_frontier_trace.json").read_text()) - assert payload["generated_at"] - assert payload["generator"] == "scripts/record_pre_pair_frontier_trace.py" - assert [entry["name"] for entry in payload["scenarios"]] == ["example_07_large_scale_routing_no_warm_start"] - assert payload["scenarios"][0]["pre_pair_frontier_trace"] is not None - assert (tmp_path / "pre_pair_frontier_trace.md").exists() - - def test_characterize_pair_local_search_script_writes_outputs(tmp_path: Path) -> None: repo_root = Path(__file__).resolve().parents[2] script_path = repo_root / "scripts" / "characterize_pair_local_search.py" diff --git a/scripts/record_iteration_trace.py b/scripts/record_iteration_trace.py deleted file mode 100644 index 7691558..0000000 --- a/scripts/record_iteration_trace.py +++ /dev/null @@ -1,186 +0,0 @@ -#!/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: - perf_registry = dict(TRACE_PERFORMANCE_SCENARIO_RUNS) - return ( - ( - "example_07_large_scale_routing_no_warm_start", - perf_registry["example_07_large_scale_routing_no_warm_start"], - ), - ( - "example_07_large_scale_routing_no_warm_start_seed43", - perf_registry["example_07_large_scale_routing_no_warm_start_seed43"], - ), - ) - - 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 iteration-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), - "iteration_trace": [asdict(entry) for entry in result.iteration_trace], - } - ) - return { - "generated_at": datetime.now().astimezone().isoformat(timespec="seconds"), - "generator": "scripts/record_iteration_trace.py", - "scenarios": scenarios, - } - - -def _render_markdown(payload: dict[str, object]) -> str: - lines = [ - "# Iteration 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.", - "", - "| Iteration | Penalty | Routed Nets | Completed | Conflict Edges | Dynamic Collisions | Nodes | Congestion Checks | Candidate Ids | Exact Pairs |", - "| --: | --: | --: | --: | --: | --: | --: | --: | --: | --: |", - ] - ) - - net_node_counts: Counter[str] = Counter() - net_check_counts: Counter[str] = Counter() - for entry in scenario["iteration_trace"]: - lines.append( - "| " - f"{entry['iteration']} | " - f"{entry['congestion_penalty']:.1f} | " - f"{len(entry['routed_net_ids'])} | " - f"{entry['completed_nets']} | " - f"{entry['conflict_edges']} | " - f"{entry['total_dynamic_collisions']} | " - f"{entry['nodes_expanded']} | " - f"{entry['congestion_check_calls']} | " - f"{entry['congestion_candidate_ids']} | " - f"{entry['congestion_exact_pair_checks']} |" - ) - for attempt in entry["net_attempts"]: - net_node_counts[attempt["net_id"]] += attempt["nodes_expanded"] - net_check_counts[attempt["net_id"]] += attempt["congestion_check_calls"] - - lines.extend(["", "Top nets by iteration-attributed nodes expanded:", ""]) - if net_node_counts: - for net_id, count in net_node_counts.most_common(10): - lines.append(f"- `{net_id}`: {count}") - else: - lines.append("- None") - - lines.extend(["", "Top nets by iteration-attributed congestion checks:", ""]) - if net_check_counts: - for net_id, count in net_check_counts.most_common(10): - lines.append(f"- `{net_id}`: {count}") - else: - lines.append("- None") - - lines.append("") - - return "\n".join(lines) - - -def main() -> None: - parser = argparse.ArgumentParser(description="Record iteration-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 iteration_trace.json and iteration_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 / "iteration_trace.json" - markdown_path = output_dir / "iteration_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_pre_pair_frontier_trace.py b/scripts/record_pre_pair_frontier_trace.py deleted file mode 100644 index 398fc0d..0000000 --- a/scripts/record_pre_pair_frontier_trace.py +++ /dev/null @@ -1,191 +0,0 @@ -#!/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: - perf_registry = dict(TRACE_PERFORMANCE_SCENARIO_RUNS) - return ( - ( - "example_07_large_scale_routing_no_warm_start", - perf_registry["example_07_large_scale_routing_no_warm_start"], - ), - ( - "example_07_large_scale_routing_no_warm_start_seed43", - perf_registry["example_07_large_scale_routing_no_warm_start_seed43"], - ), - ) - - 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 pre-pair frontier-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), - "pre_pair_frontier_trace": None - if result.pre_pair_frontier_trace is None - else asdict(result.pre_pair_frontier_trace), - } - ) - return { - "generated_at": datetime.now().astimezone().isoformat(timespec="seconds"), - "generator": "scripts/record_pre_pair_frontier_trace.py", - "scenarios": scenarios, - } - - -def _render_markdown(payload: dict[str, object]) -> str: - lines = [ - "# Pre-Pair 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.", - "", - ] - ) - trace = scenario["pre_pair_frontier_trace"] - if trace is None: - lines.extend(["No pre-pair frontier trace captured.", ""]) - continue - - lines.extend( - [ - f"Captured iteration: `{trace['iteration']}`", - "", - f"Conflict edges: `{trace['conflict_edges']}`", - "", - "| Net | Nodes | Checks | Closed-Set | Cost | Hard Collision | Guidance Seed | Frontier Samples |", - "| :-- | --: | --: | --: | --: | --: | :--: | --: |", - ] - ) - reason_counts: Counter[str] = Counter() - for net_trace in trace["nets"]: - frontier = net_trace["frontier"] - lines.append( - "| " - f"{net_trace['net_id']} | " - f"{net_trace['nodes_expanded']} | " - f"{net_trace['congestion_check_calls']} | " - f"{net_trace['pruned_closed_set']} | " - f"{net_trace['pruned_cost']} | " - f"{net_trace['pruned_hard_collision']} | " - f"{'yes' if net_trace['guidance_seed_present'] else 'no'} | " - f"{len(frontier['samples'])} |" - ) - reason_counts["closed_set"] += frontier["pruned_closed_set"] - reason_counts["hard_collision"] += frontier["pruned_hard_collision"] - reason_counts["self_collision"] += frontier["pruned_self_collision"] - reason_counts["cost"] += frontier["pruned_cost"] - - lines.extend(["", "Frontier 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.append("") - - return "\n".join(lines) - - -def main() -> None: - parser = argparse.ArgumentParser(description="Record pre-pair 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 pre_pair_frontier_trace.json and .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 / "pre_pair_frontier_trace.json" - markdown_path = output_dir / "pre_pair_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()