From 912f2603985f9b4f58b120492ffe698c51dde665 Mon Sep 17 00:00:00 2001 From: Brendan Collins Date: Fri, 29 May 2026 16:40:14 -0700 Subject: [PATCH] slope: planar dask backends declare float32 meta dtype (#2686) The planar dask paths passed meta=np.array(()) / meta=cupy.array((), which default to float64, while the chunk functions return float32. The lazy dask dtype (float64) disagreed with the computed dtype (float32) and with the numpy/cupy backends. Declare float32 meta to match, mirroring the geodesic dask paths. Adds regression tests asserting lazy dtype == computed dtype == numpy dtype (float32) across numpy, cupy, dask+numpy, and dask+cupy. --- xrspatial/slope.py | 4 ++-- xrspatial/tests/test_slope.py | 36 +++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/xrspatial/slope.py b/xrspatial/slope.py index 36e5b93f5..ac7f68d15 100644 --- a/xrspatial/slope.py +++ b/xrspatial/slope.py @@ -105,7 +105,7 @@ def _run_dask_numpy(data: da.Array, out = data.map_overlap(_func, depth=(1, 1), boundary=_boundary_to_dask(boundary), - meta=np.array(())) + meta=np.array((), dtype=np.float32)) return out @@ -121,7 +121,7 @@ def _run_dask_cupy(data: da.Array, out = data.map_overlap(_func, depth=(1, 1), boundary=_boundary_to_dask(boundary, is_cupy=True), - meta=cupy.array(())) + meta=cupy.array((), dtype=cupy.float32)) return out diff --git a/xrspatial/tests/test_slope.py b/xrspatial/tests/test_slope.py index 39fadf2e7..9e2ee98bf 100644 --- a/xrspatial/tests/test_slope.py +++ b/xrspatial/tests/test_slope.py @@ -147,3 +147,39 @@ def test_boundary_invalid(): agg = create_test_raster(data, attrs={'res': (1, 1)}) with pytest.raises(ValueError, match="boundary must be one of"): slope(agg, boundary='invalid') + + +@dask_array_available +def test_dask_numpy_lazy_dtype_matches_computed(): + # The lazy dask dtype must match both the computed dtype and the + # numpy backend (all float32). Regression for the planar dask meta + # defaulting to float64 while chunks return float32. + data = np.random.default_rng(0).random((8, 10)).astype(np.float64) * 100 + numpy_agg = create_test_raster(data, backend='numpy', attrs={'res': (1, 1)}) + dask_agg = create_test_raster(data, backend='dask+numpy', + attrs={'res': (1, 1)}, chunks=(3, 4)) + np_result = slope(numpy_agg) + da_result = slope(dask_agg) + assert np_result.dtype == np.float32 + assert da_result.dtype == np.float32 + assert da_result.data.compute().dtype == np.float32 + + +@cuda_and_cupy_available +def test_cupy_lazy_dtype_matches_computed(): + data = np.random.default_rng(0).random((8, 10)).astype(np.float64) * 100 + numpy_agg = create_test_raster(data, backend='numpy', attrs={'res': (1, 1)}) + cupy_agg = create_test_raster(data, backend='cupy', attrs={'res': (1, 1)}) + assert slope(numpy_agg).dtype == np.float32 + assert slope(cupy_agg).dtype == np.float32 + + +@dask_array_available +@cuda_and_cupy_available +def test_dask_cupy_lazy_dtype_matches_computed(): + data = np.random.default_rng(0).random((8, 10)).astype(np.float64) * 100 + dask_cupy_agg = create_test_raster(data, backend='dask+cupy', + attrs={'res': (1, 1)}, chunks=(3, 4)) + da_result = slope(dask_cupy_agg) + assert da_result.dtype == np.float32 + assert da_result.data.compute().dtype == np.float32