Reject NaN and negative max_distance in the surface distance functions - #3720
Conversation
#3711) max_distance=nan slipped past the numba kernel's rejection-form bound test (cost_u > max_distance) so the search ran unbounded, while the CUDA kernel's acceptance-form test (best <= max_distance) rejected every relaxation and returned a seeds-only raster. The same call therefore produced a full distance surface on numpy and dask and an almost entirely NaN one on cupy, with no error and no warning. A negative max_distance masked even the zero-distance sources on numpy and cupy, and reached dask as a negative map_overlap depth (ValueError: length should not be negative). Both are now rejected in _compute() with proximity()'s message, along with non-1D target_values, which previously reached _seed_sources as a 0-d array and failed with a numba TypingError that never named the parameter.
|
Verification trail for this branch, run on a CUDA host (cupy and dask+cupy execute, they are not skipped): Before the change, and After:
|
|
Integration note: PR #3718 (api-consistency sweep) and PR #3720 (error-handling sweep) both add
Whichever merges second will need a rebase, and the composed result must keep the Worth running both PRs' tests against the merged tree rather than trusting each PR's own green run. |
Fixes #3711.
surface_distance,surface_allocationandsurface_directionaccepted anymax_distancevalue and anytarget_valuesshape. Three problems came out ofthat, all found by running the failure paths on all four backends.
max_distance=nanreturned different rasters per backend. The numba kernelbounds the search with
if cost_u > max_distance: break, which is False forNaN, so it ran to completion and returned a full unbounded distance surface.
The CUDA kernel uses
if best < current and best <= max_distance, also Falsefor NaN, so no relaxation was ever accepted and only the seed pixels came back
finite. Same call, 36/36 finite pixels on numpy and dask, 1/36 on cupy, no
exception and no warning either way.
max_distancenegative masked even the zero-distance sources on numpy andcupy (
_finalize_distcomparesdist > max_distance), and reached dask as anegative
map_overlapdepth, surfacing asValueError: length should not be negativewith no mention of the parameter the caller set.Non-1D
target_valuesreached_seed_sourcesas a 0-d array.target_values=1instead of
[1]produced a numbaTypingErrorwith about a hundred lines ofoverload candidates on numpy and dask, and
TypeError: len() of unsized objecton cupy.
Change
Three guards in
_compute(), next to the existingconnectivityandmethodchecks. The
max_distancemessage is copied fromproximity()(xrspatial/proximity.py:1396), which takes the same parameter and has had this
guard since #2850 for the same reason.
Tests
26 new parametrized error-path tests: NaN and negative
max_distanceacrossthe three public functions and all four backends, plus 0-d and 2-D
target_values. Both assert the exception type and the message text.cupy and dask+cupy ran for real on this host, they are not skipped.
Compatibility
These are new exceptions on inputs that previously gave silent
backend-dependent output or an internal dask error. No documented usage
changes, and the 37 pre-existing tests are untouched and still green.
Found by /sweep-error-handling. The state CSV row for
surface_distanceisupdated in the second commit.