Optimize late no-warm reroutes
This commit is contained in:
parent
2c3aa90544
commit
46e7e13059
19 changed files with 2086 additions and 307 deletions
32
DOCS.md
32
DOCS.md
|
|
@ -131,6 +131,7 @@ Use `RoutingProblem.initial_paths` to provide semantic per-net seeds. Seeds are
|
|||
| `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
|
||||
|
||||
|
|
@ -187,7 +188,30 @@ 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. Iteration Trace
|
||||
## 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`.
|
||||
|
||||
|
|
@ -217,7 +241,7 @@ Trace types:
|
|||
|
||||
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.
|
||||
|
||||
## 10. RouteMetrics
|
||||
## 11. RouteMetrics
|
||||
|
||||
`RoutingRunResult.metrics` is an immutable per-run snapshot.
|
||||
|
||||
|
|
@ -297,13 +321,15 @@ 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_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, `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.
|
||||
|
||||
## 11. Tuning Notes
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"generated_at": "2026-04-02T16:46:00-07:00",
|
||||
"generated_at": "2026-04-02T18:51:01-07:00",
|
||||
"generator": "scripts/record_iteration_trace.py",
|
||||
"scenarios": [
|
||||
{
|
||||
|
|
@ -514,10 +514,10 @@
|
|||
"iteration": 4,
|
||||
"net_attempts": [
|
||||
{
|
||||
"congestion_check_calls": 43,
|
||||
"congestion_check_calls": 30,
|
||||
"guidance_seed_present": true,
|
||||
"net_id": "net_00",
|
||||
"nodes_expanded": 10,
|
||||
"net_id": "net_07",
|
||||
"nodes_expanded": 7,
|
||||
"pruned_closed_set": 1,
|
||||
"pruned_cost": 0,
|
||||
"pruned_hard_collision": 0,
|
||||
|
|
@ -534,10 +534,10 @@
|
|||
"reached_target": true
|
||||
},
|
||||
{
|
||||
"congestion_check_calls": 30,
|
||||
"congestion_check_calls": 43,
|
||||
"guidance_seed_present": true,
|
||||
"net_id": "net_07",
|
||||
"nodes_expanded": 7,
|
||||
"net_id": "net_00",
|
||||
"nodes_expanded": 10,
|
||||
"pruned_closed_set": 1,
|
||||
"pruned_cost": 0,
|
||||
"pruned_hard_collision": 0,
|
||||
|
|
@ -556,9 +556,9 @@
|
|||
],
|
||||
"nodes_expanded": 81,
|
||||
"routed_net_ids": [
|
||||
"net_00",
|
||||
"net_06",
|
||||
"net_07",
|
||||
"net_06",
|
||||
"net_00",
|
||||
"net_01"
|
||||
],
|
||||
"total_dynamic_collisions": 10
|
||||
|
|
@ -589,7 +589,7 @@
|
|||
"danger_map_cache_misses": 6063,
|
||||
"danger_map_lookup_calls": 17610,
|
||||
"danger_map_query_calls": 6063,
|
||||
"danger_map_total_ns": 174709728,
|
||||
"danger_map_total_ns": 171226180,
|
||||
"dynamic_grid_rebuilds": 0,
|
||||
"dynamic_path_objects_added": 399,
|
||||
"dynamic_path_objects_removed": 351,
|
||||
|
|
@ -607,6 +607,8 @@
|
|||
"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,
|
||||
|
|
@ -645,7 +647,7 @@
|
|||
"refinement_windows_considered": 0,
|
||||
"route_iterations": 5,
|
||||
"score_component_calls": 6181,
|
||||
"score_component_total_ns": 195118641,
|
||||
"score_component_total_ns": 191650546,
|
||||
"static_net_tree_rebuilds": 1,
|
||||
"static_raw_tree_rebuilds": 1,
|
||||
"static_safe_cache_hits": 1170,
|
||||
|
|
@ -1062,26 +1064,6 @@
|
|||
"congestion_penalty": 274.4,
|
||||
"iteration": 3,
|
||||
"net_attempts": [
|
||||
{
|
||||
"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": 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": 36,
|
||||
"guidance_seed_present": true,
|
||||
|
|
@ -1092,6 +1074,16 @@
|
|||
"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,
|
||||
|
|
@ -1102,6 +1094,16 @@
|
|||
"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,
|
||||
|
|
@ -1113,11 +1115,11 @@
|
|||
"reached_target": true
|
||||
},
|
||||
{
|
||||
"congestion_check_calls": 30,
|
||||
"congestion_check_calls": 35,
|
||||
"guidance_seed_present": true,
|
||||
"net_id": "net_07",
|
||||
"nodes_expanded": 7,
|
||||
"pruned_closed_set": 1,
|
||||
"net_id": "net_06",
|
||||
"nodes_expanded": 8,
|
||||
"pruned_closed_set": 0,
|
||||
"pruned_cost": 0,
|
||||
"pruned_hard_collision": 0,
|
||||
"reached_target": true
|
||||
|
|
@ -1125,34 +1127,24 @@
|
|||
],
|
||||
"nodes_expanded": 54,
|
||||
"routed_net_ids": [
|
||||
"net_06",
|
||||
"net_09",
|
||||
"net_03",
|
||||
"net_07",
|
||||
"net_02",
|
||||
"net_09",
|
||||
"net_08",
|
||||
"net_07"
|
||||
"net_06"
|
||||
],
|
||||
"total_dynamic_collisions": 15
|
||||
},
|
||||
{
|
||||
"completed_nets": 6,
|
||||
"conflict_edges": 2,
|
||||
"congestion_candidate_ids": 1126,
|
||||
"congestion_check_calls": 550,
|
||||
"congestion_exact_pair_checks": 884,
|
||||
"congestion_candidate_ids": 1025,
|
||||
"congestion_check_calls": 505,
|
||||
"congestion_exact_pair_checks": 829,
|
||||
"congestion_penalty": 384.15999999999997,
|
||||
"iteration": 4,
|
||||
"net_attempts": [
|
||||
{
|
||||
"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": 30,
|
||||
"guidance_seed_present": true,
|
||||
|
|
@ -1163,26 +1155,6 @@
|
|||
"pruned_hard_collision": 0,
|
||||
"reached_target": true
|
||||
},
|
||||
{
|
||||
"congestion_check_calls": 91,
|
||||
"guidance_seed_present": true,
|
||||
"net_id": "net_08",
|
||||
"nodes_expanded": 18,
|
||||
"pruned_closed_set": 8,
|
||||
"pruned_cost": 0,
|
||||
"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": 32,
|
||||
"guidance_seed_present": true,
|
||||
|
|
@ -1193,6 +1165,16 @@
|
|||
"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,
|
||||
|
|
@ -1202,25 +1184,45 @@
|
|||
"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": 142,
|
||||
"nodes_expanded": 136,
|
||||
"routed_net_ids": [
|
||||
"net_06",
|
||||
"net_07",
|
||||
"net_08",
|
||||
"net_09",
|
||||
"net_02",
|
||||
"net_03"
|
||||
"net_06",
|
||||
"net_03",
|
||||
"net_09",
|
||||
"net_08"
|
||||
],
|
||||
"total_dynamic_collisions": 10
|
||||
},
|
||||
{
|
||||
"completed_nets": 6,
|
||||
"conflict_edges": 2,
|
||||
"congestion_candidate_ids": 3377,
|
||||
"congestion_check_calls": 1477,
|
||||
"congestion_exact_pair_checks": 2666,
|
||||
"congestion_candidate_ids": 419,
|
||||
"congestion_check_calls": 171,
|
||||
"congestion_exact_pair_checks": 287,
|
||||
"congestion_penalty": 537.824,
|
||||
"iteration": 5,
|
||||
"net_attempts": [
|
||||
|
|
@ -1234,16 +1236,6 @@
|
|||
"pruned_hard_collision": 0,
|
||||
"reached_target": true
|
||||
},
|
||||
{
|
||||
"congestion_check_calls": 511,
|
||||
"guidance_seed_present": true,
|
||||
"net_id": "net_06",
|
||||
"nodes_expanded": 137,
|
||||
"pruned_closed_set": 13,
|
||||
"pruned_cost": 73,
|
||||
"pruned_hard_collision": 0,
|
||||
"reached_target": true
|
||||
},
|
||||
{
|
||||
"congestion_check_calls": 86,
|
||||
"guidance_seed_present": true,
|
||||
|
|
@ -1255,21 +1247,31 @@
|
|||
"reached_target": true
|
||||
},
|
||||
{
|
||||
"congestion_check_calls": 795,
|
||||
"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": 236,
|
||||
"pruned_closed_set": 28,
|
||||
"pruned_cost": 149,
|
||||
"nodes_expanded": 0,
|
||||
"pruned_closed_set": 0,
|
||||
"pruned_cost": 0,
|
||||
"pruned_hard_collision": 0,
|
||||
"reached_target": true
|
||||
}
|
||||
],
|
||||
"nodes_expanded": 406,
|
||||
"nodes_expanded": 33,
|
||||
"routed_net_ids": [
|
||||
"net_07",
|
||||
"net_06",
|
||||
"net_02",
|
||||
"net_06",
|
||||
"net_03"
|
||||
],
|
||||
"total_dynamic_collisions": 10
|
||||
|
|
@ -1277,74 +1279,76 @@
|
|||
],
|
||||
"metrics": {
|
||||
"congestion_cache_hits": 8,
|
||||
"congestion_cache_misses": 3881,
|
||||
"congestion_candidate_ids": 9232,
|
||||
"congestion_candidate_nets": 8483,
|
||||
"congestion_candidate_precheck_hits": 2207,
|
||||
"congestion_candidate_precheck_misses": 1793,
|
||||
"congestion_candidate_precheck_skips": 111,
|
||||
"congestion_check_calls": 3881,
|
||||
"congestion_exact_pair_checks": 7234,
|
||||
"congestion_grid_net_cache_hits": 2169,
|
||||
"congestion_grid_net_cache_misses": 3238,
|
||||
"congestion_grid_span_cache_hits": 1997,
|
||||
"congestion_grid_span_cache_misses": 1628,
|
||||
"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": 2311,
|
||||
"congestion_net_envelope_cache_misses": 3376,
|
||||
"congestion_presence_cache_hits": 2443,
|
||||
"congestion_presence_cache_misses": 2009,
|
||||
"congestion_presence_skips": 452,
|
||||
"danger_map_cache_hits": 14603,
|
||||
"danger_map_cache_misses": 6814,
|
||||
"danger_map_lookup_calls": 21417,
|
||||
"danger_map_query_calls": 6814,
|
||||
"danger_map_total_ns": 181736341,
|
||||
"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": 8062.5,
|
||||
"guidance_bonus_applied_bend90": 3187.5,
|
||||
"guidance_bonus_applied": 7562.5,
|
||||
"guidance_bonus_applied_bend90": 2937.5,
|
||||
"guidance_bonus_applied_sbend": 250.0,
|
||||
"guidance_bonus_applied_straight": 4625.0,
|
||||
"guidance_match_moves": 129,
|
||||
"guidance_match_moves_bend90": 51,
|
||||
"guidance_bonus_applied_straight": 4375.0,
|
||||
"guidance_match_moves": 121,
|
||||
"guidance_match_moves_bend90": 47,
|
||||
"guidance_match_moves_sbend": 4,
|
||||
"guidance_match_moves_straight": 74,
|
||||
"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,
|
||||
"move_cache_abs_hits": 1915,
|
||||
"move_cache_abs_misses": 6136,
|
||||
"move_cache_rel_hits": 5505,
|
||||
"move_cache_rel_misses": 631,
|
||||
"moves_added": 7121,
|
||||
"moves_generated": 8051,
|
||||
"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": 46,
|
||||
"nets_reached_target": 44,
|
||||
"nets_routed": 46,
|
||||
"nodes_expanded": 1582,
|
||||
"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": 399,
|
||||
"pruned_cost": 531,
|
||||
"pruned_closed_set": 354,
|
||||
"pruned_cost": 309,
|
||||
"pruned_hard_collision": 0,
|
||||
"ray_cast_calls": 5077,
|
||||
"ray_cast_calls_expand_forward": 1536,
|
||||
"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": 3522,
|
||||
"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": 316,
|
||||
"ray_cast_candidate_bounds": 170,
|
||||
"ray_cast_exact_geometry_checks": 0,
|
||||
"refine_path_calls": 10,
|
||||
"refinement_candidate_side_extents": 0,
|
||||
|
|
@ -1355,17 +1359,17 @@
|
|||
"refinement_static_bounds_checked": 0,
|
||||
"refinement_windows_considered": 0,
|
||||
"route_iterations": 6,
|
||||
"score_component_calls": 7670,
|
||||
"score_component_total_ns": 205617403,
|
||||
"score_component_calls": 5962,
|
||||
"score_component_total_ns": 164785883,
|
||||
"static_net_tree_rebuilds": 1,
|
||||
"static_raw_tree_rebuilds": 1,
|
||||
"static_safe_cache_hits": 1869,
|
||||
"static_safe_cache_hits": 1276,
|
||||
"static_tree_rebuilds": 1,
|
||||
"timeout_events": 0,
|
||||
"verify_dynamic_candidate_nets": 1906,
|
||||
"verify_dynamic_exact_pair_checks": 571,
|
||||
"verify_path_report_calls": 176,
|
||||
"verify_static_buffer_ops": 813,
|
||||
"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,
|
||||
|
|
@ -1376,7 +1380,7 @@
|
|||
"visibility_point_queries": 0,
|
||||
"visibility_tangent_candidate_corner_checks": 6,
|
||||
"visibility_tangent_candidate_ray_tests": 6,
|
||||
"visibility_tangent_candidate_scans": 1536,
|
||||
"visibility_tangent_candidate_scans": 1159,
|
||||
"warm_start_paths_built": 0,
|
||||
"warm_start_paths_used": 0
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Iteration Trace
|
||||
|
||||
Generated at 2026-04-02T16:46:00-07:00 by `scripts/record_iteration_trace.py`.
|
||||
Generated at 2026-04-02T18:51:01-07:00 by `scripts/record_iteration_trace.py`.
|
||||
|
||||
## example_07_large_scale_routing_no_warm_start
|
||||
|
||||
|
|
@ -50,17 +50,17 @@ Results: 10 valid / 10 reached / 10 total.
|
|||
| 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 | 142 | 550 | 1126 | 884 |
|
||||
| 5 | 537.8 | 4 | 6 | 2 | 10 | 406 | 1477 | 3377 | 2666 |
|
||||
| 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_03`: 435
|
||||
- `net_09`: 250
|
||||
- `net_06`: 242
|
||||
- `net_03`: 199
|
||||
- `net_00`: 177
|
||||
- `net_08`: 172
|
||||
- `net_08`: 166
|
||||
- `net_07`: 140
|
||||
- `net_06`: 105
|
||||
- `net_02`: 79
|
||||
- `net_01`: 65
|
||||
- `net_05`: 12
|
||||
|
|
@ -68,11 +68,11 @@ Top nets by iteration-attributed nodes expanded:
|
|||
|
||||
Top nets by iteration-attributed congestion checks:
|
||||
|
||||
- `net_03`: 1434
|
||||
- `net_06`: 893
|
||||
- `net_03`: 639
|
||||
- `net_07`: 454
|
||||
- `net_08`: 328
|
||||
- `net_06`: 382
|
||||
- `net_02`: 290
|
||||
- `net_08`: 283
|
||||
- `net_09`: 178
|
||||
- `net_01`: 135
|
||||
- `net_00`: 82
|
||||
|
|
|
|||
|
|
@ -3662,3 +3662,80 @@ Findings:
|
|||
- 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`.
|
||||
|
|
|
|||
|
|
@ -5,30 +5,23 @@ 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 no-warm `example_07` canaries remain performance-only and are tracked through targeted diffs plus the trace artifacts.
|
||||
|
||||
Use `scripts/characterize_pair_local_search.py` for the tracked no-warm sweep in `docs/pair_local_characterization.json` and `docs/pair_local_characterization.md`.
|
||||
Use `scripts/record_iteration_trace.py` for the current seed-42 vs seed-43 negotiated-congestion attribution in `docs/iteration_trace.json` and `docs/iteration_trace.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.0038 | 1 | 1 | 1 | 1 | 1 | 2 | 10 | 11 | 7 | 0 | 0 | 0 | 4 |
|
||||
| example_02_congestion_resolution | 0.3614 | 3 | 3 | 3 | 1 | 3 | 366 | 1164 | 1413 | 668 | 0 | 0 | 0 | 38 |
|
||||
| example_03_locked_paths | 0.1953 | 2 | 2 | 2 | 2 | 2 | 191 | 657 | 904 | 307 | 0 | 0 | 0 | 16 |
|
||||
| example_04_sbends_and_radii | 0.0277 | 2 | 2 | 2 | 1 | 2 | 15 | 70 | 123 | 65 | 0 | 0 | 0 | 8 |
|
||||
| example_05_orientation_stress | 0.2537 | 3 | 3 | 3 | 2 | 5 | 297 | 1274 | 1680 | 689 | 0 | 0 | 146 | 17 |
|
||||
| example_06_bend_collision_models | 0.2103 | 3 | 3 | 3 | 3 | 3 | 240 | 682 | 1026 | 629 | 0 | 0 | 0 | 12 |
|
||||
| example_07_large_scale_routing | 0.2074 | 10 | 10 | 10 | 1 | 10 | 78 | 383 | 372 | 227 | 0 | 0 | 0 | 40 |
|
||||
| example_08_custom_bend_geometry | 0.0186 | 2 | 2 | 2 | 2 | 2 | 18 | 56 | 78 | 56 | 0 | 0 | 0 | 8 |
|
||||
| example_09_unroutable_best_effort | 0.0079 | 1 | 0 | 0 | 1 | 1 | 3 | 13 | 16 | 10 | 0 | 0 | 0 | 1 |
|
||||
| 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 |
|
||||
|
||||
## 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.
|
||||
|
||||
The current accepted branch keeps the post-loop pair-local scratch reroute and now also narrows late negotiated reroutes to the current conflict set once all nets already reach target and only a few conflict edges remain. That keeps both no-warm `example_07` seeds at `10/10/10` while reducing main-loop work before pair-local repair.
|
||||
|
||||
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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": "scripts/record_performance_baseline.py",
|
||||
"scenarios": [
|
||||
{
|
||||
"duration_s": 0.003825429128482938,
|
||||
"duration_s": 0.003715757979080081,
|
||||
"metrics": {
|
||||
"congestion_cache_hits": 0,
|
||||
"congestion_cache_misses": 0,
|
||||
|
|
@ -47,6 +47,8 @@
|
|||
"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,
|
||||
|
|
@ -85,7 +87,7 @@
|
|||
"refinement_windows_considered": 0,
|
||||
"route_iterations": 1,
|
||||
"score_component_calls": 11,
|
||||
"score_component_total_ns": 16571,
|
||||
"score_component_total_ns": 16864,
|
||||
"static_net_tree_rebuilds": 1,
|
||||
"static_raw_tree_rebuilds": 0,
|
||||
"static_safe_cache_hits": 1,
|
||||
|
|
@ -93,7 +95,7 @@
|
|||
"timeout_events": 0,
|
||||
"verify_dynamic_candidate_nets": 0,
|
||||
"verify_dynamic_exact_pair_checks": 0,
|
||||
"verify_path_report_calls": 4,
|
||||
"verify_path_report_calls": 5,
|
||||
"verify_static_buffer_ops": 0,
|
||||
"visibility_builds": 0,
|
||||
"visibility_corner_hits_exact": 0,
|
||||
|
|
@ -115,7 +117,7 @@
|
|||
"valid_results": 1
|
||||
},
|
||||
{
|
||||
"duration_s": 0.36141274496912956,
|
||||
"duration_s": 0.33605348505079746,
|
||||
"metrics": {
|
||||
"congestion_cache_hits": 0,
|
||||
"congestion_cache_misses": 0,
|
||||
|
|
@ -159,6 +161,8 @@
|
|||
"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,
|
||||
|
|
@ -197,15 +201,15 @@
|
|||
"refinement_windows_considered": 10,
|
||||
"route_iterations": 1,
|
||||
"score_component_calls": 976,
|
||||
"score_component_total_ns": 1143187,
|
||||
"score_component_total_ns": 1109505,
|
||||
"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": 88,
|
||||
"verify_dynamic_exact_pair_checks": 86,
|
||||
"verify_path_report_calls": 38,
|
||||
"verify_dynamic_candidate_nets": 92,
|
||||
"verify_dynamic_exact_pair_checks": 90,
|
||||
"verify_path_report_calls": 41,
|
||||
"verify_static_buffer_ops": 0,
|
||||
"visibility_builds": 0,
|
||||
"visibility_corner_hits_exact": 0,
|
||||
|
|
@ -227,7 +231,7 @@
|
|||
"valid_results": 3
|
||||
},
|
||||
{
|
||||
"duration_s": 0.19532882701605558,
|
||||
"duration_s": 0.18771230895072222,
|
||||
"metrics": {
|
||||
"congestion_cache_hits": 0,
|
||||
"congestion_cache_misses": 0,
|
||||
|
|
@ -271,6 +275,8 @@
|
|||
"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,
|
||||
|
|
@ -309,16 +315,16 @@
|
|||
"refinement_windows_considered": 2,
|
||||
"route_iterations": 2,
|
||||
"score_component_calls": 504,
|
||||
"score_component_total_ns": 565663,
|
||||
"score_component_total_ns": 546567,
|
||||
"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": 9,
|
||||
"verify_dynamic_exact_pair_checks": 9,
|
||||
"verify_path_report_calls": 16,
|
||||
"verify_static_buffer_ops": 81,
|
||||
"verify_dynamic_candidate_nets": 10,
|
||||
"verify_dynamic_exact_pair_checks": 10,
|
||||
"verify_path_report_calls": 18,
|
||||
"verify_static_buffer_ops": 90,
|
||||
"visibility_builds": 0,
|
||||
"visibility_corner_hits_exact": 0,
|
||||
"visibility_corner_index_builds": 2,
|
||||
|
|
@ -339,7 +345,7 @@
|
|||
"valid_results": 2
|
||||
},
|
||||
{
|
||||
"duration_s": 0.027705274987965822,
|
||||
"duration_s": 0.026945222169160843,
|
||||
"metrics": {
|
||||
"congestion_cache_hits": 0,
|
||||
"congestion_cache_misses": 0,
|
||||
|
|
@ -383,6 +389,8 @@
|
|||
"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,
|
||||
|
|
@ -421,15 +429,15 @@
|
|||
"refinement_windows_considered": 0,
|
||||
"route_iterations": 1,
|
||||
"score_component_calls": 90,
|
||||
"score_component_total_ns": 96756,
|
||||
"score_component_total_ns": 97710,
|
||||
"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": 9,
|
||||
"verify_dynamic_candidate_nets": 12,
|
||||
"verify_dynamic_exact_pair_checks": 0,
|
||||
"verify_path_report_calls": 8,
|
||||
"verify_path_report_calls": 10,
|
||||
"verify_static_buffer_ops": 0,
|
||||
"visibility_builds": 0,
|
||||
"visibility_corner_hits_exact": 0,
|
||||
|
|
@ -451,7 +459,7 @@
|
|||
"valid_results": 2
|
||||
},
|
||||
{
|
||||
"duration_s": 0.25367443496361375,
|
||||
"duration_s": 0.23108969815075397,
|
||||
"metrics": {
|
||||
"congestion_cache_hits": 3,
|
||||
"congestion_cache_misses": 146,
|
||||
|
|
@ -495,6 +503,8 @@
|
|||
"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_misses": 1306,
|
||||
"move_cache_rel_hits": 1204,
|
||||
|
|
@ -533,7 +543,7 @@
|
|||
"refinement_windows_considered": 0,
|
||||
"route_iterations": 2,
|
||||
"score_component_calls": 1234,
|
||||
"score_component_total_ns": 1311211,
|
||||
"score_component_total_ns": 1223569,
|
||||
"static_net_tree_rebuilds": 3,
|
||||
"static_raw_tree_rebuilds": 0,
|
||||
"static_safe_cache_hits": 8,
|
||||
|
|
@ -541,7 +551,7 @@
|
|||
"timeout_events": 0,
|
||||
"verify_dynamic_candidate_nets": 8,
|
||||
"verify_dynamic_exact_pair_checks": 12,
|
||||
"verify_path_report_calls": 17,
|
||||
"verify_path_report_calls": 20,
|
||||
"verify_static_buffer_ops": 0,
|
||||
"visibility_builds": 0,
|
||||
"visibility_corner_hits_exact": 0,
|
||||
|
|
@ -563,7 +573,7 @@
|
|||
"valid_results": 3
|
||||
},
|
||||
{
|
||||
"duration_s": 0.21031348290853202,
|
||||
"duration_s": 0.19879506202414632,
|
||||
"metrics": {
|
||||
"congestion_cache_hits": 0,
|
||||
"congestion_cache_misses": 0,
|
||||
|
|
@ -589,7 +599,7 @@
|
|||
"danger_map_cache_misses": 731,
|
||||
"danger_map_lookup_calls": 1914,
|
||||
"danger_map_query_calls": 731,
|
||||
"danger_map_total_ns": 19983976,
|
||||
"danger_map_total_ns": 19050142,
|
||||
"dynamic_grid_rebuilds": 0,
|
||||
"dynamic_path_objects_added": 54,
|
||||
"dynamic_path_objects_removed": 36,
|
||||
|
|
@ -607,6 +617,8 @@
|
|||
"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,
|
||||
|
|
@ -645,7 +657,7 @@
|
|||
"refinement_windows_considered": 0,
|
||||
"route_iterations": 3,
|
||||
"score_component_calls": 842,
|
||||
"score_component_total_ns": 22474166,
|
||||
"score_component_total_ns": 21353240,
|
||||
"static_net_tree_rebuilds": 3,
|
||||
"static_raw_tree_rebuilds": 3,
|
||||
"static_safe_cache_hits": 141,
|
||||
|
|
@ -653,8 +665,8 @@
|
|||
"timeout_events": 0,
|
||||
"verify_dynamic_candidate_nets": 0,
|
||||
"verify_dynamic_exact_pair_checks": 0,
|
||||
"verify_path_report_calls": 12,
|
||||
"verify_static_buffer_ops": 72,
|
||||
"verify_path_report_calls": 15,
|
||||
"verify_static_buffer_ops": 90,
|
||||
"visibility_builds": 0,
|
||||
"visibility_corner_hits_exact": 0,
|
||||
"visibility_corner_index_builds": 3,
|
||||
|
|
@ -675,7 +687,7 @@
|
|||
"valid_results": 3
|
||||
},
|
||||
{
|
||||
"duration_s": 0.20740868314169347,
|
||||
"duration_s": 0.20880168909206986,
|
||||
"metrics": {
|
||||
"congestion_cache_hits": 0,
|
||||
"congestion_cache_misses": 0,
|
||||
|
|
@ -701,7 +713,7 @@
|
|||
"danger_map_cache_misses": 448,
|
||||
"danger_map_lookup_calls": 681,
|
||||
"danger_map_query_calls": 448,
|
||||
"danger_map_total_ns": 11224403,
|
||||
"danger_map_total_ns": 11025527,
|
||||
"dynamic_grid_rebuilds": 0,
|
||||
"dynamic_path_objects_added": 132,
|
||||
"dynamic_path_objects_removed": 88,
|
||||
|
|
@ -719,6 +731,8 @@
|
|||
"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,
|
||||
|
|
@ -757,16 +771,16 @@
|
|||
"refinement_windows_considered": 0,
|
||||
"route_iterations": 1,
|
||||
"score_component_calls": 291,
|
||||
"score_component_total_ns": 12117666,
|
||||
"score_component_total_ns": 11875928,
|
||||
"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": 370,
|
||||
"verify_dynamic_exact_pair_checks": 56,
|
||||
"verify_path_report_calls": 40,
|
||||
"verify_static_buffer_ops": 176,
|
||||
"verify_dynamic_candidate_nets": 476,
|
||||
"verify_dynamic_exact_pair_checks": 72,
|
||||
"verify_path_report_calls": 50,
|
||||
"verify_static_buffer_ops": 220,
|
||||
"visibility_builds": 0,
|
||||
"visibility_corner_hits_exact": 0,
|
||||
"visibility_corner_index_builds": 10,
|
||||
|
|
@ -787,7 +801,7 @@
|
|||
"valid_results": 10
|
||||
},
|
||||
{
|
||||
"duration_s": 0.018604618962854147,
|
||||
"duration_s": 0.017696003895252943,
|
||||
"metrics": {
|
||||
"congestion_cache_hits": 0,
|
||||
"congestion_cache_misses": 0,
|
||||
|
|
@ -831,6 +845,8 @@
|
|||
"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,
|
||||
|
|
@ -869,7 +885,7 @@
|
|||
"refinement_windows_considered": 0,
|
||||
"route_iterations": 2,
|
||||
"score_component_calls": 72,
|
||||
"score_component_total_ns": 87655,
|
||||
"score_component_total_ns": 87742,
|
||||
"static_net_tree_rebuilds": 2,
|
||||
"static_raw_tree_rebuilds": 0,
|
||||
"static_safe_cache_hits": 2,
|
||||
|
|
@ -877,7 +893,7 @@
|
|||
"timeout_events": 0,
|
||||
"verify_dynamic_candidate_nets": 0,
|
||||
"verify_dynamic_exact_pair_checks": 0,
|
||||
"verify_path_report_calls": 8,
|
||||
"verify_path_report_calls": 10,
|
||||
"verify_static_buffer_ops": 0,
|
||||
"visibility_builds": 0,
|
||||
"visibility_corner_hits_exact": 0,
|
||||
|
|
@ -899,7 +915,7 @@
|
|||
"valid_results": 2
|
||||
},
|
||||
{
|
||||
"duration_s": 0.00794802000746131,
|
||||
"duration_s": 0.005660973023623228,
|
||||
"metrics": {
|
||||
"congestion_cache_hits": 0,
|
||||
"congestion_cache_misses": 0,
|
||||
|
|
@ -925,7 +941,7 @@
|
|||
"danger_map_cache_misses": 20,
|
||||
"danger_map_lookup_calls": 30,
|
||||
"danger_map_query_calls": 20,
|
||||
"danger_map_total_ns": 675454,
|
||||
"danger_map_total_ns": 515133,
|
||||
"dynamic_grid_rebuilds": 0,
|
||||
"dynamic_path_objects_added": 2,
|
||||
"dynamic_path_objects_removed": 1,
|
||||
|
|
@ -943,6 +959,8 @@
|
|||
"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,
|
||||
|
|
@ -981,7 +999,7 @@
|
|||
"refinement_windows_considered": 0,
|
||||
"route_iterations": 1,
|
||||
"score_component_calls": 14,
|
||||
"score_component_total_ns": 722637,
|
||||
"score_component_total_ns": 554809,
|
||||
"static_net_tree_rebuilds": 1,
|
||||
"static_raw_tree_rebuilds": 1,
|
||||
"static_safe_cache_hits": 0,
|
||||
|
|
|
|||
1005
docs/pre_pair_frontier_trace.json
Normal file
1005
docs/pre_pair_frontier_trace.json
Normal file
File diff suppressed because it is too large
Load diff
48
docs/pre_pair_frontier_trace.md
Normal file
48
docs/pre_pair_frontier_trace.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# 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
|
||||
|
||||
|
|
@ -22,6 +22,8 @@ from .results import ( # noqa: PLC0414
|
|||
IterationTraceEntry as IterationTraceEntry,
|
||||
NetConflictTrace as NetConflictTrace,
|
||||
NetFrontierTrace as NetFrontierTrace,
|
||||
PrePairFrontierTraceEntry as PrePairFrontierTraceEntry,
|
||||
PrePairNetTrace as PrePairNetTrace,
|
||||
RoutingResult as RoutingResult,
|
||||
RoutingRunResult as RoutingRunResult,
|
||||
)
|
||||
|
|
@ -49,6 +51,7 @@ 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),
|
||||
)
|
||||
|
||||
|
|
@ -67,6 +70,8 @@ __all__ = [
|
|||
"FrontierPruneSample",
|
||||
"IterationNetAttemptTrace",
|
||||
"IterationTraceEntry",
|
||||
"PrePairFrontierTraceEntry",
|
||||
"PrePairNetTrace",
|
||||
"RefinementOptions",
|
||||
"RoutingOptions",
|
||||
"RoutingProblem",
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ class DiagnosticsOptions:
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -78,6 +78,26 @@ 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
|
||||
|
|
@ -210,6 +230,8 @@ 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)
|
||||
|
|
@ -258,4 +280,5 @@ 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, ...] = ()
|
||||
|
|
|
|||
|
|
@ -258,6 +258,8 @@ 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",
|
||||
|
|
@ -371,6 +373,8 @@ 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
|
||||
|
|
@ -483,6 +487,8 @@ 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
|
||||
|
|
@ -598,6 +604,8 @@ 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,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ from inire.results import (
|
|||
IterationTraceEntry,
|
||||
NetConflictTrace,
|
||||
NetFrontierTrace,
|
||||
PrePairFrontierTraceEntry,
|
||||
PrePairNetTrace,
|
||||
RoutingOutcome,
|
||||
RoutingReport,
|
||||
RoutingResult,
|
||||
|
|
@ -53,6 +55,8 @@ class _RoutingState:
|
|||
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)
|
||||
|
|
@ -68,6 +72,14 @@ 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",
|
||||
|
|
@ -92,6 +104,7 @@ class PathFinder:
|
|||
"accumulated_expanded_nodes",
|
||||
"conflict_trace",
|
||||
"frontier_trace",
|
||||
"pre_pair_frontier_trace",
|
||||
"iteration_trace",
|
||||
)
|
||||
|
||||
|
|
@ -110,6 +123,7 @@ 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:
|
||||
|
|
@ -219,6 +233,8 @@ class PathFinder:
|
|||
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)
|
||||
|
|
@ -399,6 +415,93 @@ 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],
|
||||
|
|
@ -477,15 +580,6 @@ class PathFinder:
|
|||
capture_component_conflicts=True,
|
||||
count_iteration_metrics=False,
|
||||
)
|
||||
|
||||
original_metrics = self.metrics
|
||||
original_context_metrics = self.context.metrics
|
||||
original_engine_metrics = self.context.cost_evaluator.collision_engine.metrics
|
||||
original_danger_metrics = None
|
||||
if self.context.cost_evaluator.danger_map is not None:
|
||||
original_danger_metrics = self.context.cost_evaluator.danger_map.metrics
|
||||
|
||||
try:
|
||||
for net_id in state.ordered_net_ids:
|
||||
result = state.results.get(net_id)
|
||||
detail = details_by_net.get(net_id)
|
||||
|
|
@ -498,69 +592,14 @@ class PathFinder:
|
|||
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
|
||||
),
|
||||
self._capture_single_frontier_trace(
|
||||
state,
|
||||
net_id,
|
||||
result,
|
||||
hotspot_bounds,
|
||||
)
|
||||
)
|
||||
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,
|
||||
|
|
@ -673,6 +712,50 @@ 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 _run_pair_local_attempt(
|
||||
self,
|
||||
state: _RoutingState,
|
||||
|
|
@ -780,12 +863,17 @@ class PathFinder:
|
|||
state: _RoutingState,
|
||||
iteration: int,
|
||||
net_id: str,
|
||||
*,
|
||||
node_limit_override: int | None = None,
|
||||
incumbent_fallback: RoutingResult | None = None,
|
||||
) -> tuple[RoutingResult, bool]:
|
||||
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
|
||||
|
||||
|
|
@ -808,6 +896,16 @@ class PathFinder:
|
|||
guidance_bonus = max(10.0, self.context.options.objective.bend_penalty * 0.25)
|
||||
guidance_seed_present = True
|
||||
|
||||
if (
|
||||
node_limit_override is not None
|
||||
and incumbent_fallback is not None
|
||||
and incumbent_fallback.reached_target
|
||||
and incumbent_fallback.path
|
||||
):
|
||||
self.metrics.total_late_phase_capped_fallbacks += 1
|
||||
self._install_path(net_id, incumbent_fallback.path)
|
||||
return incumbent_fallback, guidance_seed_present
|
||||
|
||||
run_config = SearchRunConfig.from_options(
|
||||
self.context.options,
|
||||
bend_collision_type=coll_model,
|
||||
|
|
@ -817,7 +915,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,
|
||||
node_limit=search.node_limit if node_limit_override is None else node_limit_override,
|
||||
)
|
||||
path = route_astar(
|
||||
net.start,
|
||||
|
|
@ -833,9 +931,17 @@ class PathFinder:
|
|||
state.accumulated_expanded_nodes.extend(self.metrics.last_expanded_nodes)
|
||||
|
||||
if not path:
|
||||
if incumbent_fallback is not None and incumbent_fallback.reached_target and incumbent_fallback.path:
|
||||
self.metrics.total_late_phase_capped_fallbacks += 1
|
||||
self._install_path(net_id, incumbent_fallback.path)
|
||||
return incumbent_fallback, guidance_seed_present
|
||||
return RoutingResult(net_id=net_id, path=(), reached_target=False), guidance_seed_present
|
||||
|
||||
reached_target = path[-1].end_port == net.target
|
||||
if not reached_target and incumbent_fallback is not None and incumbent_fallback.reached_target and incumbent_fallback.path:
|
||||
self.metrics.total_late_phase_capped_fallbacks += 1
|
||||
self._install_path(net_id, incumbent_fallback.path)
|
||||
return incumbent_fallback, guidance_seed_present
|
||||
if reached_target:
|
||||
self.metrics.total_nets_reached_target += 1
|
||||
report = None
|
||||
|
|
@ -872,9 +978,27 @@ class PathFinder:
|
|||
|
||||
iteration_penalty = self.context.congestion_penalty
|
||||
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) and state.recent_attempt_work:
|
||||
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 len(state.results) == len(state.ordered_net_ids)
|
||||
and all(result.reached_target for result in state.results.values())
|
||||
):
|
||||
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
|
||||
}
|
||||
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)
|
||||
|
||||
|
|
@ -883,14 +1007,23 @@ class PathFinder:
|
|||
self.metrics.total_timeout_events += 1
|
||||
return None
|
||||
|
||||
attempt_before = {}
|
||||
if diagnostics.capture_iteration_trace:
|
||||
attempt_before = self._capture_metric_totals(_ATTEMPT_TRACE_TOTALS)
|
||||
result, guidance_seed_present = self._route_net_once(state, iteration, net_id)
|
||||
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,
|
||||
)
|
||||
state.results[net_id] = result
|
||||
if diagnostics.capture_iteration_trace:
|
||||
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,
|
||||
|
|
@ -904,7 +1037,21 @@ class PathFinder:
|
|||
)
|
||||
)
|
||||
|
||||
state.recent_attempt_work = attempt_work
|
||||
review = self._reverify_iteration_results(state)
|
||||
all_reached_target = (
|
||||
len(state.results) == len(state.ordered_net_ids)
|
||||
and all(result.reached_target for result in state.results.values())
|
||||
)
|
||||
if all_reached_target 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),
|
||||
)
|
||||
else:
|
||||
state.pre_pair_candidate = None
|
||||
if diagnostics.capture_iteration_trace:
|
||||
iteration_after = self._capture_metric_totals(_ITERATION_TRACE_TOTALS)
|
||||
deltas = self._metric_deltas(iteration_before, iteration_after)
|
||||
|
|
@ -1072,6 +1219,7 @@ 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()
|
||||
|
|
@ -1080,13 +1228,17 @@ class PathFinder:
|
|||
timed_out = self._run_iterations(state, iteration_callback)
|
||||
self.accumulated_expanded_nodes = list(state.accumulated_expanded_nodes)
|
||||
self._restore_best_iteration(state)
|
||||
if self.context.options.diagnostics.capture_conflict_trace:
|
||||
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=True,
|
||||
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",
|
||||
|
|
@ -1095,6 +1247,13 @@ class PathFinder:
|
|||
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,
|
||||
)
|
||||
|
||||
if timed_out:
|
||||
final_results = self._verify_results(state)
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ 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),
|
||||
)
|
||||
|
||||
|
|
@ -453,6 +454,7 @@ def _build_example_07_variant_stack(
|
|||
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 = [
|
||||
|
|
@ -496,6 +498,7 @@ def _build_example_07_variant_stack(
|
|||
"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,
|
||||
|
|
@ -512,6 +515,7 @@ def _run_example_07_variant(
|
|||
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,
|
||||
|
|
@ -520,6 +524,7 @@ def _run_example_07_variant(
|
|||
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:
|
||||
|
|
@ -560,6 +565,7 @@ def _trace_example_07_variant(
|
|||
capture_conflict_trace=True,
|
||||
capture_frontier_trace=True,
|
||||
capture_iteration_trace=True,
|
||||
capture_pre_pair_frontier_trace=True,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import importlib
|
|||
import pytest
|
||||
from shapely.geometry import box
|
||||
|
||||
import inire.router._router as router_module
|
||||
from inire import (
|
||||
CongestionOptions,
|
||||
DiagnosticsOptions,
|
||||
|
|
@ -54,6 +55,7 @@ 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 == ()
|
||||
|
||||
|
||||
|
|
@ -125,6 +127,8 @@ 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:
|
||||
|
|
@ -306,6 +310,35 @@ 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),
|
||||
|
|
@ -411,7 +444,159 @@ def test_reverify_iterations_limit_late_reroutes_to_conflicting_nets(monkeypatch
|
|||
assert reroute_sets == [{"netA", "netB", "netC"}, {"netA", "netB"}]
|
||||
assert results["netA"].outcome == "colliding"
|
||||
assert results["netB"].outcome == "colliding"
|
||||
assert results["netC"].outcome == "completed"
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -289,6 +289,8 @@ def test_pair_local_context_clones_live_static_obstacles() -> None:
|
|||
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"))
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ 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"
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ 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:
|
||||
|
|
@ -300,6 +302,31 @@ def test_record_iteration_trace_script_writes_selected_scenario(tmp_path: Path)
|
|||
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"
|
||||
|
|
|
|||
191
scripts/record_pre_pair_frontier_trace.py
Normal file
191
scripts/record_pre_pair_frontier_trace.py
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
#!/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 <repo>/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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue