From ca1b4abb028ac63510588308f6e6c4abad0871b0 Mon Sep 17 00:00:00 2001 From: Brendan Collins Date: Fri, 3 Jul 2026 09:21:31 -0400 Subject: [PATCH] Return all-NaN from HPA* when a segment cannot be refined (#3631) A failed refinement used to return the partially written path image, so an unreachable goal produced a finite cost trail that dead-ends mid-grid while every other search path returns all NaN for no-path. Return a fresh all-NaN surface instead. --- xrspatial/pathfinding.py | 6 +++++- xrspatial/tests/test_pathfinding.py | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/xrspatial/pathfinding.py b/xrspatial/pathfinding.py index 8f7a23bd0..14fbbb5a4 100644 --- a/xrspatial/pathfinding.py +++ b/xrspatial/pathfinding.py @@ -520,7 +520,11 @@ def _hpa_star_search(surface_data, friction_data, start_py, start_px, local_gy = g_py - min_row local_gx = g_px - min_col if sub_path is None or not np.isfinite(sub_path[local_gy, local_gx]): - return path_img # partial result + # Refinement could not connect this segment even at the + # largest radius. Return all-NaN ("no path found") rather + # than a partial cost trail that dead-ends mid-grid, matching + # the no-path contract of the other search paths. + return np.full((h, w), np.nan, dtype=np.float64) seg_goal_cost = sub_path[local_gy, local_gx] sh, sw = sub_path.shape diff --git a/xrspatial/tests/test_pathfinding.py b/xrspatial/tests/test_pathfinding.py index 4fccd29fc..f036fabc0 100644 --- a/xrspatial/tests/test_pathfinding.py +++ b/xrspatial/tests/test_pathfinding.py @@ -699,6 +699,29 @@ def test_hpa_star_correctness(): assert (r, c) != (np.nan, np.nan) +def test_hpa_star_unreachable_goal_all_nan(): + """HPA* returns all-NaN when no path exists, not a partial trail. + + The coarse grid sees blocks containing a thin NaN wall as passable + (each block still has valid cells), so the coarse route crosses the + wall and refinement fails. The output must follow the no-path + contract (all NaN) instead of keeping the already-refined segments. + """ + H = W = 200 + data = np.ones((H, W)) + data[:, 100] = np.nan # complete wall: goal unreachable + + dy, dx, dd = _neighborhood_structure(1.0, 1.0, 8) + barriers = np.array([], dtype=np.float64) + + path_img = _hpa_star_search( + data, None, 0, 0, 199, 199, + barriers, dy, dx, dd, + 1.0, False, 1.0, 1.0, H, W) + + assert not np.isfinite(path_img).any() + + def test_auto_radius_selection(): """When mocked low memory, auto-radius kicks in and finds path.""" data = np.ones((20, 20))