diff --git a/CHANGELOG.md b/CHANGELOG.md index 099f31b23f1..2e9fed7b86e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added implementation of `dpnp.hanning` [#2358](https://github.com/IntelPython/dpnp/pull/2358) * Added implementation of `dpnp.blackman` [#2363](https://github.com/IntelPython/dpnp/pull/2363) * Added implementation of `dpnp.bartlett` [#2366](https://github.com/IntelPython/dpnp/pull/2366) +* Added implementation of `dpnp.convolve` [#2205](https://github.com/IntelPython/dpnp/pull/2205) * Added implementation of `dpnp.kaiser` [#2387](https://github.com/IntelPython/dpnp/pull/2387) ### Changed diff --git a/dpnp/dpnp_iface_manipulation.py b/dpnp/dpnp_iface_manipulation.py index f156ca6b796..28ba1a29d50 100644 --- a/dpnp/dpnp_iface_manipulation.py +++ b/dpnp/dpnp_iface_manipulation.py @@ -958,6 +958,9 @@ def atleast_1d(*arys): dpnp.check_supported_arrays_type(*arys) for ary in arys: if ary.ndim == 0: + # 0-d arrays cannot be empty + # 0-d arrays always have a size of 1, so + # reshape(1) is guaranteed to succeed result = ary.reshape(1) else: result = ary diff --git a/dpnp/dpnp_iface_mathematical.py b/dpnp/dpnp_iface_mathematical.py index 8d35d62b1a9..9f9d5dcca08 100644 --- a/dpnp/dpnp_iface_mathematical.py +++ b/dpnp/dpnp_iface_mathematical.py @@ -91,7 +91,6 @@ "clip", "conj", "conjugate", - "convolve", "copysign", "cross", "cumprod", @@ -792,24 +791,6 @@ def clip(a, /, min=None, max=None, *, out=None, order="K", **kwargs): conj = conjugate - -def convolve(a, v, mode="full"): - """ - Returns the discrete, linear convolution of two one-dimensional sequences. - - For full documentation refer to :obj:`numpy.convolve`. - - Examples - -------- - >>> ca = dpnp.convolve([1, 2, 3], [0, 1, 0.5]) - >>> print(ca) - [0. , 1. , 2.5, 4. , 1.5] - - """ - - return call_origin(numpy.convolve, a=a, v=v, mode=mode) - - _COPYSIGN_DOCSTRING = """ Composes a floating-point value with the magnitude of `x1_i` and the sign of `x2_i` for each element of input arrays `x1` and `x2`. diff --git a/dpnp/dpnp_iface_statistics.py b/dpnp/dpnp_iface_statistics.py index 4cade1d9ca1..3958127789a 100644 --- a/dpnp/dpnp_iface_statistics.py +++ b/dpnp/dpnp_iface_statistics.py @@ -62,6 +62,7 @@ "amax", "amin", "average", + "convolve", "corrcoef", "correlate", "cov", @@ -357,6 +358,146 @@ def average(a, axis=None, weights=None, returned=False, *, keepdims=False): return avg +def _convolve_impl(a, v, mode, method, rdtype): + l_pad, r_pad = _get_padding(a.size, v.size, mode) + + if method == "auto": + method = _choose_conv_method(a, v, rdtype) + + if method == "direct": + r = _run_native_sliding_dot_product1d(a, v[::-1], l_pad, r_pad, rdtype) + elif method == "fft": + r = _convolve_fft(a, v, l_pad, r_pad, rdtype) + else: + raise ValueError( + f"Unknown method: {method}. Supported methods: auto, direct, fft" + ) + + return r + + +def convolve(a, v, mode="full", method="auto"): + r""" + Returns the discrete, linear convolution of two one-dimensional sequences. + The convolution operator is often seen in signal processing, where it + models the effect of a linear time-invariant system on a signal [1]_. In + probability theory, the sum of two independent random variables is + distributed according to the convolution of their individual + distributions. + + If `v` is longer than `a`, the arrays are swapped before computation. + + For full documentation refer to :obj:`numpy.convolve`. + + Parameters + ---------- + a : {dpnp.ndarray, usm_ndarray} + First input array. + v : {dpnp.ndarray, usm_ndarray} + Second input array. + mode : {'full', 'valid', 'same'}, optional + - 'full': This returns the convolution + at each point of overlap, with an output shape of (N+M-1,). At + the end-points of the convolution, the signals do not overlap + completely, and boundary effects may be seen. + - 'same': Mode 'same' returns output of length ``max(M, N)``. Boundary + effects are still visible. + - 'valid': Mode 'valid' returns output of length + ``max(M, N) - min(M, N) + 1``. The convolution product is only given + for points where the signals overlap completely. Values outside + the signal boundary have no effect. + + Default: ``'full'``. + method : {'auto', 'direct', 'fft'}, optional + - 'direct': The convolution is determined directly from sums. + - 'fft': The Fourier Transform is used to perform the calculations. + This method is faster for long sequences but can have accuracy issues. + - 'auto': Automatically chooses direct or Fourier method based on + an estimate of which is faster. + + Note: Use of the FFT convolution on input containing NAN or INF + will lead to the entire output being NAN or INF. + Use ``method='direct'`` when your input contains NAN or INF values. + + Default: ``'auto'``. + + Returns + ------- + out : dpnp.ndarray + Discrete, linear convolution of `a` and `v`. + + See Also + -------- + :obj:`dpnp.correlate` : Cross-correlation of two 1-dimensional sequences. + + Notes + ----- + The discrete convolution operation is defined as + + .. math:: (a * v)_n = \sum_{m = -\infty}^{\infty} a_m v_{n - m} + + It can be shown that a convolution :math:`x(t) * y(t)` in time/space + is equivalent to the multiplication :math:`X(f) Y(f)` in the Fourier + domain, after appropriate padding (padding is necessary to prevent + circular convolution). Since multiplication is more efficient (faster) + than convolution, the function implements two approaches - direct and fft + which are regulated by the keyword `method`. + + References + ---------- + .. [1] Wikipedia, "Convolution", + https://en.wikipedia.org/wiki/Convolution + + Examples + -------- + Note how the convolution operator flips the second array + before "sliding" the two across one another: + + >>> import dpnp as np + >>> a = np.array([1, 2, 3], dtype=np.float32) + >>> v = np.array([0, 1, 0.5], dtype=np.float32) + >>> np.convolve(a, v) + array([0. , 1. , 2.5, 4. , 1.5], dtype=float32) + + Only return the middle values of the convolution. + Contains boundary effects, where zeros are taken + into account: + + >>> np.convolve(a, v, 'same') + array([1. , 2.5, 4. ], dtype=float32) + + The two arrays are of the same length, so there + is only one position where they completely overlap: + + >>> np.convolve(a, v, 'valid') + array([2.5], dtype=float32) + + """ + + a, v = dpnp.atleast_1d(a, v) + + if a.size == 0 or v.size == 0: + raise ValueError( + f"Array arguments cannot be empty. " + f"Received sizes: a.size={a.size}, v.size={v.size}" + ) + if a.ndim > 1 or v.ndim > 1: + raise ValueError( + f"Only 1-dimensional arrays are supported. " + f"Received shapes: a.shape={a.shape}, v.shape={v.shape}" + ) + + device = a.sycl_device + rdtype = result_type_for_device([a.dtype, v.dtype], device) + + if v.size > a.size: + a, v = v, a + + r = _convolve_impl(a, v, mode, method, rdtype) + + return dpnp.asarray(r, dtype=rdtype, order="C") + + def corrcoef(x, y=None, rowvar=True, *, dtype=None): """ Return Pearson product-moment correlation coefficients. @@ -714,17 +855,7 @@ def correlate(a, v, mode="valid", method="auto"): revert = True a, v = v, a - l_pad, r_pad = _get_padding(a.size, v.size, mode) - - if method == "auto": - method = _choose_conv_method(a, v, rdtype) - - if method == "direct": - r = _run_native_sliding_dot_product1d(a, v, l_pad, r_pad, rdtype) - elif method == "fft": - r = _convolve_fft(a, v[::-1], l_pad, r_pad, rdtype) - else: # pragma: no cover - raise ValueError(f"Unknown method: {method}") + r = _convolve_impl(a, v[::-1], mode, method, rdtype) if revert: r = r[::-1] diff --git a/dpnp/tests/helper.py b/dpnp/tests/helper.py index 54d66b815ad..da07e37949c 100644 --- a/dpnp/tests/helper.py +++ b/dpnp/tests/helper.py @@ -185,6 +185,19 @@ def generate_random_numpy_array( return a +def factor_to_tol(dtype, factor): + """ + Calculate the tolerance for comparing floating point and complex arrays. + The tolerance is based on the maximum resolution of the input dtype multiplied by the factor. + """ + + tol = 0 + if numpy.issubdtype(dtype, numpy.inexact): + tol = numpy.finfo(dtype).resolution + + return factor * tol + + def get_abs_array(data, dtype=None): if numpy.issubdtype(dtype, numpy.unsignedinteger): data = numpy.abs(data) diff --git a/dpnp/tests/test_mathematical.py b/dpnp/tests/test_mathematical.py index b66b65dc1c4..9d745ac5595 100644 --- a/dpnp/tests/test_mathematical.py +++ b/dpnp/tests/test_mathematical.py @@ -104,35 +104,6 @@ def test_conj_out(self, dtype): assert_dtype_allclose(result, expected) -@pytest.mark.usefixtures("allow_fall_back_on_numpy") -class TestConvolve: - def test_object(self): - d = [1.0] * 100 - k = [1.0] * 3 - assert_array_equal(dpnp.convolve(d, k)[2:-2], dpnp.full(98, 3.0)) - - def test_no_overwrite(self): - d = dpnp.ones(100) - k = dpnp.ones(3) - dpnp.convolve(d, k) - assert_array_equal(d, dpnp.ones(100)) - assert_array_equal(k, dpnp.ones(3)) - - def test_mode(self): - d = dpnp.ones(100) - k = dpnp.ones(3) - default_mode = dpnp.convolve(d, k) - full_mode = dpnp.convolve(d, k, mode="full") - assert_array_equal(full_mode, default_mode) - # integer mode - with assert_raises(ValueError): - dpnp.convolve(d, k, mode=-1) - assert_array_equal(dpnp.convolve(d, k, mode=2), full_mode) - # illegal arguments - with assert_raises(TypeError): - dpnp.convolve(d, k, mode=None) - - class TestClip: @pytest.mark.parametrize( "dtype", get_all_dtypes(no_bool=True, no_none=True, no_complex=True) diff --git a/dpnp/tests/test_statistics.py b/dpnp/tests/test_statistics.py index e9d303ce6b9..efcc0ae2043 100644 --- a/dpnp/tests/test_statistics.py +++ b/dpnp/tests/test_statistics.py @@ -13,6 +13,7 @@ from .helper import ( assert_dtype_allclose, + factor_to_tol, generate_random_numpy_array, get_all_dtypes, get_complex_dtypes, @@ -127,6 +128,187 @@ def test_avg_error(self): dpnp.average(a, axis=0, weights=w) +class TestConvolve: + @staticmethod + def _get_kwargs(mode=None, method=None): + dpnp_kwargs = {} + numpy_kwargs = {} + if mode is not None: + dpnp_kwargs["mode"] = mode + numpy_kwargs["mode"] = mode + if method is not None: + dpnp_kwargs["method"] = method + return dpnp_kwargs, numpy_kwargs + + @pytest.mark.parametrize( + "a, v", [([1], [1, 2, 3]), ([1, 2, 3], [1]), ([1, 2, 3], [1, 2])] + ) + @pytest.mark.parametrize("mode", [None, "full", "valid", "same"]) + @pytest.mark.parametrize("dtype", get_all_dtypes(no_bool=True)) + @pytest.mark.parametrize("method", [None, "auto", "direct", "fft"]) + def test_convolve(self, a, v, mode, dtype, method): + an = numpy.array(a, dtype=dtype) + vn = numpy.array(v, dtype=dtype) + ad = dpnp.array(an) + vd = dpnp.array(vn) + + dpnp_kwargs, numpy_kwargs = self._get_kwargs(mode, method) + + expected = numpy.convolve(an, vn, **numpy_kwargs) + result = dpnp.convolve(ad, vd, **dpnp_kwargs) + + assert_dtype_allclose(result, expected) + + @pytest.mark.parametrize("a_size", [1, 100, 10000]) + @pytest.mark.parametrize("v_size", [1, 100, 10000]) + @pytest.mark.parametrize("mode", ["full", "valid", "same"]) + @pytest.mark.parametrize("dtype", get_all_dtypes(no_none=True)) + @pytest.mark.parametrize("method", ["auto", "direct", "fft"]) + def test_convolve_random(self, a_size, v_size, mode, dtype, method): + if dtype in [numpy.int8, numpy.uint8, numpy.int16, numpy.uint16]: + pytest.skip("avoid overflow.") + an = generate_random_numpy_array( + a_size, dtype, low=-3, high=3, probability=0.9, seed_value=0 + ) + vn = generate_random_numpy_array( + v_size, dtype, low=-3, high=3, probability=0.9, seed_value=1 + ) + + ad = dpnp.array(an) + vd = dpnp.array(vn) + + dpnp_kwargs, numpy_kwargs = self._get_kwargs(mode, method) + + result = dpnp.convolve(ad, vd, **dpnp_kwargs) + expected = numpy.convolve(an, vn, **numpy_kwargs) + + if method != "fft" and ( + dpnp.issubdtype(dtype, dpnp.integer) or dtype == dpnp.bool + ): + # For 'direct' and 'auto' methods, we expect exact results for integer types + assert_array_equal(result, expected) + else: + if method == "direct": + # For 'direct' method we can use standard validation + # acceptable error depends on the kernel size + # while error grows linearly with the kernel size, + # this empirically found formula provides a good balance + # the resulting factor is 40 for kernel size = 1, + # 400 for kernel size = 100 and 4000 for kernel size = 10000 + factor = int(40 * (min(a_size, v_size) ** 0.5)) + assert_dtype_allclose(result, expected, factor=factor) + else: + rdtype = result.dtype + if dpnp.issubdtype(rdtype, dpnp.integer): + # 'fft' do its calculations in float + # and 'auto' could use fft + # also assert_dtype_allclose for integer types is + # always check for exact match + rdtype = dpnp.default_float_type(ad.device) + + result = result.astype(rdtype) + + if rdtype == dpnp.bool: + result = result.astype(dpnp.int32) + rdtype = result.dtype + + expected = expected.astype(rdtype) + + factor = 1000 + rtol = atol = factor_to_tol(rdtype, factor) + invalid = numpy.logical_not( + numpy.isclose( + result.asnumpy(), expected, rtol=rtol, atol=atol + ) + ) + + # When using the 'fft' method, we might encounter outliers. + # This usually happens when the resulting array contains values close to zero. + # For these outliers, the relative error can be significant. + # We can tolerate a few such outliers. + # max_outliers = 10 if expected.size > 1 else 0 + max_outliers = 10 if expected.size > 1 else 0 + if invalid.sum() > max_outliers: + # we already failed check, + # call assert_dtype_allclose just to report error nicely + assert_dtype_allclose(result, expected, factor=factor) + + def test_convolve_mode_error(self): + a = dpnp.arange(5) + v = dpnp.arange(3) + + # invalid mode + with pytest.raises(ValueError): + dpnp.convolve(a, v, mode="unknown") + + @pytest.mark.parametrize("a, v", [([], [1]), ([1], []), ([], [])]) + def test_convolve_empty(self, a, v): + a = dpnp.asarray(a) + v = dpnp.asarray(v) + + with pytest.raises(ValueError): + dpnp.convolve(a, v) + + @pytest.mark.parametrize("a, v", [([1], 2), (3, [4]), (5, 6)]) + def test_convolve_scalar(self, a, v): + an = numpy.asarray(a, dtype=numpy.float32) + vn = numpy.asarray(v, dtype=numpy.float32) + + ad = dpnp.asarray(a, dtype=numpy.float32) + vd = dpnp.asarray(v, dtype=numpy.float32) + + expected = numpy.convolve(an, vn) + result = dpnp.convolve(ad, vd) + + assert_dtype_allclose(result, expected) + + @pytest.mark.parametrize( + "a, v", + [ + ([[1, 2], [2, 3]], [1]), + ([1], [[1, 2], [2, 3]]), + ([[1, 2], [2, 3]], [[1, 2], [2, 3]]), + ], + ) + def test_convolve_shape_error(self, a, v): + a = dpnp.asarray(a) + v = dpnp.asarray(v) + + with pytest.raises(ValueError): + dpnp.convolve(a, v) + + @pytest.mark.parametrize("size", [2, 10**1, 10**2, 10**3, 10**4, 10**5]) + def test_convolve_different_sizes(self, size): + a = generate_random_numpy_array( + size, dtype=numpy.float32, low=0, high=1, seed_value=0 + ) + v = generate_random_numpy_array( + size // 2, dtype=numpy.float32, low=0, high=1, seed_value=1 + ) + + ad = dpnp.array(a) + vd = dpnp.array(v) + + expected = numpy.convolve(a, v) + result = dpnp.convolve(ad, vd, method="direct") + + assert_dtype_allclose(result, expected, factor=20) + + def test_convolve_another_sycl_queue(self): + a = dpnp.arange(5, sycl_queue=dpctl.SyclQueue()) + v = dpnp.arange(3, sycl_queue=dpctl.SyclQueue()) + + with pytest.raises(ValueError): + dpnp.convolve(a, v) + + def test_convolve_unkown_method(self): + a = dpnp.arange(5) + v = dpnp.arange(3) + + with pytest.raises(ValueError): + dpnp.convolve(a, v, method="unknown") + + class TestCorrcoef: @pytest.mark.usefixtures( "suppress_divide_invalid_numpy_warnings", @@ -194,9 +376,6 @@ def _get_kwargs(mode=None, method=None): dpnp_kwargs["method"] = method return dpnp_kwargs, numpy_kwargs - def setup_method(self): - numpy.random.seed(0) - @pytest.mark.parametrize( "a, v", [([1], [1, 2, 3]), ([1, 2, 3], [1]), ([1, 2, 3], [1, 2])] ) @@ -222,13 +401,13 @@ def test_correlate(self, a, v, mode, dtype, method): @pytest.mark.parametrize("dtype", get_all_dtypes(no_none=True)) @pytest.mark.parametrize("method", ["auto", "direct", "fft"]) def test_correlate_random(self, a_size, v_size, mode, dtype, method): - if dtype in [numpy.int8, numpy.uint8]: + if dtype in [numpy.int8, numpy.uint8, numpy.int16, numpy.uint16]: pytest.skip("avoid overflow.") an = generate_random_numpy_array( - a_size, dtype, low=-3, high=3, probability=0.9 + a_size, dtype, low=-3, high=3, probability=0.9, seed_value=0 ) vn = generate_random_numpy_array( - v_size, dtype, low=-3, high=3, probability=0.9 + v_size, dtype, low=-3, high=3, probability=0.9, seed_value=1 ) ad = dpnp.array(an) @@ -239,17 +418,12 @@ def test_correlate_random(self, a_size, v_size, mode, dtype, method): result = dpnp.correlate(ad, vd, **dpnp_kwargs) expected = numpy.correlate(an, vn, **numpy_kwargs) - rdtype = result.dtype - if dpnp.issubdtype(rdtype, dpnp.integer): - rdtype = dpnp.default_float_type(ad.device) - if method != "fft" and ( dpnp.issubdtype(dtype, dpnp.integer) or dtype == dpnp.bool ): # For 'direct' and 'auto' methods, we expect exact results for integer types assert_array_equal(result, expected) else: - result = result.astype(rdtype) if method == "direct": expected = numpy.correlate(an, vn, **numpy_kwargs) # For 'direct' method we can use standard validation @@ -261,28 +435,40 @@ def test_correlate_random(self, a_size, v_size, mode, dtype, method): factor = int(40 * (min(a_size, v_size) ** 0.5)) assert_dtype_allclose(result, expected, factor=factor) else: - rtol = 1e-3 - atol = 1e-3 + rdtype = result.dtype + if dpnp.issubdtype(rdtype, dpnp.integer): + # 'fft' do its calculations in float + # and 'auto' could use fft + # also assert_dtype_allclose for integer types is + # always check for exact match + rdtype = dpnp.default_float_type(ad.device) + + result = result.astype(rdtype) - if rdtype == dpnp.float64 or rdtype == dpnp.complex128: - rtol = 1e-6 - atol = 1e-6 - elif rdtype == dpnp.bool: + if rdtype == dpnp.bool: result = result.astype(dpnp.int32) rdtype = result.dtype expected = expected.astype(rdtype) - diff = numpy.abs(result.asnumpy() - expected) - invalid = diff > atol + rtol * numpy.abs(expected) + factor = 1000 + rtol = atol = factor_to_tol(rdtype, factor) + invalid = numpy.logical_not( + numpy.isclose( + result.asnumpy(), expected, rtol=rtol, atol=atol + ) + ) # When using the 'fft' method, we might encounter outliers. # This usually happens when the resulting array contains values close to zero. # For these outliers, the relative error can be significant. # We can tolerate a few such outliers. + # max_outliers = 10 if expected.size > 1 else 0 max_outliers = 10 if expected.size > 1 else 0 if invalid.sum() > max_outliers: - assert_dtype_allclose(result, expected, factor=1000) + # we already failed check, + # call assert_dtype_allclose just to report error nicely + assert_dtype_allclose(result, expected, factor=factor) def test_correlate_mode_error(self): a = dpnp.arange(5) @@ -317,8 +503,12 @@ def test_correlate_shape_error(self, a, v): @pytest.mark.parametrize("size", [2, 10**1, 10**2, 10**3, 10**4, 10**5]) def test_correlate_different_sizes(self, size): - a = numpy.random.rand(size).astype(numpy.float32) - v = numpy.random.rand(size // 2).astype(numpy.float32) + a = generate_random_numpy_array( + size, dtype=numpy.float32, low=0, high=1, seed_value=0 + ) + v = generate_random_numpy_array( + size // 2, dtype=numpy.float32, low=0, high=1, seed_value=1 + ) ad = dpnp.array(a) vd = dpnp.array(v) diff --git a/dpnp/tests/test_sycl_queue.py b/dpnp/tests/test_sycl_queue.py index b6ab0ace40e..b0112702e30 100644 --- a/dpnp/tests/test_sycl_queue.py +++ b/dpnp/tests/test_sycl_queue.py @@ -366,6 +366,7 @@ def test_1in_1out(func, data, device): pytest.param("arctan2", [-1, +1, +1, -1], [-1, -1, +1, +1]), pytest.param("compress", [0, 1, 1, 0], [0, 1, 2, 3]), pytest.param("copysign", [0.0, 1.0, 2.0], [-1.0, 0.0, 1.0]), + pytest.param("convolve", [1, 2, 3], [4, 5, 6]), pytest.param( "corrcoef", [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]], diff --git a/dpnp/tests/test_usm_type.py b/dpnp/tests/test_usm_type.py index 3f922dbbdac..1d512ce111a 100644 --- a/dpnp/tests/test_usm_type.py +++ b/dpnp/tests/test_usm_type.py @@ -652,6 +652,7 @@ def test_1in_1out(func, data, usm_type): pytest.param("copysign", [0.0, 1.0, 2.0], [-1.0, 0.0, 1.0]), pytest.param("cross", [1.0, 2.0, 3.0], [4.0, 5.0, 6.0]), pytest.param("digitize", [0.2, 6.4, 3.0], [0.0, 1.0, 2.5, 4.0]), + pytest.param("convolve", [1, 2, 3], [0, 1, 0.5]), pytest.param( "corrcoef", [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]], diff --git a/dpnp/tests/third_party/cupy/math_tests/test_misc.py b/dpnp/tests/third_party/cupy/math_tests/test_misc.py index 3fd562798c3..7746c56d325 100644 --- a/dpnp/tests/third_party/cupy/math_tests/test_misc.py +++ b/dpnp/tests/third_party/cupy/math_tests/test_misc.py @@ -531,7 +531,6 @@ def test_heaviside_nan_inf(self, xp, dtype_1, dtype_2): } ) ) -@pytest.mark.skip("convolve() is not implemented yet") class TestConvolveShapeCombination: @testing.for_all_dtypes(no_float16=True) @@ -542,7 +541,6 @@ def test_convolve(self, xp, dtype): return xp.convolve(a, b, mode=self.mode) -@pytest.mark.skip("convolve() is not implemented yet") @pytest.mark.parametrize("mode", ["valid", "same", "full"]) class TestConvolve: @@ -561,21 +559,20 @@ def test_convolve_large_non_contiguous(self, xp, dtype, mode): return xp.convolve(a[200::], b[10::70], mode=mode) @testing.for_all_dtypes_combination(names=["dtype1", "dtype2"]) - @testing.numpy_cupy_allclose(rtol=1e-2) + @testing.numpy_cupy_allclose(rtol=1e-2, type_check=has_support_aspect64()) def test_convolve_diff_types(self, xp, dtype1, dtype2, mode): a = testing.shaped_random((200,), xp, dtype1) b = testing.shaped_random((100,), xp, dtype2) return xp.convolve(a, b, mode=mode) -@pytest.mark.skip("convolve() is not implemented yet") @testing.parameterize(*testing.product({"mode": ["valid", "same", "full"]})) class TestConvolveInvalid: @testing.for_all_dtypes() def test_convolve_empty(self, dtype): for xp in (numpy, cupy): - a = xp.zeros((0,), dtype) + a = xp.zeros((0,), dtype=dtype) with pytest.raises(ValueError): xp.convolve(a, a, mode=self.mode)