Raise on infeasible tours and honor snap in multi_stop_search optimize_order#3654
Open
brendancol wants to merge 2 commits into
Open
Raise on infeasible tours and honor snap in multi_stop_search optimize_order#3654brendancol wants to merge 2 commits into
brendancol wants to merge 2 commits into
Conversation
…e_order (#3646) _held_karp returned [start, end] when every full tour was infinite (best_last stayed -1), so multi_stop_search(optimize_order=True) silently dropped interior waypoints instead of raising like the optimize_order=False path. Raise ValueError when the exact solver's total cost is infinite. The pairwise distance matrix also read each segment's cost at the unsnapped goal pixel, so with snap=True any waypoint sitting on an invalid cell got an infinite distance and fell into the same hole. Use the max-finite-cost pixel as the true goal, matching the segment loop. Claude-Session: https://claude.ai/code/session_0155N4QGamQVxgpAAPbpQNq4
brendancol
commented
Jul 8, 2026
brendancol
left a comment
Contributor
Author
There was a problem hiding this comment.
Domain-aware review of the optimize_order fix.
Blockers
None.
Suggestions
- The snap-relocation block (max-finite-cost pixel as the true goal) now exists twice: here and in the segment loop of
multi_stop_search. Both copies are four lines and the loop variant also persistswaypoint_pixels, so a shared helper is borderline; if a third caller shows up, extract one. - The raise is gated on the exact solver only. For N > 12 the heuristic tour can carry infinite legs and the failure surfaces later as
no path between waypoints i and i+1from the segment loop. That asymmetry is deliberate (an infinite heuristic cost does not prove infeasibility) and the PR body says so; worth keeping in mind if anyone later tightens the heuristic.
Nits
np.unravel_indexreturns numpy scalars forgoal_py, goal_px; the subsequentdist[i][j]assignment and comparisons handle them fine, matching what the segment loop already does.
Verified locally: both new tests fail on main for the stated reasons (finite 2-waypoint route; dropped snapped waypoint) and pass with the fix; the full pathfinding suite (59) passes including cupy and dask+cupy on a CUDA box. The Held-Karp reconstruction path for feasible tours is untouched, and test_optimize_order_finds_better_route / test_optimize_order_preserves_endpoints still pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3646
_optimize_waypoint_ordernow raisesValueErrorwhen Held-Karp's total cost is infinite. Previouslybest_laststayed-1, the reconstruction returned[start, end], andmulti_stop_search(optimize_order=True)silently dropped every interior waypoint while returning a finite route. Theoptimize_order=Falsepath already raises on the same input.snap=True, using the same max-finite-cost lookup as the segment loop. Before, a waypoint on an invalid cell gotinfdistance in every row even though snapping made it reachable, which fed the hole above._nearest_neighbor_2opt, N > 12) is untouched: it always returns a complete tour, and an infinite heuristic cost is not proof that no feasible tour exists, so the segment loop keeps raisingno path between waypointsfor that case.Backends: the fix is in the ordering pass shared by all four backends (numpy, cupy, dask+numpy, dask+cupy); no backend-specific code changed.
Test plan:
test_optimize_order_unreachable_waypoint_raises(fails on main: returns a finite 2-waypoint route)test_optimize_order_with_snap_keeps_waypoints(fails on main: drops the snapped waypoint)pytest xrspatial/tests/test_pathfinding.py— 59 passed, including cupy and dask+cupy tests on a CUDA boxAlso includes this sweep's state-CSV update.
https://claude.ai/code/session_0155N4QGamQVxgpAAPbpQNq4