Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/sweep-test-coverage-state.csv
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ resample,2026-05-29,2547;2615,HIGH,1;2;3;5,"Pass 2 (2026-05-29): added test_resa
slope,2026-05-29,2697,MEDIUM,3,"PR #2703: added degenerate-shape tests (1x1/1xN/Nx1) for all 4 planar backends + geodesic; no live bug, pins all-NaN+shape contract. CUDA host: cupy/dask+cupy ran. Backend/NaN/param/metadata coverage already complete."
zonal,2026-05-29,2619,MEDIUM,1,"Pass 2 (2026-05-29): one Cat 1 MEDIUM backend-coverage gap remained after pass 1 -- 3D crosstab on cupy / dask+cupy. The 3D GPU paths (_crosstab_cupy / _crosstab_dask_cupy with a 3D categorical values array, layer=, agg='count') were reachable and correct but untested; the existing 3D crosstab tests (test_crosstab_3d_count, test_crosstab_3d_agg_method, test_nodata_values_crosstab_3d) only parametrize numpy / dask+numpy. Added 3 parity tests to test_zonal_backend_coverage_2026_05_27.py (test_crosstab_3d_count_cupy_matches_numpy, test_crosstab_3d_count_dask_cupy_matches_numpy, test_crosstab_3d_nodata_cupy_matches_numpy) asserting cupy and dask+cupy results match numpy for agg='count' including a nodata_values case. All passed live on a CUDA host. Issue #2619, PR #2625. Test-only, no source change. Remaining LOW (documented, not fixed): get_full_extent has no direct unit test (exercised indirectly via suggest_zonal_canvas); non-square cellsize handling not exercised. Pass 1 (2026-05-27): added test_zonal_backend_coverage_2026_05_27.py with 32 tests, all passing on a CUDA host. Closes Cat 1 HIGH backend-coverage gaps: crosstab cupy + dask+cupy (_crosstab_cupy / _crosstab_dask_cupy were dispatched but never invoked by tests), regions cupy + dask+cupy (_regions_cupy via cupyx.scipy.ndimage + _regions_dask_cupy), trim dask+numpy + cupy + dask+cupy (_trim_bounds_dask isnan path and cupy data.get() path), crop dask+numpy + cupy + dask+cupy (_crop_bounds_dask + cupy data.get() path), apply 3D cupy + dask+cupy (per-layer kernel launch over the third axis in _apply_cupy and _apply_dask_cupy). Existing test_zonal.py covered only numpy + dask+numpy for crosstab/regions/trim/crop and 2D-only for cupy apply. Closes Cat 3 MEDIUM 1x1 / 1xN / Nx1 strip edge cases for trim, crop, and regions. Closes Cat 4 LOW pins: regions(neighborhood=6) ValueError, suggest_zonal_canvas(crs='Geographic') aspect-ratio pin and invalid-crs KeyError, crosstab cupy zone_ids/cat_ids filter, crosstab cupy agg='percentage'. Closes Cat 5 MEDIUM: regions coords/attrs propagation across numpy + dask+numpy, trim/crop name='trim'/'crop' default + attrs preservation. Also pins the documented numpy-vs-dask trim asymmetry on NaN sentinel (numpy _trim does equality which never matches NaN; dask _trim_bounds_dask has dedicated isnan branch). Mutation against the cupy.asnumpy() conversion in _crosstab_cupy flipped test_crosstab_cupy_matches_numpy red. Source untouched."
focal,2026-05-29,2732,HIGH,1,"Pass (2026-05-29): added test_hotspots_dask_cupy to test_focal.py closing Cat 1 HIGH backend-coverage gap. hotspots() registers dask_cupy_func=_hotspots_dask_cupy (focal.py L1414) but no test invoked it, while mean/apply/focal_stats each have a dedicated dask+cupy test. New test compares dask+cupy vs numpy on chunk interior (matches test_apply_dask_cupy/test_focal_stats_dask_cupy style). RUN on CUDA host: passes; spy confirmed routing through _hotspots_dask_cupy; path matches numpy exactly so no source fix needed. LOW (documented not fixed): Inf/-Inf inputs untested across focal funcs; 1x1 raster not explicitly tested for mean/apply/hotspots (focal_stats 1x1 covered by test_variety_single_cell). Issue #2732."
interpolate_spline,2026-06-04,,HIGH,1;3;5,scope=spline-only; cupy+dask_cupy spline backends untested (_tps_cuda_kernel) | n==2 affine branch + metadata untested | added 4 tests to TestSpline all pass on CUDA host | issue-create denied by classifier no GH issue
56 changes: 56 additions & 0 deletions xrspatial/tests/test_interpolation.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,37 @@ def test_single_point(self):
result = spline([0.5], [0.5], [42.0], template, smoothing=0.0)
np.testing.assert_allclose(result.values, 42.0, atol=1e-6)

def test_two_point_affine_fit(self):
"""n == 2 falls back to a least-squares affine fit.

With two points the full TPS system is underdetermined, so
_tps_build_and_solve fits z = a0 + a1*x + a2*y instead. Two
points on the x-axis with z = 10 and 20 define the gradient
along x; the midpoint should read 15.
"""
x = np.array([0.0, 2.0])
y = np.array([0.0, 0.0])
z = np.array([10.0, 20.0])
template = _make_template([0.0], [0.0, 1.0, 2.0])
result = spline(x, y, z, template, smoothing=0.0)
np.testing.assert_allclose(
result.values, [[10.0, 15.0, 20.0]], atol=1e-6)

def test_output_metadata(self):
"""Output DataArray preserves template coords, dims, and name."""
x, y, z = _grid_points()
template = _make_template([0.0, 1.0, 2.0], [0.0, 1.0, 2.0])
template.attrs['res'] = (1.0, 1.0)
result = spline(x, y, z, template, name='my_spline')
assert result.name == 'my_spline'
assert result.dims == template.dims
assert result.shape == template.shape
assert result.attrs == template.attrs
np.testing.assert_array_equal(result.coords['x'].values,
template.coords['x'].values)
np.testing.assert_array_equal(result.coords['y'].values,
template.coords['y'].values)

@dask_array_available
def test_dask_matches_numpy(self):
x, y, z = _grid_points()
Expand All @@ -219,6 +250,31 @@ def test_dask_matches_numpy(self):
np.testing.assert_allclose(
np_result.values, da_result.values, rtol=1e-10)

@cuda_and_cupy_available
def test_cupy_matches_numpy(self):
"""CuPy backend produces same results as numpy."""
x, y, z = _grid_points()
np_template = _make_template([0.0, 1.0, 2.0], [0.0, 1.0, 2.0])
cp_template = _make_template([0.0, 1.0, 2.0], [0.0, 1.0, 2.0],
backend='cupy')
np_result = spline(x, y, z, np_template)
cp_result = spline(x, y, z, cp_template)
np.testing.assert_allclose(
np_result.values, _to_numpy(cp_result), rtol=1e-10)

@cuda_and_cupy_available
@dask_array_available
def test_dask_cupy_matches_numpy(self):
"""Dask+CuPy backend produces same results as numpy."""
x, y, z = _grid_points()
np_template = _make_template([0.0, 1.0, 2.0], [0.0, 1.0, 2.0])
dc_template = _make_template([0.0, 1.0, 2.0], [0.0, 1.0, 2.0],
backend='dask_cupy', chunks=(2, 2))
np_result = spline(x, y, z, np_template)
dc_result = spline(x, y, z, dc_template)
np.testing.assert_allclose(
np_result.values, _to_numpy(dc_result), rtol=1e-10)


# ===================================================================
# Kriging tests
Expand Down
Loading