more perf counters

This commit is contained in:
Jan Petykiewicz 2026-03-31 17:41:15 -07:00
commit e77fd6e69f
15 changed files with 643 additions and 54 deletions

View file

@ -186,6 +186,7 @@ Use `RoutingProblem.initial_paths` to provide semantic per-net seeds. Seeds are
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=...)`. 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)**. The current implementation structure is summarized in **[docs/architecture.md](docs/architecture.md)**. The committed example-corpus counter baseline is tracked in **[docs/performance.md](docs/performance.md)**.
Use `scripts/diff_performance_baseline.py` to compare a fresh local run against that baseline. The counter baseline is currently observational and is not enforced as a CI gate.
## 9. Tuning Notes ## 9. Tuning Notes

View file

@ -3,23 +3,25 @@
Generated on 2026-03-31 by `scripts/record_performance_baseline.py`. Generated on 2026-03-31 by `scripts/record_performance_baseline.py`.
The full machine-readable snapshot lives in `docs/performance_baseline.json`. 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.
| Scenario | Duration (s) | Total | Valid | Reached | Iter | Nets Routed | Nodes | Ray Casts | Moves Gen | Moves Added | Dyn Tree | Visibility Builds | Congestion Checks | Verify Calls | | 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.0042 | 1 | 1 | 1 | 1 | 1 | 2 | 22 | 11 | 7 | 2 | 2 | 0 | 3 | | example_01_simple_route | 0.0042 | 1 | 1 | 1 | 1 | 1 | 2 | 22 | 11 | 7 | 2 | 2 | 0 | 3 |
| example_02_congestion_resolution | 0.3335 | 3 | 3 | 3 | 1 | 3 | 366 | 1176 | 1413 | 668 | 8 | 4 | 0 | 35 | | example_02_congestion_resolution | 0.3418 | 3 | 3 | 3 | 1 | 3 | 366 | 1176 | 1413 | 668 | 8 | 4 | 0 | 35 |
| example_03_locked_paths | 0.1810 | 2 | 2 | 2 | 2 | 2 | 191 | 681 | 904 | 307 | 5 | 4 | 0 | 14 | | example_03_locked_paths | 0.1827 | 2 | 2 | 2 | 2 | 2 | 191 | 681 | 904 | 307 | 5 | 4 | 0 | 14 |
| example_04_sbends_and_radii | 2.0151 | 2 | 2 | 2 | 1 | 2 | 15 | 18218 | 123 | 65 | 4 | 3 | 0 | 6 | | example_04_sbends_and_radii | 1.9938 | 2 | 2 | 2 | 1 | 2 | 15 | 18218 | 123 | 65 | 4 | 3 | 0 | 6 |
| example_05_orientation_stress | 0.2438 | 3 | 3 | 3 | 2 | 6 | 286 | 1243 | 1624 | 681 | 12 | 3 | 412 | 12 | | example_05_orientation_stress | 0.2458 | 3 | 3 | 3 | 2 | 6 | 286 | 1243 | 1624 | 681 | 12 | 3 | 412 | 12 |
| example_06_bend_collision_models | 4.1636 | 3 | 3 | 3 | 3 | 3 | 240 | 40530 | 1026 | 629 | 6 | 6 | 0 | 9 | | example_06_bend_collision_models | 4.1186 | 3 | 3 | 3 | 3 | 3 | 240 | 40530 | 1026 | 629 | 6 | 6 | 0 | 9 |
| example_07_large_scale_routing | 1.3759 | 10 | 10 | 10 | 1 | 10 | 78 | 11151 | 372 | 227 | 20 | 11 | 0 | 30 | | example_07_large_scale_routing | 1.3734 | 10 | 10 | 10 | 1 | 10 | 78 | 11151 | 372 | 227 | 20 | 11 | 0 | 30 |
| example_08_custom_bend_geometry | 0.2437 | 2 | 2 | 2 | 2 | 2 | 18 | 2308 | 78 | 56 | 4 | 4 | 0 | 6 | | example_08_custom_bend_geometry | 0.2410 | 2 | 2 | 2 | 2 | 2 | 18 | 2308 | 78 | 56 | 4 | 4 | 0 | 6 |
| example_09_unroutable_best_effort | 0.0052 | 1 | 0 | 0 | 1 | 1 | 3 | 13 | 16 | 10 | 1 | 0 | 0 | 1 | | example_09_unroutable_best_effort | 0.0052 | 1 | 0 | 0 | 1 | 1 | 3 | 13 | 16 | 10 | 1 | 0 | 0 | 1 |
## Full Counter Set ## Full Counter Set
Each scenario entry in `docs/performance_baseline.json` records the full `RouteMetrics` snapshot, including cache, index, congestion, and verification counters. 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.
Tracked metric keys: 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, move_cache_abs_hits, move_cache_abs_misses, move_cache_rel_hits, move_cache_rel_misses, static_safe_cache_hits, hard_collision_cache_hits, congestion_cache_hits, congestion_cache_misses, 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_builds, visibility_corner_pairs_checked, visibility_corner_queries, visibility_corner_hits, visibility_point_queries, visibility_point_cache_hits, visibility_point_cache_misses, ray_cast_calls, ray_cast_candidate_bounds, ray_cast_exact_geometry_checks, congestion_check_calls, congestion_exact_pair_checks, verify_path_report_calls, verify_static_buffer_ops, verify_dynamic_exact_pair_checks 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, score_component_calls, score_component_total_ns, path_cost_calls, danger_map_lookup_calls, danger_map_cache_hits, danger_map_cache_misses, danger_map_query_calls, danger_map_total_ns, move_cache_abs_hits, move_cache_abs_misses, move_cache_rel_hits, move_cache_rel_misses, static_safe_cache_hits, hard_collision_cache_hits, congestion_cache_hits, congestion_cache_misses, 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_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_exact_pair_checks, verify_path_report_calls, verify_static_buffer_ops, 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

View file

@ -3,12 +3,17 @@
"generator": "scripts/record_performance_baseline.py", "generator": "scripts/record_performance_baseline.py",
"scenarios": [ "scenarios": [
{ {
"duration_s": 0.0041740520391613245, "duration_s": 0.00415895797777921,
"metrics": { "metrics": {
"congestion_cache_hits": 0, "congestion_cache_hits": 0,
"congestion_cache_misses": 0, "congestion_cache_misses": 0,
"congestion_check_calls": 0, "congestion_check_calls": 0,
"congestion_exact_pair_checks": 0, "congestion_exact_pair_checks": 0,
"danger_map_cache_hits": 8,
"danger_map_cache_misses": 13,
"danger_map_lookup_calls": 21,
"danger_map_query_calls": 0,
"danger_map_total_ns": 27079,
"dynamic_grid_rebuilds": 0, "dynamic_grid_rebuilds": 0,
"dynamic_path_objects_added": 2, "dynamic_path_objects_added": 2,
"dynamic_path_objects_removed": 1, "dynamic_path_objects_removed": 1,
@ -23,14 +28,31 @@
"nets_reached_target": 1, "nets_reached_target": 1,
"nets_routed": 1, "nets_routed": 1,
"nodes_expanded": 2, "nodes_expanded": 2,
"path_cost_calls": 0,
"pruned_closed_set": 0, "pruned_closed_set": 0,
"pruned_cost": 4, "pruned_cost": 4,
"pruned_hard_collision": 0, "pruned_hard_collision": 0,
"ray_cast_calls": 22, "ray_cast_calls": 22,
"ray_cast_calls_expand_forward": 1,
"ray_cast_calls_expand_snap": 1,
"ray_cast_calls_other": 0,
"ray_cast_calls_straight_static": 8,
"ray_cast_calls_visibility_build": 12,
"ray_cast_calls_visibility_query": 0,
"ray_cast_calls_visibility_tangent": 0,
"ray_cast_candidate_bounds": 12, "ray_cast_candidate_bounds": 12,
"ray_cast_exact_geometry_checks": 0, "ray_cast_exact_geometry_checks": 0,
"refine_path_calls": 1, "refine_path_calls": 1,
"refinement_candidate_side_extents": 0,
"refinement_candidates_accepted": 0,
"refinement_candidates_built": 0,
"refinement_candidates_verified": 0,
"refinement_dynamic_bounds_checked": 0,
"refinement_static_bounds_checked": 0,
"refinement_windows_considered": 0,
"route_iterations": 1, "route_iterations": 1,
"score_component_calls": 11,
"score_component_total_ns": 59404,
"static_net_tree_rebuilds": 1, "static_net_tree_rebuilds": 1,
"static_raw_tree_rebuilds": 0, "static_raw_tree_rebuilds": 0,
"static_safe_cache_hits": 1, "static_safe_cache_hits": 1,
@ -40,12 +62,15 @@
"verify_path_report_calls": 3, "verify_path_report_calls": 3,
"verify_static_buffer_ops": 0, "verify_static_buffer_ops": 0,
"visibility_builds": 2, "visibility_builds": 2,
"visibility_corner_hits": 0, "visibility_corner_hits_exact": 0,
"visibility_corner_pairs_checked": 12, "visibility_corner_pairs_checked": 12,
"visibility_corner_queries": 0, "visibility_corner_queries_exact": 0,
"visibility_point_cache_hits": 0, "visibility_point_cache_hits": 0,
"visibility_point_cache_misses": 0, "visibility_point_cache_misses": 0,
"visibility_point_queries": 0, "visibility_point_queries": 0,
"visibility_tangent_candidate_corner_checks": 0,
"visibility_tangent_candidate_ray_tests": 0,
"visibility_tangent_candidate_scans": 1,
"warm_start_paths_built": 1, "warm_start_paths_built": 1,
"warm_start_paths_used": 1 "warm_start_paths_used": 1
}, },
@ -55,12 +80,17 @@
"valid_results": 1 "valid_results": 1
}, },
{ {
"duration_s": 0.3335385399404913, "duration_s": 0.34182924893684685,
"metrics": { "metrics": {
"congestion_cache_hits": 0, "congestion_cache_hits": 0,
"congestion_cache_misses": 0, "congestion_cache_misses": 0,
"congestion_check_calls": 0, "congestion_check_calls": 0,
"congestion_exact_pair_checks": 0, "congestion_exact_pair_checks": 0,
"danger_map_cache_hits": 1433,
"danger_map_cache_misses": 775,
"danger_map_lookup_calls": 2208,
"danger_map_query_calls": 0,
"danger_map_total_ns": 2165333,
"dynamic_grid_rebuilds": 0, "dynamic_grid_rebuilds": 0,
"dynamic_path_objects_added": 32, "dynamic_path_objects_added": 32,
"dynamic_path_objects_removed": 17, "dynamic_path_objects_removed": 17,
@ -75,14 +105,31 @@
"nets_reached_target": 3, "nets_reached_target": 3,
"nets_routed": 3, "nets_routed": 3,
"nodes_expanded": 366, "nodes_expanded": 366,
"path_cost_calls": 14,
"pruned_closed_set": 157, "pruned_closed_set": 157,
"pruned_cost": 208, "pruned_cost": 208,
"pruned_hard_collision": 380, "pruned_hard_collision": 380,
"ray_cast_calls": 1176, "ray_cast_calls": 1176,
"ray_cast_calls_expand_forward": 363,
"ray_cast_calls_expand_snap": 19,
"ray_cast_calls_other": 0,
"ray_cast_calls_straight_static": 529,
"ray_cast_calls_visibility_build": 12,
"ray_cast_calls_visibility_query": 0,
"ray_cast_calls_visibility_tangent": 253,
"ray_cast_candidate_bounds": 925, "ray_cast_candidate_bounds": 925,
"ray_cast_exact_geometry_checks": 136, "ray_cast_exact_geometry_checks": 136,
"refine_path_calls": 3, "refine_path_calls": 3,
"refinement_candidate_side_extents": 26,
"refinement_candidates_accepted": 2,
"refinement_candidates_built": 26,
"refinement_candidates_verified": 26,
"refinement_dynamic_bounds_checked": 20,
"refinement_static_bounds_checked": 0,
"refinement_windows_considered": 10,
"route_iterations": 1, "route_iterations": 1,
"score_component_calls": 976,
"score_component_total_ns": 4650167,
"static_net_tree_rebuilds": 3, "static_net_tree_rebuilds": 3,
"static_raw_tree_rebuilds": 0, "static_raw_tree_rebuilds": 0,
"static_safe_cache_hits": 1, "static_safe_cache_hits": 1,
@ -92,12 +139,15 @@
"verify_path_report_calls": 35, "verify_path_report_calls": 35,
"verify_static_buffer_ops": 0, "verify_static_buffer_ops": 0,
"visibility_builds": 4, "visibility_builds": 4,
"visibility_corner_hits": 0, "visibility_corner_hits_exact": 0,
"visibility_corner_pairs_checked": 12, "visibility_corner_pairs_checked": 12,
"visibility_corner_queries": 0, "visibility_corner_queries_exact": 0,
"visibility_point_cache_hits": 0, "visibility_point_cache_hits": 0,
"visibility_point_cache_misses": 0, "visibility_point_cache_misses": 0,
"visibility_point_queries": 0, "visibility_point_queries": 0,
"visibility_tangent_candidate_corner_checks": 18991,
"visibility_tangent_candidate_ray_tests": 253,
"visibility_tangent_candidate_scans": 363,
"warm_start_paths_built": 3, "warm_start_paths_built": 3,
"warm_start_paths_used": 3 "warm_start_paths_used": 3
}, },
@ -107,12 +157,17 @@
"valid_results": 3 "valid_results": 3
}, },
{ {
"duration_s": 0.1809853739105165, "duration_s": 0.18274989898782223,
"metrics": { "metrics": {
"congestion_cache_hits": 0, "congestion_cache_hits": 0,
"congestion_cache_misses": 0, "congestion_cache_misses": 0,
"congestion_check_calls": 0, "congestion_check_calls": 0,
"congestion_exact_pair_checks": 0, "congestion_exact_pair_checks": 0,
"danger_map_cache_hits": 624,
"danger_map_cache_misses": 414,
"danger_map_lookup_calls": 1038,
"danger_map_query_calls": 0,
"danger_map_total_ns": 1001517,
"dynamic_grid_rebuilds": 0, "dynamic_grid_rebuilds": 0,
"dynamic_path_objects_added": 17, "dynamic_path_objects_added": 17,
"dynamic_path_objects_removed": 10, "dynamic_path_objects_removed": 10,
@ -127,14 +182,31 @@
"nets_reached_target": 2, "nets_reached_target": 2,
"nets_routed": 2, "nets_routed": 2,
"nodes_expanded": 191, "nodes_expanded": 191,
"path_cost_calls": 9,
"pruned_closed_set": 97, "pruned_closed_set": 97,
"pruned_cost": 140, "pruned_cost": 140,
"pruned_hard_collision": 181, "pruned_hard_collision": 181,
"ray_cast_calls": 681, "ray_cast_calls": 681,
"ray_cast_calls_expand_forward": 189,
"ray_cast_calls_expand_snap": 8,
"ray_cast_calls_other": 0,
"ray_cast_calls_straight_static": 407,
"ray_cast_calls_visibility_build": 24,
"ray_cast_calls_visibility_query": 0,
"ray_cast_calls_visibility_tangent": 53,
"ray_cast_candidate_bounds": 179, "ray_cast_candidate_bounds": 179,
"ray_cast_exact_geometry_checks": 0, "ray_cast_exact_geometry_checks": 0,
"refine_path_calls": 2, "refine_path_calls": 2,
"refinement_candidate_side_extents": 8,
"refinement_candidates_accepted": 1,
"refinement_candidates_built": 8,
"refinement_candidates_verified": 8,
"refinement_dynamic_bounds_checked": 2,
"refinement_static_bounds_checked": 2,
"refinement_windows_considered": 2,
"route_iterations": 2, "route_iterations": 2,
"score_component_calls": 504,
"score_component_total_ns": 2184569,
"static_net_tree_rebuilds": 2, "static_net_tree_rebuilds": 2,
"static_raw_tree_rebuilds": 1, "static_raw_tree_rebuilds": 1,
"static_safe_cache_hits": 1, "static_safe_cache_hits": 1,
@ -144,12 +216,15 @@
"verify_path_report_calls": 14, "verify_path_report_calls": 14,
"verify_static_buffer_ops": 69, "verify_static_buffer_ops": 69,
"visibility_builds": 4, "visibility_builds": 4,
"visibility_corner_hits": 0, "visibility_corner_hits_exact": 0,
"visibility_corner_pairs_checked": 24, "visibility_corner_pairs_checked": 24,
"visibility_corner_queries": 0, "visibility_corner_queries_exact": 0,
"visibility_point_cache_hits": 0, "visibility_point_cache_hits": 0,
"visibility_point_cache_misses": 0, "visibility_point_cache_misses": 0,
"visibility_point_queries": 0, "visibility_point_queries": 0,
"visibility_tangent_candidate_corner_checks": 476,
"visibility_tangent_candidate_ray_tests": 53,
"visibility_tangent_candidate_scans": 189,
"warm_start_paths_built": 2, "warm_start_paths_built": 2,
"warm_start_paths_used": 2 "warm_start_paths_used": 2
}, },
@ -159,12 +234,17 @@
"valid_results": 2 "valid_results": 2
}, },
{ {
"duration_s": 2.0151148419827223, "duration_s": 1.993830946041271,
"metrics": { "metrics": {
"congestion_cache_hits": 0, "congestion_cache_hits": 0,
"congestion_cache_misses": 0, "congestion_cache_misses": 0,
"congestion_check_calls": 0, "congestion_check_calls": 0,
"congestion_exact_pair_checks": 0, "congestion_exact_pair_checks": 0,
"danger_map_cache_hits": 75,
"danger_map_cache_misses": 120,
"danger_map_lookup_calls": 195,
"danger_map_query_calls": 0,
"danger_map_total_ns": 207556,
"dynamic_grid_rebuilds": 0, "dynamic_grid_rebuilds": 0,
"dynamic_path_objects_added": 14, "dynamic_path_objects_added": 14,
"dynamic_path_objects_removed": 7, "dynamic_path_objects_removed": 7,
@ -179,14 +259,31 @@
"nets_reached_target": 2, "nets_reached_target": 2,
"nets_routed": 2, "nets_routed": 2,
"nodes_expanded": 15, "nodes_expanded": 15,
"path_cost_calls": 0,
"pruned_closed_set": 2, "pruned_closed_set": 2,
"pruned_cost": 25, "pruned_cost": 25,
"pruned_hard_collision": 16, "pruned_hard_collision": 16,
"ray_cast_calls": 18218, "ray_cast_calls": 18218,
"ray_cast_calls_expand_forward": 13,
"ray_cast_calls_expand_snap": 1,
"ray_cast_calls_other": 0,
"ray_cast_calls_straight_static": 56,
"ray_cast_calls_visibility_build": 18148,
"ray_cast_calls_visibility_query": 0,
"ray_cast_calls_visibility_tangent": 0,
"ray_cast_candidate_bounds": 50717, "ray_cast_candidate_bounds": 50717,
"ray_cast_exact_geometry_checks": 21265, "ray_cast_exact_geometry_checks": 21265,
"refine_path_calls": 2, "refine_path_calls": 2,
"refinement_candidate_side_extents": 0,
"refinement_candidates_accepted": 0,
"refinement_candidates_built": 0,
"refinement_candidates_verified": 0,
"refinement_dynamic_bounds_checked": 0,
"refinement_static_bounds_checked": 0,
"refinement_windows_considered": 0,
"route_iterations": 1, "route_iterations": 1,
"score_component_calls": 90,
"score_component_total_ns": 410130,
"static_net_tree_rebuilds": 2, "static_net_tree_rebuilds": 2,
"static_raw_tree_rebuilds": 0, "static_raw_tree_rebuilds": 0,
"static_safe_cache_hits": 1, "static_safe_cache_hits": 1,
@ -196,12 +293,15 @@
"verify_path_report_calls": 6, "verify_path_report_calls": 6,
"verify_static_buffer_ops": 0, "verify_static_buffer_ops": 0,
"visibility_builds": 3, "visibility_builds": 3,
"visibility_corner_hits": 0, "visibility_corner_hits_exact": 0,
"visibility_corner_pairs_checked": 18148, "visibility_corner_pairs_checked": 18148,
"visibility_corner_queries": 0, "visibility_corner_queries_exact": 0,
"visibility_point_cache_hits": 0, "visibility_point_cache_hits": 0,
"visibility_point_cache_misses": 0, "visibility_point_cache_misses": 0,
"visibility_point_queries": 0, "visibility_point_queries": 0,
"visibility_tangent_candidate_corner_checks": 394,
"visibility_tangent_candidate_ray_tests": 0,
"visibility_tangent_candidate_scans": 13,
"warm_start_paths_built": 2, "warm_start_paths_built": 2,
"warm_start_paths_used": 2 "warm_start_paths_used": 2
}, },
@ -211,12 +311,17 @@
"valid_results": 2 "valid_results": 2
}, },
{ {
"duration_s": 0.2437819039914757, "duration_s": 0.24581307696644217,
"metrics": { "metrics": {
"congestion_cache_hits": 2, "congestion_cache_hits": 2,
"congestion_cache_misses": 412, "congestion_cache_misses": 412,
"congestion_check_calls": 412, "congestion_check_calls": 412,
"congestion_exact_pair_checks": 66, "congestion_exact_pair_checks": 66,
"danger_map_cache_hits": 1386,
"danger_map_cache_misses": 693,
"danger_map_lookup_calls": 2079,
"danger_map_query_calls": 0,
"danger_map_total_ns": 1805113,
"dynamic_grid_rebuilds": 3, "dynamic_grid_rebuilds": 3,
"dynamic_path_objects_added": 37, "dynamic_path_objects_added": 37,
"dynamic_path_objects_removed": 25, "dynamic_path_objects_removed": 25,
@ -231,14 +336,31 @@
"nets_reached_target": 6, "nets_reached_target": 6,
"nets_routed": 6, "nets_routed": 6,
"nodes_expanded": 286, "nodes_expanded": 286,
"path_cost_calls": 2,
"pruned_closed_set": 139, "pruned_closed_set": 139,
"pruned_cost": 505, "pruned_cost": 505,
"pruned_hard_collision": 14, "pruned_hard_collision": 14,
"ray_cast_calls": 1243, "ray_cast_calls": 1243,
"ray_cast_calls_expand_forward": 280,
"ray_cast_calls_expand_snap": 3,
"ray_cast_calls_other": 0,
"ray_cast_calls_straight_static": 951,
"ray_cast_calls_visibility_build": 0,
"ray_cast_calls_visibility_query": 0,
"ray_cast_calls_visibility_tangent": 9,
"ray_cast_candidate_bounds": 45, "ray_cast_candidate_bounds": 45,
"ray_cast_exact_geometry_checks": 43, "ray_cast_exact_geometry_checks": 43,
"refine_path_calls": 3, "refine_path_calls": 3,
"refinement_candidate_side_extents": 0,
"refinement_candidates_accepted": 0,
"refinement_candidates_built": 0,
"refinement_candidates_verified": 0,
"refinement_dynamic_bounds_checked": 0,
"refinement_static_bounds_checked": 0,
"refinement_windows_considered": 0,
"route_iterations": 2, "route_iterations": 2,
"score_component_calls": 1198,
"score_component_total_ns": 4292875,
"static_net_tree_rebuilds": 3, "static_net_tree_rebuilds": 3,
"static_raw_tree_rebuilds": 0, "static_raw_tree_rebuilds": 0,
"static_safe_cache_hits": 3, "static_safe_cache_hits": 3,
@ -248,12 +370,15 @@
"verify_path_report_calls": 12, "verify_path_report_calls": 12,
"verify_static_buffer_ops": 0, "verify_static_buffer_ops": 0,
"visibility_builds": 3, "visibility_builds": 3,
"visibility_corner_hits": 0, "visibility_corner_hits_exact": 0,
"visibility_corner_pairs_checked": 0, "visibility_corner_pairs_checked": 0,
"visibility_corner_queries": 0, "visibility_corner_queries_exact": 0,
"visibility_point_cache_hits": 0, "visibility_point_cache_hits": 0,
"visibility_point_cache_misses": 0, "visibility_point_cache_misses": 0,
"visibility_point_queries": 0, "visibility_point_queries": 0,
"visibility_tangent_candidate_corner_checks": 1483,
"visibility_tangent_candidate_ray_tests": 9,
"visibility_tangent_candidate_scans": 280,
"warm_start_paths_built": 2, "warm_start_paths_built": 2,
"warm_start_paths_used": 2 "warm_start_paths_used": 2
}, },
@ -263,12 +388,17 @@
"valid_results": 3 "valid_results": 3
}, },
{ {
"duration_s": 4.163613382959738, "duration_s": 4.1186372829834,
"metrics": { "metrics": {
"congestion_cache_hits": 0, "congestion_cache_hits": 0,
"congestion_cache_misses": 0, "congestion_cache_misses": 0,
"congestion_check_calls": 0, "congestion_check_calls": 0,
"congestion_exact_pair_checks": 0, "congestion_exact_pair_checks": 0,
"danger_map_cache_hits": 1183,
"danger_map_cache_misses": 731,
"danger_map_lookup_calls": 1914,
"danger_map_query_calls": 731,
"danger_map_total_ns": 18374289,
"dynamic_grid_rebuilds": 0, "dynamic_grid_rebuilds": 0,
"dynamic_path_objects_added": 36, "dynamic_path_objects_added": 36,
"dynamic_path_objects_removed": 18, "dynamic_path_objects_removed": 18,
@ -283,14 +413,31 @@
"nets_reached_target": 3, "nets_reached_target": 3,
"nets_routed": 3, "nets_routed": 3,
"nodes_expanded": 240, "nodes_expanded": 240,
"path_cost_calls": 0,
"pruned_closed_set": 108, "pruned_closed_set": 108,
"pruned_cost": 204, "pruned_cost": 204,
"pruned_hard_collision": 85, "pruned_hard_collision": 85,
"ray_cast_calls": 40530, "ray_cast_calls": 40530,
"ray_cast_calls_expand_forward": 237,
"ray_cast_calls_expand_snap": 3,
"ray_cast_calls_other": 0,
"ray_cast_calls_straight_static": 408,
"ray_cast_calls_visibility_build": 39848,
"ray_cast_calls_visibility_query": 0,
"ray_cast_calls_visibility_tangent": 34,
"ray_cast_candidate_bounds": 121732, "ray_cast_candidate_bounds": 121732,
"ray_cast_exact_geometry_checks": 36858, "ray_cast_exact_geometry_checks": 36858,
"refine_path_calls": 3, "refine_path_calls": 3,
"refinement_candidate_side_extents": 0,
"refinement_candidates_accepted": 0,
"refinement_candidates_built": 0,
"refinement_candidates_verified": 0,
"refinement_dynamic_bounds_checked": 0,
"refinement_static_bounds_checked": 0,
"refinement_windows_considered": 0,
"route_iterations": 3, "route_iterations": 3,
"score_component_calls": 842,
"score_component_total_ns": 20652599,
"static_net_tree_rebuilds": 3, "static_net_tree_rebuilds": 3,
"static_raw_tree_rebuilds": 3, "static_raw_tree_rebuilds": 3,
"static_safe_cache_hits": 141, "static_safe_cache_hits": 141,
@ -300,12 +447,15 @@
"verify_path_report_calls": 9, "verify_path_report_calls": 9,
"verify_static_buffer_ops": 54, "verify_static_buffer_ops": 54,
"visibility_builds": 6, "visibility_builds": 6,
"visibility_corner_hits": 0, "visibility_corner_hits_exact": 0,
"visibility_corner_pairs_checked": 39848, "visibility_corner_pairs_checked": 39848,
"visibility_corner_queries": 0, "visibility_corner_queries_exact": 0,
"visibility_point_cache_hits": 0, "visibility_point_cache_hits": 0,
"visibility_point_cache_misses": 0, "visibility_point_cache_misses": 0,
"visibility_point_queries": 0, "visibility_point_queries": 0,
"visibility_tangent_candidate_corner_checks": 2400,
"visibility_tangent_candidate_ray_tests": 34,
"visibility_tangent_candidate_scans": 237,
"warm_start_paths_built": 3, "warm_start_paths_built": 3,
"warm_start_paths_used": 3 "warm_start_paths_used": 3
}, },
@ -315,12 +465,17 @@
"valid_results": 3 "valid_results": 3
}, },
{ {
"duration_s": 1.375933071016334, "duration_s": 1.373430646955967,
"metrics": { "metrics": {
"congestion_cache_hits": 0, "congestion_cache_hits": 0,
"congestion_cache_misses": 0, "congestion_cache_misses": 0,
"congestion_check_calls": 0, "congestion_check_calls": 0,
"congestion_exact_pair_checks": 0, "congestion_exact_pair_checks": 0,
"danger_map_cache_hits": 233,
"danger_map_cache_misses": 448,
"danger_map_lookup_calls": 681,
"danger_map_query_calls": 448,
"danger_map_total_ns": 10728422,
"dynamic_grid_rebuilds": 0, "dynamic_grid_rebuilds": 0,
"dynamic_path_objects_added": 88, "dynamic_path_objects_added": 88,
"dynamic_path_objects_removed": 44, "dynamic_path_objects_removed": 44,
@ -335,14 +490,31 @@
"nets_reached_target": 10, "nets_reached_target": 10,
"nets_routed": 10, "nets_routed": 10,
"nodes_expanded": 78, "nodes_expanded": 78,
"path_cost_calls": 0,
"pruned_closed_set": 20, "pruned_closed_set": 20,
"pruned_cost": 64, "pruned_cost": 64,
"pruned_hard_collision": 61, "pruned_hard_collision": 61,
"ray_cast_calls": 11151, "ray_cast_calls": 11151,
"ray_cast_calls_expand_forward": 68,
"ray_cast_calls_expand_snap": 6,
"ray_cast_calls_other": 0,
"ray_cast_calls_straight_static": 232,
"ray_cast_calls_visibility_build": 10768,
"ray_cast_calls_visibility_query": 0,
"ray_cast_calls_visibility_tangent": 77,
"ray_cast_candidate_bounds": 21198, "ray_cast_candidate_bounds": 21198,
"ray_cast_exact_geometry_checks": 11651, "ray_cast_exact_geometry_checks": 11651,
"refine_path_calls": 10, "refine_path_calls": 10,
"refinement_candidate_side_extents": 0,
"refinement_candidates_accepted": 0,
"refinement_candidates_built": 0,
"refinement_candidates_verified": 0,
"refinement_dynamic_bounds_checked": 0,
"refinement_static_bounds_checked": 0,
"refinement_windows_considered": 0,
"route_iterations": 1, "route_iterations": 1,
"score_component_calls": 291,
"score_component_total_ns": 11574800,
"static_net_tree_rebuilds": 10, "static_net_tree_rebuilds": 10,
"static_raw_tree_rebuilds": 1, "static_raw_tree_rebuilds": 1,
"static_safe_cache_hits": 6, "static_safe_cache_hits": 6,
@ -352,12 +524,15 @@
"verify_path_report_calls": 30, "verify_path_report_calls": 30,
"verify_static_buffer_ops": 132, "verify_static_buffer_ops": 132,
"visibility_builds": 11, "visibility_builds": 11,
"visibility_corner_hits": 0, "visibility_corner_hits_exact": 0,
"visibility_corner_pairs_checked": 10768, "visibility_corner_pairs_checked": 10768,
"visibility_corner_queries": 0, "visibility_corner_queries_exact": 0,
"visibility_point_cache_hits": 0, "visibility_point_cache_hits": 0,
"visibility_point_cache_misses": 0, "visibility_point_cache_misses": 0,
"visibility_point_queries": 0, "visibility_point_queries": 0,
"visibility_tangent_candidate_corner_checks": 34735,
"visibility_tangent_candidate_ray_tests": 77,
"visibility_tangent_candidate_scans": 68,
"warm_start_paths_built": 10, "warm_start_paths_built": 10,
"warm_start_paths_used": 10 "warm_start_paths_used": 10
}, },
@ -367,12 +542,17 @@
"valid_results": 10 "valid_results": 10
}, },
{ {
"duration_s": 0.2436628290452063, "duration_s": 0.2410298540489748,
"metrics": { "metrics": {
"congestion_cache_hits": 0, "congestion_cache_hits": 0,
"congestion_cache_misses": 0, "congestion_cache_misses": 0,
"congestion_check_calls": 0, "congestion_check_calls": 0,
"congestion_exact_pair_checks": 0, "congestion_exact_pair_checks": 0,
"danger_map_cache_hits": 58,
"danger_map_cache_misses": 110,
"danger_map_lookup_calls": 168,
"danger_map_query_calls": 0,
"danger_map_total_ns": 178104,
"dynamic_grid_rebuilds": 0, "dynamic_grid_rebuilds": 0,
"dynamic_path_objects_added": 12, "dynamic_path_objects_added": 12,
"dynamic_path_objects_removed": 6, "dynamic_path_objects_removed": 6,
@ -387,14 +567,31 @@
"nets_reached_target": 2, "nets_reached_target": 2,
"nets_routed": 2, "nets_routed": 2,
"nodes_expanded": 18, "nodes_expanded": 18,
"path_cost_calls": 0,
"pruned_closed_set": 6, "pruned_closed_set": 6,
"pruned_cost": 16, "pruned_cost": 16,
"pruned_hard_collision": 0, "pruned_hard_collision": 0,
"ray_cast_calls": 2308, "ray_cast_calls": 2308,
"ray_cast_calls_expand_forward": 16,
"ray_cast_calls_expand_snap": 2,
"ray_cast_calls_other": 0,
"ray_cast_calls_straight_static": 38,
"ray_cast_calls_visibility_build": 2252,
"ray_cast_calls_visibility_query": 0,
"ray_cast_calls_visibility_tangent": 0,
"ray_cast_candidate_bounds": 3802, "ray_cast_candidate_bounds": 3802,
"ray_cast_exact_geometry_checks": 1904, "ray_cast_exact_geometry_checks": 1904,
"refine_path_calls": 2, "refine_path_calls": 2,
"refinement_candidate_side_extents": 0,
"refinement_candidates_accepted": 0,
"refinement_candidates_built": 0,
"refinement_candidates_verified": 0,
"refinement_dynamic_bounds_checked": 0,
"refinement_static_bounds_checked": 0,
"refinement_windows_considered": 0,
"route_iterations": 2, "route_iterations": 2,
"score_component_calls": 72,
"score_component_total_ns": 352865,
"static_net_tree_rebuilds": 2, "static_net_tree_rebuilds": 2,
"static_raw_tree_rebuilds": 0, "static_raw_tree_rebuilds": 0,
"static_safe_cache_hits": 2, "static_safe_cache_hits": 2,
@ -404,12 +601,15 @@
"verify_path_report_calls": 6, "verify_path_report_calls": 6,
"verify_static_buffer_ops": 0, "verify_static_buffer_ops": 0,
"visibility_builds": 4, "visibility_builds": 4,
"visibility_corner_hits": 0, "visibility_corner_hits_exact": 0,
"visibility_corner_pairs_checked": 2252, "visibility_corner_pairs_checked": 2252,
"visibility_corner_queries": 0, "visibility_corner_queries_exact": 0,
"visibility_point_cache_hits": 0, "visibility_point_cache_hits": 0,
"visibility_point_cache_misses": 0, "visibility_point_cache_misses": 0,
"visibility_point_queries": 0, "visibility_point_queries": 0,
"visibility_tangent_candidate_corner_checks": 0,
"visibility_tangent_candidate_ray_tests": 0,
"visibility_tangent_candidate_scans": 16,
"warm_start_paths_built": 2, "warm_start_paths_built": 2,
"warm_start_paths_used": 2 "warm_start_paths_used": 2
}, },
@ -419,12 +619,17 @@
"valid_results": 2 "valid_results": 2
}, },
{ {
"duration_s": 0.0052433289820328355, "duration_s": 0.0052388140466064215,
"metrics": { "metrics": {
"congestion_cache_hits": 0, "congestion_cache_hits": 0,
"congestion_cache_misses": 0, "congestion_cache_misses": 0,
"congestion_check_calls": 0, "congestion_check_calls": 0,
"congestion_exact_pair_checks": 0, "congestion_exact_pair_checks": 0,
"danger_map_cache_hits": 10,
"danger_map_cache_misses": 20,
"danger_map_lookup_calls": 30,
"danger_map_query_calls": 20,
"danger_map_total_ns": 502052,
"dynamic_grid_rebuilds": 0, "dynamic_grid_rebuilds": 0,
"dynamic_path_objects_added": 1, "dynamic_path_objects_added": 1,
"dynamic_path_objects_removed": 0, "dynamic_path_objects_removed": 0,
@ -439,14 +644,31 @@
"nets_reached_target": 0, "nets_reached_target": 0,
"nets_routed": 1, "nets_routed": 1,
"nodes_expanded": 3, "nodes_expanded": 3,
"path_cost_calls": 0,
"pruned_closed_set": 0, "pruned_closed_set": 0,
"pruned_cost": 4, "pruned_cost": 4,
"pruned_hard_collision": 2, "pruned_hard_collision": 2,
"ray_cast_calls": 13, "ray_cast_calls": 13,
"ray_cast_calls_expand_forward": 3,
"ray_cast_calls_expand_snap": 0,
"ray_cast_calls_other": 0,
"ray_cast_calls_straight_static": 10,
"ray_cast_calls_visibility_build": 0,
"ray_cast_calls_visibility_query": 0,
"ray_cast_calls_visibility_tangent": 0,
"ray_cast_candidate_bounds": 5, "ray_cast_candidate_bounds": 5,
"ray_cast_exact_geometry_checks": 0, "ray_cast_exact_geometry_checks": 0,
"refine_path_calls": 0, "refine_path_calls": 0,
"refinement_candidate_side_extents": 0,
"refinement_candidates_accepted": 0,
"refinement_candidates_built": 0,
"refinement_candidates_verified": 0,
"refinement_dynamic_bounds_checked": 0,
"refinement_static_bounds_checked": 0,
"refinement_windows_considered": 0,
"route_iterations": 1, "route_iterations": 1,
"score_component_calls": 14,
"score_component_total_ns": 538947,
"static_net_tree_rebuilds": 1, "static_net_tree_rebuilds": 1,
"static_raw_tree_rebuilds": 1, "static_raw_tree_rebuilds": 1,
"static_safe_cache_hits": 0, "static_safe_cache_hits": 0,
@ -456,12 +678,15 @@
"verify_path_report_calls": 1, "verify_path_report_calls": 1,
"verify_static_buffer_ops": 1, "verify_static_buffer_ops": 1,
"visibility_builds": 0, "visibility_builds": 0,
"visibility_corner_hits": 0, "visibility_corner_hits_exact": 0,
"visibility_corner_pairs_checked": 0, "visibility_corner_pairs_checked": 0,
"visibility_corner_queries": 0, "visibility_corner_queries_exact": 0,
"visibility_point_cache_hits": 0, "visibility_point_cache_hits": 0,
"visibility_point_cache_misses": 0, "visibility_point_cache_misses": 0,
"visibility_point_queries": 0, "visibility_point_queries": 0,
"visibility_tangent_candidate_corner_checks": 10,
"visibility_tangent_candidate_ray_tests": 0,
"visibility_tangent_candidate_scans": 3,
"warm_start_paths_built": 0, "warm_start_paths_built": 0,
"warm_start_paths_used": 0 "warm_start_paths_used": 0
}, },

View file

@ -103,7 +103,13 @@ class RoutingWorld:
self._dynamic_paths.remove_path(net_id) self._dynamic_paths.remove_path(net_id)
def check_move_straight_static(self, start_port: Port, length: float, net_width: float) -> bool: def check_move_straight_static(self, start_port: Port, length: float, net_width: float) -> bool:
reach = self.ray_cast(start_port, start_port.r, max_dist=length + 0.01, net_width=net_width) reach = self.ray_cast(
start_port,
start_port.r,
max_dist=length + 0.01,
net_width=net_width,
caller="straight_static",
)
return reach < length - 0.001 return reach < length - 0.001
def _is_in_safety_zone_fast(self, idx: int, start_port: Port | None, end_port: Port | None) -> bool: def _is_in_safety_zone_fast(self, idx: int, start_port: Port | None, end_port: Port | None) -> bool:
@ -393,9 +399,24 @@ class RoutingWorld:
angle_deg: float, angle_deg: float,
max_dist: float = 2000.0, max_dist: float = 2000.0,
net_width: float | None = None, net_width: float | None = None,
caller: str = "other",
) -> float: ) -> float:
if self.metrics is not None: if self.metrics is not None:
self.metrics.total_ray_cast_calls += 1 self.metrics.total_ray_cast_calls += 1
if caller == "straight_static":
self.metrics.total_ray_cast_calls_straight_static += 1
elif caller == "expand_snap":
self.metrics.total_ray_cast_calls_expand_snap += 1
elif caller == "expand_forward":
self.metrics.total_ray_cast_calls_expand_forward += 1
elif caller == "visibility_build":
self.metrics.total_ray_cast_calls_visibility_build += 1
elif caller == "visibility_query":
self.metrics.total_ray_cast_calls_visibility_query += 1
elif caller == "visibility_tangent":
self.metrics.total_ray_cast_calls_visibility_tangent += 1
else:
self.metrics.total_ray_cast_calls_other += 1
static_obstacles = self._static_obstacles static_obstacles = self._static_obstacles
tree: STRtree | None tree: STRtree | None
is_rect_array: numpy.ndarray | None is_rect_array: numpy.ndarray | None

View file

@ -45,6 +45,14 @@ class RouteMetrics:
warm_start_paths_used: int warm_start_paths_used: int
refine_path_calls: int refine_path_calls: int
timeout_events: int timeout_events: int
score_component_calls: int
score_component_total_ns: int
path_cost_calls: int
danger_map_lookup_calls: int
danger_map_cache_hits: int
danger_map_cache_misses: int
danger_map_query_calls: int
danger_map_total_ns: int
move_cache_abs_hits: int move_cache_abs_hits: int
move_cache_abs_misses: int move_cache_abs_misses: int
move_cache_rel_hits: int move_cache_rel_hits: int
@ -62,12 +70,22 @@ class RouteMetrics:
static_net_tree_rebuilds: int static_net_tree_rebuilds: int
visibility_builds: int visibility_builds: int
visibility_corner_pairs_checked: int visibility_corner_pairs_checked: int
visibility_corner_queries: int visibility_corner_queries_exact: int
visibility_corner_hits: int visibility_corner_hits_exact: int
visibility_point_queries: int visibility_point_queries: int
visibility_point_cache_hits: int visibility_point_cache_hits: int
visibility_point_cache_misses: int visibility_point_cache_misses: int
visibility_tangent_candidate_scans: int
visibility_tangent_candidate_corner_checks: int
visibility_tangent_candidate_ray_tests: int
ray_cast_calls: int ray_cast_calls: int
ray_cast_calls_straight_static: int
ray_cast_calls_expand_snap: int
ray_cast_calls_expand_forward: int
ray_cast_calls_visibility_build: int
ray_cast_calls_visibility_query: int
ray_cast_calls_visibility_tangent: int
ray_cast_calls_other: int
ray_cast_candidate_bounds: int ray_cast_candidate_bounds: int
ray_cast_exact_geometry_checks: int ray_cast_exact_geometry_checks: int
congestion_check_calls: int congestion_check_calls: int
@ -75,6 +93,13 @@ class RouteMetrics:
verify_path_report_calls: int verify_path_report_calls: int
verify_static_buffer_ops: int verify_static_buffer_ops: int
verify_dynamic_exact_pair_checks: int verify_dynamic_exact_pair_checks: int
refinement_windows_considered: int
refinement_static_bounds_checked: int
refinement_dynamic_bounds_checked: int
refinement_candidate_side_extents: int
refinement_candidates_built: int
refinement_candidates_verified: int
refinement_candidates_accepted: int
@dataclass(frozen=True, slots=True) @dataclass(frozen=True, slots=True)

View file

@ -64,6 +64,7 @@ def _visible_straight_candidates(
visibility_manager = context.visibility_manager visibility_manager = context.visibility_manager
visibility_manager._ensure_current() visibility_manager._ensure_current()
context.metrics.total_visibility_tangent_candidate_scans += 1
max_bend_radius = max(search_options.bend_radii, default=0.0) max_bend_radius = max(search_options.bend_radii, default=0.0)
if max_bend_radius <= 0 or not visibility_manager.corners: if max_bend_radius <= 0 or not visibility_manager.corners:
return [] return []
@ -76,6 +77,7 @@ def _visible_straight_candidates(
scored: list[tuple[float, float, float, float, float]] = [] scored: list[tuple[float, float, float, float, float]] = []
for idx in candidate_ids: for idx in candidate_ids:
context.metrics.total_visibility_tangent_candidate_corner_checks += 1
cx, cy = visibility_manager.corners[idx] cx, cy = visibility_manager.corners[idx]
dx = cx - current.x dx = cx - current.x
dy = cy - current.y dy = cy - current.y
@ -101,8 +103,15 @@ def _visible_straight_candidates(
collision_engine = context.cost_evaluator.collision_engine collision_engine = context.cost_evaluator.collision_engine
tangent_candidates: set[int] = set() tangent_candidates: set[int] = set()
for _, dist, length, dx, dy in sorted(scored)[:4]: for _, dist, length, dx, dy in sorted(scored)[:4]:
context.metrics.total_visibility_tangent_candidate_ray_tests += 1
angle = math.degrees(math.atan2(dy, dx)) angle = math.degrees(math.atan2(dy, dx))
corner_reach = collision_engine.ray_cast(current, angle, max_dist=dist + 0.05, net_width=net_width) corner_reach = collision_engine.ray_cast(
current,
angle,
max_dist=dist + 0.05,
net_width=net_width,
caller="visibility_tangent",
)
if corner_reach < dist - 0.01: if corner_reach < dist - 0.01:
continue continue
qlen = int(round(length)) qlen = int(round(length))
@ -156,7 +165,13 @@ def expand_moves(
dy_local = perp_t dy_local = perp_t
if proj_t > 0 and abs(perp_t) < 1e-6 and cp.r == target.r: if proj_t > 0 and abs(perp_t) < 1e-6 and cp.r == target.r:
max_reach = context.cost_evaluator.collision_engine.ray_cast(cp, cp.r, proj_t + 1.0, net_width=net_width) max_reach = context.cost_evaluator.collision_engine.ray_cast(
cp,
cp.r,
proj_t + 1.0,
net_width=net_width,
caller="expand_snap",
)
if max_reach >= proj_t - 0.01 and ( if max_reach >= proj_t - 0.01 and (
prev_straight_length is None or proj_t < prev_straight_length - TOLERANCE_LINEAR prev_straight_length is None or proj_t < prev_straight_length - TOLERANCE_LINEAR
): ):
@ -175,7 +190,13 @@ def expand_moves(
(int(round(proj_t)),), (int(round(proj_t)),),
) )
max_reach = context.cost_evaluator.collision_engine.ray_cast(cp, cp.r, search_options.max_straight_length, net_width=net_width) max_reach = context.cost_evaluator.collision_engine.ray_cast(
cp,
cp.r,
search_options.max_straight_length,
net_width=net_width,
caller="expand_forward",
)
candidate_lengths = [ candidate_lengths = [
search_options.min_straight_length, search_options.min_straight_length,
max_reach, max_reach,

View file

@ -94,6 +94,14 @@ class AStarMetrics:
"total_warm_start_paths_used", "total_warm_start_paths_used",
"total_refine_path_calls", "total_refine_path_calls",
"total_timeout_events", "total_timeout_events",
"total_score_component_calls",
"total_score_component_total_ns",
"total_path_cost_calls",
"total_danger_map_lookup_calls",
"total_danger_map_cache_hits",
"total_danger_map_cache_misses",
"total_danger_map_query_calls",
"total_danger_map_total_ns",
"total_move_cache_abs_hits", "total_move_cache_abs_hits",
"total_move_cache_abs_misses", "total_move_cache_abs_misses",
"total_move_cache_rel_hits", "total_move_cache_rel_hits",
@ -111,12 +119,22 @@ class AStarMetrics:
"total_static_net_tree_rebuilds", "total_static_net_tree_rebuilds",
"total_visibility_builds", "total_visibility_builds",
"total_visibility_corner_pairs_checked", "total_visibility_corner_pairs_checked",
"total_visibility_corner_queries", "total_visibility_corner_queries_exact",
"total_visibility_corner_hits", "total_visibility_corner_hits_exact",
"total_visibility_point_queries", "total_visibility_point_queries",
"total_visibility_point_cache_hits", "total_visibility_point_cache_hits",
"total_visibility_point_cache_misses", "total_visibility_point_cache_misses",
"total_visibility_tangent_candidate_scans",
"total_visibility_tangent_candidate_corner_checks",
"total_visibility_tangent_candidate_ray_tests",
"total_ray_cast_calls", "total_ray_cast_calls",
"total_ray_cast_calls_straight_static",
"total_ray_cast_calls_expand_snap",
"total_ray_cast_calls_expand_forward",
"total_ray_cast_calls_visibility_build",
"total_ray_cast_calls_visibility_query",
"total_ray_cast_calls_visibility_tangent",
"total_ray_cast_calls_other",
"total_ray_cast_candidate_bounds", "total_ray_cast_candidate_bounds",
"total_ray_cast_exact_geometry_checks", "total_ray_cast_exact_geometry_checks",
"total_congestion_check_calls", "total_congestion_check_calls",
@ -124,6 +142,13 @@ class AStarMetrics:
"total_verify_path_report_calls", "total_verify_path_report_calls",
"total_verify_static_buffer_ops", "total_verify_static_buffer_ops",
"total_verify_dynamic_exact_pair_checks", "total_verify_dynamic_exact_pair_checks",
"total_refinement_windows_considered",
"total_refinement_static_bounds_checked",
"total_refinement_dynamic_bounds_checked",
"total_refinement_candidate_side_extents",
"total_refinement_candidates_built",
"total_refinement_candidates_verified",
"total_refinement_candidates_accepted",
"last_expanded_nodes", "last_expanded_nodes",
"nodes_expanded", "nodes_expanded",
"moves_generated", "moves_generated",
@ -147,6 +172,14 @@ class AStarMetrics:
self.total_warm_start_paths_used = 0 self.total_warm_start_paths_used = 0
self.total_refine_path_calls = 0 self.total_refine_path_calls = 0
self.total_timeout_events = 0 self.total_timeout_events = 0
self.total_score_component_calls = 0
self.total_score_component_total_ns = 0
self.total_path_cost_calls = 0
self.total_danger_map_lookup_calls = 0
self.total_danger_map_cache_hits = 0
self.total_danger_map_cache_misses = 0
self.total_danger_map_query_calls = 0
self.total_danger_map_total_ns = 0
self.total_move_cache_abs_hits = 0 self.total_move_cache_abs_hits = 0
self.total_move_cache_abs_misses = 0 self.total_move_cache_abs_misses = 0
self.total_move_cache_rel_hits = 0 self.total_move_cache_rel_hits = 0
@ -164,12 +197,22 @@ class AStarMetrics:
self.total_static_net_tree_rebuilds = 0 self.total_static_net_tree_rebuilds = 0
self.total_visibility_builds = 0 self.total_visibility_builds = 0
self.total_visibility_corner_pairs_checked = 0 self.total_visibility_corner_pairs_checked = 0
self.total_visibility_corner_queries = 0 self.total_visibility_corner_queries_exact = 0
self.total_visibility_corner_hits = 0 self.total_visibility_corner_hits_exact = 0
self.total_visibility_point_queries = 0 self.total_visibility_point_queries = 0
self.total_visibility_point_cache_hits = 0 self.total_visibility_point_cache_hits = 0
self.total_visibility_point_cache_misses = 0 self.total_visibility_point_cache_misses = 0
self.total_visibility_tangent_candidate_scans = 0
self.total_visibility_tangent_candidate_corner_checks = 0
self.total_visibility_tangent_candidate_ray_tests = 0
self.total_ray_cast_calls = 0 self.total_ray_cast_calls = 0
self.total_ray_cast_calls_straight_static = 0
self.total_ray_cast_calls_expand_snap = 0
self.total_ray_cast_calls_expand_forward = 0
self.total_ray_cast_calls_visibility_build = 0
self.total_ray_cast_calls_visibility_query = 0
self.total_ray_cast_calls_visibility_tangent = 0
self.total_ray_cast_calls_other = 0
self.total_ray_cast_candidate_bounds = 0 self.total_ray_cast_candidate_bounds = 0
self.total_ray_cast_exact_geometry_checks = 0 self.total_ray_cast_exact_geometry_checks = 0
self.total_congestion_check_calls = 0 self.total_congestion_check_calls = 0
@ -177,6 +220,13 @@ class AStarMetrics:
self.total_verify_path_report_calls = 0 self.total_verify_path_report_calls = 0
self.total_verify_static_buffer_ops = 0 self.total_verify_static_buffer_ops = 0
self.total_verify_dynamic_exact_pair_checks = 0 self.total_verify_dynamic_exact_pair_checks = 0
self.total_refinement_windows_considered = 0
self.total_refinement_static_bounds_checked = 0
self.total_refinement_dynamic_bounds_checked = 0
self.total_refinement_candidate_side_extents = 0
self.total_refinement_candidates_built = 0
self.total_refinement_candidates_verified = 0
self.total_refinement_candidates_accepted = 0
self.last_expanded_nodes: list[tuple[int, int, int]] = [] self.last_expanded_nodes: list[tuple[int, int, int]] = []
self.nodes_expanded = 0 self.nodes_expanded = 0
self.moves_generated = 0 self.moves_generated = 0
@ -199,6 +249,14 @@ class AStarMetrics:
self.total_warm_start_paths_used = 0 self.total_warm_start_paths_used = 0
self.total_refine_path_calls = 0 self.total_refine_path_calls = 0
self.total_timeout_events = 0 self.total_timeout_events = 0
self.total_score_component_calls = 0
self.total_score_component_total_ns = 0
self.total_path_cost_calls = 0
self.total_danger_map_lookup_calls = 0
self.total_danger_map_cache_hits = 0
self.total_danger_map_cache_misses = 0
self.total_danger_map_query_calls = 0
self.total_danger_map_total_ns = 0
self.total_move_cache_abs_hits = 0 self.total_move_cache_abs_hits = 0
self.total_move_cache_abs_misses = 0 self.total_move_cache_abs_misses = 0
self.total_move_cache_rel_hits = 0 self.total_move_cache_rel_hits = 0
@ -216,12 +274,22 @@ class AStarMetrics:
self.total_static_net_tree_rebuilds = 0 self.total_static_net_tree_rebuilds = 0
self.total_visibility_builds = 0 self.total_visibility_builds = 0
self.total_visibility_corner_pairs_checked = 0 self.total_visibility_corner_pairs_checked = 0
self.total_visibility_corner_queries = 0 self.total_visibility_corner_queries_exact = 0
self.total_visibility_corner_hits = 0 self.total_visibility_corner_hits_exact = 0
self.total_visibility_point_queries = 0 self.total_visibility_point_queries = 0
self.total_visibility_point_cache_hits = 0 self.total_visibility_point_cache_hits = 0
self.total_visibility_point_cache_misses = 0 self.total_visibility_point_cache_misses = 0
self.total_visibility_tangent_candidate_scans = 0
self.total_visibility_tangent_candidate_corner_checks = 0
self.total_visibility_tangent_candidate_ray_tests = 0
self.total_ray_cast_calls = 0 self.total_ray_cast_calls = 0
self.total_ray_cast_calls_straight_static = 0
self.total_ray_cast_calls_expand_snap = 0
self.total_ray_cast_calls_expand_forward = 0
self.total_ray_cast_calls_visibility_build = 0
self.total_ray_cast_calls_visibility_query = 0
self.total_ray_cast_calls_visibility_tangent = 0
self.total_ray_cast_calls_other = 0
self.total_ray_cast_candidate_bounds = 0 self.total_ray_cast_candidate_bounds = 0
self.total_ray_cast_exact_geometry_checks = 0 self.total_ray_cast_exact_geometry_checks = 0
self.total_congestion_check_calls = 0 self.total_congestion_check_calls = 0
@ -229,6 +297,13 @@ class AStarMetrics:
self.total_verify_path_report_calls = 0 self.total_verify_path_report_calls = 0
self.total_verify_static_buffer_ops = 0 self.total_verify_static_buffer_ops = 0
self.total_verify_dynamic_exact_pair_checks = 0 self.total_verify_dynamic_exact_pair_checks = 0
self.total_refinement_windows_considered = 0
self.total_refinement_static_bounds_checked = 0
self.total_refinement_dynamic_bounds_checked = 0
self.total_refinement_candidate_side_extents = 0
self.total_refinement_candidates_built = 0
self.total_refinement_candidates_verified = 0
self.total_refinement_candidates_accepted = 0
def reset_per_route(self) -> None: def reset_per_route(self) -> None:
self.nodes_expanded = 0 self.nodes_expanded = 0
@ -254,6 +329,14 @@ class AStarMetrics:
warm_start_paths_used=self.total_warm_start_paths_used, warm_start_paths_used=self.total_warm_start_paths_used,
refine_path_calls=self.total_refine_path_calls, refine_path_calls=self.total_refine_path_calls,
timeout_events=self.total_timeout_events, timeout_events=self.total_timeout_events,
score_component_calls=self.total_score_component_calls,
score_component_total_ns=self.total_score_component_total_ns,
path_cost_calls=self.total_path_cost_calls,
danger_map_lookup_calls=self.total_danger_map_lookup_calls,
danger_map_cache_hits=self.total_danger_map_cache_hits,
danger_map_cache_misses=self.total_danger_map_cache_misses,
danger_map_query_calls=self.total_danger_map_query_calls,
danger_map_total_ns=self.total_danger_map_total_ns,
move_cache_abs_hits=self.total_move_cache_abs_hits, move_cache_abs_hits=self.total_move_cache_abs_hits,
move_cache_abs_misses=self.total_move_cache_abs_misses, move_cache_abs_misses=self.total_move_cache_abs_misses,
move_cache_rel_hits=self.total_move_cache_rel_hits, move_cache_rel_hits=self.total_move_cache_rel_hits,
@ -271,12 +354,22 @@ class AStarMetrics:
static_net_tree_rebuilds=self.total_static_net_tree_rebuilds, static_net_tree_rebuilds=self.total_static_net_tree_rebuilds,
visibility_builds=self.total_visibility_builds, visibility_builds=self.total_visibility_builds,
visibility_corner_pairs_checked=self.total_visibility_corner_pairs_checked, visibility_corner_pairs_checked=self.total_visibility_corner_pairs_checked,
visibility_corner_queries=self.total_visibility_corner_queries, visibility_corner_queries_exact=self.total_visibility_corner_queries_exact,
visibility_corner_hits=self.total_visibility_corner_hits, visibility_corner_hits_exact=self.total_visibility_corner_hits_exact,
visibility_point_queries=self.total_visibility_point_queries, visibility_point_queries=self.total_visibility_point_queries,
visibility_point_cache_hits=self.total_visibility_point_cache_hits, visibility_point_cache_hits=self.total_visibility_point_cache_hits,
visibility_point_cache_misses=self.total_visibility_point_cache_misses, visibility_point_cache_misses=self.total_visibility_point_cache_misses,
visibility_tangent_candidate_scans=self.total_visibility_tangent_candidate_scans,
visibility_tangent_candidate_corner_checks=self.total_visibility_tangent_candidate_corner_checks,
visibility_tangent_candidate_ray_tests=self.total_visibility_tangent_candidate_ray_tests,
ray_cast_calls=self.total_ray_cast_calls, ray_cast_calls=self.total_ray_cast_calls,
ray_cast_calls_straight_static=self.total_ray_cast_calls_straight_static,
ray_cast_calls_expand_snap=self.total_ray_cast_calls_expand_snap,
ray_cast_calls_expand_forward=self.total_ray_cast_calls_expand_forward,
ray_cast_calls_visibility_build=self.total_ray_cast_calls_visibility_build,
ray_cast_calls_visibility_query=self.total_ray_cast_calls_visibility_query,
ray_cast_calls_visibility_tangent=self.total_ray_cast_calls_visibility_tangent,
ray_cast_calls_other=self.total_ray_cast_calls_other,
ray_cast_candidate_bounds=self.total_ray_cast_candidate_bounds, ray_cast_candidate_bounds=self.total_ray_cast_candidate_bounds,
ray_cast_exact_geometry_checks=self.total_ray_cast_exact_geometry_checks, ray_cast_exact_geometry_checks=self.total_ray_cast_exact_geometry_checks,
congestion_check_calls=self.total_congestion_check_calls, congestion_check_calls=self.total_congestion_check_calls,
@ -284,6 +377,13 @@ class AStarMetrics:
verify_path_report_calls=self.total_verify_path_report_calls, verify_path_report_calls=self.total_verify_path_report_calls,
verify_static_buffer_ops=self.total_verify_static_buffer_ops, verify_static_buffer_ops=self.total_verify_static_buffer_ops,
verify_dynamic_exact_pair_checks=self.total_verify_dynamic_exact_pair_checks, verify_dynamic_exact_pair_checks=self.total_verify_dynamic_exact_pair_checks,
refinement_windows_considered=self.total_refinement_windows_considered,
refinement_static_bounds_checked=self.total_refinement_static_bounds_checked,
refinement_dynamic_bounds_checked=self.total_refinement_dynamic_bounds_checked,
refinement_candidate_side_extents=self.total_refinement_candidate_side_extents,
refinement_candidates_built=self.total_refinement_candidates_built,
refinement_candidates_verified=self.total_refinement_candidates_verified,
refinement_candidates_accepted=self.total_refinement_candidates_accepted,
) )

View file

@ -48,6 +48,8 @@ class PathFinder:
self.metrics = self.context.metrics if metrics is None else metrics self.metrics = self.context.metrics if metrics is None else metrics
self.context.metrics = self.metrics self.context.metrics = self.metrics
self.context.cost_evaluator.collision_engine.metrics = self.metrics self.context.cost_evaluator.collision_engine.metrics = self.metrics
if self.context.cost_evaluator.danger_map is not None:
self.context.cost_evaluator.danger_map.metrics = self.metrics
self.refiner = PathRefiner(self.context) self.refiner = PathRefiner(self.context)
self.accumulated_expanded_nodes: list[tuple[int, int, int]] = [] self.accumulated_expanded_nodes: list[tuple[int, int, int]] = []

View file

@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
from time import perf_counter_ns
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import numpy as np import numpy as np
@ -130,10 +131,16 @@ class CostEvaluator:
start_port: Port | None = None, start_port: Port | None = None,
weights: ObjectiveWeights | None = None, weights: ObjectiveWeights | None = None,
) -> float: ) -> float:
metrics = self.collision_engine.metrics
if metrics is not None:
metrics.total_score_component_calls += 1
start_ns = perf_counter_ns()
active_weights = self._resolve_weights(weights) active_weights = self._resolve_weights(weights)
danger_map = self.danger_map danger_map = self.danger_map
end_port = component.end_port end_port = component.end_port
if danger_map is not None and not danger_map.is_within_bounds(end_port.x, end_port.y): if danger_map is not None and not danger_map.is_within_bounds(end_port.x, end_port.y):
if metrics is not None:
metrics.total_score_component_total_ns += perf_counter_ns() - start_ns
return 1e15 return 1e15
move_radius = None move_radius = None
@ -155,6 +162,8 @@ class CostEvaluator:
total_cost += component.length * active_weights.danger_weight * (cost_s + cost_m + cost_e) / 3.0 total_cost += component.length * active_weights.danger_weight * (cost_s + cost_m + cost_e) / 3.0
else: else:
total_cost += component.length * active_weights.danger_weight * cost_e total_cost += component.length * active_weights.danger_weight * cost_e
if metrics is not None:
metrics.total_score_component_total_ns += perf_counter_ns() - start_ns
return total_cost return total_cost
def component_penalty( def component_penalty(
@ -181,6 +190,9 @@ class CostEvaluator:
*, *,
weights: ObjectiveWeights | None = None, weights: ObjectiveWeights | None = None,
) -> float: ) -> float:
metrics = self.collision_engine.metrics
if metrics is not None:
metrics.total_path_cost_calls += 1
active_weights = self._resolve_weights(weights) active_weights = self._resolve_weights(weights)
total = 0.0 total = 0.0
current_port = start_port current_port = start_port

View file

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
from collections import OrderedDict from collections import OrderedDict
from time import perf_counter_ns
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import numpy import numpy
@ -8,6 +9,7 @@ from scipy.spatial import cKDTree
if TYPE_CHECKING: if TYPE_CHECKING:
from shapely.geometry import Polygon from shapely.geometry import Polygon
from inire.router._astar_types import AStarMetrics
_COST_CACHE_SIZE = 100000 _COST_CACHE_SIZE = 100000
@ -18,7 +20,7 @@ class DangerMap:
A proximity cost evaluator using a KD-Tree of obstacle boundary points. A proximity cost evaluator using a KD-Tree of obstacle boundary points.
Scales with obstacle perimeter rather than design area. Scales with obstacle perimeter rather than design area.
""" """
__slots__ = ('minx', 'miny', 'maxx', 'maxy', 'resolution', 'safety_threshold', 'k', 'tree', '_cost_cache') __slots__ = ('minx', 'miny', 'maxx', 'maxy', 'resolution', 'safety_threshold', 'k', 'tree', '_cost_cache', 'metrics')
def __init__( def __init__(
self, self,
@ -42,6 +44,7 @@ class DangerMap:
self.k = k self.k = k
self.tree: cKDTree | None = None self.tree: cKDTree | None = None
self._cost_cache: OrderedDict[tuple[int, int], float] = OrderedDict() self._cost_cache: OrderedDict[tuple[int, int], float] = OrderedDict()
self.metrics: AStarMetrics | None = None
def precompute(self, obstacles: list[Polygon]) -> None: def precompute(self, obstacles: list[Polygon]) -> None:
""" """
@ -82,17 +85,28 @@ class DangerMap:
Get the proximity cost at a specific coordinate using the KD-Tree. Get the proximity cost at a specific coordinate using the KD-Tree.
Coordinates are quantized to 1nm to improve cache performance. Coordinates are quantized to 1nm to improve cache performance.
""" """
metrics = self.metrics
if metrics is not None:
metrics.total_danger_map_lookup_calls += 1
start_ns = perf_counter_ns()
qx_milli = int(round(x * 1000)) qx_milli = int(round(x * 1000))
qy_milli = int(round(y * 1000)) qy_milli = int(round(y * 1000))
key = (qx_milli, qy_milli) key = (qx_milli, qy_milli)
if key in self._cost_cache: if key in self._cost_cache:
if metrics is not None:
metrics.total_danger_map_cache_hits += 1
metrics.total_danger_map_total_ns += perf_counter_ns() - start_ns
self._cost_cache.move_to_end(key) self._cost_cache.move_to_end(key)
return self._cost_cache[key] return self._cost_cache[key]
if metrics is not None:
metrics.total_danger_map_cache_misses += 1
cost = self._compute_cost_quantized(qx_milli, qy_milli) cost = self._compute_cost_quantized(qx_milli, qy_milli)
self._cost_cache[key] = cost self._cost_cache[key] = cost
if len(self._cost_cache) > _COST_CACHE_SIZE: if len(self._cost_cache) > _COST_CACHE_SIZE:
self._cost_cache.popitem(last=False) self._cost_cache.popitem(last=False)
if metrics is not None:
metrics.total_danger_map_total_ns += perf_counter_ns() - start_ns
return cost return cost
def _compute_cost_quantized(self, qx_milli: int, qy_milli: int) -> float: def _compute_cost_quantized(self, qx_milli: int, qy_milli: int) -> float:
@ -102,6 +116,8 @@ class DangerMap:
return 1e15 return 1e15
if self.tree is None: if self.tree is None:
return 0.0 return 0.0
if self.metrics is not None:
self.metrics.total_danger_map_query_calls += 1
dist, _ = self.tree.query([qx, qy], distance_upper_bound=self.safety_threshold) dist, _ = self.tree.query([qx, qy], distance_upper_bound=self.safety_threshold)
if dist >= self.safety_threshold: if dist >= self.safety_threshold:
return 0.0 return 0.0

View file

@ -128,6 +128,7 @@ class PathRefiner:
x_max = max(0.0, float(local_dx)) + 0.01 x_max = max(0.0, float(local_dx)) + 0.01
for bounds in self.collision_engine.iter_static_obstacle_bounds(query_bounds): for bounds in self.collision_engine.iter_static_obstacle_bounds(query_bounds):
self.context.metrics.total_refinement_static_bounds_checked += 1
local_corners = ( local_corners = (
self._to_local_xy(start, bounds[0], bounds[1]), self._to_local_xy(start, bounds[0], bounds[1]),
self._to_local_xy(start, bounds[0], bounds[3]), self._to_local_xy(start, bounds[0], bounds[3]),
@ -144,6 +145,7 @@ class PathRefiner:
negative_anchors.add(obs_min_y) negative_anchors.add(obs_min_y)
for bounds in self.collision_engine.iter_dynamic_path_bounds(query_bounds): for bounds in self.collision_engine.iter_dynamic_path_bounds(query_bounds):
self.context.metrics.total_refinement_dynamic_bounds_checked += 1
local_corners = ( local_corners = (
self._to_local_xy(start, bounds[0], bounds[1]), self._to_local_xy(start, bounds[0], bounds[1]),
self._to_local_xy(start, bounds[0], bounds[3]), self._to_local_xy(start, bounds[0], bounds[3]),
@ -166,6 +168,7 @@ class PathRefiner:
if anchor < min(0.0, float(local_dy)) + 0.01: if anchor < min(0.0, float(local_dy)) + 0.01:
direct_extents.add(anchor - pad) direct_extents.add(anchor - pad)
self.context.metrics.total_refinement_candidate_side_extents += len(direct_extents)
return sorted(direct_extents, key=lambda value: (abs(value), value)) return sorted(direct_extents, key=lambda value: (abs(value), value))
def _build_same_orientation_dogleg( def _build_same_orientation_dogleg(
@ -243,6 +246,7 @@ class PathRefiner:
local_dx, _ = self._to_local(window_start, window_end) local_dx, _ = self._to_local(window_start, window_end)
if local_dx < 4.0 * min_radius - 0.01: if local_dx < 4.0 * min_radius - 0.01:
continue continue
self.context.metrics.total_refinement_windows_considered += 1
windows.append((start_idx, end_idx)) windows.append((start_idx, end_idx))
return windows return windows
@ -270,12 +274,15 @@ class PathRefiner:
replacement = self._build_same_orientation_dogleg(window_start, window_end, net_width, radius, side_extent) replacement = self._build_same_orientation_dogleg(window_start, window_end, net_width, radius, side_extent)
if replacement is None: if replacement is None:
continue continue
self.context.metrics.total_refinement_candidates_built += 1
candidate_path = path[:start_idx] + replacement + path[end_idx:] candidate_path = path[:start_idx] + replacement + path[end_idx:]
self.context.metrics.total_refinement_candidates_verified += 1
report = self.collision_engine.verify_path_report(net_id, candidate_path) report = self.collision_engine.verify_path_report(net_id, candidate_path)
if not report.is_valid: if not report.is_valid:
continue continue
candidate_cost = self.path_cost(candidate_path) candidate_cost = self.path_cost(candidate_path)
if candidate_cost + 1e-6 < best_candidate_cost: if candidate_cost + 1e-6 < best_candidate_cost:
self.context.metrics.total_refinement_candidates_accepted += 1
best_candidate_cost = candidate_cost best_candidate_cost = candidate_cost
best_path = candidate_path best_path = candidate_path

View file

@ -93,7 +93,7 @@ class VisibilityManager:
dx, dy = cx - p1.x, cy - p1.y dx, dy = cx - p1.x, cy - p1.y
dist = numpy.sqrt(dx**2 + dy**2) dist = numpy.sqrt(dx**2 + dy**2)
angle = numpy.degrees(numpy.arctan2(dy, dx)) angle = numpy.degrees(numpy.arctan2(dy, dx))
reach = self.collision_engine.ray_cast(p1, angle, max_dist=dist + 0.05) reach = self.collision_engine.ray_cast(p1, angle, max_dist=dist + 0.05, caller="visibility_build")
if reach >= dist - 0.01: if reach >= dist - 0.01:
self._corner_graph[i].append((cx, cy, dist)) self._corner_graph[i].append((cx, cy, dist))
@ -143,7 +143,7 @@ class VisibilityManager:
continue continue
angle = numpy.degrees(numpy.arctan2(dy, dx)) angle = numpy.degrees(numpy.arctan2(dy, dx))
reach = self.collision_engine.ray_cast(origin, angle, max_dist=dist + 0.05) reach = self.collision_engine.ray_cast(origin, angle, max_dist=dist + 0.05, caller="visibility_query")
if reach >= dist - 0.01: if reach >= dist - 0.01:
visible.append((cx, cy, dist)) visible.append((cx, cy, dist))
@ -156,7 +156,7 @@ class VisibilityManager:
This avoids the expensive arbitrary-point visibility scan in hot search paths. This avoids the expensive arbitrary-point visibility scan in hot search paths.
""" """
if self.collision_engine.metrics is not None: if self.collision_engine.metrics is not None:
self.collision_engine.metrics.total_visibility_corner_queries += 1 self.collision_engine.metrics.total_visibility_corner_queries_exact += 1
self._ensure_current() self._ensure_current()
if max_dist < 0: if max_dist < 0:
return [] return []
@ -164,6 +164,6 @@ class VisibilityManager:
corner_idx = self._corner_idx_at(origin) corner_idx = self._corner_idx_at(origin)
if corner_idx is not None and corner_idx in self._corner_graph: if corner_idx is not None and corner_idx in self._corner_graph:
if self.collision_engine.metrics is not None: if self.collision_engine.metrics is not None:
self.collision_engine.metrics.total_visibility_corner_hits += 1 self.collision_engine.metrics.total_visibility_corner_hits_exact += 1
return [corner for corner in self._corner_graph[corner_idx] if corner[2] <= max_dist] return [corner for corner in self._corner_graph[corner_idx] if corner[2] <= max_dist]
return [] return []

View file

@ -16,10 +16,14 @@ def test_snapshot_example_01_exposes_metrics() -> None:
assert snapshot.metrics.route_iterations >= 1 assert snapshot.metrics.route_iterations >= 1
assert snapshot.metrics.nets_routed >= 1 assert snapshot.metrics.nets_routed >= 1
assert snapshot.metrics.nodes_expanded > 0 assert snapshot.metrics.nodes_expanded > 0
assert snapshot.metrics.score_component_calls >= 0
assert snapshot.metrics.danger_map_lookup_calls >= 0
assert snapshot.metrics.move_cache_abs_misses >= 0 assert snapshot.metrics.move_cache_abs_misses >= 0
assert snapshot.metrics.ray_cast_calls >= 0 assert snapshot.metrics.ray_cast_calls >= 0
assert snapshot.metrics.ray_cast_calls_expand_forward >= 0
assert snapshot.metrics.dynamic_tree_rebuilds >= 0 assert snapshot.metrics.dynamic_tree_rebuilds >= 0
assert snapshot.metrics.visibility_builds >= 0 assert snapshot.metrics.visibility_builds >= 0
assert snapshot.metrics.refinement_candidates_verified >= 0
def test_record_performance_baseline_script_writes_selected_scenario(tmp_path: Path) -> None: def test_record_performance_baseline_script_writes_selected_scenario(tmp_path: Path) -> None:
@ -43,3 +47,41 @@ def test_record_performance_baseline_script_writes_selected_scenario(tmp_path: P
assert payload["generator"] == "scripts/record_performance_baseline.py" assert payload["generator"] == "scripts/record_performance_baseline.py"
assert [entry["name"] for entry in payload["scenarios"]] == ["example_01_simple_route"] assert [entry["name"] for entry in payload["scenarios"]] == ["example_01_simple_route"]
assert (tmp_path / "performance.md").exists() assert (tmp_path / "performance.md").exists()
def test_diff_performance_baseline_script_writes_selected_scenario(tmp_path: Path) -> None:
repo_root = Path(__file__).resolve().parents[2]
record_script = repo_root / "scripts" / "record_performance_baseline.py"
diff_script = repo_root / "scripts" / "diff_performance_baseline.py"
baseline_dir = tmp_path / "baseline"
baseline_dir.mkdir()
output_path = tmp_path / "diff.md"
subprocess.run(
[
sys.executable,
str(record_script),
"--output-dir",
str(baseline_dir),
"--scenario",
"example_01_simple_route",
],
check=True,
)
subprocess.run(
[
sys.executable,
str(diff_script),
"--baseline",
str(baseline_dir / "performance_baseline.json"),
"--scenario",
"example_01_simple_route",
"--output",
str(output_path),
],
check=True,
)
report = output_path.read_text()
assert "Performance Baseline Diff" in report
assert "example_01_simple_route" in report

View file

@ -0,0 +1,113 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
from dataclasses import asdict
from pathlib import Path
from inire.tests.example_scenarios import SCENARIO_SNAPSHOTS
SUMMARY_KEYS = (
"duration_s",
"route_iterations",
"nets_routed",
"nodes_expanded",
"ray_cast_calls",
"moves_generated",
"moves_added",
"congestion_check_calls",
"verify_path_report_calls",
)
def _current_snapshots(selected_scenarios: tuple[str, ...] | None) -> dict[str, dict[str, object]]:
allowed = None if selected_scenarios is None else set(selected_scenarios)
snapshots: dict[str, dict[str, object]] = {}
for name, run in SCENARIO_SNAPSHOTS:
if allowed is not None and name not in allowed:
continue
snapshots[name] = asdict(run())
return snapshots
def _load_baseline(path: Path, selected_scenarios: tuple[str, ...] | None) -> dict[str, dict[str, object]]:
payload = json.loads(path.read_text())
allowed = None if selected_scenarios is None else set(selected_scenarios)
return {
entry["name"]: entry
for entry in payload["scenarios"]
if allowed is None or entry["name"] in allowed
}
def _metric_value(snapshot: dict[str, object], key: str) -> float:
if key == "duration_s":
return float(snapshot["duration_s"])
return float(snapshot["metrics"][key])
def _render_report(baseline: dict[str, dict[str, object]], current: dict[str, dict[str, object]]) -> str:
scenario_names = sorted(set(baseline) | set(current))
lines = [
"# Performance Baseline Diff",
"",
"| Scenario | Metric | Baseline | Current | Delta |",
"| :-- | :-- | --: | --: | --: |",
]
for scenario in scenario_names:
base_snapshot = baseline.get(scenario)
curr_snapshot = current.get(scenario)
if base_snapshot is None:
lines.append(f"| {scenario} | added | - | - | - |")
continue
if curr_snapshot is None:
lines.append(f"| {scenario} | missing | - | - | - |")
continue
for key in SUMMARY_KEYS:
base_value = _metric_value(base_snapshot, key)
curr_value = _metric_value(curr_snapshot, key)
lines.append(
f"| {scenario} | {key} | {base_value:.4f} | {curr_value:.4f} | {curr_value - base_value:+.4f} |"
)
return "\n".join(lines) + "\n"
def main() -> None:
parser = argparse.ArgumentParser(description="Diff the committed performance baseline against a fresh run.")
parser.add_argument(
"--baseline",
type=Path,
default=Path("docs/performance_baseline.json"),
help="Baseline JSON to compare against.",
)
parser.add_argument(
"--output",
type=Path,
default=None,
help="Optional file to write the report to. Defaults to stdout.",
)
parser.add_argument(
"--scenario",
action="append",
dest="scenarios",
default=[],
help="Optional scenario name to include. May be passed more than once.",
)
args = parser.parse_args()
selected = tuple(args.scenarios) if args.scenarios else None
baseline = _load_baseline(args.baseline, selected)
current = _current_snapshots(selected)
report = _render_report(baseline, current)
if args.output is None:
print(report, end="")
else:
args.output.write_text(report)
print(f"Wrote {args.output}")
if __name__ == "__main__":
main()

View file

@ -46,6 +46,7 @@ def _render_markdown(payload: dict[str, object]) -> str:
f"Generated on {payload['generated_on']} by `{payload['generator']}`.", f"Generated on {payload['generated_on']} by `{payload['generator']}`.",
"", "",
"The full machine-readable snapshot lives in `docs/performance_baseline.json`.", "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.",
"", "",
"| Scenario | Duration (s) | Total | Valid | Reached | Iter | Nets Routed | Nodes | Ray Casts | Moves Gen | Moves Added | Dyn Tree | Visibility Builds | Congestion Checks | Verify Calls |", "| Scenario | Duration (s) | Total | Valid | Reached | Iter | Nets Routed | Nodes | Ray Casts | Moves Gen | Moves Added | Dyn Tree | Visibility Builds | Congestion Checks | Verify Calls |",
"| :-- | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: |", "| :-- | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: |",
@ -77,6 +78,7 @@ def _render_markdown(payload: dict[str, object]) -> str:
"## Full Counter Set", "## Full Counter Set",
"", "",
"Each scenario entry in `docs/performance_baseline.json` records the full `RouteMetrics` snapshot, including cache, index, congestion, and verification counters.", "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.",
"", "",
"Tracked metric keys:", "Tracked metric keys:",
"", "",