From 9d663288ec87b5cb9367d5c87a66ad0de47fdcdb Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Wed, 22 Jan 2025 10:39:55 -0800 Subject: [PATCH 1/4] Address more comments --- scipy/_lib/tests/test_array_api.py | 2 +- scipy/fft/tests/test_helper.py | 2 + scipy/fft/tests/test_real_transforms.py | 12 +- scipy/ndimage/_morphology.py | 6 +- scipy/ndimage/tests/test_filters.py | 12 +- scipy/ndimage/tests/test_measurements.py | 13 ++- scipy/ndimage/tests/test_morphology.py | 6 +- scipy/signal/_filter_design.py | 5 +- scipy/signal/_signaltools.py | 5 +- scipy/signal/tests/test_signaltools.py | 34 +++--- scipy/signal/tests/test_windows.py | 10 +- scipy/special/tests/test_logsumexp.py | 8 +- .../test_support_alternative_backends.py | 3 +- scipy/stats/tests/test_stats.py | 107 +++++++++++------- scipy/stats/tests/test_variation.py | 9 +- 15 files changed, 137 insertions(+), 97 deletions(-) diff --git a/scipy/_lib/tests/test_array_api.py b/scipy/_lib/tests/test_array_api.py index b967431ed3df..a156386c3275 100644 --- a/scipy/_lib/tests/test_array_api.py +++ b/scipy/_lib/tests/test_array_api.py @@ -68,7 +68,7 @@ def test_array_api_extra_hook(self): @skip_xp_backends( "dask.array", - reason="raw dask.array namespace doesn't ignores copy=True in asarray" + reason="raw dask.array namespace ignores copy=True in asarray" ) def test_copy(self, xp): for _xp in [xp, None]: diff --git a/scipy/fft/tests/test_helper.py b/scipy/fft/tests/test_helper.py index 1db9cde4de63..6a5fa58492fa 100644 --- a/scipy/fft/tests/test_helper.py +++ b/scipy/fft/tests/test_helper.py @@ -518,6 +518,7 @@ def test_definition(self, xp): x2 = xp.asarray([0, 1, 2, 3, 4, -5, -4, -3, -2, -1], dtype=xp.float64) # default dtype varies across backends + y = 9 * fft.fftfreq(9, xp=xp) xp_assert_close(y, x, check_dtype=False, check_namespace=True) @@ -549,6 +550,7 @@ def test_definition(self, xp): x2 = xp.asarray([0, 1, 2, 3, 4, 5], dtype=xp.float64) # default dtype varies across backends + y = 9 * fft.rfftfreq(9, xp=xp) xp_assert_close(y, x, check_dtype=False, check_namespace=True) diff --git a/scipy/fft/tests/test_real_transforms.py b/scipy/fft/tests/test_real_transforms.py index d93dc1725278..1ce29515451b 100644 --- a/scipy/fft/tests/test_real_transforms.py +++ b/scipy/fft/tests/test_real_transforms.py @@ -6,7 +6,7 @@ from scipy.fft import dct, idct, dctn, idctn, dst, idst, dstn, idstn import scipy.fft as fft from scipy import fftpack -from scipy._lib._array_api import xp_assert_close +from scipy._lib._array_api import xp_copy, xp_assert_close skip_xp_backends = pytest.mark.skip_xp_backends @@ -195,10 +195,9 @@ def test_orthogonalize_noop(func, type, norm, xp): @skip_xp_backends(cpu_only=True) @pytest.mark.parametrize("norm", ["backward", "ortho", "forward"]) def test_orthogonalize_dct1(norm, xp): - x_np = np.random.rand(100) - x = xp.asarray(x_np) + x = xp.asarray(np.random.rand(100)) - x2 = xp.asarray(x_np.copy()) + x2 = xp_copy(x, xp=xp) x2[0] *= SQRT_2 x2[-1] *= SQRT_2 @@ -230,9 +229,8 @@ def test_orthogonalize_dcst2(func, norm, xp): @pytest.mark.parametrize("norm", ["backward", "ortho", "forward"]) @pytest.mark.parametrize("func", [dct, dst]) def test_orthogonalize_dcst3(func, norm, xp): - x_np = np.random.rand(100) - x = xp.asarray(x_np) - x2 = xp.asarray(x_np.copy()) + x = xp.asarray(np.random.rand(100)) + x2 = xp_copy(x, xp=xp) x2[0 if func == dct else -1] *= SQRT_2 y1 = func(x, type=3, norm=norm, orthogonalize=True) diff --git a/scipy/ndimage/_morphology.py b/scipy/ndimage/_morphology.py index daf0f1f43c1c..a093a38d0394 100644 --- a/scipy/ndimage/_morphology.py +++ b/scipy/ndimage/_morphology.py @@ -36,7 +36,7 @@ from . import _nd_image from . import _filters -from scipy._lib._array_api import is_dask, array_namespace +from scipy._lib.array_api_compat import is_dask_array __all__ = ['iterate_structure', 'generate_binary_structure', 'binary_erosion', 'binary_dilation', 'binary_opening', 'binary_closing', @@ -222,7 +222,7 @@ def _binary_erosion(input, structure, iterations, mask, output, except TypeError as e: raise TypeError('iterations parameter should be an integer') from e - if is_dask(array_namespace(input)): + if is_dask_array(input): # Note: If you create an dask array with ones # it does a stride trick where it makes an array # (with stride 0) using a scalar @@ -1800,6 +1800,7 @@ def morphological_laplace(input, size=None, footprint=None, structure=None, Output """ + input = np.asarray(input) tmp1 = grey_dilation(input, size, footprint, structure, None, mode, cval, origin, axes=axes) if isinstance(output, np.ndarray): @@ -1812,7 +1813,6 @@ def morphological_laplace(input, size=None, footprint=None, structure=None, tmp2 = grey_erosion(input, size, footprint, structure, None, mode, cval, origin, axes=axes) np.add(tmp1, tmp2, tmp2) - input = np.asarray(input) np.subtract(tmp2, input, tmp2) np.subtract(tmp2, input, tmp2) return tmp2 diff --git a/scipy/ndimage/tests/test_filters.py b/scipy/ndimage/tests/test_filters.py index e417600b3078..5ebc2d47214e 100644 --- a/scipy/ndimage/tests/test_filters.py +++ b/scipy/ndimage/tests/test_filters.py @@ -191,7 +191,7 @@ def test_correlate01(self, xp): @xfail_xp_backends('cupy', reason="Differs by a factor of two?") @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @skip_xp_backends("dask.array", reason="wrong answer") + @xfail_xp_backends("dask.array", reason="wrong answer") def test_correlate01_overlap(self, xp): array = xp.reshape(xp.arange(256), (16, 16)) weights = xp.asarray([2]) @@ -881,7 +881,7 @@ def test_gauss06(self, xp): assert_array_almost_equal(output1, output2) @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @skip_xp_backends("dask.array", reason="wrong result") + @xfail_xp_backends("dask.array", reason="output keyword not handled properly") def test_gauss_memory_overlap(self, xp): input = xp.arange(100 * 100, dtype=xp.float32) input = xp.reshape(input, (100, 100)) @@ -1228,7 +1228,7 @@ def test_prewitt01(self, dtype, xp): assert_array_almost_equal(t, output) @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @skip_xp_backends("dask.array", reason="output array is read-only.") + @xfail_xp_backends("dask.array", reason="output array is read-only.") @pytest.mark.parametrize('dtype', types + complex_types) def test_prewitt02(self, dtype, xp): if is_torch(xp) and dtype in ("uint16", "uint32", "uint64"): @@ -1838,9 +1838,7 @@ def test_rank06(self, xp): @skip_xp_backends("jax.numpy", reason="assignment destination is read-only", ) - @xfail_xp_backends("dask.array", - reason="wrong answer", - ) + @xfail_xp_backends("dask.array", reason="wrong answer") def test_rank06_overlap(self, xp): array = xp.asarray([[3, 2, 5, 1, 4], [5, 8, 3, 7, 1], @@ -2647,7 +2645,7 @@ def test_gaussian_radius_invalid(xp): @skip_xp_backends("jax.numpy", reason="output array is read-only") -@skip_xp_backends("dask.array", reason="wrong answer") +@xfail_xp_backends("dask.array", reason="wrong answer") class TestThreading: def check_func_thread(self, n, fun, args, out): from threading import Thread diff --git a/scipy/ndimage/tests/test_measurements.py b/scipy/ndimage/tests/test_measurements.py index 28bc3722e321..d2854bb85216 100644 --- a/scipy/ndimage/tests/test_measurements.py +++ b/scipy/ndimage/tests/test_measurements.py @@ -548,17 +548,16 @@ def test_value_indices02(xp): ndimage.value_indices(data) -@skip_xp_backends("dask.array", reason="len on data-dependent output shapes") def test_value_indices03(xp): "Test different input array shapes, from 1-D to 4-D" for shape in [(36,), (18, 2), (3, 3, 4), (3, 3, 2, 2)]: a = xp.asarray((12*[1]+12*[2]+12*[3]), dtype=xp.int32) a = xp.reshape(a, shape) - trueKeys = xp.unique_values(a) + # convert to numpy to prevent issues with data-dependent shapes + # from unique for dask + trueKeys = np.asarray(xp.unique_values(a)) vi = ndimage.value_indices(a) - # TODO: list(trueKeys) needs len of trueKeys - # (which is unknown for dask since it is the result of an unique call) assert list(vi.keys()) == list(trueKeys) for k in [int(x) for x in trueKeys]: trueNdx = xp.nonzero(a == k) @@ -688,6 +687,7 @@ def test_sum_labels(xp): assert xp.all(output_sum == output_labels) assert_array_almost_equal(output_labels, xp.asarray([4.0, 0.0, 5.0])) + def test_mean01(xp): labels = np.asarray([1, 0], dtype=bool) labels = xp.asarray(labels) @@ -819,7 +819,6 @@ def test_maximum05(xp): assert ndimage.maximum(x) == -1 -@pytest.mark.filterwarnings("ignore::FutureWarning:dask") def test_median01(xp): a = xp.asarray([[1, 2, 0, 1], [5, 3, 0, 4], @@ -862,6 +861,7 @@ def test_median_gh12836_bool(xp): output = ndimage.median(a, labels=xp.ones((2,)), index=xp.asarray([1])) assert_array_almost_equal(output, xp.asarray([1.0])) + def test_median_no_int_overflow(xp): # test integer overflow fix on example from gh-12836 a = xp.asarray([65, 70], dtype=xp.int8) @@ -902,6 +902,7 @@ def test_variance04(xp): output = ndimage.variance(input) assert_almost_equal(output, xp.asarray(0.25), check_0d=False) + def test_variance05(xp): labels = xp.asarray([2, 2, 3]) for type in types: @@ -911,6 +912,7 @@ def test_variance05(xp): output = ndimage.variance(input, labels, 2) assert_almost_equal(output, xp.asarray(1.0), check_0d=False) + def test_variance06(xp): labels = xp.asarray([2, 2, 3, 3, 4]) with np.errstate(all='ignore'): @@ -1126,6 +1128,7 @@ def test_maximum_position06(xp): assert output[0] == (0, 0) assert output[1] == (1, 1) + @xfail_xp_backends("torch", reason="output[1] is wrong on pytorch") def test_maximum_position07(xp): # Test float labels diff --git a/scipy/ndimage/tests/test_morphology.py b/scipy/ndimage/tests/test_morphology.py index 0aa13cebff98..40ac409cbc84 100644 --- a/scipy/ndimage/tests/test_morphology.py +++ b/scipy/ndimage/tests/test_morphology.py @@ -2315,7 +2315,6 @@ def test_grey_erosion01(self, xp): [5, 5, 3, 3, 1]])) @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @skip_xp_backends("dask.array", reason="output array is read-only.") @xfail_xp_backends("cupy", reason="https://github.com/cupy/cupy/issues/8398") def test_grey_erosion01_overlap(self, xp): @@ -2521,6 +2520,8 @@ def test_white_tophat01(self, xp): tmp = ndimage.grey_opening(array, footprint=footprint, structure=structure) expected = array - tmp + # array created by xp.zeros is non-writeable for dask + # and jax output = xp.zeros(array.shape, dtype=array.dtype) ndimage.white_tophat(array, footprint=footprint, structure=structure, output=output) @@ -2574,6 +2575,8 @@ def test_white_tophat04(self, xp): structure = xp.asarray(structure) # Check that type mismatch is properly handled + # This output array is read-only for dask and jax + # TODO: investigate why for dask? output = xp.empty_like(array, dtype=xp.float64) ndimage.white_tophat(array, structure=structure, output=output) @@ -2588,6 +2591,7 @@ def test_black_tophat01(self, xp): tmp = ndimage.grey_closing(array, footprint=footprint, structure=structure) expected = tmp - array + # This output array is read-only for dask and jax output = xp.zeros(array.shape, dtype=array.dtype) ndimage.black_tophat(array, footprint=footprint, structure=structure, output=output) diff --git a/scipy/signal/_filter_design.py b/scipy/signal/_filter_design.py index b409b606ec66..6ee04edf5399 100644 --- a/scipy/signal/_filter_design.py +++ b/scipy/signal/_filter_design.py @@ -1791,7 +1791,10 @@ def normalize(b, a): raise ValueError("Denominator must have at least on nonzero element.") # Trim leading zeros in denominator, leave at least one. - den = np.trim_zeros(den, 'f') + + # cast to numpy by hand to avoid libraries like dask + # trying to dispatch this function via NEP 18 + den = np.trim_zeros(np.asarray(den), 'f') # Normalize transfer function num, den = num / den[0], den / den[0] diff --git a/scipy/signal/_signaltools.py b/scipy/signal/_signaltools.py index 014df0a9a7ee..ac9d2bb83d8c 100644 --- a/scipy/signal/_signaltools.py +++ b/scipy/signal/_signaltools.py @@ -4087,7 +4087,10 @@ def detrend(data: np.ndarray, axis: int = -1, else: dshape = data.shape N = dshape[axis] - bp = np.sort(np.unique(np.concatenate(np.atleast_1d(0, bp, N)))) + # Manually cast to numpy to prevent + # NEP18 dispatching for libraries like dask + bp = np.asarray(np.concatenate(np.atleast_1d(0, bp, N))) + bp = np.sort(np.unique(bp)) if np.any(bp > N): raise ValueError("Breakpoints must be less than length " "of data along given axis.") diff --git a/scipy/signal/tests/test_signaltools.py b/scipy/signal/tests/test_signaltools.py index bba2cfa3e989..8019e06eae83 100644 --- a/scipy/signal/tests/test_signaltools.py +++ b/scipy/signal/tests/test_signaltools.py @@ -291,7 +291,7 @@ def test_dtype_deprecation(self, xp): convolve(a, b) -@pytest.mark.filterwarnings("ignore::FutureWarning:dask") + @skip_xp_backends(cpu_only=True, exceptions=['cupy']) class TestConvolve2d: @@ -477,8 +477,12 @@ def test_consistency_convolve_funcs(self, xp): a = xp.arange(5) b = xp.asarray([3.2, 1.4, 3]) for mode in ['full', 'valid', 'same']: - xp_assert_close(xp.asarray(np.convolve(a, b, mode=mode)), - signal.convolve(a, b, mode=mode)) + # cast to numpy when calling np.convolve + # to prevent NEP18 dispatching e.g. from dask + xp_assert_close( + xp.asarray(np.convolve(np.asarray(a), np.asarray(b), mode=mode)), + signal.convolve(a, b, mode=mode) + ) xp_assert_close( xp.squeeze( signal.convolve2d(xp.asarray([a]), xp.asarray([b]), mode=mode), @@ -508,7 +512,7 @@ def test_large_array(self, xp): assert fails[0].size == 0 -@pytest.mark.filterwarnings("ignore::FutureWarning:dask") + @skip_xp_backends(cpu_only=True, exceptions=['cupy']) class TestFFTConvolve: @@ -845,7 +849,9 @@ def test_random_data(self, axes, xp): np.random.seed(1234) a = xp.asarray(np.random.rand(1233) + 1j * np.random.rand(1233)) b = xp.asarray(np.random.rand(1321) + 1j * np.random.rand(1321)) - expected = xp.asarray(np.convolve(a, b, 'full')) + # cast to numpy before np.convolve + # to prevent NEP 18 dispatching for e.g. dask + expected = xp.asarray(np.convolve(np.asarray(a), np.asarray(b), 'full')) if axes == '': out = fftconvolve(a, b, 'full') @@ -860,7 +866,9 @@ def test_random_data_axes(self, axes, xp): np.random.seed(1234) a = xp.asarray(np.random.rand(1233) + 1j * np.random.rand(1233)) b = xp.asarray(np.random.rand(1321) + 1j * np.random.rand(1321)) - expected = xp.asarray(np.convolve(a, b, 'full')) + # cast to numpy before np.convolve + # to prevent NEP 18 dispatching for e.g. dask + expected = xp.asarray(np.convolve(np.asarray(a), np.asarray(b), 'full')) a = xp.asarray(np.tile(a, [2, 1])) b = xp.asarray(np.tile(b, [2, 1])) @@ -913,7 +921,9 @@ def test_random_data_multidim_axes(self, axes, xp): def test_many_sizes(self, n, xp): a = xp.asarray(np.random.rand(n) + 1j * np.random.rand(n)) b = xp.asarray(np.random.rand(n) + 1j * np.random.rand(n)) - expected = xp.asarray(np.convolve(a, b, 'full')) + # cast to numpy before np.convolve + # to prevent NEP 18 dispatching for e.g. dask + expected = xp.asarray(np.convolve(np.asarray(a), np.asarray(b), 'full')) out = fftconvolve(a, b, 'full') xp_assert_close(out, expected, atol=1e-10) @@ -965,10 +975,7 @@ def gen_oa_shapes_eq(sizes): @skip_xp_backends("jax.numpy", reason="fails all around") -@skip_xp_backends("dask.array", - reason="Gets converted to numpy at some point for some reason. " - "Probably also suffers from boolean indexing issues" -) +@skip_xp_backends("dask.array", reason="wrong answer") class TestOAConvolve: @pytest.mark.slow() @pytest.mark.parametrize('shape_a_0, shape_b_0', @@ -2452,7 +2459,6 @@ def decimal(self, dt, xp): dt = np.cdouble # emulate np.finfo(dt).precision for complex64 and complex128 - # note: unwrapped dask has no finfo prec = {64: 15, 32: 6}[xp.finfo(dt).bits] return int(2 * prec / 3) @@ -4018,7 +4024,6 @@ def test_nonnumeric_dtypes(func, xp): class TestSOSFilt: # The test_rank* tests are pulled from _TestLinearFilter - @pytest.mark.filterwarnings("ignore::FutureWarning") # for dask @skip_xp_backends('jax.numpy', reason='buffer array is read-only') def test_rank1(self, dt, xp): dt = getattr(xp, dt) @@ -4050,7 +4055,6 @@ def test_rank1(self, dt, xp): y = sosfilt(sos, x) xp_assert_close(y, xp.asarray([1.0, 2, 2, 2, 2, 2, 2, 2])) - @pytest.mark.filterwarnings("ignore::FutureWarning") # for dask @skip_xp_backends('jax.numpy', reason='buffer array is read-only') def test_rank2(self, dt, xp): dt = getattr(xp, dt) @@ -4078,7 +4082,6 @@ def test_rank2(self, dt, xp): y = sosfilt(sos, x, axis=1) assert_array_almost_equal(y_r2_a1, y) - @pytest.mark.filterwarnings("ignore::FutureWarning") # for dask @skip_xp_backends('jax.numpy', reason='buffer array is read-only') def test_rank3(self, dt, xp): dt = getattr(xp, dt) @@ -4334,7 +4337,6 @@ def test_bp(self, xp): with assert_raises(ValueError): detrend(data, type="linear", bp=3) - @pytest.mark.filterwarnings("ignore::FutureWarning:dask") @pytest.mark.parametrize('bp', [np.array([0, 2]), [0, 2]]) def test_detrend_array_bp(self, bp, xp): # regression test for https://github.com/scipy/scipy/issues/18675 diff --git a/scipy/signal/tests/test_windows.py b/scipy/signal/tests/test_windows.py index 1a459ea0745f..c58a5e90d64e 100644 --- a/scipy/signal/tests/test_windows.py +++ b/scipy/signal/tests/test_windows.py @@ -9,8 +9,8 @@ from scipy.fft import fft from scipy.signal import windows, get_window, resample from scipy._lib._array_api import ( - xp_assert_close, xp_assert_equal, array_namespace, is_dask, - is_torch, is_jax, is_cupy, assert_array_almost_equal, SCIPY_DEVICE, + xp_assert_close, xp_assert_equal, array_namespace, is_torch, is_jax, is_cupy, + assert_array_almost_equal, SCIPY_DEVICE, ) skip_xp_backends = pytest.mark.skip_xp_backends @@ -852,16 +852,12 @@ def test_lanczos(self, xp): get_window('sinc', 6, xp=xp)) +@skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/2620") def test_windowfunc_basics(xp): for window_name, params in window_funcs: window = getattr(windows, window_name) if is_jax(xp) and window_name in ['taylor', 'chebwin']: pytest.skip(reason=f'{window_name = }: item assignment') - if is_dask(xp): - # https://github.com/dask/dask/issues/2620 - pytest.skip( - reason="dask doesn't support FFT along axis containing multiple chunks" - ) if window_name in ['dpss']: if is_cupy(xp): pytest.skip(reason='dpss window is not implemented for cupy') diff --git a/scipy/special/tests/test_logsumexp.py b/scipy/special/tests/test_logsumexp.py index 5a185f504235..7793e0b9a64f 100644 --- a/scipy/special/tests/test_logsumexp.py +++ b/scipy/special/tests/test_logsumexp.py @@ -64,7 +64,6 @@ def test_logsumexp(self, xp): nan = xp.asarray([xp.nan]) xp_assert_equal(logsumexp(inf), inf[0]) xp_assert_equal(logsumexp(-inf), -inf[0]) - xp_assert_equal(logsumexp(nan), nan[0]) xp_assert_equal(logsumexp(xp.asarray([-xp.inf, -xp.inf])), -inf[0]) @@ -110,7 +109,8 @@ def test_logsumexp_sign(self, xp): xp_assert_close(r, xp.asarray(1.)) xp_assert_equal(s, xp.asarray(-1.)) - @pytest.mark.filterwarnings("ignore::RuntimeWarning") + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") def test_logsumexp_sign_zero(self, xp): a = xp.asarray([1, 1]) b = xp.asarray([1, -1]) @@ -215,7 +215,9 @@ def test_gh18295(self, xp): ref = xp.logaddexp(a[0], a[1]) xp_assert_close(res, ref) - @pytest.mark.filterwarnings("ignore::FutureWarning:dask") + @pytest.mark.filterwarnings( + "ignore:The `numpy.copyto` function is not implemented:FutureWarning:dask" + ) @pytest.mark.parametrize('dtype', ['complex64', 'complex128']) def test_gh21610(self, xp, dtype): # gh-21610 noted that `logsumexp` could return imaginary components diff --git a/scipy/special/tests/test_support_alternative_backends.py b/scipy/special/tests/test_support_alternative_backends.py index bbc1bed78db3..ec50b5673299 100644 --- a/scipy/special/tests/test_support_alternative_backends.py +++ b/scipy/special/tests/test_support_alternative_backends.py @@ -52,8 +52,7 @@ def test_rel_entr_generic(dtype): @pytest.mark.fail_slow(5) # `reversed` is for developer convenience: test new function first = less waiting @pytest.mark.parametrize('f_name,n_args', reversed(array_special_func_map.items())) -# numpy warning filter doesn't work for dask -@pytest.mark.filterwarnings("ignore::RuntimeWarning") +@pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") @pytest.mark.parametrize('dtype', ['float32', 'float64']) @pytest.mark.parametrize('shapes', [[(0,)]*4, [tuple()]*4, [(10,)]*4, [(10,), (11, 1), (12, 1, 1), (13, 1, 1, 1)]]) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 0dbbef128859..fb953f893fbe 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -83,7 +83,9 @@ class TestTrimmedStats: # TODO: write these tests to handle missing values properly dprec = np.finfo(np.float64).precision - @pytest.mark.filterwarnings("ignore:invalid value encountered in divide") # for dask + @pytest.mark.filterwarnings( + "ignore:invalid value encountered in divide:RuntimeWarning:dask" + ) def test_tmean(self, xp): x = xp.asarray(X.tolist()) # use default dtype of xp @@ -130,7 +132,9 @@ def test_tmean(self, xp): y_true = [4.5, 10, 17, 21, xp.nan, xp.nan, xp.nan, xp.nan, xp.nan] xp_assert_close(y, xp.asarray(y_true)) - @pytest.mark.filterwarnings("ignore:invalid value encountered in divide") # for dask + @pytest.mark.filterwarnings( + "ignore:invalid value encountered in divide:RuntimeWarning:dask" + ) @skip_xp_backends('cupy', reason="cupy/cupy#8391") def test_tvar(self, xp): @@ -2328,7 +2332,7 @@ def test_regress_two_inputs_horizontal_line(self): def test_nist_norris(self): # If this causes a lint failure in the future, please note the history of # requests to allow extra whitespace in table formatting (e.g. gh-12367). - # Also see https://github.com/scipy/scipy/wiki/Why-do-we-not-use-an-auto%E2%80%90formatter%3F # noqa: E501 + # Also see https://github.com/scipy/scipy/wiki/Why-do-we-not-use-an-auto%E2%80%90formatter%3F # noqa: E501 x = [ 0.2, 337.4, 118.2, 884.6, 10.1, 226.5, 666.3, 996.3, 448.6, 777.0, 558.2, 0.4, 0.6, 775.5, 666.9, 338.0, 447.5, 11.6, @@ -3024,8 +3028,6 @@ def test_zscore_constant_input_1d(self, xp): z = stats.zscore(x) xp_assert_equal(z, xp.full(x.shape, xp.nan)) - @pytest.mark.filterwarnings("ignore::FutureWarning") # for dask - @pytest.mark.filterwarnings("ignore:invalid value encountered in divide") # for dask def test_zscore_constant_input_2d(self, xp): x = xp.asarray([[10.0, 10.0, 10.0, 10.0], [10.0, 11.0, 12.0, 13.0]]) @@ -3047,7 +3049,6 @@ def test_zscore_constant_input_2d(self, xp): z = stats.zscore(y, axis=None) xp_assert_equal(z, xp.full(y.shape, xp.asarray(xp.nan))) - @pytest.mark.filterwarnings("ignore:invalid value encountered in divide") # for dask def test_zscore_constant_input_2d_nan_policy_omit(self, xp): x = xp.asarray([[10.0, 10.0, 10.0, 10.0], [10.0, 11.0, 12.0, xp.nan], @@ -3067,7 +3068,6 @@ def test_zscore_constant_input_2d_nan_policy_omit(self, xp): [-s, 0, s, xp.nan], [-s2/2, s2, xp.nan, -s2/2]])) - @pytest.mark.filterwarnings("ignore:invalid value encountered in divide") # for dask def test_zscore_2d_all_nan_row(self, xp): # A row is all nan, and we use axis=1. x = xp.asarray([[np.nan, np.nan, np.nan, np.nan], @@ -3076,7 +3076,6 @@ def test_zscore_2d_all_nan_row(self, xp): xp_assert_close(z, xp.asarray([[np.nan, np.nan, np.nan, np.nan], [-1.0, -1.0, 1.0, 1.0]])) - @pytest.mark.filterwarnings("ignore::RuntimeWarning") # for dask, multiple warnings @skip_xp_backends('cupy', reason="cupy/cupy#8391") def test_zscore_2d_all_nan(self, xp): # The entire 2d array is nan, and we use axis=None. @@ -3133,7 +3132,6 @@ def test_zscore_masked_element_0_gh19039(self, xp): res = stats.zscore(y, axis=None) assert_equal(res[1:], np.nan) - @pytest.mark.filterwarnings("ignore::RuntimeWarning") # for dask, multiple warnings def test_degenerate_input(self, xp): scores = xp.arange(3) compare = xp.ones(3) @@ -3476,8 +3474,9 @@ def _assert_equal(self, actual, expect, *, shape=None, dtype=None): @pytest.mark.parametrize('size', [10, (10, 2)]) @pytest.mark.parametrize('m, c', product((0, 1, 2, 3), (None, 0, 1))) - # ignore warning for dask - @pytest.mark.filterwarnings("ignore:divide by zero encountered in divide") + @pytest.mark.filterwarnings( + "ignore:divide by zero encountered in divide:RuntimeWarning:dask" + ) def test_moment_center_scalar_moment(self, size, m, c, xp): rng = np.random.default_rng(6581432544381372042) x = xp.asarray(rng.random(size=size)) @@ -3488,8 +3487,9 @@ def test_moment_center_scalar_moment(self, size, m, c, xp): @pytest.mark.parametrize('size', [10, (10, 2)]) @pytest.mark.parametrize('c', (None, 0, 1)) - # ignore warning for dask - @pytest.mark.filterwarnings("ignore:divide by zero encountered in divide") + @pytest.mark.filterwarnings( + "ignore:divide by zero encountered in divide:RuntimeWarning:dask" + ) def test_moment_center_array_moment(self, size, c, xp): rng = np.random.default_rng(1706828300224046506) x = xp.asarray(rng.random(size=size)) @@ -3616,8 +3616,9 @@ def test_moment_accuracy(self): @pytest.mark.parametrize('order', [0, 1, 2, 3]) @pytest.mark.parametrize('axis', [-1, 0, 1]) @pytest.mark.parametrize('center', [None, 0]) - # ignore warning for dask - @pytest.mark.filterwarnings("ignore:divide by zero encountered in divide") + @pytest.mark.filterwarnings( + "ignore:divide by zero encountered in divide:RuntimeWarning:dask" + ) def test_moment_array_api(self, xp, order, axis, center): rng = np.random.default_rng(34823589259425) x = rng.random(size=(5, 6, 7)) @@ -3647,8 +3648,9 @@ def test_empty_1d(self, stat_fun, xp): res = stat_fun(x) xp_assert_equal(res, xp.asarray(xp.nan)) - # ignore warning for dask - @pytest.mark.filterwarnings("ignore:invalid value encountered in scalar divide") + @pytest.mark.filterwarnings( + "ignore:invalid value encountered in scalar divide:RuntimeWarning:dask" + ) @skip_xp_backends('jax.numpy', reason="JAX arrays do not support item assignment") def test_skewness(self, xp): @@ -3896,7 +3898,8 @@ class TestStudentTest: P1_1_l = P1_1 / 2 P1_1_g = 1 - (P1_1 / 2) - @pytest.mark.filterwarnings("ignore::RuntimeWarning") # for dask, multiple warnings + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") @skip_xp_backends(cpu_only=True, exceptions=["cupy", "jax.numpy"]) def test_onesample(self, xp): with suppress_warnings() as sup, \ @@ -6046,7 +6049,12 @@ def test_ttest_ind_with_uneq_var(xp): xp_assert_close(res.pvalue, pr_2D) -@pytest.mark.filterwarnings("ignore::RuntimeWarning") # for dask, multiple warnings +@pytest.mark.filterwarnings( + "ignore:divide by zero encountered:RuntimeWarning" +) # for dask +@pytest.mark.filterwarnings( + "ignore:invalid value encountered:RuntimeWarning" +) # for dask @pytest.mark.skip_xp_backends(cpu_only=True, exceptions=["cupy", "jax.numpy"]) def test_ttest_ind_zero_division(xp): # test zero division problem @@ -6090,8 +6098,10 @@ def test_ttest_ind_nan_2nd_arg(): assert_allclose(r2, (-2.5354627641855498, 0.052181400457057901), atol=1e-15) - -@pytest.mark.filterwarnings("ignore::FutureWarning") # for dask +# internal dask warning we can't do anything about +@pytest.mark.filterwarnings( + "ignore:The `numpy.copyto` function is not implemented:FutureWarning:dask" +) def test_ttest_ind_empty_1d_returns_nan(xp): # Two empty inputs should return a TtestResult containing nan # for both values. @@ -6107,7 +6117,10 @@ def test_ttest_ind_empty_1d_returns_nan(xp): @skip_xp_backends('cupy', reason='cupy/cupy#8391') -@pytest.mark.filterwarnings("ignore::FutureWarning") # for dask +# internal dask warning we can't do anything about +@pytest.mark.filterwarnings( + "ignore:The `numpy.copyto` function is not implemented:FutureWarning:dask" +) @pytest.mark.parametrize('b, expected_shape', [(np.empty((1, 5, 0)), (3, 5)), (np.empty((1, 0, 0)), (3, 0))]) @@ -6164,7 +6177,7 @@ def test_gh5686(xp): stats.ttest_ind_from_stats(mean1, std1, nobs1, mean2, std2, nobs2) -@pytest.mark.filterwarnings("ignore::RuntimeWarning") # for dask, multiple warnings +@pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning") @skip_xp_backends(cpu_only=True, exceptions=["cupy", "jax.numpy"]) def test_ttest_ind_from_stats_inputs_zero(xp): # Regression test for gh-6409. @@ -6177,7 +6190,7 @@ def test_ttest_ind_from_stats_inputs_zero(xp): @pytest.mark.skip_xp_backends(cpu_only=True, reason='Test uses ks_1samp') -@pytest.mark.filterwarnings("ignore::RuntimeWarning") # for dask, multiple warnings +@pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning") def test_ttest_uniform_pvalues(xp): # test that p-values are uniformly distributed under the null hypothesis rng = np.random.default_rng(246834602926842) @@ -6216,7 +6229,8 @@ def _convert_pvalue_alternative(t, p, alt, xp): @pytest.mark.slow -@pytest.mark.filterwarnings("ignore::RuntimeWarning") # for dask, multiple warnings +@pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") +@pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") def test_ttest_1samp_new(xp): n1, n2, n3 = (10, 15, 20) rvn1 = stats.norm.rvs(loc=5, scale=10, size=(n1, n2, n3)) @@ -6333,7 +6347,7 @@ def test_ttest_1samp_popmean_array(xp): class TestDescribe: - @pytest.mark.filterwarnings("ignore::RuntimeWarning") # for dask + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") def test_describe_scalar(self, xp): with suppress_warnings() as sup, \ np.errstate(invalid="ignore", divide="ignore"): @@ -6913,7 +6927,7 @@ def check_equal_pmean(*args, **kwargs): class TestHMean: - @pytest.mark.filterwarnings("ignore::RuntimeWarning") # for dask, multiple warnings + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") def test_0(self, xp): a = [1, 0, 2] desired = 0 @@ -6929,13 +6943,16 @@ def test_1d(self, xp): desired = 4. / (1. / 1 + 1. / 2 + 1. / 3 + 1. / 4) check_equal_hmean(a, desired, xp=xp) - @pytest.mark.filterwarnings("ignore::RuntimeWarning") # for dask, multiple warnings + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") def test_1d_with_zero(self, xp): a = np.array([1, 0]) desired = 0.0 check_equal_hmean(a, desired, xp=xp, rtol=0.0) - @pytest.mark.filterwarnings("ignore::RuntimeWarning") # for dask, multiple warnings + @pytest.mark.filterwarnings( + "ignore:divide by zero encountered:RuntimeWarning" + ) # for dask @skip_xp_backends('array_api_strict', reason=("`array_api_strict.where` `fillvalue` doesn't " "accept Python scalars. See data-apis/array-api#807.")) @@ -6959,7 +6976,7 @@ def test_2d_axis0(self, xp): desired = np.array([22.88135593, 39.13043478, 52.90076336, 65.45454545]) check_equal_hmean(a, desired, axis=0, xp=xp) - @pytest.mark.filterwarnings("ignore::RuntimeWarning") # for dask, multiple warnings + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") def test_2d_axis0_with_zero(self, xp): a = [[10, 0, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]] desired = np.array([22.88135593, 0.0, 52.90076336, 65.45454545]) @@ -6971,7 +6988,7 @@ def test_2d_axis1(self, xp): desired = np.array([19.2, 63.03939962, 103.80078637]) check_equal_hmean(a, desired, axis=1, xp=xp) - @pytest.mark.filterwarnings("ignore::RuntimeWarning") # for dask, multiple warnings + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") def test_2d_axis1_with_zero(self, xp): a = [[10, 0, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]] desired = np.array([0.0, 63.03939962, 103.80078637]) @@ -7030,7 +7047,9 @@ def test_weights_masked_1d_array(self, xp): class TestGMean: - @pytest.mark.filterwarnings("ignore:divide by zero encountered in log") # for dask + @pytest.mark.filterwarnings( + "ignore:divide by zero encountered in log:RuntimeWarning:dask" + ) def test_0(self, xp): a = [1, 0, 2] desired = 0 @@ -7083,7 +7102,9 @@ def test_large_values(self, xp): desired = 1e200 check_equal_gmean(a, desired, rtol=1e-13, xp=xp) - @pytest.mark.filterwarnings("ignore:divide by zero encountered in log") # for dask + @pytest.mark.filterwarnings( + "ignore:divide by zero encountered in log:RuntimeWarning:dask" + ) def test_1d_with_0(self, xp): # Test a 1d case with zero element a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 0] @@ -7091,7 +7112,9 @@ def test_1d_with_0(self, xp): with np.errstate(all='ignore'): check_equal_gmean(a, desired, xp=xp) - @pytest.mark.filterwarnings("ignore:invalid value encountered in log") # for dask + @pytest.mark.filterwarnings( + "ignore:invalid value encountered in log:RuntimeWarning:dask" + ) def test_1d_neg(self, xp): # Test a 1d case with negative element a = [10, 20, 30, 40, 50, 60, 70, 80, 90, -1] @@ -7163,7 +7186,8 @@ def test_1d(self, xp): desired = np.sqrt((1**2 + 2**2 + 3**2 + 4**2) / 4) check_equal_pmean(a, p, desired, xp=xp) - @pytest.mark.filterwarnings("ignore::RuntimeWarning") + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") def test_1d_with_zero(self, xp): a, p = np.array([1, 0]), -1 desired = 0.0 @@ -8177,7 +8201,6 @@ def test_weighted_stouffer(self, xp, weights, expected_statistic, expected_pvalu methods = ["fisher", "pearson", "tippett", "stouffer", "mudholkar_george"] @skip_xp_backends('dask.array', reason='no sorting in Dask') - @skip_xp_backends(cpu_only=True, exceptions=['cupy', 'jax.numpy']) @pytest.mark.parametrize("variant", ["single", "all", "random"]) @pytest.mark.parametrize("method", methods) def test_monotonicity(self, variant, method, xp): @@ -9404,7 +9427,8 @@ def test_non_broadcastable(self, xp): with pytest.raises(ValueError, match=message): _xp_mean(x, weights=w) - @pytest.mark.filterwarnings("ignore::RuntimeWarning") # for dask, multiple warnings + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") def test_special_cases(self, xp): # weights sum to zero weights = xp.asarray([-1., 0., 1.]) @@ -9418,7 +9442,9 @@ def test_special_cases(self, xp): res = _xp_mean(xp.asarray([1., 1., 2.]), weights=weights) xp_assert_close(res, xp.asarray(np.inf)) - @pytest.mark.filterwarnings("ignore::RuntimeWarning") # for dask, multiple warnings + @pytest.mark.filterwarnings( + "ignore:invalid value encountered:RuntimeWarning" + ) # for dask def test_nan_policy(self, xp): x = xp.arange(10.) mask = (x == 3) @@ -9472,7 +9498,9 @@ def test_empty(self, xp): ref = xp.asarray([]) xp_assert_equal(res, ref) - @pytest.mark.filterwarnings("ignore:overflow encountered in reduce") # for dask + @pytest.mark.filterwarnings( + "ignore:overflow encountered in reduce:RuntimeWarning" + ) # for dask def test_dtype(self, xp): max = xp.finfo(xp.float32).max x_np = np.asarray([max, max], dtype=np.float32) @@ -9501,7 +9529,6 @@ def test_integer(self, xp): @skip_xp_backends('jax.numpy', reason='JAX arrays do not support item assignment') @skip_xp_backends('dask.array', reason='boolean index assignment') class TestXP_Var: - @pytest.mark.parametrize('axis', [None, 1, -1, (-2, 2)]) @pytest.mark.parametrize('keepdims', [False, True]) @pytest.mark.parametrize('correction', [0, 1]) diff --git a/scipy/stats/tests/test_variation.py b/scipy/stats/tests/test_variation.py index f746bb47e32b..61bbfef2a12d 100644 --- a/scipy/stats/tests/test_variation.py +++ b/scipy/stats/tests/test_variation.py @@ -112,7 +112,7 @@ def test_bad_axis(self, xp): with pytest.raises((AxisError, IndexError)): variation(x, axis=10) - @pytest.mark.filterwarnings("ignore::RuntimeWarning") # for dask + @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") def test_mean_zero(self, xp): # Check that `variation` returns inf for a sequence that is not # identically zero but whose mean is zero. @@ -124,7 +124,7 @@ def test_mean_zero(self, xp): y2 = variation(x2, axis=1) xp_assert_equal(y2, xp.asarray([xp.inf, xp.inf])) - @pytest.mark.filterwarnings("ignore:invalid value encountered") # for dask + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") @pytest.mark.parametrize('x', [[0.]*5, [1, 2, np.inf, 9]]) def test_return_nan(self, x, xp): x = xp.asarray(x) @@ -132,7 +132,10 @@ def test_return_nan(self, x, xp): y = variation(x) xp_assert_equal(y, xp.asarray(xp.nan, dtype=x.dtype)) - @pytest.mark.filterwarnings("ignore::FutureWarning") # for dask + # internal dask warning we can't do anything about + @pytest.mark.filterwarnings( + "ignore:The `numpy.copyto` function is not implemented:FutureWarning:dask" + ) @pytest.mark.parametrize('axis, expected', [(0, []), (1, [np.nan]*3), (None, np.nan)]) def test_2d_size_zero_with_axis(self, axis, expected, xp): From 8f426f97494dee779e8c27c6aaf8b587a1a0dcdf Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Thu, 23 Jan 2025 08:39:53 -0800 Subject: [PATCH 2/4] Address more comments Co-authored-by: Guido Imperiale --- scipy/_lib/tests/test_array_api.py | 4 ---- scipy/conftest.py | 7 ++++--- scipy/ndimage/tests/test_filters.py | 22 +++++++++++----------- scipy/ndimage/tests/test_morphology.py | 12 ++++-------- scipy/signal/_filter_design.py | 8 ++++---- scipy/signal/_signaltools.py | 4 ++-- scipy/signal/tests/test_signaltools.py | 4 +--- 7 files changed, 26 insertions(+), 35 deletions(-) diff --git a/scipy/_lib/tests/test_array_api.py b/scipy/_lib/tests/test_array_api.py index a156386c3275..633de1c60a42 100644 --- a/scipy/_lib/tests/test_array_api.py +++ b/scipy/_lib/tests/test_array_api.py @@ -66,10 +66,6 @@ def test_array_api_extra_hook(self): with pytest.raises(TypeError, match=msg): xpx.atleast_nd("abc", ndim=0) - @skip_xp_backends( - "dask.array", - reason="raw dask.array namespace ignores copy=True in asarray" - ) def test_copy(self, xp): for _xp in [xp, None]: x = xp.asarray([1, 2, 3]) diff --git a/scipy/conftest.py b/scipy/conftest.py index ec2bd6287f0d..6d7d463f49e9 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -399,9 +399,10 @@ def skip_or_xfail_xp_backends(request: pytest.FixtureRequest, if 'cpu' not in d.device_kind: skip_or_xfail(reason=reason) elif xp.__name__ == 'dask.array' and 'dask.array' not in exceptions: - if xp_device(xp.empty(0)) != 'cpu': - skip_or_xfail(reason=reason) - + # dask has no device. 'cpu' is a hack introduced by array-api-compat. + # Force to revisit this when in the future + # dask adds proper device support + assert xp_device(xp.empty(0)) == 'cpu' # Following the approach of NumPy's conftest.py... # Use a known and persistent tmpdir for hypothesis' caches, which diff --git a/scipy/ndimage/tests/test_filters.py b/scipy/ndimage/tests/test_filters.py index 5ebc2d47214e..54df0517bae4 100644 --- a/scipy/ndimage/tests/test_filters.py +++ b/scipy/ndimage/tests/test_filters.py @@ -534,7 +534,7 @@ def test_correlate22(self, dtype_array, dtype_output, xp): assert_array_almost_equal(output, expected) @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @skip_xp_backends("dask.array", reason="output array is read-only.") + @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") @pytest.mark.parametrize('dtype_array', types) @pytest.mark.parametrize('dtype_output', types) def test_correlate23(self, dtype_array, dtype_output, xp): @@ -554,7 +554,7 @@ def test_correlate23(self, dtype_array, dtype_output, xp): assert_array_almost_equal(output, expected) @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @skip_xp_backends("dask.array", reason="output array is read-only.") + @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") @pytest.mark.parametrize('dtype_array', types) @pytest.mark.parametrize('dtype_output', types) def test_correlate24(self, dtype_array, dtype_output, xp): @@ -575,7 +575,7 @@ def test_correlate24(self, dtype_array, dtype_output, xp): assert_array_almost_equal(output, tcov) @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @skip_xp_backends("dask.array", reason="output array is read-only.") + @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") @pytest.mark.parametrize('dtype_array', types) @pytest.mark.parametrize('dtype_output', types) def test_correlate25(self, dtype_array, dtype_output, xp): @@ -881,7 +881,7 @@ def test_gauss06(self, xp): assert_array_almost_equal(output1, output2) @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @xfail_xp_backends("dask.array", reason="output keyword not handled properly") + @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") def test_gauss_memory_overlap(self, xp): input = xp.arange(100 * 100, dtype=xp.float32) input = xp.reshape(input, (100, 100)) @@ -1228,7 +1228,7 @@ def test_prewitt01(self, dtype, xp): assert_array_almost_equal(t, output) @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @xfail_xp_backends("dask.array", reason="output array is read-only.") + @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") @pytest.mark.parametrize('dtype', types + complex_types) def test_prewitt02(self, dtype, xp): if is_torch(xp) and dtype in ("uint16", "uint32", "uint64"): @@ -1291,7 +1291,7 @@ def test_sobel01(self, dtype, xp): assert_array_almost_equal(t, output) @skip_xp_backends("jax.numpy", reason="output array is read-only.",) - @skip_xp_backends("dask.array", reason="output array is read-only.") + @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") @pytest.mark.parametrize('dtype', types + complex_types) def test_sobel02(self, dtype, xp): if is_torch(xp) and dtype in ("uint16", "uint32", "uint64"): @@ -1352,7 +1352,7 @@ def test_laplace01(self, dtype, xp): assert_array_almost_equal(tmp1 + tmp2, output) @skip_xp_backends("jax.numpy", reason="output array is read-only",) - @skip_xp_backends("dask.array", reason="output array is read-only.") + @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") @pytest.mark.parametrize('dtype', ["int32", "float32", "float64", "complex64", "complex128"]) @@ -1383,7 +1383,7 @@ def test_gaussian_laplace01(self, dtype, xp): assert_array_almost_equal(tmp1 + tmp2, output) @skip_xp_backends("jax.numpy", reason="output array is read-only") - @skip_xp_backends("dask.array", reason="output array is read-only.") + @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") @pytest.mark.parametrize('dtype', ["int32", "float32", "float64", "complex64", "complex128"]) @@ -1400,7 +1400,7 @@ def test_gaussian_laplace02(self, dtype, xp): assert_array_almost_equal(tmp1 + tmp2, output) @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @skip_xp_backends("dask.array", reason="output array is read-only.") + @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") @pytest.mark.parametrize('dtype', types + complex_types) def test_generic_laplace01(self, dtype, xp): if is_torch(xp) and dtype in ("uint16", "uint32", "uint64"): @@ -1426,7 +1426,7 @@ def derivative2(input, axis, output, mode, cval, a, b): assert_array_almost_equal(tmp, output) @skip_xp_backends("jax.numpy", reason="output array is read-only") - @skip_xp_backends("dask.array", reason="output array is read-only.") + @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") @pytest.mark.parametrize('dtype', ["int32", "float32", "float64", "complex64", "complex128"]) @@ -1447,7 +1447,7 @@ def test_gaussian_gradient_magnitude01(self, dtype, xp): xp_assert_close(output, expected, rtol=1e-6, atol=1e-6) @skip_xp_backends("jax.numpy", reason="output array is read-only") - @skip_xp_backends("dask.array", reason="output array is read-only.") + @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") @pytest.mark.parametrize('dtype', ["int32", "float32", "float64", "complex64", "complex128"]) diff --git a/scipy/ndimage/tests/test_morphology.py b/scipy/ndimage/tests/test_morphology.py index 40ac409cbc84..a91f4af245ca 100644 --- a/scipy/ndimage/tests/test_morphology.py +++ b/scipy/ndimage/tests/test_morphology.py @@ -2510,7 +2510,7 @@ def test_morphological_laplace02(self, xp): assert_array_almost_equal(output, expected) @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @skip_xp_backends("dask.array", reason="output array is read-only.") + @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") def test_white_tophat01(self, xp): array = xp.asarray([[3, 2, 5, 1, 4], [7, 6, 9, 3, 5], @@ -2520,8 +2520,6 @@ def test_white_tophat01(self, xp): tmp = ndimage.grey_opening(array, footprint=footprint, structure=structure) expected = array - tmp - # array created by xp.zeros is non-writeable for dask - # and jax output = xp.zeros(array.shape, dtype=array.dtype) ndimage.white_tophat(array, footprint=footprint, structure=structure, output=output) @@ -2566,7 +2564,7 @@ def test_white_tophat03(self, xp): xp_assert_equal(output, expected) @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @skip_xp_backends("dask.array", reason="output array is read-only.") + @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") def test_white_tophat04(self, xp): array = np.eye(5, dtype=bool) structure = np.ones((3, 3), dtype=bool) @@ -2575,13 +2573,11 @@ def test_white_tophat04(self, xp): structure = xp.asarray(structure) # Check that type mismatch is properly handled - # This output array is read-only for dask and jax - # TODO: investigate why for dask? output = xp.empty_like(array, dtype=xp.float64) ndimage.white_tophat(array, structure=structure, output=output) @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @skip_xp_backends("dask.array", reason="output array is read-only.") + @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") def test_black_tophat01(self, xp): array = xp.asarray([[3, 2, 5, 1, 4], [7, 6, 9, 3, 5], @@ -2636,7 +2632,7 @@ def test_black_tophat03(self, xp): xp_assert_equal(output, expected) @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @skip_xp_backends("dask.array", reason="output array is read-only.") + @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") def test_black_tophat04(self, xp): array = xp.asarray(np.eye(5, dtype=bool)) structure = xp.asarray(np.ones((3, 3), dtype=bool)) diff --git a/scipy/signal/_filter_design.py b/scipy/signal/_filter_design.py index 6ee04edf5399..ff0bf93dfa78 100644 --- a/scipy/signal/_filter_design.py +++ b/scipy/signal/_filter_design.py @@ -1779,6 +1779,9 @@ def normalize(b, a): """ num, den = b, a + # cast to numpy by hand to avoid libraries like dask + # trying to dispatch this function via NEP 18 + den = np.asarray(den) den = np.atleast_1d(den) num = np.atleast_2d(_align_nums(num)) @@ -1791,10 +1794,7 @@ def normalize(b, a): raise ValueError("Denominator must have at least on nonzero element.") # Trim leading zeros in denominator, leave at least one. - - # cast to numpy by hand to avoid libraries like dask - # trying to dispatch this function via NEP 18 - den = np.trim_zeros(np.asarray(den), 'f') + den = np.trim_zeros(den, 'f') # Normalize transfer function num, den = num / den[0], den / den[0] diff --git a/scipy/signal/_signaltools.py b/scipy/signal/_signaltools.py index ac9d2bb83d8c..01736e346ac6 100644 --- a/scipy/signal/_signaltools.py +++ b/scipy/signal/_signaltools.py @@ -4089,8 +4089,8 @@ def detrend(data: np.ndarray, axis: int = -1, N = dshape[axis] # Manually cast to numpy to prevent # NEP18 dispatching for libraries like dask - bp = np.asarray(np.concatenate(np.atleast_1d(0, bp, N))) - bp = np.sort(np.unique(bp)) + bp = np.asarray(bp) + bp = np.sort(np.unique(np.concatenate(np.atleast_1d(0, bp, N)))) if np.any(bp > N): raise ValueError("Breakpoints must be less than length " "of data along given axis.") diff --git a/scipy/signal/tests/test_signaltools.py b/scipy/signal/tests/test_signaltools.py index 8019e06eae83..3296b1865263 100644 --- a/scipy/signal/tests/test_signaltools.py +++ b/scipy/signal/tests/test_signaltools.py @@ -291,7 +291,6 @@ def test_dtype_deprecation(self, xp): convolve(a, b) - @skip_xp_backends(cpu_only=True, exceptions=['cupy']) class TestConvolve2d: @@ -512,7 +511,6 @@ def test_large_array(self, xp): assert fails[0].size == 0 - @skip_xp_backends(cpu_only=True, exceptions=['cupy']) class TestFFTConvolve: @@ -975,7 +973,7 @@ def gen_oa_shapes_eq(sizes): @skip_xp_backends("jax.numpy", reason="fails all around") -@skip_xp_backends("dask.array", reason="wrong answer") +@xfail_xp_backends("dask.array", reason="wrong answer") class TestOAConvolve: @pytest.mark.slow() @pytest.mark.parametrize('shape_a_0, shape_b_0', From 209f65b524106cf55f6d1cde4a887e48f27e3486 Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Fri, 24 Jan 2025 08:04:16 -0800 Subject: [PATCH 3/4] update comment --- scipy/ndimage/tests/test_filters.py | 22 +++++++++++----------- scipy/ndimage/tests/test_morphology.py | 8 ++++---- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/scipy/ndimage/tests/test_filters.py b/scipy/ndimage/tests/test_filters.py index 54df0517bae4..eaa3b24bff0b 100644 --- a/scipy/ndimage/tests/test_filters.py +++ b/scipy/ndimage/tests/test_filters.py @@ -534,7 +534,7 @@ def test_correlate22(self, dtype_array, dtype_output, xp): assert_array_almost_equal(output, expected) @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") + @skip_xp_backends("dask.array", reason="converts dask output array to numpy") @pytest.mark.parametrize('dtype_array', types) @pytest.mark.parametrize('dtype_output', types) def test_correlate23(self, dtype_array, dtype_output, xp): @@ -554,7 +554,7 @@ def test_correlate23(self, dtype_array, dtype_output, xp): assert_array_almost_equal(output, expected) @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") + @skip_xp_backends("dask.array", reason="converts dask output array to numpy") @pytest.mark.parametrize('dtype_array', types) @pytest.mark.parametrize('dtype_output', types) def test_correlate24(self, dtype_array, dtype_output, xp): @@ -575,7 +575,7 @@ def test_correlate24(self, dtype_array, dtype_output, xp): assert_array_almost_equal(output, tcov) @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") + @skip_xp_backends("dask.array", reason="converts dask output array to numpy") @pytest.mark.parametrize('dtype_array', types) @pytest.mark.parametrize('dtype_output', types) def test_correlate25(self, dtype_array, dtype_output, xp): @@ -881,7 +881,7 @@ def test_gauss06(self, xp): assert_array_almost_equal(output1, output2) @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") + @skip_xp_backends("dask.array", reason="converts dask output array to numpy") def test_gauss_memory_overlap(self, xp): input = xp.arange(100 * 100, dtype=xp.float32) input = xp.reshape(input, (100, 100)) @@ -1228,7 +1228,7 @@ def test_prewitt01(self, dtype, xp): assert_array_almost_equal(t, output) @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") + @skip_xp_backends("dask.array", reason="converts dask output array to numpy") @pytest.mark.parametrize('dtype', types + complex_types) def test_prewitt02(self, dtype, xp): if is_torch(xp) and dtype in ("uint16", "uint32", "uint64"): @@ -1291,7 +1291,7 @@ def test_sobel01(self, dtype, xp): assert_array_almost_equal(t, output) @skip_xp_backends("jax.numpy", reason="output array is read-only.",) - @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") + @skip_xp_backends("dask.array", reason="converts dask output array to numpy") @pytest.mark.parametrize('dtype', types + complex_types) def test_sobel02(self, dtype, xp): if is_torch(xp) and dtype in ("uint16", "uint32", "uint64"): @@ -1352,7 +1352,7 @@ def test_laplace01(self, dtype, xp): assert_array_almost_equal(tmp1 + tmp2, output) @skip_xp_backends("jax.numpy", reason="output array is read-only",) - @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") + @skip_xp_backends("dask.array", reason="converts dask output array to numpy") @pytest.mark.parametrize('dtype', ["int32", "float32", "float64", "complex64", "complex128"]) @@ -1383,7 +1383,7 @@ def test_gaussian_laplace01(self, dtype, xp): assert_array_almost_equal(tmp1 + tmp2, output) @skip_xp_backends("jax.numpy", reason="output array is read-only") - @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") + @skip_xp_backends("dask.array", reason="converts dask output array to numpy") @pytest.mark.parametrize('dtype', ["int32", "float32", "float64", "complex64", "complex128"]) @@ -1400,7 +1400,7 @@ def test_gaussian_laplace02(self, dtype, xp): assert_array_almost_equal(tmp1 + tmp2, output) @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") + @skip_xp_backends("dask.array", reason="converts dask output array to numpy") @pytest.mark.parametrize('dtype', types + complex_types) def test_generic_laplace01(self, dtype, xp): if is_torch(xp) and dtype in ("uint16", "uint32", "uint64"): @@ -1426,7 +1426,7 @@ def derivative2(input, axis, output, mode, cval, a, b): assert_array_almost_equal(tmp, output) @skip_xp_backends("jax.numpy", reason="output array is read-only") - @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") + @skip_xp_backends("dask.array", reason="converts dask output array to numpy") @pytest.mark.parametrize('dtype', ["int32", "float32", "float64", "complex64", "complex128"]) @@ -1447,7 +1447,7 @@ def test_gaussian_gradient_magnitude01(self, dtype, xp): xp_assert_close(output, expected, rtol=1e-6, atol=1e-6) @skip_xp_backends("jax.numpy", reason="output array is read-only") - @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") + @skip_xp_backends("dask.array", reason="converts dask output array to numpy") @pytest.mark.parametrize('dtype', ["int32", "float32", "float64", "complex64", "complex128"]) diff --git a/scipy/ndimage/tests/test_morphology.py b/scipy/ndimage/tests/test_morphology.py index a91f4af245ca..f942b7431687 100644 --- a/scipy/ndimage/tests/test_morphology.py +++ b/scipy/ndimage/tests/test_morphology.py @@ -2510,7 +2510,7 @@ def test_morphological_laplace02(self, xp): assert_array_almost_equal(output, expected) @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") + @skip_xp_backends("dask.array", reason="converts dask output array to numpy") def test_white_tophat01(self, xp): array = xp.asarray([[3, 2, 5, 1, 4], [7, 6, 9, 3, 5], @@ -2564,7 +2564,7 @@ def test_white_tophat03(self, xp): xp_assert_equal(output, expected) @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") + @skip_xp_backends("dask.array", reason="converts dask output array to numpy") def test_white_tophat04(self, xp): array = np.eye(5, dtype=bool) structure = np.ones((3, 3), dtype=bool) @@ -2577,7 +2577,7 @@ def test_white_tophat04(self, xp): ndimage.white_tophat(array, structure=structure, output=output) @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") + @skip_xp_backends("dask.array", reason="converts dask output array to numpy") def test_black_tophat01(self, xp): array = xp.asarray([[3, 2, 5, 1, 4], [7, 6, 9, 3, 5], @@ -2632,7 +2632,7 @@ def test_black_tophat03(self, xp): xp_assert_equal(output, expected) @skip_xp_backends("jax.numpy", reason="output array is read-only.") - @skip_xp_backends("dask.array", reason="output kw doesn't make sense for dask") + @skip_xp_backends("dask.array", reason="converts dask output array to numpy") def test_black_tophat04(self, xp): array = xp.asarray(np.eye(5, dtype=bool)) structure = xp.asarray(np.ones((3, 3), dtype=bool)) From bdd6f3ae53a61af6fe559421661973316d38f92d Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Fri, 24 Jan 2025 14:47:19 -0800 Subject: [PATCH 4/4] Update scipy/ndimage/tests/test_morphology.py Co-authored-by: Guido Imperiale --- scipy/ndimage/tests/test_morphology.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scipy/ndimage/tests/test_morphology.py b/scipy/ndimage/tests/test_morphology.py index f942b7431687..ae5347bdc00c 100644 --- a/scipy/ndimage/tests/test_morphology.py +++ b/scipy/ndimage/tests/test_morphology.py @@ -2587,7 +2587,6 @@ def test_black_tophat01(self, xp): tmp = ndimage.grey_closing(array, footprint=footprint, structure=structure) expected = tmp - array - # This output array is read-only for dask and jax output = xp.zeros(array.shape, dtype=array.dtype) ndimage.black_tophat(array, footprint=footprint, structure=structure, output=output)