Describe the bug
Error-handling audit of xrspatial/pathfinding.py, with the bad-input battery executed on numpy, dask+numpy, cupy, and dask+cupy. Four validation gaps in a_star_search / multi_stop_search.
1. String-typed barriers values are silently ignored (severity: critical)
Numba compares the float cell value against a unicode element, which is always False, so every barrier is dropped. No error, no warning, and the returned path crosses the cells the caller asked to block:
import numpy as np, xarray as xr
from xrspatial import a_star_search
data = np.ones((5, 9))
data[:, 4] = 0.0 # wall of zeros
r = xr.DataArray(data, dims=('y', 'x'), attrs={'res': (1.0, 1.0)})
r['y'] = np.linspace(4, 0, 5); r['x'] = np.linspace(0, 8, 9)
a_star_search(r, (2.0, 0.0), (2.0, 8.0), barriers=[0]) # all-NaN: wall blocks the path (correct)
a_star_search(r, (2.0, 0.0), (2.0, 8.0), barriers=['0']) # routes straight through the wall
Barrier values that arrive as strings (argparse, CSV, JSON config) give a wrong path that looks valid. A scalar (barriers=0 instead of [0]) dies instead with a numba TypingError: iteration over a 0-d array on the numpy/cupy backends and a bare TypeError on dask. Same on all four backends.
2. search_radius is never validated (severity: high)
a_star_search(agg, start, goal, search_radius=-1) returns all-NaN ("no path") on numpy and cupy even though a path exists. Through multi_stop_search the same input surfaces as the misleading ValueError: no path between waypoints 0 and 1.
- Other geometries crash deep in the slicing code instead:
ValueError: negative dimensions are not allowed (radius -3, interior points) or TypeError: slice indices must be integers or None or have an __index__ method (radius 2.5). Whether a float radius crashes or works depends on where start/goal sit relative to the grid edges.
3. multi_stop_search with mismatched dim names raises bare KeyError: 'y' (severity: medium)
a_star_search checks surface.dims != (y, x) and raises ValueError; multi_stop_search skips that check, so the same mistake dies inside xarray internals:
multi_stop_search(raster_with_lat_lon_dims, [start, goal])
# KeyError: 'y' (from xarray's dataset_utils, no mention of the x=/y= parameters)
The a_star_search message has its own problems: `surface.coords` should be named as coordinates:(y, x) says "coords" where it means dims, is missing a space, and never shows the dims the raster actually has.
4. Scalar start/goal in a_star_search (severity: medium)
a_star_search(agg, 3.0, goal) raises TypeError: 'float' object is not subscriptable from inside _get_pixel_id. multi_stop_search already checks each waypoint has exactly 2 elements; a_star_search accepts anything, and a scalar waypoint in multi_stop_search itself hits TypeError: object of type 'float' has no len() at the check site.
Expected behavior
- Non-numeric or non-1-D
barriers raise a clear error naming the parameter and the offending dtype, before any kernel runs.
search_radius other than None or a non-negative integer is rejected up front.
multi_stop_search raises the same dims ValueError as a_star_search, and the message names the actual dims plus the x=/y= parameters.
start/goal get the same 2-element check that multi_stop_search applies to waypoints.
Found by the error-handling sweep on 2026-07-08. Exception types stay as they are for currently-raising paths; the two silent cases (1 and 2) start raising, which only affects calls that today return wrong output.
Describe the bug
Error-handling audit of
xrspatial/pathfinding.py, with the bad-input battery executed on numpy, dask+numpy, cupy, and dask+cupy. Four validation gaps ina_star_search/multi_stop_search.1. String-typed
barriersvalues are silently ignored (severity: critical)Numba compares the float cell value against a unicode element, which is always False, so every barrier is dropped. No error, no warning, and the returned path crosses the cells the caller asked to block:
Barrier values that arrive as strings (argparse, CSV, JSON config) give a wrong path that looks valid. A scalar (
barriers=0instead of[0]) dies instead with a numbaTypingError: iteration over a 0-d arrayon the numpy/cupy backends and a bareTypeErroron dask. Same on all four backends.2.
search_radiusis never validated (severity: high)a_star_search(agg, start, goal, search_radius=-1)returns all-NaN ("no path") on numpy and cupy even though a path exists. Throughmulti_stop_searchthe same input surfaces as the misleadingValueError: no path between waypoints 0 and 1.ValueError: negative dimensions are not allowed(radius -3, interior points) orTypeError: slice indices must be integers or None or have an __index__ method(radius 2.5). Whether a float radius crashes or works depends on where start/goal sit relative to the grid edges.3.
multi_stop_searchwith mismatched dim names raises bareKeyError: 'y'(severity: medium)a_star_searchcheckssurface.dims != (y, x)and raisesValueError;multi_stop_searchskips that check, so the same mistake dies inside xarray internals:The
a_star_searchmessage has its own problems:`surface.coords` should be named as coordinates:(y, x)says "coords" where it means dims, is missing a space, and never shows the dims the raster actually has.4. Scalar
start/goalina_star_search(severity: medium)a_star_search(agg, 3.0, goal)raisesTypeError: 'float' object is not subscriptablefrom inside_get_pixel_id.multi_stop_searchalready checks each waypoint has exactly 2 elements;a_star_searchaccepts anything, and a scalar waypoint inmulti_stop_searchitself hitsTypeError: object of type 'float' has no len()at the check site.Expected behavior
barriersraise a clear error naming the parameter and the offending dtype, before any kernel runs.search_radiusother than None or a non-negative integer is rejected up front.multi_stop_searchraises the same dimsValueErrorasa_star_search, and the message names the actual dims plus thex=/y=parameters.start/goalget the same 2-element check thatmulti_stop_searchapplies to waypoints.Found by the error-handling sweep on 2026-07-08. Exception types stay as they are for currently-raising paths; the two silent cases (1 and 2) start raising, which only affects calls that today return wrong output.