Compare commits
No commits in common. "7e0d96f9876575d94c0497f6c21a984f8eb565e1" and "2049353ee9e7bcc0a8349d36411ef447f75ae510" have entirely different histories.
7e0d96f987
...
2049353ee9
21 changed files with 266 additions and 4193 deletions
61
DOCS.md
61
DOCS.md
|
|
@ -130,8 +130,6 @@ Use `RoutingProblem.initial_paths` to provide semantic per-net seeds. Seeds are
|
|||
| `capture_expanded` | `False` | Record expanded nodes for diagnostics and visualization. |
|
||||
| `capture_conflict_trace` | `False` | Capture authoritative post-reverify conflict trace entries for debugging negotiated-congestion failures. |
|
||||
| `capture_frontier_trace` | `False` | Run an analysis-only reroute for reached-but-colliding nets and capture prune causes near their final conflict hotspots. |
|
||||
| `capture_iteration_trace` | `False` | Capture per-iteration and per-net route-attempt attribution for negotiated-congestion diagnosis. |
|
||||
| `capture_pre_pair_frontier_trace` | `False` | Capture the final unresolved pre-pair-local subset iteration plus hotspot-adjacent frontier prunes for the routed nets in that basin. |
|
||||
|
||||
## 7. Conflict Trace
|
||||
|
||||
|
|
@ -188,60 +186,7 @@ Use `scripts/record_frontier_trace.py` to capture JSON and Markdown frontier-pru
|
|||
|
||||
Separately from the observational trace tooling, the router may run a bounded post-loop pair-local scratch reroute before refinement when the restored best snapshot ends with final two-net reached-target dynamic conflicts. That repair phase is part of normal routing behavior and is reported through the `pair_local_search_*` counters below.
|
||||
|
||||
## 9. Pre-Pair Frontier Trace
|
||||
|
||||
`RoutingRunResult.pre_pair_frontier_trace` is either a single immutable trace entry or `None`. It is populated only when `RoutingOptions.diagnostics.capture_pre_pair_frontier_trace=True`.
|
||||
|
||||
Trace types:
|
||||
|
||||
- `PrePairFrontierTraceEntry`
|
||||
- `iteration`: The final unresolved subset-reroute iteration immediately before pair-local handoff
|
||||
- `routed_net_ids`: Nets rerouted in that iteration, in routing order
|
||||
- `conflict_edges`: Dynamic conflict edges reported for that unresolved basin
|
||||
- `nets`: Per-net attempt attribution plus hotspot-adjacent frontier rerun data
|
||||
- `PrePairNetTrace`
|
||||
- `net_id`
|
||||
- `nodes_expanded`
|
||||
- `congestion_check_calls`
|
||||
- `pruned_closed_set`
|
||||
- `pruned_cost`
|
||||
- `pruned_hard_collision`
|
||||
- `guidance_seed_present`
|
||||
- `frontier`: A `NetFrontierTrace` captured against the restored best unresolved state
|
||||
|
||||
Use `scripts/record_pre_pair_frontier_trace.py` to capture JSON and Markdown artifacts. Its default comparison target is the solved seed-42 no-warm canary versus the heavier seed-43 no-warm canary.
|
||||
|
||||
## 10. Iteration Trace
|
||||
|
||||
`RoutingRunResult.iteration_trace` is an immutable tuple of negotiated-congestion iteration summaries. It is empty unless `RoutingOptions.diagnostics.capture_iteration_trace=True`.
|
||||
|
||||
Trace types:
|
||||
|
||||
- `IterationTraceEntry`
|
||||
- `iteration`
|
||||
- `congestion_penalty`: Penalty in effect for that iteration
|
||||
- `routed_net_ids`: Nets rerouted during that iteration, in routing order
|
||||
- `completed_nets`
|
||||
- `conflict_edges`
|
||||
- `total_dynamic_collisions`
|
||||
- `nodes_expanded`
|
||||
- `congestion_check_calls`
|
||||
- `congestion_candidate_ids`
|
||||
- `congestion_exact_pair_checks`
|
||||
- `net_attempts`: Per-net attribution for that iteration
|
||||
- `IterationNetAttemptTrace`
|
||||
- `net_id`
|
||||
- `reached_target`
|
||||
- `nodes_expanded`
|
||||
- `congestion_check_calls`
|
||||
- `pruned_closed_set`
|
||||
- `pruned_cost`
|
||||
- `pruned_hard_collision`
|
||||
- `guidance_seed_present`
|
||||
|
||||
Use `scripts/record_iteration_trace.py` to capture JSON and Markdown iteration-attribution artifacts. Its default comparison target is the solved seed-42 no-warm canary versus the pathological seed-43 no-warm canary.
|
||||
|
||||
## 11. RouteMetrics
|
||||
## 9. RouteMetrics
|
||||
|
||||
`RoutingRunResult.metrics` is an immutable per-run snapshot.
|
||||
|
||||
|
|
@ -321,15 +266,13 @@ Use `scripts/record_iteration_trace.py` to capture JSON and Markdown iteration-a
|
|||
- `pair_local_search_attempts`: Number of pair-local-search reroute attempts executed across all considered pairs.
|
||||
- `pair_local_search_accepts`: Number of pair-local-search attempts accepted into the whole routed result set.
|
||||
- `pair_local_search_nodes_expanded`: Total A* node expansions spent inside pair-local-search attempts.
|
||||
- `late_phase_capped_nets`: Number of late all-reached heavy-net reroutes run under the bounded node-limit cap before pair-local handoff.
|
||||
- `late_phase_capped_fallbacks`: Number of those capped late-phase reroutes that fell back to the incumbent reached-target path instead of replacing it.
|
||||
|
||||
## 10. Internal Modules
|
||||
|
||||
Lower-level search and collision modules are semi-private implementation details. They remain accessible through deep imports for advanced use, but they are unstable and may change without notice. The stable supported entrypoint is `route(problem, options=...)`.
|
||||
|
||||
The current implementation structure is summarized in **[docs/architecture.md](docs/architecture.md)**. The committed example-corpus counter baseline is tracked in **[docs/performance.md](docs/performance.md)**.
|
||||
Use `scripts/diff_performance_baseline.py` to compare a fresh local run against that baseline. Use `scripts/record_conflict_trace.py` for opt-in conflict-hotspot traces, `scripts/record_frontier_trace.py` for hotspot-adjacent prune traces, `scripts/record_pre_pair_frontier_trace.py` for the final unresolved pre-pair basin, `scripts/record_iteration_trace.py` for per-iteration negotiated-congestion attribution, and `scripts/characterize_pair_local_search.py` to sweep example_07-style no-warm runs for pair-local repair behavior. The counter baseline is currently observational and is not enforced as a CI gate.
|
||||
Use `scripts/diff_performance_baseline.py` to compare a fresh local run against that baseline. Use `scripts/record_conflict_trace.py` for opt-in conflict-hotspot traces, `scripts/record_frontier_trace.py` for hotspot-adjacent prune traces, and `scripts/characterize_pair_local_search.py` to sweep example_07-style no-warm runs for pair-local repair behavior. The counter baseline is currently observational and is not enforced as a CI gate.
|
||||
|
||||
## 11. Tuning Notes
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,81 +0,0 @@
|
|||
# Iteration Trace
|
||||
|
||||
Generated at 2026-04-02T18:51:01-07:00 by `scripts/record_iteration_trace.py`.
|
||||
|
||||
## example_07_large_scale_routing_no_warm_start
|
||||
|
||||
Results: 10 valid / 10 reached / 10 total.
|
||||
|
||||
| Iteration | Penalty | Routed Nets | Completed | Conflict Edges | Dynamic Collisions | Nodes | Congestion Checks | Candidate Ids | Exact Pairs |
|
||||
| --: | --: | --: | --: | --: | --: | --: | --: | --: | --: |
|
||||
| 0 | 100.0 | 10 | 1 | 16 | 50 | 571 | 0 | 0 | 0 |
|
||||
| 1 | 140.0 | 10 | 2 | 12 | 54 | 253 | 974 | 2378 | 1998 |
|
||||
| 2 | 196.0 | 10 | 4 | 5 | 22 | 253 | 993 | 1928 | 1571 |
|
||||
| 3 | 274.4 | 10 | 6 | 2 | 10 | 100 | 437 | 852 | 698 |
|
||||
| 4 | 384.2 | 4 | 6 | 2 | 10 | 81 | 332 | 627 | 513 |
|
||||
|
||||
Top nets by iteration-attributed nodes expanded:
|
||||
|
||||
- `net_09`: 242
|
||||
- `net_00`: 201
|
||||
- `net_02`: 157
|
||||
- `net_06`: 155
|
||||
- `net_01`: 147
|
||||
- `net_08`: 144
|
||||
- `net_03`: 141
|
||||
- `net_07`: 45
|
||||
- `net_04`: 13
|
||||
- `net_05`: 13
|
||||
|
||||
Top nets by iteration-attributed congestion checks:
|
||||
|
||||
- `net_06`: 569
|
||||
- `net_02`: 514
|
||||
- `net_01`: 468
|
||||
- `net_03`: 425
|
||||
- `net_00`: 203
|
||||
- `net_08`: 170
|
||||
- `net_07`: 143
|
||||
- `net_09`: 124
|
||||
- `net_04`: 60
|
||||
- `net_05`: 60
|
||||
|
||||
## example_07_large_scale_routing_no_warm_start_seed43
|
||||
|
||||
Results: 10 valid / 10 reached / 10 total.
|
||||
|
||||
| Iteration | Penalty | Routed Nets | Completed | Conflict Edges | Dynamic Collisions | Nodes | Congestion Checks | Candidate Ids | Exact Pairs |
|
||||
| --: | --: | --: | --: | --: | --: | --: | --: | --: | --: |
|
||||
| 0 | 100.0 | 10 | 1 | 16 | 50 | 571 | 0 | 0 | 0 |
|
||||
| 1 | 140.0 | 10 | 1 | 13 | 53 | 269 | 961 | 2562 | 2032 |
|
||||
| 2 | 196.0 | 10 | 4 | 3 | 15 | 140 | 643 | 1610 | 1224 |
|
||||
| 3 | 274.4 | 6 | 4 | 3 | 15 | 54 | 250 | 557 | 428 |
|
||||
| 4 | 384.2 | 6 | 6 | 2 | 10 | 136 | 505 | 1025 | 829 |
|
||||
| 5 | 537.8 | 4 | 6 | 2 | 10 | 33 | 171 | 419 | 287 |
|
||||
|
||||
Top nets by iteration-attributed nodes expanded:
|
||||
|
||||
- `net_09`: 250
|
||||
- `net_03`: 199
|
||||
- `net_00`: 177
|
||||
- `net_08`: 166
|
||||
- `net_07`: 140
|
||||
- `net_06`: 105
|
||||
- `net_02`: 79
|
||||
- `net_01`: 65
|
||||
- `net_05`: 12
|
||||
- `net_04`: 10
|
||||
|
||||
Top nets by iteration-attributed congestion checks:
|
||||
|
||||
- `net_03`: 639
|
||||
- `net_07`: 454
|
||||
- `net_06`: 382
|
||||
- `net_02`: 290
|
||||
- `net_08`: 283
|
||||
- `net_09`: 178
|
||||
- `net_01`: 135
|
||||
- `net_00`: 82
|
||||
- `net_05`: 47
|
||||
- `net_04`: 40
|
||||
|
||||
|
|
@ -3629,113 +3629,3 @@ Findings:
|
|||
| example_07_large_scale_routing_no_warm_start | pair_local_search_attempts | - | 2.0000 | - |
|
||||
| example_07_large_scale_routing_no_warm_start | pair_local_search_accepts | - | 2.0000 | - |
|
||||
| example_07_large_scale_routing_no_warm_start | pair_local_search_nodes_expanded | - | 68.0000 | - |
|
||||
|
||||
## Step 64 seed-43 iteration-trace diagnosis
|
||||
|
||||
Measured on 2026-04-02T16:11:39-07:00.
|
||||
|
||||
Findings:
|
||||
|
||||
- Added `capture_iteration_trace` plus `scripts/record_iteration_trace.py` and tracked the first `seed 42` vs `seed 43` no-warm comparison in `docs/iteration_trace.json` and `docs/iteration_trace.md`.
|
||||
- The pathological `seed 43` basin is not front-loaded. It matches the solved `seed 42` path through iteration `5`, then falls into three extra iterations with only `4` completed nets and `4` conflict edges.
|
||||
- The late blowup is concentrated in two nets, not the whole routing set: `net_06` contributes `31604` attributed nodes and `83752` congestion checks, while `net_03` contributes `27532` nodes and `75019` congestion checks.
|
||||
- This points the next optimization work at late-iteration reroute behavior for a small subset of nets rather than another global congestion or pair-local-search change.
|
||||
|
||||
## Step 65 stop after fully reached two-edge plateau
|
||||
|
||||
Measured on 2026-04-02T16:21:02-07:00.
|
||||
|
||||
Findings:
|
||||
|
||||
- Added a narrow late-iteration stop rule: once every net already reaches target and the best snapshot is down to the final `<=2` dynamic-conflict-edge basin, stop after the first no-improvement iteration and hand off to bounded pair-local repair.
|
||||
- The solved seed-42 no-warm canary improved from `6` to `5` negotiated-congestion iterations and dropped from about `1764` to `1303` nodes and from `4625` to `2921` congestion checks, while staying `10/10/10`.
|
||||
- The former seed-43 pathological basin collapsed from about `50s`, `61259` nodes, and `165223` congestion checks to about `2.53s`, `1691` nodes, and `4330` congestion checks, still finishing `10/10/10`.
|
||||
- Guardrails held unchanged: warmed `example_07` stayed `10/10/10`, and `example_05_orientation_stress` stayed `3/3/3`.
|
||||
|
||||
## Step 66 reroute only current conflict nets in late all-reached phase
|
||||
|
||||
Measured on 2026-04-02T16:46:00-07:00.
|
||||
|
||||
Findings:
|
||||
|
||||
- Once all nets already reach target and the live conflict graph is down to `<=3` edges, the next negotiated iteration now reroutes only the currently conflicting nets instead of all nets.
|
||||
- The solved seed-42 no-warm canary stayed `10/10/10` and improved from `50` routed nets / `1303` nodes / `2921` congestion checks to `44` routed nets / `1258` nodes / `2736` congestion checks.
|
||||
- The seed-43 no-warm canary stayed `10/10/10` and improved from `60` routed nets / `1691` nodes / `4330` congestion checks to `46` routed nets / `1582` nodes / `3881` congestion checks.
|
||||
- Guardrails held: warmed `example_07` stayed `10/10/10`, and `example_05_orientation_stress` stayed `3/3/3` while trimming slightly to `5` routed nets, `297` nodes, and `146` congestion checks.
|
||||
|
||||
## Step 67 route lighter late conflict nets first
|
||||
|
||||
Measured on 2026-04-02T17:16:54-07:00.
|
||||
|
||||
Findings:
|
||||
|
||||
- Kept the late all-reached conflict-set reroute, but now order those subset reroutes by the previous iteration's attributed work ascending so the lighter partner nets settle first and the heavier nets route later against a more stable late-phase context.
|
||||
- The solved seed-42 no-warm canary stayed effectively flat at `10/10/10`, `44` routed nets, `1258` nodes, and `2736` congestion checks.
|
||||
- The seed-43 no-warm canary stayed `10/10/10` and improved slightly from `1582` nodes / `3881` congestion checks to `1576` nodes / `3836` congestion checks.
|
||||
- The remaining late-phase hotspot is still concentrated in `net_03` and `net_06`, especially the final 4-net iteration in the seed-43 trace.
|
||||
|
||||
## Step 68 pre-pair frontier diagnostics kept, scratch reroute rejected
|
||||
|
||||
Measured on 2026-04-02T17:57:48-07:00.
|
||||
|
||||
Findings:
|
||||
|
||||
- Kept a new opt-in `capture_pre_pair_frontier_trace` surface plus `scripts/record_pre_pair_frontier_trace.py`, and tracked the first seed-42 vs seed-43 artifacts in `docs/pre_pair_frontier_trace.json` and `docs/pre_pair_frontier_trace.md`.
|
||||
- The final unresolved subset iteration is now explicit: seed `42` captures iteration `4` with routed nets `net_07`, `net_06`, `net_00`, `net_01`; seed `43` captures iteration `5` with routed nets `net_07`, `net_02`, `net_06`, `net_03`.
|
||||
- The seed-43 heavy-net concentration is confirmed by the new trace: `net_03` and `net_06` account for most of the last unresolved iteration's work, and the hotspot-adjacent sampled prunes in that basin are closed-set dominated rather than hard-collision dominated.
|
||||
- I also measured a bounded pre-pair scratch reroute for the two heaviest traced nets, but rejected it: it added runtime, produced `0` accepted repairs, and left the solved canaries at the same `1258 / 2736` and `1576 / 3836` node/check totals after revert.
|
||||
|
||||
## Step 69 cap heavy late-phase reroutes with incumbent fallback
|
||||
|
||||
Measured on 2026-04-02T18:20:00-07:00.
|
||||
|
||||
Findings:
|
||||
|
||||
- In the final all-reached 4-net subset iteration, the router now caps only the heavy reroute endpoints whose previous-iteration attributed work is already pathological, and falls back to their incumbent reached-target paths if the capped reroute does not finish cleanly.
|
||||
- The solved seed-42 no-warm canary stays flat at `10/10/10`, `44` routed nets, `1258` nodes, and `2736` congestion checks, with `late_phase_capped_nets=0`.
|
||||
- The heavier seed-43 no-warm canary stays `10/10/10` and improves from `1576` nodes / `3836` congestion checks to `1459` nodes / `3455` congestion checks, with `late_phase_capped_nets=2` and `late_phase_capped_fallbacks=2`.
|
||||
- Guardrails held: warmed `example_07` stayed `10/10/10`, and `example_05_orientation_stress` stayed `3/3/3` with no late-phase capping activity.
|
||||
|
||||
## Step 70 tighten late-phase cap from 128 to 64
|
||||
|
||||
Measured on 2026-04-02T18:33:00-07:00.
|
||||
|
||||
Findings:
|
||||
|
||||
- Tightened the bounded heavy-net late-phase reroute cap from `128` nodes to `64`, keeping the same incumbent fallback behavior and the same heavy-net selection rule.
|
||||
- The solved seed-42 no-warm canary stays flat at `10/10/10`, `44` routed nets, `1258` nodes, and `2736` congestion checks, with `late_phase_capped_nets=0`.
|
||||
- The heavier seed-43 no-warm canary stays `10/10/10` and improves again from `1459` nodes / `3455` congestion checks to `1331` nodes / `3012` congestion checks, still with `late_phase_capped_nets=2` and `late_phase_capped_fallbacks=2`.
|
||||
- Guardrails held: warmed `example_07` stayed `10/10/10`, and `example_05_orientation_stress` stayed `3/3/3` with no late-phase capping activity.
|
||||
|
||||
## Step 71 tighten late-phase cap from 64 to 32 after cap sweep
|
||||
|
||||
Measured on 2026-04-02T18:43:00-07:00.
|
||||
|
||||
Findings:
|
||||
|
||||
- Ran a cap sweep across `32`, `48`, `64`, `96`, `128`, and uncapped behavior for the two no-warm seeds. The winner was `32`: it preserved both `10/10/10` canaries and gave the best seed-43 node/check totals while leaving seed-42 flat.
|
||||
- Landed that tighter cap in the router.
|
||||
- The solved seed-42 no-warm canary stays flat at `10/10/10`, `44` routed nets, `1258` nodes, and `2736` congestion checks, with `late_phase_capped_nets=0`.
|
||||
- The heavier seed-43 no-warm canary stays `10/10/10` and improves again from `1331` nodes / `3012` congestion checks to `1267` nodes / `2813` congestion checks, still with `late_phase_capped_nets=2` and `late_phase_capped_fallbacks=2`.
|
||||
|
||||
## Step 72 tighten late-phase cap from 32 to 1 after floor sweep
|
||||
|
||||
Measured on 2026-04-02T18:45:00-07:00.
|
||||
|
||||
Findings:
|
||||
|
||||
- Extended the cap sweep below `32` and found the same pattern continued all the way down to `1`: seed-42 stayed flat because the cap never fires there, while seed-43 kept getting cheaper and still converged through the same incumbent-fallback path.
|
||||
- Landed the tightest safe setting, `1`, so late pathological reroutes now act as a minimal probe before immediately falling back to the incumbent reached-target path if they do not finish cleanly.
|
||||
- The solved seed-42 no-warm canary stays flat at `10/10/10`, `44` routed nets, `1258` nodes, and `2736` congestion checks, with `late_phase_capped_nets=0`.
|
||||
- The heavier seed-43 no-warm canary stays `10/10/10` and improves again from `1267` nodes / `2813` congestion checks to `1205` nodes / `2548` congestion checks, still with `late_phase_capped_nets=2` and `late_phase_capped_fallbacks=2`.
|
||||
|
||||
## Step 73 skip capped late-phase reroutes and carry the incumbent directly
|
||||
|
||||
Measured on 2026-04-02T18:52:00-07:00.
|
||||
|
||||
Findings:
|
||||
|
||||
- Characterization showed the two capped late seed-43 reroutes were pure churn even at a `1`-node cap: they always fell back to the incumbent reached-target path and pair-local repair still resolved the final pairs.
|
||||
- Moved that behavior into `_route_net_once()` directly: when a late heavy reroute is already capped and has a reached-target incumbent fallback, the router now reinstalls the incumbent immediately instead of calling `route_astar()` for a doomed probe.
|
||||
- The solved seed-42 no-warm canary stays flat at `10/10/10`, `44` routed nets, `1258` nodes, and `2736` congestion checks, with `late_phase_capped_nets=0`.
|
||||
- The heavier seed-43 no-warm canary stays `10/10/10` and improves again from `1205` nodes / `2548` congestion checks to `1203` nodes / `2530` congestion checks, still with `late_phase_capped_nets=2` and `late_phase_capped_fallbacks=2`.
|
||||
|
|
|
|||
|
|
@ -5,23 +5,37 @@ Generated on 2026-04-02 by `scripts/record_performance_baseline.py`.
|
|||
The full machine-readable snapshot lives in `docs/performance_baseline.json`.
|
||||
Use `scripts/diff_performance_baseline.py` to compare a fresh run against that snapshot.
|
||||
|
||||
The default baseline table below covers the standard example corpus only. The heavier `example_07_large_scale_routing_no_warm_start` canary remains performance-only and is tracked through targeted diffs plus the conflict/frontier trace artifacts.
|
||||
|
||||
Use `scripts/characterize_pair_local_search.py` when you want a small parameter sweep over example_07-style no-warm runs instead of a single canary reading.
|
||||
The current tracked sweep output lives in `docs/pair_local_characterization.json` and `docs/pair_local_characterization.md`.
|
||||
|
||||
| Scenario | Duration (s) | Total | Valid | Reached | Iter | Nets Routed | Nodes | Ray Casts | Moves Gen | Moves Added | Dyn Tree | Visibility Builds | Congestion Checks | Verify Calls |
|
||||
| :-- | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: |
|
||||
| example_01_simple_route | 0.0037 | 1 | 1 | 1 | 1 | 1 | 2 | 10 | 11 | 7 | 0 | 0 | 0 | 5 |
|
||||
| example_02_congestion_resolution | 0.3361 | 3 | 3 | 3 | 1 | 3 | 366 | 1164 | 1413 | 668 | 0 | 0 | 0 | 41 |
|
||||
| example_03_locked_paths | 0.1877 | 2 | 2 | 2 | 2 | 2 | 191 | 657 | 904 | 307 | 0 | 0 | 0 | 18 |
|
||||
| example_04_sbends_and_radii | 0.0269 | 2 | 2 | 2 | 1 | 2 | 15 | 70 | 123 | 65 | 0 | 0 | 0 | 10 |
|
||||
| example_05_orientation_stress | 0.2311 | 3 | 3 | 3 | 2 | 5 | 297 | 1274 | 1680 | 689 | 0 | 0 | 146 | 20 |
|
||||
| example_06_bend_collision_models | 0.1988 | 3 | 3 | 3 | 3 | 3 | 240 | 682 | 1026 | 629 | 0 | 0 | 0 | 15 |
|
||||
| example_07_large_scale_routing | 0.2088 | 10 | 10 | 10 | 1 | 10 | 78 | 383 | 372 | 227 | 0 | 0 | 0 | 50 |
|
||||
| example_08_custom_bend_geometry | 0.0177 | 2 | 2 | 2 | 2 | 2 | 18 | 56 | 78 | 56 | 0 | 0 | 0 | 10 |
|
||||
| example_09_unroutable_best_effort | 0.0057 | 1 | 0 | 0 | 1 | 1 | 3 | 13 | 16 | 10 | 0 | 0 | 0 | 1 |
|
||||
| example_01_simple_route | 0.0040 | 1 | 1 | 1 | 1 | 1 | 2 | 10 | 11 | 7 | 0 | 0 | 0 | 4 |
|
||||
| example_02_congestion_resolution | 0.3378 | 3 | 3 | 3 | 1 | 3 | 366 | 1164 | 1413 | 668 | 0 | 0 | 0 | 38 |
|
||||
| example_03_locked_paths | 0.1929 | 2 | 2 | 2 | 2 | 2 | 191 | 657 | 904 | 307 | 0 | 0 | 0 | 16 |
|
||||
| example_04_sbends_and_radii | 0.0279 | 2 | 2 | 2 | 1 | 2 | 15 | 70 | 123 | 65 | 0 | 0 | 0 | 8 |
|
||||
| example_05_orientation_stress | 0.2367 | 3 | 3 | 3 | 2 | 6 | 299 | 1284 | 1691 | 696 | 0 | 0 | 149 | 18 |
|
||||
| example_06_bend_collision_models | 0.1998 | 3 | 3 | 3 | 3 | 3 | 240 | 682 | 1026 | 629 | 0 | 0 | 0 | 12 |
|
||||
| example_07_large_scale_routing | 0.2005 | 10 | 10 | 10 | 1 | 10 | 78 | 383 | 372 | 227 | 0 | 0 | 0 | 40 |
|
||||
| example_08_custom_bend_geometry | 0.0176 | 2 | 2 | 2 | 2 | 2 | 18 | 56 | 78 | 56 | 0 | 0 | 0 | 8 |
|
||||
| example_09_unroutable_best_effort | 0.0058 | 1 | 0 | 0 | 1 | 1 | 3 | 13 | 16 | 10 | 0 | 0 | 0 | 1 |
|
||||
|
||||
## Full Counter Set
|
||||
|
||||
Each scenario entry in `docs/performance_baseline.json` records the full `RouteMetrics` snapshot, including cache, index, congestion, and verification counters.
|
||||
These counters are currently observational only and are not enforced as CI regression gates.
|
||||
|
||||
For the current accepted branch, the most important performance-only canary is `example_07_large_scale_routing_no_warm_start`, which now finishes `10/10/10` after a bounded post-loop pair-local scratch reroute. The relevant counters for that phase are:
|
||||
|
||||
- `pair_local_search_pairs_considered`
|
||||
- `pair_local_search_attempts`
|
||||
- `pair_local_search_accepts`
|
||||
- `pair_local_search_nodes_expanded`
|
||||
|
||||
The latest tracked characterization sweep confirms there is no smaller stable pair-local smoke case under the `<=1.0s` rule, so the 10-net no-warm-start canary remains the primary regression target for this behavior.
|
||||
|
||||
Tracked metric keys:
|
||||
|
||||
nodes_expanded, moves_generated, moves_added, pruned_closed_set, pruned_hard_collision, pruned_cost, route_iterations, nets_routed, nets_reached_target, warm_start_paths_built, warm_start_paths_used, refine_path_calls, timeout_events, iteration_reverify_calls, iteration_reverified_nets, iteration_conflicting_nets, iteration_conflict_edges, nets_carried_forward, score_component_calls, score_component_total_ns, path_cost_calls, danger_map_lookup_calls, danger_map_cache_hits, danger_map_cache_misses, danger_map_query_calls, danger_map_total_ns, move_cache_abs_hits, move_cache_abs_misses, move_cache_rel_hits, move_cache_rel_misses, guidance_match_moves, guidance_match_moves_straight, guidance_match_moves_bend90, guidance_match_moves_sbend, guidance_bonus_applied, guidance_bonus_applied_straight, guidance_bonus_applied_bend90, guidance_bonus_applied_sbend, static_safe_cache_hits, hard_collision_cache_hits, congestion_cache_hits, congestion_cache_misses, congestion_presence_cache_hits, congestion_presence_cache_misses, congestion_presence_skips, congestion_candidate_precheck_hits, congestion_candidate_precheck_misses, congestion_candidate_precheck_skips, congestion_grid_net_cache_hits, congestion_grid_net_cache_misses, congestion_grid_span_cache_hits, congestion_grid_span_cache_misses, congestion_candidate_nets, congestion_net_envelope_cache_hits, congestion_net_envelope_cache_misses, dynamic_path_objects_added, dynamic_path_objects_removed, dynamic_tree_rebuilds, dynamic_grid_rebuilds, static_tree_rebuilds, static_raw_tree_rebuilds, static_net_tree_rebuilds, visibility_corner_index_builds, visibility_builds, visibility_corner_pairs_checked, visibility_corner_queries_exact, visibility_corner_hits_exact, visibility_point_queries, visibility_point_cache_hits, visibility_point_cache_misses, visibility_tangent_candidate_scans, visibility_tangent_candidate_corner_checks, visibility_tangent_candidate_ray_tests, ray_cast_calls, ray_cast_calls_straight_static, ray_cast_calls_expand_snap, ray_cast_calls_expand_forward, ray_cast_calls_visibility_build, ray_cast_calls_visibility_query, ray_cast_calls_visibility_tangent, ray_cast_calls_other, ray_cast_candidate_bounds, ray_cast_exact_geometry_checks, congestion_check_calls, congestion_lazy_resolutions, congestion_lazy_requeues, congestion_candidate_ids, congestion_exact_pair_checks, verify_path_report_calls, verify_static_buffer_ops, verify_dynamic_candidate_nets, verify_dynamic_exact_pair_checks, refinement_windows_considered, refinement_static_bounds_checked, refinement_dynamic_bounds_checked, refinement_candidate_side_extents, refinement_candidates_built, refinement_candidates_verified, refinement_candidates_accepted, pair_local_search_pairs_considered, pair_local_search_attempts, pair_local_search_accepts, pair_local_search_nodes_expanded, late_phase_capped_nets, late_phase_capped_fallbacks
|
||||
nodes_expanded, moves_generated, moves_added, pruned_closed_set, pruned_hard_collision, pruned_cost, route_iterations, nets_routed, nets_reached_target, warm_start_paths_built, warm_start_paths_used, refine_path_calls, timeout_events, iteration_reverify_calls, iteration_reverified_nets, iteration_conflicting_nets, iteration_conflict_edges, nets_carried_forward, score_component_calls, score_component_total_ns, path_cost_calls, danger_map_lookup_calls, danger_map_cache_hits, danger_map_cache_misses, danger_map_query_calls, danger_map_total_ns, move_cache_abs_hits, move_cache_abs_misses, move_cache_rel_hits, move_cache_rel_misses, guidance_match_moves, guidance_match_moves_straight, guidance_match_moves_bend90, guidance_match_moves_sbend, guidance_bonus_applied, guidance_bonus_applied_straight, guidance_bonus_applied_bend90, guidance_bonus_applied_sbend, static_safe_cache_hits, hard_collision_cache_hits, congestion_cache_hits, congestion_cache_misses, congestion_presence_cache_hits, congestion_presence_cache_misses, congestion_presence_skips, congestion_candidate_precheck_hits, congestion_candidate_precheck_misses, congestion_candidate_precheck_skips, congestion_grid_net_cache_hits, congestion_grid_net_cache_misses, congestion_grid_span_cache_hits, congestion_grid_span_cache_misses, congestion_candidate_nets, congestion_net_envelope_cache_hits, congestion_net_envelope_cache_misses, dynamic_path_objects_added, dynamic_path_objects_removed, dynamic_tree_rebuilds, dynamic_grid_rebuilds, static_tree_rebuilds, static_raw_tree_rebuilds, static_net_tree_rebuilds, visibility_corner_index_builds, visibility_builds, visibility_corner_pairs_checked, visibility_corner_queries_exact, visibility_corner_hits_exact, visibility_point_queries, visibility_point_cache_hits, visibility_point_cache_misses, visibility_tangent_candidate_scans, visibility_tangent_candidate_corner_checks, visibility_tangent_candidate_ray_tests, ray_cast_calls, ray_cast_calls_straight_static, ray_cast_calls_expand_snap, ray_cast_calls_expand_forward, ray_cast_calls_visibility_build, ray_cast_calls_visibility_query, ray_cast_calls_visibility_tangent, ray_cast_calls_other, ray_cast_candidate_bounds, ray_cast_exact_geometry_checks, congestion_check_calls, congestion_lazy_resolutions, congestion_lazy_requeues, congestion_candidate_ids, congestion_exact_pair_checks, verify_path_report_calls, verify_static_buffer_ops, verify_dynamic_candidate_nets, verify_dynamic_exact_pair_checks, refinement_windows_considered, refinement_static_bounds_checked, refinement_dynamic_bounds_checked, refinement_candidate_side_extents, refinement_candidates_built, refinement_candidates_verified, refinement_candidates_accepted, pair_local_search_pairs_considered, pair_local_search_attempts, pair_local_search_accepts, pair_local_search_nodes_expanded
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"generator": "scripts/record_performance_baseline.py",
|
||||
"scenarios": [
|
||||
{
|
||||
"duration_s": 0.003715757979080081,
|
||||
"duration_s": 0.003964120987802744,
|
||||
"metrics": {
|
||||
"congestion_cache_hits": 0,
|
||||
"congestion_cache_misses": 0,
|
||||
|
|
@ -47,8 +47,6 @@
|
|||
"iteration_conflicting_nets": 0,
|
||||
"iteration_reverified_nets": 1,
|
||||
"iteration_reverify_calls": 1,
|
||||
"late_phase_capped_fallbacks": 0,
|
||||
"late_phase_capped_nets": 0,
|
||||
"move_cache_abs_hits": 1,
|
||||
"move_cache_abs_misses": 10,
|
||||
"move_cache_rel_hits": 0,
|
||||
|
|
@ -87,7 +85,7 @@
|
|||
"refinement_windows_considered": 0,
|
||||
"route_iterations": 1,
|
||||
"score_component_calls": 11,
|
||||
"score_component_total_ns": 16864,
|
||||
"score_component_total_ns": 18064,
|
||||
"static_net_tree_rebuilds": 1,
|
||||
"static_raw_tree_rebuilds": 0,
|
||||
"static_safe_cache_hits": 1,
|
||||
|
|
@ -95,7 +93,7 @@
|
|||
"timeout_events": 0,
|
||||
"verify_dynamic_candidate_nets": 0,
|
||||
"verify_dynamic_exact_pair_checks": 0,
|
||||
"verify_path_report_calls": 5,
|
||||
"verify_path_report_calls": 4,
|
||||
"verify_static_buffer_ops": 0,
|
||||
"visibility_builds": 0,
|
||||
"visibility_corner_hits_exact": 0,
|
||||
|
|
@ -117,7 +115,7 @@
|
|||
"valid_results": 1
|
||||
},
|
||||
{
|
||||
"duration_s": 0.33605348505079746,
|
||||
"duration_s": 0.3377689190674573,
|
||||
"metrics": {
|
||||
"congestion_cache_hits": 0,
|
||||
"congestion_cache_misses": 0,
|
||||
|
|
@ -161,8 +159,6 @@
|
|||
"iteration_conflicting_nets": 0,
|
||||
"iteration_reverified_nets": 3,
|
||||
"iteration_reverify_calls": 1,
|
||||
"late_phase_capped_fallbacks": 0,
|
||||
"late_phase_capped_nets": 0,
|
||||
"move_cache_abs_hits": 12,
|
||||
"move_cache_abs_misses": 1401,
|
||||
"move_cache_rel_hits": 1293,
|
||||
|
|
@ -201,15 +197,15 @@
|
|||
"refinement_windows_considered": 10,
|
||||
"route_iterations": 1,
|
||||
"score_component_calls": 976,
|
||||
"score_component_total_ns": 1109505,
|
||||
"score_component_total_ns": 1140704,
|
||||
"static_net_tree_rebuilds": 3,
|
||||
"static_raw_tree_rebuilds": 0,
|
||||
"static_safe_cache_hits": 1,
|
||||
"static_tree_rebuilds": 2,
|
||||
"timeout_events": 0,
|
||||
"verify_dynamic_candidate_nets": 92,
|
||||
"verify_dynamic_exact_pair_checks": 90,
|
||||
"verify_path_report_calls": 41,
|
||||
"verify_dynamic_candidate_nets": 88,
|
||||
"verify_dynamic_exact_pair_checks": 86,
|
||||
"verify_path_report_calls": 38,
|
||||
"verify_static_buffer_ops": 0,
|
||||
"visibility_builds": 0,
|
||||
"visibility_corner_hits_exact": 0,
|
||||
|
|
@ -231,7 +227,7 @@
|
|||
"valid_results": 3
|
||||
},
|
||||
{
|
||||
"duration_s": 0.18771230895072222,
|
||||
"duration_s": 0.1929313091095537,
|
||||
"metrics": {
|
||||
"congestion_cache_hits": 0,
|
||||
"congestion_cache_misses": 0,
|
||||
|
|
@ -275,8 +271,6 @@
|
|||
"iteration_conflicting_nets": 0,
|
||||
"iteration_reverified_nets": 2,
|
||||
"iteration_reverify_calls": 2,
|
||||
"late_phase_capped_fallbacks": 0,
|
||||
"late_phase_capped_nets": 0,
|
||||
"move_cache_abs_hits": 1,
|
||||
"move_cache_abs_misses": 903,
|
||||
"move_cache_rel_hits": 821,
|
||||
|
|
@ -315,16 +309,16 @@
|
|||
"refinement_windows_considered": 2,
|
||||
"route_iterations": 2,
|
||||
"score_component_calls": 504,
|
||||
"score_component_total_ns": 546567,
|
||||
"score_component_total_ns": 565410,
|
||||
"static_net_tree_rebuilds": 2,
|
||||
"static_raw_tree_rebuilds": 1,
|
||||
"static_safe_cache_hits": 1,
|
||||
"static_tree_rebuilds": 1,
|
||||
"timeout_events": 0,
|
||||
"verify_dynamic_candidate_nets": 10,
|
||||
"verify_dynamic_exact_pair_checks": 10,
|
||||
"verify_path_report_calls": 18,
|
||||
"verify_static_buffer_ops": 90,
|
||||
"verify_dynamic_candidate_nets": 9,
|
||||
"verify_dynamic_exact_pair_checks": 9,
|
||||
"verify_path_report_calls": 16,
|
||||
"verify_static_buffer_ops": 81,
|
||||
"visibility_builds": 0,
|
||||
"visibility_corner_hits_exact": 0,
|
||||
"visibility_corner_index_builds": 2,
|
||||
|
|
@ -345,7 +339,7 @@
|
|||
"valid_results": 2
|
||||
},
|
||||
{
|
||||
"duration_s": 0.026945222169160843,
|
||||
"duration_s": 0.02791503700427711,
|
||||
"metrics": {
|
||||
"congestion_cache_hits": 0,
|
||||
"congestion_cache_misses": 0,
|
||||
|
|
@ -389,8 +383,6 @@
|
|||
"iteration_conflicting_nets": 0,
|
||||
"iteration_reverified_nets": 2,
|
||||
"iteration_reverify_calls": 1,
|
||||
"late_phase_capped_fallbacks": 0,
|
||||
"late_phase_capped_nets": 0,
|
||||
"move_cache_abs_hits": 1,
|
||||
"move_cache_abs_misses": 122,
|
||||
"move_cache_rel_hits": 80,
|
||||
|
|
@ -429,15 +421,15 @@
|
|||
"refinement_windows_considered": 0,
|
||||
"route_iterations": 1,
|
||||
"score_component_calls": 90,
|
||||
"score_component_total_ns": 97710,
|
||||
"score_component_total_ns": 100083,
|
||||
"static_net_tree_rebuilds": 2,
|
||||
"static_raw_tree_rebuilds": 0,
|
||||
"static_safe_cache_hits": 1,
|
||||
"static_tree_rebuilds": 1,
|
||||
"timeout_events": 0,
|
||||
"verify_dynamic_candidate_nets": 12,
|
||||
"verify_dynamic_candidate_nets": 9,
|
||||
"verify_dynamic_exact_pair_checks": 0,
|
||||
"verify_path_report_calls": 10,
|
||||
"verify_path_report_calls": 8,
|
||||
"verify_static_buffer_ops": 0,
|
||||
"visibility_builds": 0,
|
||||
"visibility_corner_hits_exact": 0,
|
||||
|
|
@ -459,75 +451,73 @@
|
|||
"valid_results": 2
|
||||
},
|
||||
{
|
||||
"duration_s": 0.23108969815075397,
|
||||
"duration_s": 0.23665715800598264,
|
||||
"metrics": {
|
||||
"congestion_cache_hits": 3,
|
||||
"congestion_cache_misses": 146,
|
||||
"congestion_cache_hits": 4,
|
||||
"congestion_cache_misses": 149,
|
||||
"congestion_candidate_ids": 32,
|
||||
"congestion_candidate_nets": 23,
|
||||
"congestion_candidate_precheck_hits": 129,
|
||||
"congestion_candidate_precheck_misses": 20,
|
||||
"congestion_candidate_precheck_hits": 131,
|
||||
"congestion_candidate_precheck_misses": 22,
|
||||
"congestion_candidate_precheck_skips": 0,
|
||||
"congestion_check_calls": 146,
|
||||
"congestion_check_calls": 149,
|
||||
"congestion_exact_pair_checks": 30,
|
||||
"congestion_grid_net_cache_hits": 16,
|
||||
"congestion_grid_net_cache_misses": 26,
|
||||
"congestion_grid_net_cache_misses": 28,
|
||||
"congestion_grid_span_cache_hits": 15,
|
||||
"congestion_grid_span_cache_misses": 7,
|
||||
"congestion_lazy_requeues": 0,
|
||||
"congestion_lazy_resolutions": 0,
|
||||
"congestion_net_envelope_cache_hits": 127,
|
||||
"congestion_net_envelope_cache_misses": 39,
|
||||
"congestion_presence_cache_hits": 196,
|
||||
"congestion_presence_cache_misses": 27,
|
||||
"congestion_presence_skips": 74,
|
||||
"congestion_net_envelope_cache_hits": 128,
|
||||
"congestion_net_envelope_cache_misses": 43,
|
||||
"congestion_presence_cache_hits": 200,
|
||||
"congestion_presence_cache_misses": 30,
|
||||
"congestion_presence_skips": 77,
|
||||
"danger_map_cache_hits": 0,
|
||||
"danger_map_cache_misses": 0,
|
||||
"danger_map_lookup_calls": 0,
|
||||
"danger_map_query_calls": 0,
|
||||
"danger_map_total_ns": 0,
|
||||
"dynamic_grid_rebuilds": 0,
|
||||
"dynamic_path_objects_added": 48,
|
||||
"dynamic_path_objects_removed": 36,
|
||||
"dynamic_path_objects_added": 49,
|
||||
"dynamic_path_objects_removed": 37,
|
||||
"dynamic_tree_rebuilds": 0,
|
||||
"guidance_bonus_applied": 562.5,
|
||||
"guidance_bonus_applied": 687.5,
|
||||
"guidance_bonus_applied_bend90": 500.0,
|
||||
"guidance_bonus_applied_sbend": 0.0,
|
||||
"guidance_bonus_applied_straight": 62.5,
|
||||
"guidance_match_moves": 9,
|
||||
"guidance_bonus_applied_straight": 187.5,
|
||||
"guidance_match_moves": 11,
|
||||
"guidance_match_moves_bend90": 8,
|
||||
"guidance_match_moves_sbend": 0,
|
||||
"guidance_match_moves_straight": 1,
|
||||
"guidance_match_moves_straight": 3,
|
||||
"hard_collision_cache_hits": 0,
|
||||
"iteration_conflict_edges": 1,
|
||||
"iteration_conflicting_nets": 2,
|
||||
"iteration_reverified_nets": 6,
|
||||
"iteration_reverify_calls": 2,
|
||||
"late_phase_capped_fallbacks": 0,
|
||||
"late_phase_capped_nets": 0,
|
||||
"move_cache_abs_hits": 374,
|
||||
"move_cache_abs_hits": 385,
|
||||
"move_cache_abs_misses": 1306,
|
||||
"move_cache_rel_hits": 1204,
|
||||
"move_cache_rel_misses": 102,
|
||||
"moves_added": 689,
|
||||
"moves_generated": 1680,
|
||||
"nets_carried_forward": 1,
|
||||
"nets_reached_target": 5,
|
||||
"nets_routed": 5,
|
||||
"nodes_expanded": 297,
|
||||
"moves_added": 696,
|
||||
"moves_generated": 1691,
|
||||
"nets_carried_forward": 0,
|
||||
"nets_reached_target": 6,
|
||||
"nets_routed": 6,
|
||||
"nodes_expanded": 299,
|
||||
"pair_local_search_accepts": 0,
|
||||
"pair_local_search_attempts": 0,
|
||||
"pair_local_search_nodes_expanded": 0,
|
||||
"pair_local_search_pairs_considered": 0,
|
||||
"path_cost_calls": 2,
|
||||
"pruned_closed_set": 159,
|
||||
"pruned_cost": 533,
|
||||
"pruned_cost": 537,
|
||||
"pruned_hard_collision": 14,
|
||||
"ray_cast_calls": 1274,
|
||||
"ray_cast_calls_expand_forward": 292,
|
||||
"ray_cast_calls_expand_snap": 2,
|
||||
"ray_cast_calls": 1284,
|
||||
"ray_cast_calls_expand_forward": 293,
|
||||
"ray_cast_calls_expand_snap": 3,
|
||||
"ray_cast_calls_other": 0,
|
||||
"ray_cast_calls_straight_static": 971,
|
||||
"ray_cast_calls_straight_static": 979,
|
||||
"ray_cast_calls_visibility_build": 0,
|
||||
"ray_cast_calls_visibility_query": 0,
|
||||
"ray_cast_calls_visibility_tangent": 9,
|
||||
|
|
@ -542,16 +532,16 @@
|
|||
"refinement_static_bounds_checked": 0,
|
||||
"refinement_windows_considered": 0,
|
||||
"route_iterations": 2,
|
||||
"score_component_calls": 1234,
|
||||
"score_component_total_ns": 1223569,
|
||||
"score_component_calls": 1245,
|
||||
"score_component_total_ns": 1260961,
|
||||
"static_net_tree_rebuilds": 3,
|
||||
"static_raw_tree_rebuilds": 0,
|
||||
"static_safe_cache_hits": 8,
|
||||
"static_safe_cache_hits": 9,
|
||||
"static_tree_rebuilds": 1,
|
||||
"timeout_events": 0,
|
||||
"verify_dynamic_candidate_nets": 8,
|
||||
"verify_dynamic_exact_pair_checks": 12,
|
||||
"verify_path_report_calls": 20,
|
||||
"verify_path_report_calls": 18,
|
||||
"verify_static_buffer_ops": 0,
|
||||
"visibility_builds": 0,
|
||||
"visibility_corner_hits_exact": 0,
|
||||
|
|
@ -563,7 +553,7 @@
|
|||
"visibility_point_queries": 0,
|
||||
"visibility_tangent_candidate_corner_checks": 70,
|
||||
"visibility_tangent_candidate_ray_tests": 9,
|
||||
"visibility_tangent_candidate_scans": 292,
|
||||
"visibility_tangent_candidate_scans": 293,
|
||||
"warm_start_paths_built": 2,
|
||||
"warm_start_paths_used": 2
|
||||
},
|
||||
|
|
@ -573,7 +563,7 @@
|
|||
"valid_results": 3
|
||||
},
|
||||
{
|
||||
"duration_s": 0.19879506202414632,
|
||||
"duration_s": 0.19982667709700763,
|
||||
"metrics": {
|
||||
"congestion_cache_hits": 0,
|
||||
"congestion_cache_misses": 0,
|
||||
|
|
@ -599,7 +589,7 @@
|
|||
"danger_map_cache_misses": 731,
|
||||
"danger_map_lookup_calls": 1914,
|
||||
"danger_map_query_calls": 731,
|
||||
"danger_map_total_ns": 19050142,
|
||||
"danger_map_total_ns": 18959782,
|
||||
"dynamic_grid_rebuilds": 0,
|
||||
"dynamic_path_objects_added": 54,
|
||||
"dynamic_path_objects_removed": 36,
|
||||
|
|
@ -617,8 +607,6 @@
|
|||
"iteration_conflicting_nets": 0,
|
||||
"iteration_reverified_nets": 3,
|
||||
"iteration_reverify_calls": 3,
|
||||
"late_phase_capped_fallbacks": 0,
|
||||
"late_phase_capped_nets": 0,
|
||||
"move_cache_abs_hits": 186,
|
||||
"move_cache_abs_misses": 840,
|
||||
"move_cache_rel_hits": 702,
|
||||
|
|
@ -657,7 +645,7 @@
|
|||
"refinement_windows_considered": 0,
|
||||
"route_iterations": 3,
|
||||
"score_component_calls": 842,
|
||||
"score_component_total_ns": 21353240,
|
||||
"score_component_total_ns": 21338709,
|
||||
"static_net_tree_rebuilds": 3,
|
||||
"static_raw_tree_rebuilds": 3,
|
||||
"static_safe_cache_hits": 141,
|
||||
|
|
@ -665,8 +653,8 @@
|
|||
"timeout_events": 0,
|
||||
"verify_dynamic_candidate_nets": 0,
|
||||
"verify_dynamic_exact_pair_checks": 0,
|
||||
"verify_path_report_calls": 15,
|
||||
"verify_static_buffer_ops": 90,
|
||||
"verify_path_report_calls": 12,
|
||||
"verify_static_buffer_ops": 72,
|
||||
"visibility_builds": 0,
|
||||
"visibility_corner_hits_exact": 0,
|
||||
"visibility_corner_index_builds": 3,
|
||||
|
|
@ -687,7 +675,7 @@
|
|||
"valid_results": 3
|
||||
},
|
||||
{
|
||||
"duration_s": 0.20880168909206986,
|
||||
"duration_s": 0.20046633295714855,
|
||||
"metrics": {
|
||||
"congestion_cache_hits": 0,
|
||||
"congestion_cache_misses": 0,
|
||||
|
|
@ -713,7 +701,7 @@
|
|||
"danger_map_cache_misses": 448,
|
||||
"danger_map_lookup_calls": 681,
|
||||
"danger_map_query_calls": 448,
|
||||
"danger_map_total_ns": 11025527,
|
||||
"danger_map_total_ns": 11017087,
|
||||
"dynamic_grid_rebuilds": 0,
|
||||
"dynamic_path_objects_added": 132,
|
||||
"dynamic_path_objects_removed": 88,
|
||||
|
|
@ -731,8 +719,6 @@
|
|||
"iteration_conflicting_nets": 0,
|
||||
"iteration_reverified_nets": 10,
|
||||
"iteration_reverify_calls": 1,
|
||||
"late_phase_capped_fallbacks": 0,
|
||||
"late_phase_capped_nets": 0,
|
||||
"move_cache_abs_hits": 6,
|
||||
"move_cache_abs_misses": 366,
|
||||
"move_cache_rel_hits": 275,
|
||||
|
|
@ -771,16 +757,16 @@
|
|||
"refinement_windows_considered": 0,
|
||||
"route_iterations": 1,
|
||||
"score_component_calls": 291,
|
||||
"score_component_total_ns": 11875928,
|
||||
"score_component_total_ns": 11869917,
|
||||
"static_net_tree_rebuilds": 10,
|
||||
"static_raw_tree_rebuilds": 1,
|
||||
"static_safe_cache_hits": 6,
|
||||
"static_tree_rebuilds": 10,
|
||||
"timeout_events": 0,
|
||||
"verify_dynamic_candidate_nets": 476,
|
||||
"verify_dynamic_exact_pair_checks": 72,
|
||||
"verify_path_report_calls": 50,
|
||||
"verify_static_buffer_ops": 220,
|
||||
"verify_dynamic_candidate_nets": 370,
|
||||
"verify_dynamic_exact_pair_checks": 56,
|
||||
"verify_path_report_calls": 40,
|
||||
"verify_static_buffer_ops": 176,
|
||||
"visibility_builds": 0,
|
||||
"visibility_corner_hits_exact": 0,
|
||||
"visibility_corner_index_builds": 10,
|
||||
|
|
@ -801,7 +787,7 @@
|
|||
"valid_results": 10
|
||||
},
|
||||
{
|
||||
"duration_s": 0.017696003895252943,
|
||||
"duration_s": 0.01759456400759518,
|
||||
"metrics": {
|
||||
"congestion_cache_hits": 0,
|
||||
"congestion_cache_misses": 0,
|
||||
|
|
@ -845,8 +831,6 @@
|
|||
"iteration_conflicting_nets": 0,
|
||||
"iteration_reverified_nets": 2,
|
||||
"iteration_reverify_calls": 2,
|
||||
"late_phase_capped_fallbacks": 0,
|
||||
"late_phase_capped_nets": 0,
|
||||
"move_cache_abs_hits": 2,
|
||||
"move_cache_abs_misses": 76,
|
||||
"move_cache_rel_hits": 32,
|
||||
|
|
@ -885,7 +869,7 @@
|
|||
"refinement_windows_considered": 0,
|
||||
"route_iterations": 2,
|
||||
"score_component_calls": 72,
|
||||
"score_component_total_ns": 87742,
|
||||
"score_component_total_ns": 85864,
|
||||
"static_net_tree_rebuilds": 2,
|
||||
"static_raw_tree_rebuilds": 0,
|
||||
"static_safe_cache_hits": 2,
|
||||
|
|
@ -893,7 +877,7 @@
|
|||
"timeout_events": 0,
|
||||
"verify_dynamic_candidate_nets": 0,
|
||||
"verify_dynamic_exact_pair_checks": 0,
|
||||
"verify_path_report_calls": 10,
|
||||
"verify_path_report_calls": 8,
|
||||
"verify_static_buffer_ops": 0,
|
||||
"visibility_builds": 0,
|
||||
"visibility_corner_hits_exact": 0,
|
||||
|
|
@ -915,7 +899,7 @@
|
|||
"valid_results": 2
|
||||
},
|
||||
{
|
||||
"duration_s": 0.005660973023623228,
|
||||
"duration_s": 0.005838233977556229,
|
||||
"metrics": {
|
||||
"congestion_cache_hits": 0,
|
||||
"congestion_cache_misses": 0,
|
||||
|
|
@ -941,7 +925,7 @@
|
|||
"danger_map_cache_misses": 20,
|
||||
"danger_map_lookup_calls": 30,
|
||||
"danger_map_query_calls": 20,
|
||||
"danger_map_total_ns": 515133,
|
||||
"danger_map_total_ns": 523870,
|
||||
"dynamic_grid_rebuilds": 0,
|
||||
"dynamic_path_objects_added": 2,
|
||||
"dynamic_path_objects_removed": 1,
|
||||
|
|
@ -959,8 +943,6 @@
|
|||
"iteration_conflicting_nets": 0,
|
||||
"iteration_reverified_nets": 0,
|
||||
"iteration_reverify_calls": 1,
|
||||
"late_phase_capped_fallbacks": 0,
|
||||
"late_phase_capped_nets": 0,
|
||||
"move_cache_abs_hits": 0,
|
||||
"move_cache_abs_misses": 16,
|
||||
"move_cache_rel_hits": 2,
|
||||
|
|
@ -999,7 +981,7 @@
|
|||
"refinement_windows_considered": 0,
|
||||
"route_iterations": 1,
|
||||
"score_component_calls": 14,
|
||||
"score_component_total_ns": 554809,
|
||||
"score_component_total_ns": 563611,
|
||||
"static_net_tree_rebuilds": 1,
|
||||
"static_raw_tree_rebuilds": 1,
|
||||
"static_safe_cache_hits": 0,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,48 +0,0 @@
|
|||
# Pre-Pair Frontier Trace
|
||||
|
||||
Generated at 2026-04-02T18:51:01-07:00 by `scripts/record_pre_pair_frontier_trace.py`.
|
||||
|
||||
## example_07_large_scale_routing_no_warm_start
|
||||
|
||||
Results: 10 valid / 10 reached / 10 total.
|
||||
|
||||
Captured iteration: `4`
|
||||
|
||||
Conflict edges: `(('net_01', 'net_02'), ('net_06', 'net_07'))`
|
||||
|
||||
| Net | Nodes | Checks | Closed-Set | Cost | Hard Collision | Guidance Seed | Frontier Samples |
|
||||
| :-- | --: | --: | --: | --: | --: | :--: | --: |
|
||||
| net_07 | 7 | 30 | 1 | 0 | 0 | yes | 0 |
|
||||
| net_06 | 46 | 179 | 7 | 15 | 0 | yes | 5 |
|
||||
| net_00 | 10 | 43 | 1 | 0 | 0 | yes | 0 |
|
||||
| net_01 | 18 | 80 | 3 | 0 | 0 | yes | 0 |
|
||||
|
||||
Frontier prune totals by reason:
|
||||
|
||||
- `closed_set`: 5
|
||||
- `hard_collision`: 0
|
||||
- `self_collision`: 0
|
||||
- `cost`: 0
|
||||
|
||||
## example_07_large_scale_routing_no_warm_start_seed43
|
||||
|
||||
Results: 10 valid / 10 reached / 10 total.
|
||||
|
||||
Captured iteration: `5`
|
||||
|
||||
Conflict edges: `(('net_02', 'net_03'), ('net_06', 'net_07'))`
|
||||
|
||||
| Net | Nodes | Checks | Closed-Set | Cost | Hard Collision | Guidance Seed | Frontier Samples |
|
||||
| :-- | --: | --: | --: | --: | --: | :--: | --: |
|
||||
| net_07 | 16 | 85 | 3 | 0 | 0 | yes | 2 |
|
||||
| net_02 | 17 | 86 | 4 | 0 | 0 | yes | 3 |
|
||||
| net_06 | 0 | 0 | 0 | 0 | 0 | yes | 8 |
|
||||
| net_03 | 0 | 0 | 0 | 0 | 0 | yes | 12 |
|
||||
|
||||
Frontier prune totals by reason:
|
||||
|
||||
- `closed_set`: 25
|
||||
- `hard_collision`: 0
|
||||
- `self_collision`: 0
|
||||
- `cost`: 0
|
||||
|
||||
|
|
@ -18,12 +18,8 @@ from .results import ( # noqa: PLC0414
|
|||
ComponentConflictTrace as ComponentConflictTrace,
|
||||
ConflictTraceEntry as ConflictTraceEntry,
|
||||
FrontierPruneSample as FrontierPruneSample,
|
||||
IterationNetAttemptTrace as IterationNetAttemptTrace,
|
||||
IterationTraceEntry as IterationTraceEntry,
|
||||
NetConflictTrace as NetConflictTrace,
|
||||
NetFrontierTrace as NetFrontierTrace,
|
||||
PrePairFrontierTraceEntry as PrePairFrontierTraceEntry,
|
||||
PrePairNetTrace as PrePairNetTrace,
|
||||
RoutingResult as RoutingResult,
|
||||
RoutingRunResult as RoutingRunResult,
|
||||
)
|
||||
|
|
@ -51,8 +47,6 @@ def route(
|
|||
expanded_nodes=tuple(finder.accumulated_expanded_nodes),
|
||||
conflict_trace=tuple(finder.conflict_trace),
|
||||
frontier_trace=tuple(finder.frontier_trace),
|
||||
pre_pair_frontier_trace=finder.pre_pair_frontier_trace,
|
||||
iteration_trace=tuple(finder.iteration_trace),
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
|
|
@ -68,10 +62,6 @@ __all__ = [
|
|||
"PathSeed",
|
||||
"Port",
|
||||
"FrontierPruneSample",
|
||||
"IterationNetAttemptTrace",
|
||||
"IterationTraceEntry",
|
||||
"PrePairFrontierTraceEntry",
|
||||
"PrePairNetTrace",
|
||||
"RefinementOptions",
|
||||
"RoutingOptions",
|
||||
"RoutingProblem",
|
||||
|
|
|
|||
|
|
@ -107,8 +107,6 @@ class DiagnosticsOptions:
|
|||
capture_expanded: bool = False
|
||||
capture_conflict_trace: bool = False
|
||||
capture_frontier_trace: bool = False
|
||||
capture_iteration_trace: bool = False
|
||||
capture_pre_pair_frontier_trace: bool = False
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
|
|
|
|||
|
|
@ -78,53 +78,6 @@ class NetFrontierTrace:
|
|||
samples: tuple[FrontierPruneSample, ...] = ()
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class PrePairNetTrace:
|
||||
net_id: str
|
||||
nodes_expanded: int
|
||||
congestion_check_calls: int
|
||||
pruned_closed_set: int
|
||||
pruned_cost: int
|
||||
pruned_hard_collision: int
|
||||
guidance_seed_present: bool
|
||||
frontier: NetFrontierTrace
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class PrePairFrontierTraceEntry:
|
||||
iteration: int
|
||||
routed_net_ids: tuple[str, ...]
|
||||
conflict_edges: tuple[tuple[str, str], ...]
|
||||
nets: tuple[PrePairNetTrace, ...]
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class IterationNetAttemptTrace:
|
||||
net_id: str
|
||||
reached_target: bool
|
||||
nodes_expanded: int
|
||||
congestion_check_calls: int
|
||||
pruned_closed_set: int
|
||||
pruned_cost: int
|
||||
pruned_hard_collision: int
|
||||
guidance_seed_present: bool
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class IterationTraceEntry:
|
||||
iteration: int
|
||||
congestion_penalty: float
|
||||
routed_net_ids: tuple[str, ...]
|
||||
completed_nets: int
|
||||
conflict_edges: int
|
||||
total_dynamic_collisions: int
|
||||
nodes_expanded: int
|
||||
congestion_check_calls: int
|
||||
congestion_candidate_ids: int
|
||||
congestion_exact_pair_checks: int
|
||||
net_attempts: tuple[IterationNetAttemptTrace, ...] = ()
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class RouteMetrics:
|
||||
nodes_expanded: int
|
||||
|
|
@ -230,8 +183,6 @@ class RouteMetrics:
|
|||
pair_local_search_attempts: int
|
||||
pair_local_search_accepts: int
|
||||
pair_local_search_nodes_expanded: int
|
||||
late_phase_capped_nets: int
|
||||
late_phase_capped_fallbacks: int
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
|
|
@ -280,5 +231,3 @@ class RoutingRunResult:
|
|||
expanded_nodes: tuple[tuple[int, int, int], ...] = ()
|
||||
conflict_trace: tuple[ConflictTraceEntry, ...] = ()
|
||||
frontier_trace: tuple[NetFrontierTrace, ...] = ()
|
||||
pre_pair_frontier_trace: PrePairFrontierTraceEntry | None = None
|
||||
iteration_trace: tuple[IterationTraceEntry, ...] = ()
|
||||
|
|
|
|||
|
|
@ -258,8 +258,6 @@ class AStarMetrics:
|
|||
"total_pair_local_search_attempts",
|
||||
"total_pair_local_search_accepts",
|
||||
"total_pair_local_search_nodes_expanded",
|
||||
"total_late_phase_capped_nets",
|
||||
"total_late_phase_capped_fallbacks",
|
||||
"last_expanded_nodes",
|
||||
"nodes_expanded",
|
||||
"moves_generated",
|
||||
|
|
@ -373,8 +371,6 @@ class AStarMetrics:
|
|||
self.total_pair_local_search_attempts = 0
|
||||
self.total_pair_local_search_accepts = 0
|
||||
self.total_pair_local_search_nodes_expanded = 0
|
||||
self.total_late_phase_capped_nets = 0
|
||||
self.total_late_phase_capped_fallbacks = 0
|
||||
self.last_expanded_nodes: list[tuple[int, int, int]] = []
|
||||
self.nodes_expanded = 0
|
||||
self.moves_generated = 0
|
||||
|
|
@ -487,8 +483,6 @@ class AStarMetrics:
|
|||
self.total_pair_local_search_attempts = 0
|
||||
self.total_pair_local_search_accepts = 0
|
||||
self.total_pair_local_search_nodes_expanded = 0
|
||||
self.total_late_phase_capped_nets = 0
|
||||
self.total_late_phase_capped_fallbacks = 0
|
||||
|
||||
def reset_per_route(self) -> None:
|
||||
self.nodes_expanded = 0
|
||||
|
|
@ -604,8 +598,6 @@ class AStarMetrics:
|
|||
pair_local_search_attempts=self.total_pair_local_search_attempts,
|
||||
pair_local_search_accepts=self.total_pair_local_search_accepts,
|
||||
pair_local_search_nodes_expanded=self.total_pair_local_search_nodes_expanded,
|
||||
late_phase_capped_nets=self.total_late_phase_capped_nets,
|
||||
late_phase_capped_fallbacks=self.total_late_phase_capped_fallbacks,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -11,12 +11,8 @@ from inire.results import (
|
|||
ComponentConflictTrace,
|
||||
ConflictTraceEntry,
|
||||
FrontierPruneSample,
|
||||
IterationNetAttemptTrace,
|
||||
IterationTraceEntry,
|
||||
NetConflictTrace,
|
||||
NetFrontierTrace,
|
||||
PrePairFrontierTraceEntry,
|
||||
PrePairNetTrace,
|
||||
RoutingOutcome,
|
||||
RoutingReport,
|
||||
RoutingResult,
|
||||
|
|
@ -54,9 +50,6 @@ class _RoutingState:
|
|||
last_conflict_signature: tuple[tuple[str, str], ...]
|
||||
last_conflict_edge_count: int
|
||||
repeated_conflict_count: int
|
||||
pair_local_plateau_count: int
|
||||
recent_attempt_work: dict[str, int]
|
||||
pre_pair_candidate: _PrePairCandidate | None
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
|
|
@ -72,30 +65,6 @@ class _PairLocalTarget:
|
|||
net_ids: tuple[str, str]
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class _PrePairCandidate:
|
||||
iteration: int
|
||||
routed_net_ids: tuple[str, ...]
|
||||
conflict_edges: tuple[tuple[str, str], ...]
|
||||
net_attempts: tuple[IterationNetAttemptTrace, ...]
|
||||
|
||||
|
||||
_ITERATION_TRACE_TOTALS = (
|
||||
"nodes_expanded",
|
||||
"congestion_check_calls",
|
||||
"congestion_candidate_ids",
|
||||
"congestion_exact_pair_checks",
|
||||
)
|
||||
|
||||
_ATTEMPT_TRACE_TOTALS = (
|
||||
"nodes_expanded",
|
||||
"congestion_check_calls",
|
||||
"pruned_closed_set",
|
||||
"pruned_cost",
|
||||
"pruned_hard_collision",
|
||||
)
|
||||
|
||||
|
||||
class PathFinder:
|
||||
__slots__ = (
|
||||
"context",
|
||||
|
|
@ -104,8 +73,6 @@ class PathFinder:
|
|||
"accumulated_expanded_nodes",
|
||||
"conflict_trace",
|
||||
"frontier_trace",
|
||||
"pre_pair_frontier_trace",
|
||||
"iteration_trace",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
|
|
@ -123,48 +90,6 @@ class PathFinder:
|
|||
self.accumulated_expanded_nodes: list[tuple[int, int, int]] = []
|
||||
self.conflict_trace: list[ConflictTraceEntry] = []
|
||||
self.frontier_trace: list[NetFrontierTrace] = []
|
||||
self.pre_pair_frontier_trace: PrePairFrontierTraceEntry | None = None
|
||||
self.iteration_trace: list[IterationTraceEntry] = []
|
||||
|
||||
def _metric_total(self, metric_name: str) -> int:
|
||||
return int(getattr(self.metrics, f"total_{metric_name}"))
|
||||
|
||||
def _capture_metric_totals(self, metric_names: tuple[str, ...]) -> dict[str, int]:
|
||||
return {metric_name: self._metric_total(metric_name) for metric_name in metric_names}
|
||||
|
||||
def _metric_deltas(self, before: dict[str, int], after: dict[str, int]) -> dict[str, int]:
|
||||
return {metric_name: after[metric_name] - before[metric_name] for metric_name in before}
|
||||
|
||||
def _results_all_reached_target(self, state: _RoutingState) -> bool:
|
||||
return (
|
||||
len(state.results) == len(state.ordered_net_ids)
|
||||
and all(result.reached_target for result in state.results.values())
|
||||
)
|
||||
|
||||
def _has_incumbent_fallback(self, result: RoutingResult | None) -> bool:
|
||||
return bool(result and result.reached_target and result.path)
|
||||
|
||||
def _restore_incumbent_fallback(
|
||||
self,
|
||||
net_id: str,
|
||||
result: RoutingResult,
|
||||
guidance_seed_present: bool,
|
||||
) -> tuple[RoutingResult, bool]:
|
||||
self.metrics.total_late_phase_capped_fallbacks += 1
|
||||
self._install_path(net_id, result.path)
|
||||
return result, guidance_seed_present
|
||||
|
||||
def _guidance_for_result(
|
||||
self,
|
||||
result: RoutingResult | None,
|
||||
) -> tuple[Sequence[ComponentResult] | None, float, bool]:
|
||||
if result is None or not result.reached_target or not result.path:
|
||||
return None, 0.0, False
|
||||
return (
|
||||
result.as_seed().segments,
|
||||
max(10.0, self.context.options.objective.bend_penalty * 0.25),
|
||||
True,
|
||||
)
|
||||
|
||||
def _install_path(self, net_id: str, path: Sequence[ComponentResult]) -> None:
|
||||
all_geoms: list[Polygon] = []
|
||||
|
|
@ -263,9 +188,6 @@ class PathFinder:
|
|||
last_conflict_signature=(),
|
||||
last_conflict_edge_count=0,
|
||||
repeated_conflict_count=0,
|
||||
pair_local_plateau_count=0,
|
||||
recent_attempt_work={},
|
||||
pre_pair_candidate=None,
|
||||
)
|
||||
if state.initial_paths is None and congestion.warm_start_enabled:
|
||||
state.initial_paths = self._build_greedy_warm_start_paths(net_specs, congestion.net_order)
|
||||
|
|
@ -303,38 +225,6 @@ class PathFinder:
|
|||
if result and result.path:
|
||||
self._install_path(net_id, result.path)
|
||||
|
||||
def _analyze_restored_best(
|
||||
self,
|
||||
state: _RoutingState,
|
||||
) -> tuple[dict[str, PathVerificationDetail], _IterationReview]:
|
||||
capture_component_conflicts = (
|
||||
self.context.options.diagnostics.capture_conflict_trace
|
||||
or self.context.options.diagnostics.capture_pre_pair_frontier_trace
|
||||
)
|
||||
state.results, details_by_net, review = self._analyze_results(
|
||||
state.ordered_net_ids,
|
||||
state.results,
|
||||
capture_component_conflicts=capture_component_conflicts,
|
||||
count_iteration_metrics=False,
|
||||
)
|
||||
if self.context.options.diagnostics.capture_conflict_trace:
|
||||
self._capture_conflict_trace_entry(
|
||||
state,
|
||||
stage="restored_best",
|
||||
iteration=None,
|
||||
results=state.results,
|
||||
details_by_net=details_by_net,
|
||||
review=review,
|
||||
)
|
||||
if self.context.options.diagnostics.capture_pre_pair_frontier_trace:
|
||||
self.pre_pair_frontier_trace = self._materialize_pre_pair_frontier_trace(
|
||||
state,
|
||||
state.results,
|
||||
details_by_net,
|
||||
review,
|
||||
)
|
||||
return details_by_net, review
|
||||
|
||||
def _update_best_iteration(self, state: _RoutingState, review: _IterationReview) -> bool:
|
||||
completed_nets = len(review.completed_net_ids)
|
||||
conflict_edges = len(review.conflict_edges)
|
||||
|
|
@ -478,93 +368,6 @@ class PathFinder:
|
|||
|
||||
return tuple(hotspot_bounds)
|
||||
|
||||
def _capture_single_frontier_trace(
|
||||
self,
|
||||
state: _RoutingState,
|
||||
net_id: str,
|
||||
result: RoutingResult,
|
||||
hotspot_bounds: tuple[tuple[float, float, float, float], ...],
|
||||
) -> NetFrontierTrace:
|
||||
if not hotspot_bounds:
|
||||
return NetFrontierTrace(
|
||||
net_id=net_id,
|
||||
hotspot_bounds=(),
|
||||
pruned_closed_set=0,
|
||||
pruned_hard_collision=0,
|
||||
pruned_self_collision=0,
|
||||
pruned_cost=0,
|
||||
)
|
||||
|
||||
original_metrics = self.metrics
|
||||
original_context_metrics = self.context.metrics
|
||||
original_engine_metrics = self.context.cost_evaluator.collision_engine.metrics
|
||||
original_danger_metrics = None
|
||||
if self.context.cost_evaluator.danger_map is not None:
|
||||
original_danger_metrics = self.context.cost_evaluator.danger_map.metrics
|
||||
|
||||
try:
|
||||
scratch_metrics = AStarMetrics()
|
||||
self.context.metrics = scratch_metrics
|
||||
self.context.cost_evaluator.collision_engine.metrics = scratch_metrics
|
||||
if self.context.cost_evaluator.danger_map is not None:
|
||||
self.context.cost_evaluator.danger_map.metrics = scratch_metrics
|
||||
|
||||
guidance_seed = result.as_seed().segments if result.path else None
|
||||
guidance_bonus = 0.0
|
||||
if guidance_seed:
|
||||
guidance_bonus = max(10.0, self.context.options.objective.bend_penalty * 0.25)
|
||||
collector = FrontierTraceCollector(hotspot_bounds=hotspot_bounds)
|
||||
run_config = SearchRunConfig.from_options(
|
||||
self.context.options,
|
||||
return_partial=True,
|
||||
store_expanded=False,
|
||||
guidance_seed=guidance_seed,
|
||||
guidance_bonus=guidance_bonus,
|
||||
frontier_trace=collector,
|
||||
self_collision_check=(net_id in state.needs_self_collision_check),
|
||||
node_limit=self.context.options.search.node_limit,
|
||||
)
|
||||
|
||||
self.context.cost_evaluator.collision_engine.remove_path(net_id)
|
||||
try:
|
||||
route_astar(
|
||||
state.net_specs[net_id].start,
|
||||
state.net_specs[net_id].target,
|
||||
state.net_specs[net_id].width,
|
||||
context=self.context,
|
||||
metrics=scratch_metrics,
|
||||
net_id=net_id,
|
||||
config=run_config,
|
||||
)
|
||||
finally:
|
||||
if result.path:
|
||||
self._install_path(net_id, result.path)
|
||||
|
||||
return NetFrontierTrace(
|
||||
net_id=net_id,
|
||||
hotspot_bounds=hotspot_bounds,
|
||||
pruned_closed_set=collector.pruned_closed_set,
|
||||
pruned_hard_collision=collector.pruned_hard_collision,
|
||||
pruned_self_collision=collector.pruned_self_collision,
|
||||
pruned_cost=collector.pruned_cost,
|
||||
samples=tuple(
|
||||
FrontierPruneSample(
|
||||
reason=reason, # type: ignore[arg-type]
|
||||
move_type=move_type,
|
||||
hotspot_index=hotspot_index,
|
||||
parent_state=parent_state,
|
||||
end_state=end_state,
|
||||
)
|
||||
for reason, move_type, hotspot_index, parent_state, end_state in collector.samples
|
||||
),
|
||||
)
|
||||
finally:
|
||||
self.metrics = original_metrics
|
||||
self.context.metrics = original_context_metrics
|
||||
self.context.cost_evaluator.collision_engine.metrics = original_engine_metrics
|
||||
if self.context.cost_evaluator.danger_map is not None:
|
||||
self.context.cost_evaluator.danger_map.metrics = original_danger_metrics
|
||||
|
||||
def _analyze_results(
|
||||
self,
|
||||
ordered_net_ids: Sequence[str],
|
||||
|
|
@ -643,6 +446,15 @@ 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)
|
||||
|
|
@ -655,14 +467,69 @@ 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(
|
||||
self._capture_single_frontier_trace(
|
||||
state,
|
||||
net_id,
|
||||
result,
|
||||
hotspot_bounds,
|
||||
NetFrontierTrace(
|
||||
net_id=net_id,
|
||||
hotspot_bounds=hotspot_bounds,
|
||||
pruned_closed_set=collector.pruned_closed_set,
|
||||
pruned_hard_collision=collector.pruned_hard_collision,
|
||||
pruned_self_collision=collector.pruned_self_collision,
|
||||
pruned_cost=collector.pruned_cost,
|
||||
samples=tuple(
|
||||
FrontierPruneSample(
|
||||
reason=reason, # type: ignore[arg-type]
|
||||
move_type=move_type,
|
||||
hotspot_index=hotspot_index,
|
||||
parent_state=parent_state,
|
||||
end_state=end_state,
|
||||
)
|
||||
for reason, move_type, hotspot_index, parent_state, end_state in collector.samples
|
||||
),
|
||||
)
|
||||
)
|
||||
finally:
|
||||
self.metrics = original_metrics
|
||||
self.context.metrics = original_context_metrics
|
||||
self.context.cost_evaluator.collision_engine.metrics = original_engine_metrics
|
||||
if self.context.cost_evaluator.danger_map is not None:
|
||||
self.context.cost_evaluator.danger_map.metrics = original_danger_metrics
|
||||
|
||||
def _whole_set_is_better(
|
||||
self,
|
||||
|
|
@ -698,9 +565,6 @@ class PathFinder:
|
|||
return candidate_length < incumbent_length
|
||||
return False
|
||||
|
||||
def _pair_local_attempt_orders(self, target: _PairLocalTarget) -> tuple[tuple[str, str], tuple[str, str]]:
|
||||
return target.net_ids, target.net_ids[::-1]
|
||||
|
||||
def _collect_pair_local_targets(
|
||||
self,
|
||||
state: _RoutingState,
|
||||
|
|
@ -778,141 +642,6 @@ class PathFinder:
|
|||
metrics=AStarMetrics(),
|
||||
)
|
||||
|
||||
def _materialize_pre_pair_frontier_trace(
|
||||
self,
|
||||
state: _RoutingState,
|
||||
results: dict[str, RoutingResult],
|
||||
details_by_net: dict[str, PathVerificationDetail],
|
||||
review: _IterationReview,
|
||||
) -> PrePairFrontierTraceEntry | None:
|
||||
candidate = state.pre_pair_candidate
|
||||
if candidate is None:
|
||||
return None
|
||||
|
||||
result_by_net = dict(results)
|
||||
detail_by_net = dict(details_by_net)
|
||||
nets: list[PrePairNetTrace] = []
|
||||
attempt_by_net = {attempt.net_id: attempt for attempt in candidate.net_attempts}
|
||||
for net_id in candidate.routed_net_ids:
|
||||
attempt = attempt_by_net.get(net_id)
|
||||
result = result_by_net.get(net_id)
|
||||
detail = detail_by_net.get(net_id)
|
||||
if attempt is None or result is None or detail is None or not result.reached_target:
|
||||
continue
|
||||
hotspot_bounds = self._build_frontier_hotspot_bounds(state, net_id, detail_by_net)
|
||||
nets.append(
|
||||
PrePairNetTrace(
|
||||
net_id=net_id,
|
||||
nodes_expanded=attempt.nodes_expanded,
|
||||
congestion_check_calls=attempt.congestion_check_calls,
|
||||
pruned_closed_set=attempt.pruned_closed_set,
|
||||
pruned_cost=attempt.pruned_cost,
|
||||
pruned_hard_collision=attempt.pruned_hard_collision,
|
||||
guidance_seed_present=attempt.guidance_seed_present,
|
||||
frontier=self._capture_single_frontier_trace(state, net_id, result, hotspot_bounds),
|
||||
)
|
||||
)
|
||||
|
||||
if not nets:
|
||||
return None
|
||||
return PrePairFrontierTraceEntry(
|
||||
iteration=candidate.iteration,
|
||||
routed_net_ids=candidate.routed_net_ids,
|
||||
conflict_edges=candidate.conflict_edges,
|
||||
nets=tuple(nets),
|
||||
)
|
||||
|
||||
def _build_iteration_reroute_plan(
|
||||
self,
|
||||
state: _RoutingState,
|
||||
reroute_net_ids: set[str],
|
||||
) -> tuple[list[str], set[str]]:
|
||||
routed_net_ids = [net_id for net_id in state.ordered_net_ids if net_id in reroute_net_ids]
|
||||
capped_net_ids: set[str] = set()
|
||||
if len(reroute_net_ids) >= len(state.ordered_net_ids) or not state.recent_attempt_work:
|
||||
return routed_net_ids, capped_net_ids
|
||||
|
||||
order_index = {net_id: idx for idx, net_id in enumerate(state.ordered_net_ids)}
|
||||
routed_net_ids.sort(key=lambda net_id: (state.recent_attempt_work.get(net_id, 0), order_index[net_id]))
|
||||
if (
|
||||
len(routed_net_ids) == 4
|
||||
and state.best_conflict_edges <= 2
|
||||
and self._results_all_reached_target(state)
|
||||
):
|
||||
heavy_net_ids = sorted(
|
||||
routed_net_ids,
|
||||
key=lambda net_id: (-state.recent_attempt_work.get(net_id, 0), order_index[net_id]),
|
||||
)[:2]
|
||||
capped_net_ids = {
|
||||
net_id for net_id in heavy_net_ids if state.recent_attempt_work.get(net_id, 0) >= 200
|
||||
}
|
||||
return routed_net_ids, capped_net_ids
|
||||
|
||||
def _update_pre_pair_candidate(
|
||||
self,
|
||||
state: _RoutingState,
|
||||
*,
|
||||
iteration: int,
|
||||
reroute_net_ids: set[str],
|
||||
routed_net_ids: list[str],
|
||||
attempt_traces: list[IterationNetAttemptTrace],
|
||||
review: _IterationReview,
|
||||
) -> None:
|
||||
if self._results_all_reached_target(state) and len(reroute_net_ids) < len(state.ordered_net_ids) and review.conflict_edges:
|
||||
state.pre_pair_candidate = _PrePairCandidate(
|
||||
iteration=iteration,
|
||||
routed_net_ids=tuple(routed_net_ids),
|
||||
conflict_edges=tuple(sorted(review.conflict_edges)),
|
||||
net_attempts=tuple(attempt_traces),
|
||||
)
|
||||
return
|
||||
state.pre_pair_candidate = None
|
||||
|
||||
def _next_reroute_net_ids(
|
||||
self,
|
||||
state: _RoutingState,
|
||||
review: _IterationReview,
|
||||
) -> set[str]:
|
||||
if self._results_all_reached_target(state) and 0 < len(review.conflict_edges) <= 3:
|
||||
return set(review.conflicting_nets)
|
||||
return set(state.ordered_net_ids)
|
||||
|
||||
def _should_stop_for_pair_local_plateau(
|
||||
self,
|
||||
state: _RoutingState,
|
||||
*,
|
||||
improved: bool,
|
||||
) -> bool:
|
||||
if improved:
|
||||
state.pair_local_plateau_count = 0
|
||||
return False
|
||||
if self._results_all_reached_target(state) and state.best_conflict_edges <= 2:
|
||||
# Once the run is fully reached-target and already in the final <=2-edge
|
||||
# basin, another non-improving negotiated iteration is just churn before
|
||||
# the bounded pair-local repair.
|
||||
state.pair_local_plateau_count += 1
|
||||
return state.pair_local_plateau_count >= 1
|
||||
state.pair_local_plateau_count = 0
|
||||
return False
|
||||
|
||||
def _update_repeated_conflict_state(
|
||||
self,
|
||||
state: _RoutingState,
|
||||
review: _IterationReview,
|
||||
) -> bool:
|
||||
current_signature = tuple(sorted(review.conflict_edges))
|
||||
repeated = (
|
||||
bool(current_signature)
|
||||
and (
|
||||
current_signature == state.last_conflict_signature
|
||||
or len(current_signature) == state.last_conflict_edge_count
|
||||
)
|
||||
)
|
||||
state.repeated_conflict_count = state.repeated_conflict_count + 1 if repeated else 0
|
||||
state.last_conflict_signature = current_signature
|
||||
state.last_conflict_edge_count = len(current_signature)
|
||||
return state.repeated_conflict_count >= 2
|
||||
|
||||
def _run_pair_local_attempt(
|
||||
self,
|
||||
state: _RoutingState,
|
||||
|
|
@ -924,7 +653,12 @@ class PathFinder:
|
|||
|
||||
for net_id in pair_order:
|
||||
net = state.net_specs[net_id]
|
||||
guidance_seed, guidance_bonus, _ = self._guidance_for_result(incumbent_results.get(net_id))
|
||||
guidance_result = incumbent_results.get(net_id)
|
||||
guidance_seed = None
|
||||
guidance_bonus = 0.0
|
||||
if guidance_result and guidance_result.reached_target and guidance_result.path:
|
||||
guidance_seed = guidance_result.as_seed().segments
|
||||
guidance_bonus = max(10.0, self.context.options.objective.bend_penalty * 0.25)
|
||||
|
||||
run_config = SearchRunConfig.from_options(
|
||||
self.context.options,
|
||||
|
|
@ -963,13 +697,29 @@ class PathFinder:
|
|||
|
||||
return local_results, local_context.metrics.total_nodes_expanded
|
||||
|
||||
def _apply_pair_local_candidate(
|
||||
self,
|
||||
state: _RoutingState,
|
||||
candidate_results: dict[str, RoutingResult],
|
||||
incumbent_results: dict[str, RoutingResult],
|
||||
incumbent_review: _IterationReview,
|
||||
) -> tuple[bool, _IterationReview]:
|
||||
def _run_pair_local_search(self, state: _RoutingState) -> None:
|
||||
state.results, _details_by_net, review = self._analyze_results(
|
||||
state.ordered_net_ids,
|
||||
state.results,
|
||||
capture_component_conflicts=True,
|
||||
count_iteration_metrics=False,
|
||||
)
|
||||
targets = self._collect_pair_local_targets(state, state.results, review)
|
||||
if not targets:
|
||||
return
|
||||
|
||||
for target in targets[:2]:
|
||||
self.metrics.total_pair_local_search_pairs_considered += 1
|
||||
incumbent_results = dict(state.results)
|
||||
incumbent_review = review
|
||||
accepted = False
|
||||
for pair_order in (target.net_ids, target.net_ids[::-1]):
|
||||
self.metrics.total_pair_local_search_attempts += 1
|
||||
candidate = self._run_pair_local_attempt(state, incumbent_results, pair_order)
|
||||
if candidate is None:
|
||||
continue
|
||||
candidate_results, nodes_expanded = candidate
|
||||
self.metrics.total_pair_local_search_nodes_expanded += nodes_expanded
|
||||
self._replace_installed_paths(state, candidate_results)
|
||||
candidate_results, _candidate_details_by_net, candidate_review = self._analyze_results(
|
||||
state.ordered_net_ids,
|
||||
|
|
@ -985,72 +735,27 @@ class PathFinder:
|
|||
):
|
||||
self.metrics.total_pair_local_search_accepts += 1
|
||||
state.results = candidate_results
|
||||
return True, candidate_review
|
||||
|
||||
review = candidate_review
|
||||
accepted = True
|
||||
break
|
||||
self._replace_installed_paths(state, incumbent_results)
|
||||
return False, incumbent_review
|
||||
|
||||
def _run_pair_local_target(
|
||||
self,
|
||||
state: _RoutingState,
|
||||
target: _PairLocalTarget,
|
||||
review: _IterationReview,
|
||||
) -> _IterationReview:
|
||||
incumbent_results = dict(state.results)
|
||||
incumbent_review = review
|
||||
self.metrics.total_pair_local_search_pairs_considered += 1
|
||||
for pair_order in self._pair_local_attempt_orders(target):
|
||||
self.metrics.total_pair_local_search_attempts += 1
|
||||
candidate = self._run_pair_local_attempt(state, incumbent_results, pair_order)
|
||||
if candidate is None:
|
||||
continue
|
||||
candidate_results, nodes_expanded = candidate
|
||||
self.metrics.total_pair_local_search_nodes_expanded += nodes_expanded
|
||||
accepted, next_review = self._apply_pair_local_candidate(
|
||||
state,
|
||||
candidate_results,
|
||||
incumbent_results,
|
||||
incumbent_review,
|
||||
)
|
||||
if accepted:
|
||||
return next_review
|
||||
|
||||
if not accepted:
|
||||
state.results = incumbent_results
|
||||
self._replace_installed_paths(state, incumbent_results)
|
||||
return incumbent_review
|
||||
|
||||
def _run_pair_local_search(self, state: _RoutingState) -> None:
|
||||
state.results, _details_by_net, review = self._analyze_results(
|
||||
state.ordered_net_ids,
|
||||
state.results,
|
||||
capture_component_conflicts=True,
|
||||
count_iteration_metrics=False,
|
||||
)
|
||||
targets = self._collect_pair_local_targets(state, state.results, review)
|
||||
if not targets:
|
||||
return
|
||||
|
||||
for target in targets[:2]:
|
||||
review = self._run_pair_local_target(state, target, review)
|
||||
|
||||
def _route_net_once(
|
||||
self,
|
||||
state: _RoutingState,
|
||||
iteration: int,
|
||||
net_id: str,
|
||||
*,
|
||||
node_limit_override: int | None = None,
|
||||
incumbent_fallback: RoutingResult | None = None,
|
||||
) -> tuple[RoutingResult, bool]:
|
||||
) -> RoutingResult:
|
||||
search = self.context.options.search
|
||||
congestion = self.context.options.congestion
|
||||
diagnostics = self.context.options.diagnostics
|
||||
net = state.net_specs[net_id]
|
||||
self.metrics.total_nets_routed += 1
|
||||
if node_limit_override is not None:
|
||||
self.metrics.total_late_phase_capped_nets += 1
|
||||
self.context.cost_evaluator.collision_engine.remove_path(net_id)
|
||||
guidance_seed_present = False
|
||||
|
||||
if iteration == 0 and state.initial_paths and net_id in state.initial_paths:
|
||||
self.metrics.total_warm_start_paths_used += 1
|
||||
|
|
@ -1058,18 +763,17 @@ class PathFinder:
|
|||
else:
|
||||
coll_model, _ = resolve_bend_geometry(search)
|
||||
skip_congestion = False
|
||||
guidance_seed, guidance_bonus, guidance_seed_present = (None, 0.0, False)
|
||||
guidance_seed = None
|
||||
guidance_bonus = 0.0
|
||||
if congestion.use_tiered_strategy and iteration == 0:
|
||||
skip_congestion = True
|
||||
if coll_model == "arc":
|
||||
coll_model = "clipped_bbox"
|
||||
elif iteration > 0:
|
||||
guidance_seed, guidance_bonus, guidance_seed_present = self._guidance_for_result(
|
||||
state.results.get(net_id)
|
||||
)
|
||||
|
||||
if node_limit_override is not None and self._has_incumbent_fallback(incumbent_fallback):
|
||||
return self._restore_incumbent_fallback(net_id, incumbent_fallback, guidance_seed_present)
|
||||
guidance_result = state.results.get(net_id)
|
||||
if guidance_result and guidance_result.reached_target and guidance_result.path:
|
||||
guidance_seed = guidance_result.as_seed().segments
|
||||
guidance_bonus = max(10.0, self.context.options.objective.bend_penalty * 0.25)
|
||||
|
||||
run_config = SearchRunConfig.from_options(
|
||||
self.context.options,
|
||||
|
|
@ -1080,7 +784,7 @@ class PathFinder:
|
|||
guidance_bonus=guidance_bonus,
|
||||
skip_congestion=skip_congestion,
|
||||
self_collision_check=(net_id in state.needs_self_collision_check),
|
||||
node_limit=search.node_limit if node_limit_override is None else node_limit_override,
|
||||
node_limit=search.node_limit,
|
||||
)
|
||||
path = route_astar(
|
||||
net.start,
|
||||
|
|
@ -1096,13 +800,9 @@ class PathFinder:
|
|||
state.accumulated_expanded_nodes.extend(self.metrics.last_expanded_nodes)
|
||||
|
||||
if not path:
|
||||
if self._has_incumbent_fallback(incumbent_fallback):
|
||||
return self._restore_incumbent_fallback(net_id, incumbent_fallback, guidance_seed_present)
|
||||
return RoutingResult(net_id=net_id, path=(), reached_target=False), guidance_seed_present
|
||||
return RoutingResult(net_id=net_id, path=(), reached_target=False)
|
||||
|
||||
reached_target = path[-1].end_port == net.target
|
||||
if not reached_target and self._has_incumbent_fallback(incumbent_fallback):
|
||||
return self._restore_incumbent_fallback(net_id, incumbent_fallback, guidance_seed_present)
|
||||
if reached_target:
|
||||
self.metrics.total_nets_reached_target += 1
|
||||
report = None
|
||||
|
|
@ -1112,14 +812,11 @@ class PathFinder:
|
|||
if report.self_collision_count > 0:
|
||||
state.needs_self_collision_check.add(net_id)
|
||||
|
||||
return (
|
||||
RoutingResult(
|
||||
return RoutingResult(
|
||||
net_id=net_id,
|
||||
path=tuple(path),
|
||||
reached_target=reached_target,
|
||||
report=RoutingReport() if report is None else report,
|
||||
),
|
||||
guidance_seed_present,
|
||||
)
|
||||
|
||||
def _run_iteration(
|
||||
|
|
@ -1130,85 +827,24 @@ class PathFinder:
|
|||
iteration_callback: Callable[[int, dict[str, RoutingResult]], None] | None,
|
||||
) -> _IterationReview | None:
|
||||
congestion = self.context.options.congestion
|
||||
diagnostics = self.context.options.diagnostics
|
||||
self.metrics.total_route_iterations += 1
|
||||
self.metrics.reset_per_route()
|
||||
if congestion.shuffle_nets and (iteration > 0 or state.initial_paths is None):
|
||||
iteration_seed = (congestion.seed + iteration) if congestion.seed is not None else None
|
||||
random.Random(iteration_seed).shuffle(state.ordered_net_ids)
|
||||
|
||||
iteration_penalty = self.context.congestion_penalty
|
||||
routed_net_ids, capped_net_ids = self._build_iteration_reroute_plan(state, reroute_net_ids)
|
||||
routed_net_ids = [net_id for net_id in state.ordered_net_ids if net_id in reroute_net_ids]
|
||||
self.metrics.total_nets_carried_forward += len(state.ordered_net_ids) - len(routed_net_ids)
|
||||
iteration_before = {}
|
||||
attempt_traces: list[IterationNetAttemptTrace] = []
|
||||
attempt_work: dict[str, int] = {}
|
||||
if diagnostics.capture_iteration_trace:
|
||||
iteration_before = self._capture_metric_totals(_ITERATION_TRACE_TOTALS)
|
||||
|
||||
for net_id in routed_net_ids:
|
||||
if time.monotonic() - state.start_time > state.timeout_s:
|
||||
self.metrics.total_timeout_events += 1
|
||||
return None
|
||||
|
||||
attempt_before = self._capture_metric_totals(_ATTEMPT_TRACE_TOTALS)
|
||||
node_limit_override = None
|
||||
incumbent_fallback = None
|
||||
if net_id in capped_net_ids:
|
||||
node_limit_override = min(self.context.options.search.node_limit, 1)
|
||||
incumbent_fallback = state.results.get(net_id)
|
||||
result, guidance_seed_present = self._route_net_once(
|
||||
state,
|
||||
iteration,
|
||||
net_id,
|
||||
node_limit_override=node_limit_override,
|
||||
incumbent_fallback=incumbent_fallback,
|
||||
)
|
||||
result = self._route_net_once(state, iteration, net_id)
|
||||
state.results[net_id] = result
|
||||
attempt_after = self._capture_metric_totals(_ATTEMPT_TRACE_TOTALS)
|
||||
deltas = self._metric_deltas(attempt_before, attempt_after)
|
||||
attempt_work[net_id] = deltas["nodes_expanded"] + deltas["congestion_check_calls"]
|
||||
attempt_traces.append(
|
||||
IterationNetAttemptTrace(
|
||||
net_id=net_id,
|
||||
reached_target=result.reached_target,
|
||||
nodes_expanded=deltas["nodes_expanded"],
|
||||
congestion_check_calls=deltas["congestion_check_calls"],
|
||||
pruned_closed_set=deltas["pruned_closed_set"],
|
||||
pruned_cost=deltas["pruned_cost"],
|
||||
pruned_hard_collision=deltas["pruned_hard_collision"],
|
||||
guidance_seed_present=guidance_seed_present,
|
||||
)
|
||||
)
|
||||
|
||||
state.recent_attempt_work = attempt_work
|
||||
review = self._reverify_iteration_results(state)
|
||||
self._update_pre_pair_candidate(
|
||||
state,
|
||||
iteration=iteration,
|
||||
reroute_net_ids=reroute_net_ids,
|
||||
routed_net_ids=routed_net_ids,
|
||||
attempt_traces=attempt_traces,
|
||||
review=review,
|
||||
)
|
||||
if diagnostics.capture_iteration_trace:
|
||||
iteration_after = self._capture_metric_totals(_ITERATION_TRACE_TOTALS)
|
||||
deltas = self._metric_deltas(iteration_before, iteration_after)
|
||||
self.iteration_trace.append(
|
||||
IterationTraceEntry(
|
||||
iteration=iteration,
|
||||
congestion_penalty=iteration_penalty,
|
||||
routed_net_ids=tuple(routed_net_ids),
|
||||
completed_nets=len(review.completed_net_ids),
|
||||
conflict_edges=len(review.conflict_edges),
|
||||
total_dynamic_collisions=review.total_dynamic_collisions,
|
||||
nodes_expanded=deltas["nodes_expanded"],
|
||||
congestion_check_calls=deltas["congestion_check_calls"],
|
||||
congestion_candidate_ids=deltas["congestion_candidate_ids"],
|
||||
congestion_exact_pair_checks=deltas["congestion_exact_pair_checks"],
|
||||
net_attempts=tuple(attempt_traces),
|
||||
)
|
||||
)
|
||||
|
||||
if iteration_callback:
|
||||
iteration_callback(iteration, state.results)
|
||||
|
|
@ -1237,28 +873,34 @@ class PathFinder:
|
|||
iteration_callback: Callable[[int, dict[str, RoutingResult]], None] | None,
|
||||
) -> bool:
|
||||
congestion = self.context.options.congestion
|
||||
reroute_net_ids = set(state.ordered_net_ids)
|
||||
for iteration in range(congestion.max_iterations):
|
||||
review = self._run_iteration(
|
||||
state,
|
||||
iteration,
|
||||
reroute_net_ids,
|
||||
set(state.ordered_net_ids),
|
||||
iteration_callback,
|
||||
)
|
||||
if review is None:
|
||||
return True
|
||||
improved = self._update_best_iteration(state, review)
|
||||
self._update_best_iteration(state, review)
|
||||
if not any(
|
||||
result.outcome in {"colliding", "partial", "unroutable"}
|
||||
for result in state.results.values()
|
||||
):
|
||||
return False
|
||||
|
||||
reroute_net_ids = self._next_reroute_net_ids(state, review)
|
||||
if self._should_stop_for_pair_local_plateau(state, improved=improved):
|
||||
return False
|
||||
|
||||
if self._update_repeated_conflict_state(state, review):
|
||||
current_signature = tuple(sorted(review.conflict_edges))
|
||||
repeated = (
|
||||
bool(current_signature)
|
||||
and (
|
||||
current_signature == state.last_conflict_signature
|
||||
or len(current_signature) == state.last_conflict_edge_count
|
||||
)
|
||||
)
|
||||
state.repeated_conflict_count = state.repeated_conflict_count + 1 if repeated else 0
|
||||
state.last_conflict_signature = current_signature
|
||||
state.last_conflict_edge_count = len(current_signature)
|
||||
if state.repeated_conflict_count >= 2:
|
||||
return False
|
||||
self.context.congestion_penalty *= congestion.multiplier
|
||||
return False
|
||||
|
|
@ -1331,8 +973,6 @@ class PathFinder:
|
|||
self.accumulated_expanded_nodes = []
|
||||
self.conflict_trace = []
|
||||
self.frontier_trace = []
|
||||
self.pre_pair_frontier_trace = None
|
||||
self.iteration_trace = []
|
||||
self.metrics.reset_totals()
|
||||
self.metrics.reset_per_route()
|
||||
|
||||
|
|
@ -1340,7 +980,21 @@ class PathFinder:
|
|||
timed_out = self._run_iterations(state, iteration_callback)
|
||||
self.accumulated_expanded_nodes = list(state.accumulated_expanded_nodes)
|
||||
self._restore_best_iteration(state)
|
||||
self._analyze_restored_best(state)
|
||||
if self.context.options.diagnostics.capture_conflict_trace:
|
||||
state.results, details_by_net, review = self._analyze_results(
|
||||
state.ordered_net_ids,
|
||||
state.results,
|
||||
capture_component_conflicts=True,
|
||||
count_iteration_metrics=False,
|
||||
)
|
||||
self._capture_conflict_trace_entry(
|
||||
state,
|
||||
stage="restored_best",
|
||||
iteration=None,
|
||||
results=state.results,
|
||||
details_by_net=details_by_net,
|
||||
review=review,
|
||||
)
|
||||
|
||||
if timed_out:
|
||||
final_results = self._verify_results(state)
|
||||
|
|
|
|||
|
|
@ -91,8 +91,6 @@ def _make_run_result(
|
|||
expanded_nodes=tuple(pathfinder.accumulated_expanded_nodes),
|
||||
conflict_trace=tuple(pathfinder.conflict_trace),
|
||||
frontier_trace=tuple(pathfinder.frontier_trace),
|
||||
pre_pair_frontier_trace=pathfinder.pre_pair_frontier_trace,
|
||||
iteration_trace=tuple(pathfinder.iteration_trace),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -426,14 +424,6 @@ def snapshot_example_07_no_warm_start() -> ScenarioSnapshot:
|
|||
)
|
||||
|
||||
|
||||
def snapshot_example_07_no_warm_start_seed43() -> ScenarioSnapshot:
|
||||
return _snapshot_example_07_variant(
|
||||
"example_07_large_scale_routing_no_warm_start_seed43",
|
||||
warm_start_enabled=False,
|
||||
seed=43,
|
||||
)
|
||||
|
||||
|
||||
def trace_example_07() -> RoutingRunResult:
|
||||
return _trace_example_07_variant(warm_start_enabled=True)
|
||||
|
||||
|
|
@ -442,10 +432,6 @@ def trace_example_07_no_warm_start() -> RoutingRunResult:
|
|||
return _trace_example_07_variant(warm_start_enabled=False)
|
||||
|
||||
|
||||
def trace_example_07_no_warm_start_seed43() -> RoutingRunResult:
|
||||
return _trace_example_07_variant(warm_start_enabled=False, seed=43)
|
||||
|
||||
|
||||
def _build_example_07_variant_stack(
|
||||
*,
|
||||
num_nets: int,
|
||||
|
|
@ -453,8 +439,6 @@ def _build_example_07_variant_stack(
|
|||
warm_start_enabled: bool,
|
||||
capture_conflict_trace: bool = False,
|
||||
capture_frontier_trace: bool = False,
|
||||
capture_iteration_trace: bool = False,
|
||||
capture_pre_pair_frontier_trace: bool = False,
|
||||
) -> tuple[CostEvaluator, AStarMetrics, PathFinder]:
|
||||
bounds = (0, 0, 1000, 1000)
|
||||
obstacles = [
|
||||
|
|
@ -497,8 +481,6 @@ def _build_example_07_variant_stack(
|
|||
"capture_expanded": True,
|
||||
"capture_conflict_trace": capture_conflict_trace,
|
||||
"capture_frontier_trace": capture_frontier_trace,
|
||||
"capture_iteration_trace": capture_iteration_trace,
|
||||
"capture_pre_pair_frontier_trace": capture_pre_pair_frontier_trace,
|
||||
"shuffle_nets": True,
|
||||
"seed": seed,
|
||||
"warm_start_enabled": warm_start_enabled,
|
||||
|
|
@ -514,8 +496,6 @@ def _run_example_07_variant(
|
|||
warm_start_enabled: bool,
|
||||
capture_conflict_trace: bool = False,
|
||||
capture_frontier_trace: bool = False,
|
||||
capture_iteration_trace: bool = False,
|
||||
capture_pre_pair_frontier_trace: bool = False,
|
||||
) -> RoutingRunResult:
|
||||
evaluator, metrics, pathfinder = _build_example_07_variant_stack(
|
||||
num_nets=num_nets,
|
||||
|
|
@ -523,8 +503,6 @@ def _run_example_07_variant(
|
|||
warm_start_enabled=warm_start_enabled,
|
||||
capture_conflict_trace=capture_conflict_trace,
|
||||
capture_frontier_trace=capture_frontier_trace,
|
||||
capture_iteration_trace=capture_iteration_trace,
|
||||
capture_pre_pair_frontier_trace=capture_pre_pair_frontier_trace,
|
||||
)
|
||||
|
||||
def iteration_callback(idx: int, current_results: dict[str, RoutingResult]) -> None:
|
||||
|
|
@ -541,12 +519,11 @@ def _snapshot_example_07_variant(
|
|||
name: str,
|
||||
*,
|
||||
warm_start_enabled: bool,
|
||||
seed: int = 42,
|
||||
) -> ScenarioSnapshot:
|
||||
t0 = perf_counter()
|
||||
run = _run_example_07_variant(
|
||||
num_nets=10,
|
||||
seed=seed,
|
||||
seed=42,
|
||||
warm_start_enabled=warm_start_enabled,
|
||||
)
|
||||
t1 = perf_counter()
|
||||
|
|
@ -556,16 +533,13 @@ def _snapshot_example_07_variant(
|
|||
def _trace_example_07_variant(
|
||||
*,
|
||||
warm_start_enabled: bool,
|
||||
seed: int = 42,
|
||||
) -> RoutingRunResult:
|
||||
return _run_example_07_variant(
|
||||
num_nets=10,
|
||||
seed=seed,
|
||||
seed=42,
|
||||
warm_start_enabled=warm_start_enabled,
|
||||
capture_conflict_trace=True,
|
||||
capture_frontier_trace=True,
|
||||
capture_iteration_trace=True,
|
||||
capture_pre_pair_frontier_trace=True,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -670,7 +644,6 @@ SCENARIO_SNAPSHOTS: tuple[tuple[str, ScenarioSnapshotRun], ...] = (
|
|||
|
||||
PERFORMANCE_SCENARIO_SNAPSHOTS: tuple[tuple[str, ScenarioSnapshotRun], ...] = (
|
||||
("example_07_large_scale_routing_no_warm_start", snapshot_example_07_no_warm_start),
|
||||
("example_07_large_scale_routing_no_warm_start_seed43", snapshot_example_07_no_warm_start_seed43),
|
||||
)
|
||||
|
||||
TRACE_SCENARIO_RUNS: tuple[tuple[str, TraceScenarioRun], ...] = (
|
||||
|
|
@ -680,7 +653,6 @@ TRACE_SCENARIO_RUNS: tuple[tuple[str, TraceScenarioRun], ...] = (
|
|||
|
||||
TRACE_PERFORMANCE_SCENARIO_RUNS: tuple[tuple[str, TraceScenarioRun], ...] = (
|
||||
("example_07_large_scale_routing_no_warm_start", trace_example_07_no_warm_start),
|
||||
("example_07_large_scale_routing_no_warm_start_seed43", trace_example_07_no_warm_start_seed43),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import importlib
|
|||
import pytest
|
||||
from shapely.geometry import box
|
||||
|
||||
import inire.router._router as router_module
|
||||
from inire import (
|
||||
CongestionOptions,
|
||||
DiagnosticsOptions,
|
||||
|
|
@ -55,8 +54,6 @@ def test_route_problem_smoke() -> None:
|
|||
assert run.results_by_net["net1"].is_valid
|
||||
assert run.conflict_trace == ()
|
||||
assert run.frontier_trace == ()
|
||||
assert run.pre_pair_frontier_trace is None
|
||||
assert run.iteration_trace == ()
|
||||
|
||||
|
||||
def test_route_problem_supports_configs_and_debug_data() -> None:
|
||||
|
|
@ -127,8 +124,6 @@ def test_route_problem_supports_configs_and_debug_data() -> None:
|
|||
assert run.metrics.pair_local_search_attempts >= 0
|
||||
assert run.metrics.pair_local_search_accepts >= 0
|
||||
assert run.metrics.pair_local_search_nodes_expanded >= 0
|
||||
assert run.metrics.late_phase_capped_nets >= 0
|
||||
assert run.metrics.late_phase_capped_fallbacks >= 0
|
||||
|
||||
|
||||
def test_iteration_callback_observes_reverified_conflicts() -> None:
|
||||
|
|
@ -187,72 +182,6 @@ def test_capture_conflict_trace_preserves_route_outputs() -> None:
|
|||
assert [entry.stage for entry in run_with_trace.conflict_trace] == ["iteration", "restored_best", "final"]
|
||||
|
||||
|
||||
def test_capture_iteration_trace_preserves_route_outputs() -> None:
|
||||
problem = RoutingProblem(
|
||||
bounds=(0, 0, 100, 100),
|
||||
nets=(
|
||||
NetSpec("horizontal", Port(10, 50, 0), Port(90, 50, 0), width=2.0),
|
||||
NetSpec("vertical", Port(50, 10, 90), Port(50, 90, 90), width=2.0),
|
||||
),
|
||||
)
|
||||
base_options = RoutingOptions(
|
||||
congestion=CongestionOptions(max_iterations=1, warm_start_enabled=False),
|
||||
refinement=RefinementOptions(enabled=False),
|
||||
)
|
||||
|
||||
run_without_trace = route(problem, options=base_options)
|
||||
run_with_trace = route(
|
||||
problem,
|
||||
options=RoutingOptions(
|
||||
congestion=base_options.congestion,
|
||||
refinement=base_options.refinement,
|
||||
diagnostics=DiagnosticsOptions(capture_iteration_trace=True),
|
||||
),
|
||||
)
|
||||
|
||||
assert {net_id: result.outcome for net_id, result in run_without_trace.results_by_net.items()} == {
|
||||
net_id: result.outcome for net_id, result in run_with_trace.results_by_net.items()
|
||||
}
|
||||
assert len(run_with_trace.iteration_trace) == 1
|
||||
|
||||
|
||||
def test_capture_iteration_trace_records_iteration_and_attempt_deltas() -> None:
|
||||
problem = RoutingProblem(
|
||||
bounds=(0, 0, 100, 100),
|
||||
nets=(
|
||||
NetSpec("horizontal", Port(10, 50, 0), Port(90, 50, 0), width=2.0),
|
||||
NetSpec("vertical", Port(50, 10, 90), Port(50, 90, 90), width=2.0),
|
||||
),
|
||||
)
|
||||
run = route(
|
||||
problem,
|
||||
options=RoutingOptions(
|
||||
congestion=CongestionOptions(max_iterations=1, warm_start_enabled=False),
|
||||
refinement=RefinementOptions(enabled=False),
|
||||
diagnostics=DiagnosticsOptions(capture_iteration_trace=True),
|
||||
),
|
||||
)
|
||||
|
||||
entry = run.iteration_trace[0]
|
||||
assert entry.iteration == 0
|
||||
assert entry.congestion_penalty == 100.0
|
||||
assert entry.routed_net_ids == ("horizontal", "vertical")
|
||||
assert entry.completed_nets == 0
|
||||
assert entry.conflict_edges == 1
|
||||
assert entry.total_dynamic_collisions >= 2
|
||||
assert entry.nodes_expanded >= 0
|
||||
assert entry.congestion_check_calls >= 0
|
||||
assert entry.congestion_candidate_ids >= 0
|
||||
assert entry.congestion_exact_pair_checks >= 0
|
||||
assert len(entry.net_attempts) == 2
|
||||
assert [attempt.net_id for attempt in entry.net_attempts] == ["horizontal", "vertical"]
|
||||
assert all(attempt.nodes_expanded >= 0 for attempt in entry.net_attempts)
|
||||
assert all(attempt.congestion_check_calls >= 0 for attempt in entry.net_attempts)
|
||||
assert all(not attempt.guidance_seed_present for attempt in entry.net_attempts)
|
||||
assert sum(attempt.nodes_expanded for attempt in entry.net_attempts) == entry.nodes_expanded
|
||||
assert sum(attempt.congestion_check_calls for attempt in entry.net_attempts) == entry.congestion_check_calls
|
||||
|
||||
|
||||
def test_capture_conflict_trace_records_component_pairs() -> None:
|
||||
problem = RoutingProblem(
|
||||
bounds=(0, 0, 100, 100),
|
||||
|
|
@ -310,35 +239,6 @@ def test_capture_frontier_trace_preserves_route_outputs() -> None:
|
|||
assert {trace.net_id for trace in run_with_trace.frontier_trace} == {"horizontal", "vertical"}
|
||||
|
||||
|
||||
def test_capture_pre_pair_frontier_trace_preserves_route_outputs() -> None:
|
||||
problem = RoutingProblem(
|
||||
bounds=(0, 0, 100, 100),
|
||||
nets=(
|
||||
NetSpec("horizontal", Port(10, 50, 0), Port(90, 50, 0), width=2.0),
|
||||
NetSpec("vertical", Port(50, 10, 90), Port(50, 90, 90), width=2.0),
|
||||
),
|
||||
)
|
||||
base_options = RoutingOptions(
|
||||
congestion=CongestionOptions(max_iterations=1, warm_start_enabled=False),
|
||||
refinement=RefinementOptions(enabled=False),
|
||||
)
|
||||
|
||||
run_without_trace = route(problem, options=base_options)
|
||||
run_with_trace = route(
|
||||
problem,
|
||||
options=RoutingOptions(
|
||||
congestion=base_options.congestion,
|
||||
refinement=base_options.refinement,
|
||||
diagnostics=DiagnosticsOptions(capture_pre_pair_frontier_trace=True),
|
||||
),
|
||||
)
|
||||
|
||||
assert {net_id: result.outcome for net_id, result in run_without_trace.results_by_net.items()} == {
|
||||
net_id: result.outcome for net_id, result in run_with_trace.results_by_net.items()
|
||||
}
|
||||
assert run_with_trace.pre_pair_frontier_trace is None
|
||||
|
||||
|
||||
def test_capture_frontier_trace_records_prune_reasons() -> None:
|
||||
problem = RoutingProblem(
|
||||
bounds=(0, 0, 100, 100),
|
||||
|
|
@ -385,220 +285,6 @@ def test_reverify_iterations_stop_early_on_stalled_conflict_graph() -> None:
|
|||
assert run.metrics.route_iterations < 10
|
||||
|
||||
|
||||
def test_reverify_iterations_limit_late_reroutes_to_conflicting_nets(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
problem = RoutingProblem(
|
||||
bounds=(0, 0, 100, 100),
|
||||
nets=(
|
||||
NetSpec("netA", Port(10, 50, 0), Port(90, 50, 0), width=2.0),
|
||||
NetSpec("netB", Port(50, 10, 90), Port(50, 90, 90), width=2.0),
|
||||
NetSpec("netC", Port(10, 20, 0), Port(90, 20, 0), width=2.0),
|
||||
),
|
||||
)
|
||||
options = RoutingOptions(
|
||||
congestion=CongestionOptions(max_iterations=10, warm_start_enabled=False),
|
||||
refinement=RefinementOptions(enabled=False),
|
||||
)
|
||||
evaluator = CostEvaluator(RoutingWorld(clearance=2.0), DangerMap(bounds=problem.bounds))
|
||||
pathfinder = PathFinder(AStarContext(evaluator, problem, options))
|
||||
colliding_a = RoutingResult(
|
||||
net_id="netA",
|
||||
path=(Straight.generate(Port(10, 50, 0), 80.0, 2.0, dilation=1.0),),
|
||||
reached_target=True,
|
||||
report=RoutingReport(dynamic_collision_count=1, total_length=80.0),
|
||||
)
|
||||
colliding_b = RoutingResult(
|
||||
net_id="netB",
|
||||
path=(Straight.generate(Port(50, 10, 90), 80.0, 2.0, dilation=1.0),),
|
||||
reached_target=True,
|
||||
report=RoutingReport(dynamic_collision_count=1, total_length=80.0),
|
||||
)
|
||||
completed_c = RoutingResult(
|
||||
net_id="netC",
|
||||
path=(Straight.generate(Port(10, 20, 0), 80.0, 2.0, dilation=1.0),),
|
||||
reached_target=True,
|
||||
report=RoutingReport(total_length=80.0),
|
||||
)
|
||||
iterations_seen: list[int] = []
|
||||
reroute_sets: list[set[str]] = []
|
||||
|
||||
def fake_run_iteration(self, state, iteration, reroute_net_ids, iteration_callback):
|
||||
_ = self
|
||||
_ = iteration_callback
|
||||
iterations_seen.append(iteration)
|
||||
reroute_sets.append(set(reroute_net_ids))
|
||||
state.results = {"netA": colliding_a, "netB": colliding_b, "netC": completed_c}
|
||||
return _IterationReview(
|
||||
conflicting_nets={"netA", "netB"},
|
||||
conflict_edges={("netA", "netB")},
|
||||
completed_net_ids={"netC"},
|
||||
total_dynamic_collisions=2,
|
||||
)
|
||||
|
||||
monkeypatch.setattr(PathFinder, "_run_iteration", fake_run_iteration)
|
||||
monkeypatch.setattr(PathFinder, "_verify_results", lambda self, state: dict(state.results))
|
||||
monkeypatch.setattr(PathFinder, "_run_pair_local_search", lambda self, state: None)
|
||||
|
||||
results = pathfinder.route_all()
|
||||
|
||||
assert iterations_seen == [0, 1]
|
||||
assert reroute_sets == [{"netA", "netB", "netC"}, {"netA", "netB"}]
|
||||
assert results["netA"].outcome == "colliding"
|
||||
assert results["netB"].outcome == "colliding"
|
||||
assert results["netC"].reached_target
|
||||
|
||||
|
||||
def test_run_iteration_orders_subset_reroutes_by_recent_work(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
problem = RoutingProblem(
|
||||
bounds=(0, 0, 100, 100),
|
||||
nets=(
|
||||
NetSpec("netA", Port(10, 50, 0), Port(90, 50, 0), width=2.0),
|
||||
NetSpec("netB", Port(50, 10, 90), Port(50, 90, 90), width=2.0),
|
||||
NetSpec("netC", Port(10, 20, 0), Port(90, 20, 0), width=2.0),
|
||||
),
|
||||
)
|
||||
options = RoutingOptions(
|
||||
congestion=CongestionOptions(max_iterations=2, warm_start_enabled=False, shuffle_nets=False),
|
||||
refinement=RefinementOptions(enabled=False),
|
||||
)
|
||||
evaluator = CostEvaluator(RoutingWorld(clearance=2.0), DangerMap(bounds=problem.bounds))
|
||||
pathfinder = PathFinder(AStarContext(evaluator, problem, options))
|
||||
state = pathfinder._prepare_state()
|
||||
state.recent_attempt_work = {"netA": 200, "netB": 20}
|
||||
route_order: list[str] = []
|
||||
|
||||
def fake_route_net_once(self, state, iteration, net_id, *, node_limit_override=None, incumbent_fallback=None):
|
||||
_ = self
|
||||
_ = state
|
||||
_ = iteration
|
||||
route_order.append(net_id)
|
||||
assert node_limit_override is None
|
||||
assert incumbent_fallback is None
|
||||
return RoutingResult(net_id=net_id, path=(), reached_target=False), False
|
||||
|
||||
def fake_reverify(self, state):
|
||||
_ = self
|
||||
_ = state
|
||||
return _IterationReview(
|
||||
conflicting_nets={"netA", "netB"},
|
||||
conflict_edges={("netA", "netB")},
|
||||
completed_net_ids=set(),
|
||||
total_dynamic_collisions=2,
|
||||
)
|
||||
|
||||
monkeypatch.setattr(PathFinder, "_route_net_once", fake_route_net_once)
|
||||
monkeypatch.setattr(PathFinder, "_reverify_iteration_results", fake_reverify)
|
||||
|
||||
review = pathfinder._run_iteration(state, 1, {"netA", "netB"}, None)
|
||||
|
||||
assert review is not None
|
||||
assert route_order == ["netB", "netA"]
|
||||
|
||||
|
||||
def test_run_iteration_caps_two_heaviest_late_phase_nets(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
problem = RoutingProblem(
|
||||
bounds=(0, 0, 100, 100),
|
||||
nets=(
|
||||
NetSpec("netA", Port(10, 50, 0), Port(90, 50, 0), width=2.0),
|
||||
NetSpec("netB", Port(50, 10, 90), Port(50, 90, 90), width=2.0),
|
||||
NetSpec("netC", Port(10, 20, 0), Port(90, 20, 0), width=2.0),
|
||||
NetSpec("netD", Port(10, 80, 0), Port(90, 80, 0), width=2.0),
|
||||
NetSpec("netE", Port(10, 65, 0), Port(90, 65, 0), width=2.0),
|
||||
),
|
||||
)
|
||||
options = RoutingOptions(
|
||||
objective=ObjectiveWeights(bend_penalty=100.0),
|
||||
congestion=CongestionOptions(max_iterations=2, warm_start_enabled=False, shuffle_nets=False),
|
||||
refinement=RefinementOptions(enabled=False),
|
||||
)
|
||||
evaluator = CostEvaluator(RoutingWorld(clearance=2.0), DangerMap(bounds=problem.bounds))
|
||||
pathfinder = PathFinder(AStarContext(evaluator, problem, options))
|
||||
state = pathfinder._prepare_state()
|
||||
state.results = {
|
||||
net_id: RoutingResult(net_id=net_id, path=(Straight.generate(spec.start, 80.0, 2.0, dilation=1.0),), reached_target=True)
|
||||
for net_id, spec in state.net_specs.items()
|
||||
}
|
||||
state.best_conflict_edges = 2
|
||||
state.recent_attempt_work = {"netA": 20, "netB": 40, "netC": 400, "netD": 220}
|
||||
incumbents = dict(state.results)
|
||||
caps_by_net: dict[str, tuple[int | None, RoutingResult | None]] = {}
|
||||
|
||||
def fake_route_net_once(self, state, iteration, net_id, *, node_limit_override=None, incumbent_fallback=None):
|
||||
_ = self
|
||||
_ = state
|
||||
_ = iteration
|
||||
caps_by_net[net_id] = (node_limit_override, incumbent_fallback)
|
||||
return RoutingResult(net_id=net_id, path=(), reached_target=False), False
|
||||
|
||||
def fake_reverify(self, state):
|
||||
_ = self
|
||||
_ = state
|
||||
return _IterationReview(
|
||||
conflicting_nets={"netA", "netB", "netC", "netD"},
|
||||
conflict_edges={("netA", "netB"), ("netC", "netD")},
|
||||
completed_net_ids=set(),
|
||||
total_dynamic_collisions=2,
|
||||
)
|
||||
|
||||
monkeypatch.setattr(PathFinder, "_route_net_once", fake_route_net_once)
|
||||
monkeypatch.setattr(PathFinder, "_reverify_iteration_results", fake_reverify)
|
||||
|
||||
review = pathfinder._run_iteration(state, 1, {"netA", "netB", "netC", "netD"}, None)
|
||||
|
||||
assert review is not None
|
||||
assert caps_by_net["netA"] == (None, None)
|
||||
assert caps_by_net["netB"] == (None, None)
|
||||
assert caps_by_net["netC"][0] == 1
|
||||
assert caps_by_net["netD"][0] == 1
|
||||
assert caps_by_net["netC"][1] is incumbents["netC"]
|
||||
assert caps_by_net["netD"][1] is incumbents["netD"]
|
||||
|
||||
|
||||
def test_route_net_once_skips_search_for_capped_incumbent_fallback(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
problem = RoutingProblem(
|
||||
bounds=(0, 0, 100, 100),
|
||||
nets=(NetSpec("netA", Port(10, 50, 0), Port(90, 50, 0), width=2.0),),
|
||||
)
|
||||
options = RoutingOptions(
|
||||
objective=ObjectiveWeights(bend_penalty=100.0),
|
||||
congestion=CongestionOptions(max_iterations=2, warm_start_enabled=False),
|
||||
refinement=RefinementOptions(enabled=False),
|
||||
)
|
||||
evaluator = CostEvaluator(RoutingWorld(clearance=2.0), DangerMap(bounds=problem.bounds))
|
||||
pathfinder = PathFinder(AStarContext(evaluator, problem, options))
|
||||
state = pathfinder._prepare_state()
|
||||
incumbent = RoutingResult(
|
||||
net_id="netA",
|
||||
path=(Straight.generate(problem.nets[0].start, 80.0, 2.0, dilation=1.0),),
|
||||
reached_target=True,
|
||||
)
|
||||
state.results["netA"] = incumbent
|
||||
installed: list[tuple[str, tuple[object, ...]]] = []
|
||||
|
||||
def fail_route_astar(*args, **kwargs):
|
||||
raise AssertionError("route_astar should not run for capped incumbent fallback")
|
||||
|
||||
def record_install(self, net_id, path):
|
||||
_ = self
|
||||
installed.append((net_id, tuple(path)))
|
||||
|
||||
monkeypatch.setattr(router_module, "route_astar", fail_route_astar)
|
||||
monkeypatch.setattr(PathFinder, "_install_path", record_install)
|
||||
|
||||
result, guidance_seed_present = pathfinder._route_net_once(
|
||||
state,
|
||||
1,
|
||||
"netA",
|
||||
node_limit_override=1,
|
||||
incumbent_fallback=incumbent,
|
||||
)
|
||||
|
||||
assert result is incumbent
|
||||
assert guidance_seed_present is True
|
||||
assert installed == [("netA", incumbent.path)]
|
||||
assert pathfinder.metrics.total_late_phase_capped_nets == 1
|
||||
assert pathfinder.metrics.total_late_phase_capped_fallbacks == 1
|
||||
|
||||
|
||||
def test_route_all_restores_best_iteration_snapshot(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
problem = RoutingProblem(
|
||||
bounds=(0, 0, 100, 100),
|
||||
|
|
|
|||
|
|
@ -288,9 +288,6 @@ def test_pair_local_context_clones_live_static_obstacles() -> None:
|
|||
last_conflict_signature=(),
|
||||
last_conflict_edge_count=0,
|
||||
repeated_conflict_count=0,
|
||||
pair_local_plateau_count=0,
|
||||
recent_attempt_work={},
|
||||
pre_pair_candidate=None,
|
||||
)
|
||||
|
||||
local_context = finder._build_pair_local_context(state, {}, ("pair_a", "pair_b"))
|
||||
|
|
|
|||
|
|
@ -6,13 +6,7 @@ from typing import TYPE_CHECKING
|
|||
|
||||
import pytest
|
||||
|
||||
from inire.tests.example_scenarios import (
|
||||
SCENARIOS,
|
||||
ScenarioOutcome,
|
||||
snapshot_example_07_no_warm_start,
|
||||
snapshot_example_07_no_warm_start_seed43,
|
||||
trace_example_07_no_warm_start_seed43,
|
||||
)
|
||||
from inire.tests.example_scenarios import SCENARIOS, ScenarioOutcome, snapshot_example_07_no_warm_start
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
|
@ -22,7 +16,6 @@ RUN_PERFORMANCE = os.environ.get("INIRE_RUN_PERFORMANCE") == "1"
|
|||
PERFORMANCE_REPEATS = 3
|
||||
REGRESSION_FACTOR = 1.5
|
||||
NO_WARM_START_REGRESSION_SECONDS = 15.0
|
||||
NO_WARM_START_SEED43_REGRESSION_SECONDS = 20.0
|
||||
|
||||
# Baselines are measured from clean 6a28dcf-style runs without plotting.
|
||||
BASELINE_SECONDS = {
|
||||
|
|
@ -92,32 +85,3 @@ def test_example_07_no_warm_start_runtime_regression() -> None:
|
|||
f"{snapshot.duration_s:.4f}s exceeded guardrail "
|
||||
f"{NO_WARM_START_REGRESSION_SECONDS:.1f}s"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.performance
|
||||
@pytest.mark.skipif(not RUN_PERFORMANCE, reason="set INIRE_RUN_PERFORMANCE=1 to run runtime regression checks")
|
||||
def test_example_07_no_warm_start_seed43_runtime_regression() -> None:
|
||||
snapshot = snapshot_example_07_no_warm_start_seed43()
|
||||
run = trace_example_07_no_warm_start_seed43()
|
||||
|
||||
assert snapshot.total_results == 10
|
||||
assert snapshot.valid_results == 10
|
||||
assert snapshot.reached_targets == 10
|
||||
assert snapshot.metrics.warm_start_paths_built == 0
|
||||
assert snapshot.metrics.warm_start_paths_used == 0
|
||||
assert snapshot.metrics.pair_local_search_pairs_considered >= 1
|
||||
assert snapshot.metrics.pair_local_search_accepts >= 1
|
||||
assert snapshot.duration_s <= NO_WARM_START_SEED43_REGRESSION_SECONDS, (
|
||||
"example_07_large_scale_routing_no_warm_start_seed43 runtime "
|
||||
f"{snapshot.duration_s:.4f}s exceeded guardrail "
|
||||
f"{NO_WARM_START_SEED43_REGRESSION_SECONDS:.1f}s"
|
||||
)
|
||||
|
||||
assert run.iteration_trace
|
||||
assert len(run.results_by_net) == 10
|
||||
assert sum(result.is_valid for result in run.results_by_net.values()) == 10
|
||||
assert sum(result.reached_target for result in run.results_by_net.values()) == 10
|
||||
assert run.metrics.warm_start_paths_built == 0
|
||||
assert run.metrics.warm_start_paths_used == 0
|
||||
assert run.metrics.pair_local_search_pairs_considered >= 1
|
||||
assert run.metrics.pair_local_search_accepts >= 1
|
||||
|
|
|
|||
|
|
@ -75,7 +75,6 @@ def test_example_07_no_warm_start_trace_finishes_without_conflict_edges() -> Non
|
|||
assert sum(result.reached_target for result in run.results_by_net.values()) == 10
|
||||
assert run.metrics.pair_local_search_pairs_considered >= 1
|
||||
assert run.metrics.pair_local_search_accepts >= 1
|
||||
assert run.pre_pair_frontier_trace is not None
|
||||
|
||||
final_entry = run.conflict_trace[-1]
|
||||
assert final_entry.stage == "final"
|
||||
|
|
|
|||
|
|
@ -48,8 +48,6 @@ def test_snapshot_example_01_exposes_metrics() -> None:
|
|||
assert snapshot.metrics.pair_local_search_attempts >= 0
|
||||
assert snapshot.metrics.pair_local_search_accepts >= 0
|
||||
assert snapshot.metrics.pair_local_search_nodes_expanded >= 0
|
||||
assert snapshot.metrics.late_phase_capped_nets >= 0
|
||||
assert snapshot.metrics.late_phase_capped_fallbacks >= 0
|
||||
|
||||
|
||||
def test_record_performance_baseline_script_writes_selected_scenario(tmp_path: Path) -> None:
|
||||
|
|
@ -278,55 +276,6 @@ def test_record_frontier_trace_script_writes_selected_scenario(tmp_path: Path) -
|
|||
assert (tmp_path / "frontier_trace.md").exists()
|
||||
|
||||
|
||||
def test_record_iteration_trace_script_writes_selected_scenario(tmp_path: Path) -> None:
|
||||
repo_root = Path(__file__).resolve().parents[2]
|
||||
script_path = repo_root / "scripts" / "record_iteration_trace.py"
|
||||
|
||||
subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(script_path),
|
||||
"--include-performance-only",
|
||||
"--scenario",
|
||||
"example_07_large_scale_routing_no_warm_start",
|
||||
"--output-dir",
|
||||
str(tmp_path),
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
||||
payload = json.loads((tmp_path / "iteration_trace.json").read_text())
|
||||
assert payload["generated_at"]
|
||||
assert payload["generator"] == "scripts/record_iteration_trace.py"
|
||||
assert [entry["name"] for entry in payload["scenarios"]] == ["example_07_large_scale_routing_no_warm_start"]
|
||||
assert (tmp_path / "iteration_trace.md").exists()
|
||||
|
||||
|
||||
def test_record_pre_pair_frontier_trace_script_writes_selected_scenario(tmp_path: Path) -> None:
|
||||
repo_root = Path(__file__).resolve().parents[2]
|
||||
script_path = repo_root / "scripts" / "record_pre_pair_frontier_trace.py"
|
||||
|
||||
subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(script_path),
|
||||
"--include-performance-only",
|
||||
"--scenario",
|
||||
"example_07_large_scale_routing_no_warm_start",
|
||||
"--output-dir",
|
||||
str(tmp_path),
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
||||
payload = json.loads((tmp_path / "pre_pair_frontier_trace.json").read_text())
|
||||
assert payload["generated_at"]
|
||||
assert payload["generator"] == "scripts/record_pre_pair_frontier_trace.py"
|
||||
assert [entry["name"] for entry in payload["scenarios"]] == ["example_07_large_scale_routing_no_warm_start"]
|
||||
assert payload["scenarios"][0]["pre_pair_frontier_trace"] is not None
|
||||
assert (tmp_path / "pre_pair_frontier_trace.md").exists()
|
||||
|
||||
|
||||
def test_characterize_pair_local_search_script_writes_outputs(tmp_path: Path) -> None:
|
||||
repo_root = Path(__file__).resolve().parents[2]
|
||||
script_path = repo_root / "scripts" / "characterize_pair_local_search.py"
|
||||
|
|
|
|||
|
|
@ -1,186 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from collections import Counter
|
||||
from dataclasses import asdict
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from inire.tests.example_scenarios import TRACE_PERFORMANCE_SCENARIO_RUNS, TRACE_SCENARIO_RUNS
|
||||
|
||||
|
||||
def _trace_registry(include_performance_only: bool) -> tuple[tuple[str, object], ...]:
|
||||
if include_performance_only:
|
||||
return TRACE_SCENARIO_RUNS + TRACE_PERFORMANCE_SCENARIO_RUNS
|
||||
return TRACE_SCENARIO_RUNS
|
||||
|
||||
|
||||
def _selected_runs(
|
||||
selected_scenarios: tuple[str, ...] | None,
|
||||
*,
|
||||
include_performance_only: bool,
|
||||
) -> tuple[tuple[str, object], ...]:
|
||||
if selected_scenarios is None:
|
||||
perf_registry = dict(TRACE_PERFORMANCE_SCENARIO_RUNS)
|
||||
return (
|
||||
(
|
||||
"example_07_large_scale_routing_no_warm_start",
|
||||
perf_registry["example_07_large_scale_routing_no_warm_start"],
|
||||
),
|
||||
(
|
||||
"example_07_large_scale_routing_no_warm_start_seed43",
|
||||
perf_registry["example_07_large_scale_routing_no_warm_start_seed43"],
|
||||
),
|
||||
)
|
||||
|
||||
registry = dict(TRACE_SCENARIO_RUNS + TRACE_PERFORMANCE_SCENARIO_RUNS)
|
||||
allowed_standard = dict(_trace_registry(include_performance_only))
|
||||
runs = []
|
||||
for name in selected_scenarios:
|
||||
if name in allowed_standard:
|
||||
runs.append((name, allowed_standard[name]))
|
||||
continue
|
||||
if name in registry:
|
||||
runs.append((name, registry[name]))
|
||||
continue
|
||||
valid = ", ".join(sorted(registry))
|
||||
raise SystemExit(f"Unknown iteration-trace scenario: {name}. Valid scenarios: {valid}")
|
||||
return tuple(runs)
|
||||
|
||||
|
||||
def _build_payload(
|
||||
selected_scenarios: tuple[str, ...] | None,
|
||||
*,
|
||||
include_performance_only: bool,
|
||||
) -> dict[str, object]:
|
||||
scenarios = []
|
||||
for name, run in _selected_runs(selected_scenarios, include_performance_only=include_performance_only):
|
||||
result = run()
|
||||
scenarios.append(
|
||||
{
|
||||
"name": name,
|
||||
"summary": {
|
||||
"total_results": len(result.results_by_net),
|
||||
"valid_results": sum(1 for entry in result.results_by_net.values() if entry.is_valid),
|
||||
"reached_targets": sum(1 for entry in result.results_by_net.values() if entry.reached_target),
|
||||
},
|
||||
"metrics": asdict(result.metrics),
|
||||
"iteration_trace": [asdict(entry) for entry in result.iteration_trace],
|
||||
}
|
||||
)
|
||||
return {
|
||||
"generated_at": datetime.now().astimezone().isoformat(timespec="seconds"),
|
||||
"generator": "scripts/record_iteration_trace.py",
|
||||
"scenarios": scenarios,
|
||||
}
|
||||
|
||||
|
||||
def _render_markdown(payload: dict[str, object]) -> str:
|
||||
lines = [
|
||||
"# Iteration Trace",
|
||||
"",
|
||||
f"Generated at {payload['generated_at']} by `{payload['generator']}`.",
|
||||
"",
|
||||
]
|
||||
|
||||
for scenario in payload["scenarios"]:
|
||||
lines.extend(
|
||||
[
|
||||
f"## {scenario['name']}",
|
||||
"",
|
||||
f"Results: {scenario['summary']['valid_results']} valid / "
|
||||
f"{scenario['summary']['reached_targets']} reached / "
|
||||
f"{scenario['summary']['total_results']} total.",
|
||||
"",
|
||||
"| Iteration | Penalty | Routed Nets | Completed | Conflict Edges | Dynamic Collisions | Nodes | Congestion Checks | Candidate Ids | Exact Pairs |",
|
||||
"| --: | --: | --: | --: | --: | --: | --: | --: | --: | --: |",
|
||||
]
|
||||
)
|
||||
|
||||
net_node_counts: Counter[str] = Counter()
|
||||
net_check_counts: Counter[str] = Counter()
|
||||
for entry in scenario["iteration_trace"]:
|
||||
lines.append(
|
||||
"| "
|
||||
f"{entry['iteration']} | "
|
||||
f"{entry['congestion_penalty']:.1f} | "
|
||||
f"{len(entry['routed_net_ids'])} | "
|
||||
f"{entry['completed_nets']} | "
|
||||
f"{entry['conflict_edges']} | "
|
||||
f"{entry['total_dynamic_collisions']} | "
|
||||
f"{entry['nodes_expanded']} | "
|
||||
f"{entry['congestion_check_calls']} | "
|
||||
f"{entry['congestion_candidate_ids']} | "
|
||||
f"{entry['congestion_exact_pair_checks']} |"
|
||||
)
|
||||
for attempt in entry["net_attempts"]:
|
||||
net_node_counts[attempt["net_id"]] += attempt["nodes_expanded"]
|
||||
net_check_counts[attempt["net_id"]] += attempt["congestion_check_calls"]
|
||||
|
||||
lines.extend(["", "Top nets by iteration-attributed nodes expanded:", ""])
|
||||
if net_node_counts:
|
||||
for net_id, count in net_node_counts.most_common(10):
|
||||
lines.append(f"- `{net_id}`: {count}")
|
||||
else:
|
||||
lines.append("- None")
|
||||
|
||||
lines.extend(["", "Top nets by iteration-attributed congestion checks:", ""])
|
||||
if net_check_counts:
|
||||
for net_id, count in net_check_counts.most_common(10):
|
||||
lines.append(f"- `{net_id}`: {count}")
|
||||
else:
|
||||
lines.append("- None")
|
||||
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Record iteration-trace artifacts for selected trace scenarios.")
|
||||
parser.add_argument(
|
||||
"--scenario",
|
||||
action="append",
|
||||
dest="scenarios",
|
||||
default=[],
|
||||
help="Optional trace scenario name to include. May be passed more than once.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--include-performance-only",
|
||||
action="store_true",
|
||||
help="Include performance-only trace scenarios when selecting from the standard registry.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output-dir",
|
||||
type=Path,
|
||||
default=None,
|
||||
help="Directory to write iteration_trace.json and iteration_trace.md into. Defaults to <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 / "iteration_trace.json"
|
||||
markdown_path = output_dir / "iteration_trace.md"
|
||||
|
||||
json_path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n")
|
||||
markdown_path.write_text(_render_markdown(payload) + "\n")
|
||||
|
||||
if json_path.is_relative_to(repo_root):
|
||||
print(f"Wrote {json_path.relative_to(repo_root)}")
|
||||
else:
|
||||
print(f"Wrote {json_path}")
|
||||
if markdown_path.is_relative_to(repo_root):
|
||||
print(f"Wrote {markdown_path.relative_to(repo_root)}")
|
||||
else:
|
||||
print(f"Wrote {markdown_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,191 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from collections import Counter
|
||||
from dataclasses import asdict
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from inire.tests.example_scenarios import TRACE_PERFORMANCE_SCENARIO_RUNS, TRACE_SCENARIO_RUNS
|
||||
|
||||
|
||||
def _trace_registry(include_performance_only: bool) -> tuple[tuple[str, object], ...]:
|
||||
if include_performance_only:
|
||||
return TRACE_SCENARIO_RUNS + TRACE_PERFORMANCE_SCENARIO_RUNS
|
||||
return TRACE_SCENARIO_RUNS
|
||||
|
||||
|
||||
def _selected_runs(
|
||||
selected_scenarios: tuple[str, ...] | None,
|
||||
*,
|
||||
include_performance_only: bool,
|
||||
) -> tuple[tuple[str, object], ...]:
|
||||
if selected_scenarios is None:
|
||||
perf_registry = dict(TRACE_PERFORMANCE_SCENARIO_RUNS)
|
||||
return (
|
||||
(
|
||||
"example_07_large_scale_routing_no_warm_start",
|
||||
perf_registry["example_07_large_scale_routing_no_warm_start"],
|
||||
),
|
||||
(
|
||||
"example_07_large_scale_routing_no_warm_start_seed43",
|
||||
perf_registry["example_07_large_scale_routing_no_warm_start_seed43"],
|
||||
),
|
||||
)
|
||||
|
||||
registry = dict(TRACE_SCENARIO_RUNS + TRACE_PERFORMANCE_SCENARIO_RUNS)
|
||||
allowed_standard = dict(_trace_registry(include_performance_only))
|
||||
runs = []
|
||||
for name in selected_scenarios:
|
||||
if name in allowed_standard:
|
||||
runs.append((name, allowed_standard[name]))
|
||||
continue
|
||||
if name in registry:
|
||||
runs.append((name, registry[name]))
|
||||
continue
|
||||
valid = ", ".join(sorted(registry))
|
||||
raise SystemExit(f"Unknown pre-pair frontier-trace scenario: {name}. Valid scenarios: {valid}")
|
||||
return tuple(runs)
|
||||
|
||||
|
||||
def _build_payload(
|
||||
selected_scenarios: tuple[str, ...] | None,
|
||||
*,
|
||||
include_performance_only: bool,
|
||||
) -> dict[str, object]:
|
||||
scenarios = []
|
||||
for name, run in _selected_runs(selected_scenarios, include_performance_only=include_performance_only):
|
||||
result = run()
|
||||
scenarios.append(
|
||||
{
|
||||
"name": name,
|
||||
"summary": {
|
||||
"total_results": len(result.results_by_net),
|
||||
"valid_results": sum(1 for entry in result.results_by_net.values() if entry.is_valid),
|
||||
"reached_targets": sum(1 for entry in result.results_by_net.values() if entry.reached_target),
|
||||
},
|
||||
"metrics": asdict(result.metrics),
|
||||
"pre_pair_frontier_trace": None
|
||||
if result.pre_pair_frontier_trace is None
|
||||
else asdict(result.pre_pair_frontier_trace),
|
||||
}
|
||||
)
|
||||
return {
|
||||
"generated_at": datetime.now().astimezone().isoformat(timespec="seconds"),
|
||||
"generator": "scripts/record_pre_pair_frontier_trace.py",
|
||||
"scenarios": scenarios,
|
||||
}
|
||||
|
||||
|
||||
def _render_markdown(payload: dict[str, object]) -> str:
|
||||
lines = [
|
||||
"# Pre-Pair Frontier Trace",
|
||||
"",
|
||||
f"Generated at {payload['generated_at']} by `{payload['generator']}`.",
|
||||
"",
|
||||
]
|
||||
|
||||
for scenario in payload["scenarios"]:
|
||||
lines.extend(
|
||||
[
|
||||
f"## {scenario['name']}",
|
||||
"",
|
||||
f"Results: {scenario['summary']['valid_results']} valid / "
|
||||
f"{scenario['summary']['reached_targets']} reached / "
|
||||
f"{scenario['summary']['total_results']} total.",
|
||||
"",
|
||||
]
|
||||
)
|
||||
trace = scenario["pre_pair_frontier_trace"]
|
||||
if trace is None:
|
||||
lines.extend(["No pre-pair frontier trace captured.", ""])
|
||||
continue
|
||||
|
||||
lines.extend(
|
||||
[
|
||||
f"Captured iteration: `{trace['iteration']}`",
|
||||
"",
|
||||
f"Conflict edges: `{trace['conflict_edges']}`",
|
||||
"",
|
||||
"| Net | Nodes | Checks | Closed-Set | Cost | Hard Collision | Guidance Seed | Frontier Samples |",
|
||||
"| :-- | --: | --: | --: | --: | --: | :--: | --: |",
|
||||
]
|
||||
)
|
||||
reason_counts: Counter[str] = Counter()
|
||||
for net_trace in trace["nets"]:
|
||||
frontier = net_trace["frontier"]
|
||||
lines.append(
|
||||
"| "
|
||||
f"{net_trace['net_id']} | "
|
||||
f"{net_trace['nodes_expanded']} | "
|
||||
f"{net_trace['congestion_check_calls']} | "
|
||||
f"{net_trace['pruned_closed_set']} | "
|
||||
f"{net_trace['pruned_cost']} | "
|
||||
f"{net_trace['pruned_hard_collision']} | "
|
||||
f"{'yes' if net_trace['guidance_seed_present'] else 'no'} | "
|
||||
f"{len(frontier['samples'])} |"
|
||||
)
|
||||
reason_counts["closed_set"] += frontier["pruned_closed_set"]
|
||||
reason_counts["hard_collision"] += frontier["pruned_hard_collision"]
|
||||
reason_counts["self_collision"] += frontier["pruned_self_collision"]
|
||||
reason_counts["cost"] += frontier["pruned_cost"]
|
||||
|
||||
lines.extend(["", "Frontier prune totals by reason:", ""])
|
||||
if reason_counts:
|
||||
for reason, count in reason_counts.most_common():
|
||||
lines.append(f"- `{reason}`: {count}")
|
||||
else:
|
||||
lines.append("- None")
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Record pre-pair frontier-trace artifacts for selected trace scenarios.")
|
||||
parser.add_argument(
|
||||
"--scenario",
|
||||
action="append",
|
||||
dest="scenarios",
|
||||
default=[],
|
||||
help="Optional trace scenario name to include. May be passed more than once.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--include-performance-only",
|
||||
action="store_true",
|
||||
help="Include performance-only trace scenarios when selecting from the standard registry.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output-dir",
|
||||
type=Path,
|
||||
default=None,
|
||||
help="Directory to write pre_pair_frontier_trace.json and .md into. Defaults to <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