diff --git a/.claude/sweep-test-coverage-state.csv b/.claude/sweep-test-coverage-state.csv index 986865bd3..6abc6e8d2 100644 --- a/.claude/sweep-test-coverage-state.csv +++ b/.claude/sweep-test-coverage-state.csv @@ -43,3 +43,4 @@ slope,2026-05-29,2697,MEDIUM,3,"PR #2703: added degenerate-shape tests (1x1/1xN/ 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 +idw,2026-06-04,2919,HIGH,1;4,"cupy/dask+cupy backends untested (Cat1 HIGH); GPU k-reject error path untested (Cat4 MED). Added 6 GPU tests, validated on CUDA host. Inf-in-points (Cat2) and attrs-preservation (Cat5) are LOW, documented not fixed." diff --git a/xrspatial/tests/test_interpolation.py b/xrspatial/tests/test_interpolation.py index 4f70b60fb..c941d3179 100644 --- a/xrspatial/tests/test_interpolation.py +++ b/xrspatial/tests/test_interpolation.py @@ -154,6 +154,60 @@ def test_dask_knearest_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 (all-points) 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 = idw(x, y, z, np_template) + cp_result = idw(x, y, z, cp_template) + np.testing.assert_allclose( + np_result.values, _to_numpy(cp_result), rtol=1e-10) + + @cuda_and_cupy_available + def test_cupy_exact_interpolation(self): + """CuPy exact-match path returns the coincident point's value.""" + x, y, z = _grid_points() + cp_template = _make_template([0.0, 1.0, 2.0], [0.0, 1.0, 2.0], + backend='cupy') + result = idw(x, y, z, cp_template, power=2.0) + expected = z.reshape(3, 3) + np.testing.assert_allclose(_to_numpy(result), expected) + + @cuda_and_cupy_available + def test_cupy_knearest_rejected(self): + """k-nearest mode is rejected on the CuPy backend.""" + x, y, z = _grid_points() + cp_template = _make_template([0.0, 1.0, 2.0], [0.0, 1.0, 2.0], + backend='cupy') + with pytest.raises(NotImplementedError, match='k-nearest'): + idw(x, y, z, cp_template, k=2) + + @cuda_and_cupy_available + @dask_array_available + def test_dask_cupy_matches_numpy(self): + """Dask+CuPy backend (all-points) 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 = idw(x, y, z, np_template) + dc_result = idw(x, y, z, dc_template) + np.testing.assert_allclose( + np_result.values, _to_numpy(dc_result), rtol=1e-10) + + @cuda_and_cupy_available + @dask_array_available + def test_dask_cupy_knearest_rejected(self): + """k-nearest mode is rejected on the Dask+CuPy backend.""" + x, y, z = _grid_points() + dc_template = _make_template([0.0, 1.0, 2.0], [0.0, 1.0, 2.0], + backend='dask_cupy', chunks=(2, 2)) + with pytest.raises(NotImplementedError): + idw(x, y, z, dc_template, k=2) + # =================================================================== # Spline tests