From 73526f3787245c829a39bec6c37702625574fcb0 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Tue, 14 Jan 2025 12:04:39 +0100 Subject: [PATCH 01/12] Implement ndarray.__array__ interface method --- dpnp/dpnp_array.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dpnp/dpnp_array.py b/dpnp/dpnp_array.py index d81e0157032f..2073af9f5572 100644 --- a/dpnp/dpnp_array.py +++ b/dpnp/dpnp_array.py @@ -185,7 +185,12 @@ def __and__(self, other): """Return ``self&value``.""" return dpnp.bitwise_and(self, other) - # '__array__', + def __array__(self, dtype=None, /, *, copy=None): + raise TypeError( + "Implicit conversion to a NumPy array is not allowed. " + "Please use `.asnumpy()` to construct a NumPy array explicitly." + ) + # '__array_finalize__', # '__array_function__', # '__array_interface__', From e0b1ee22634be31d8566331600a72b9739f87a19 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Tue, 14 Jan 2025 12:05:25 +0100 Subject: [PATCH 02/12] Enable third party test --- dpnp/tests/third_party/cupy/core_tests/test_ndarray.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dpnp/tests/third_party/cupy/core_tests/test_ndarray.py b/dpnp/tests/third_party/cupy/core_tests/test_ndarray.py index 808a8a8e5f99..47fd08556e55 100644 --- a/dpnp/tests/third_party/cupy/core_tests/test_ndarray.py +++ b/dpnp/tests/third_party/cupy/core_tests/test_ndarray.py @@ -691,7 +691,6 @@ def test_format(self, xp): return format(x, ".2f") -@pytest.mark.skip("implicit conversation to numpy does not raise an exception") class TestNdarrayImplicitConversion(unittest.TestCase): def test_array(self): From 26d3e7f5709a052305adfaf2710960588b9e9a45 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Tue, 14 Jan 2025 13:54:12 +0100 Subject: [PATCH 03/12] Update random tests --- .../third_party/cupy/random_tests/test_permutations.py | 2 +- dpnp/tests/third_party/cupy/random_tests/test_sample.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dpnp/tests/third_party/cupy/random_tests/test_permutations.py b/dpnp/tests/third_party/cupy/random_tests/test_permutations.py index eed47320e51b..e703856728a2 100644 --- a/dpnp/tests/third_party/cupy/random_tests/test_permutations.py +++ b/dpnp/tests/third_party/cupy/random_tests/test_permutations.py @@ -129,7 +129,7 @@ class TestPermutationSoundness(unittest.TestCase): def setUp(self): a = cupy.random.permutation(self.num) - self.a = a + self.a = a.asnumpy() # Test soundness diff --git a/dpnp/tests/third_party/cupy/random_tests/test_sample.py b/dpnp/tests/third_party/cupy/random_tests/test_sample.py index 52afc7139a95..b71ca1a43e7b 100644 --- a/dpnp/tests/third_party/cupy/random_tests/test_sample.py +++ b/dpnp/tests/third_party/cupy/random_tests/test_sample.py @@ -89,7 +89,7 @@ def test_bound_float2(self): def test_goodness_of_fit(self): mx = 5 trial = 100 - vals = [random.randint(mx) for _ in range(trial)] + vals = [random.randint(mx).asnumpy() for _ in range(trial)] counts = numpy.histogram(vals, bins=numpy.arange(mx + 1))[0] expected = numpy.array([float(trial) / mx] * mx) assert _hypothesis.chi_square_test(counts, expected) @@ -97,7 +97,7 @@ def test_goodness_of_fit(self): @_condition.repeat(3, 10) def test_goodness_of_fit_2(self): mx = 5 - vals = random.randint(mx, size=(5, 20)) + vals = random.randint(mx, size=(5, 20)).asnumpy() counts = numpy.histogram(vals, bins=numpy.arange(mx + 1))[0] expected = numpy.array([float(vals.size) / mx] * mx) assert _hypothesis.chi_square_test(counts, expected) @@ -191,7 +191,7 @@ def test_bound_2(self): def test_goodness_of_fit(self): mx = 5 trial = 100 - vals = [random.randint(0, mx) for _ in range(trial)] + vals = [random.randint(0, mx).asnumpy() for _ in range(trial)] counts = numpy.histogram(vals, bins=numpy.arange(mx + 1))[0] expected = numpy.array([float(trial) / mx] * mx) assert _hypothesis.chi_square_test(counts, expected) @@ -199,7 +199,7 @@ def test_goodness_of_fit(self): @_condition.repeat(3, 10) def test_goodness_of_fit_2(self): mx = 5 - vals = random.randint(0, mx, (5, 20)) + vals = random.randint(0, mx, (5, 20)).asnumpy() counts = numpy.histogram(vals, bins=numpy.arange(mx + 1))[0] expected = numpy.array([float(vals.size) / mx] * mx) assert _hypothesis.chi_square_test(counts, expected) From 20153a36a8ab81919e7afa6e547f42dc0ecfafd5 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Sun, 19 Jan 2025 12:38:14 +0100 Subject: [PATCH 04/12] Explicitly cast an input to numpy array within pad utils function _as_pairs --- dpnp/dpnp_utils/dpnp_utils_pad.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dpnp/dpnp_utils/dpnp_utils_pad.py b/dpnp/dpnp_utils/dpnp_utils_pad.py index 919b76a40a54..65f314bbf2fd 100644 --- a/dpnp/dpnp_utils/dpnp_utils_pad.py +++ b/dpnp/dpnp_utils/dpnp_utils_pad.py @@ -74,7 +74,12 @@ def _as_pairs(x, ndim, as_index=False): x = round(x) return ((x, x),) * ndim - x = numpy.array(x) + # explicitly cast input "x" to NumPy array + if dpnp.is_supported_array_type(x): + x = dpnp.asnumpy(x) + else: + x = numpy.array(x) + if as_index: x = numpy.asarray(numpy.round(x), dtype=numpy.intp) From ad43560bd1f868fab1ed8294636e4ff6625259fa Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Sun, 19 Jan 2025 12:48:29 +0100 Subject: [PATCH 05/12] Add proper casting of input arrays passed to assert_almost_equal --- dpnp/tests/__init__.py | 1 + dpnp/tests/testing/__init__.py | 1 + dpnp/tests/testing/array.py | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/dpnp/tests/__init__.py b/dpnp/tests/__init__.py index d7c95f9d701d..b26e444615be 100644 --- a/dpnp/tests/__init__.py +++ b/dpnp/tests/__init__.py @@ -3,5 +3,6 @@ from . import testing numpy.testing.assert_allclose = testing.assert_allclose +numpy.testing.assert_almost_equal = testing.assert_almost_equal numpy.testing.assert_array_equal = testing.assert_array_equal numpy.testing.assert_equal = testing.assert_equal diff --git a/dpnp/tests/testing/__init__.py b/dpnp/tests/testing/__init__.py index 138c46b56473..84a02507d271 100644 --- a/dpnp/tests/testing/__init__.py +++ b/dpnp/tests/testing/__init__.py @@ -1,5 +1,6 @@ from .array import ( assert_allclose, + assert_almost_equal, assert_array_equal, assert_equal, ) diff --git a/dpnp/tests/testing/array.py b/dpnp/tests/testing/array.py index 5aae4087187e..a61ecf980aeb 100644 --- a/dpnp/tests/testing/array.py +++ b/dpnp/tests/testing/array.py @@ -29,6 +29,7 @@ from dpnp.dpnp_utils import convert_item assert_allclose_orig = numpy.testing.assert_allclose +assert_almost_equal_orig = numpy.testing.assert_almost_equal assert_array_equal_orig = numpy.testing.assert_array_equal assert_equal_orig = numpy.testing.assert_equal @@ -44,6 +45,10 @@ def assert_allclose(result, expected, *args, **kwargs): _assert(assert_allclose_orig, result, expected, *args, **kwargs) +def assert_almost_equal(result, expected, *args, **kwargs): + _assert(assert_almost_equal_orig, result, expected, *args, **kwargs) + + def assert_array_equal(result, expected, *args, **kwargs): _assert(assert_array_equal_orig, result, expected, *args, **kwargs) From e7d6e63ad0777fb0191efc5634af733e9f0cb69d Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Sun, 19 Jan 2025 13:32:01 +0100 Subject: [PATCH 06/12] Update indexing tests --- dpnp/tests/test_indexing.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/dpnp/tests/test_indexing.py b/dpnp/tests/test_indexing.py index 595151008eb7..20a43a09ffa9 100644 --- a/dpnp/tests/test_indexing.py +++ b/dpnp/tests/test_indexing.py @@ -17,7 +17,12 @@ import dpnp from dpnp.dpnp_array import dpnp_array -from .helper import get_all_dtypes, get_integer_dtypes, has_support_aspect64 +from .helper import ( + get_all_dtypes, + get_array, + get_integer_dtypes, + has_support_aspect64, +) from .third_party.cupy import testing @@ -441,16 +446,15 @@ class TestPut: ) @pytest.mark.parametrize("ind_dt", get_all_dtypes(no_none=True)) @pytest.mark.parametrize( - "vals", + "ivals", [0, [1, 2], (2, 2), dpnp.array([1, 2])], ids=["0", "[1, 2]", "(2, 2)", "dpnp.array([1,2])"], ) @pytest.mark.parametrize("mode", ["clip", "wrap"]) - def test_input_1d(self, a_dt, indices, ind_dt, vals, mode): + def test_input_1d(self, a_dt, indices, ind_dt, ivals, mode): a = numpy.array([-2, -1, 0, 1, 2], dtype=a_dt) - b = numpy.copy(a) - ia = dpnp.array(a) - ib = dpnp.array(b) + b, vals = numpy.copy(a), get_array(numpy, ivals) + ia, ib = dpnp.array(a), dpnp.array(b) ind = numpy.array(indices, dtype=ind_dt) if ind_dt == dpnp.bool and ind.all(): @@ -459,18 +463,18 @@ def test_input_1d(self, a_dt, indices, ind_dt, vals, mode): if numpy.can_cast(ind_dt, numpy.intp, casting="safe"): numpy.put(a, ind, vals, mode=mode) - dpnp.put(ia, iind, vals, mode=mode) + dpnp.put(ia, iind, ivals, mode=mode) assert_array_equal(ia, a) b.put(ind, vals, mode=mode) - ib.put(iind, vals, mode=mode) + ib.put(iind, ivals, mode=mode) assert_array_equal(ib, b) else: assert_raises(TypeError, numpy.put, a, ind, vals, mode=mode) - assert_raises(TypeError, dpnp.put, ia, iind, vals, mode=mode) + assert_raises(TypeError, dpnp.put, ia, iind, ivals, mode=mode) assert_raises(TypeError, b.put, ind, vals, mode=mode) - assert_raises(TypeError, ib.put, iind, vals, mode=mode) + assert_raises(TypeError, ib.put, iind, ivals, mode=mode) @pytest.mark.parametrize("a_dt", get_all_dtypes(no_none=True)) @pytest.mark.parametrize( @@ -637,7 +641,7 @@ def test_values(self, arr_dt, idx_dt, ndim, values): ia, iind = dpnp.array(a), dpnp.array(ind) for axis in range(ndim): - numpy.put_along_axis(a, ind, values, axis) + numpy.put_along_axis(a, ind, get_array(numpy, values), axis) dpnp.put_along_axis(ia, iind, values, axis) assert_array_equal(ia, a) From cd8f68dbd1e59ea62e88763ea0d482f57979dc0b Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Sun, 19 Jan 2025 14:07:13 +0100 Subject: [PATCH 07/12] Add proper casting of input arrays passed to assert_array_almost_equal --- dpnp/tests/__init__.py | 1 + dpnp/tests/testing/__init__.py | 1 + dpnp/tests/testing/array.py | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/dpnp/tests/__init__.py b/dpnp/tests/__init__.py index b26e444615be..773a2f8367c4 100644 --- a/dpnp/tests/__init__.py +++ b/dpnp/tests/__init__.py @@ -4,5 +4,6 @@ numpy.testing.assert_allclose = testing.assert_allclose numpy.testing.assert_almost_equal = testing.assert_almost_equal +numpy.testing.assert_array_almost_equal = testing.assert_array_almost_equal numpy.testing.assert_array_equal = testing.assert_array_equal numpy.testing.assert_equal = testing.assert_equal diff --git a/dpnp/tests/testing/__init__.py b/dpnp/tests/testing/__init__.py index 84a02507d271..a45218211e2a 100644 --- a/dpnp/tests/testing/__init__.py +++ b/dpnp/tests/testing/__init__.py @@ -1,6 +1,7 @@ from .array import ( assert_allclose, assert_almost_equal, + assert_array_almost_equal, assert_array_equal, assert_equal, ) diff --git a/dpnp/tests/testing/array.py b/dpnp/tests/testing/array.py index a61ecf980aeb..df4db0855dd2 100644 --- a/dpnp/tests/testing/array.py +++ b/dpnp/tests/testing/array.py @@ -30,6 +30,7 @@ assert_allclose_orig = numpy.testing.assert_allclose assert_almost_equal_orig = numpy.testing.assert_almost_equal +assert_array_almost_equal_orig = numpy.testing.assert_array_almost_equal assert_array_equal_orig = numpy.testing.assert_array_equal assert_equal_orig = numpy.testing.assert_equal @@ -49,6 +50,10 @@ def assert_almost_equal(result, expected, *args, **kwargs): _assert(assert_almost_equal_orig, result, expected, *args, **kwargs) +def assert_array_almost_equal(result, expected, *args, **kwargs): + _assert(assert_array_almost_equal_orig, result, expected, *args, **kwargs) + + def assert_array_equal(result, expected, *args, **kwargs): _assert(assert_array_equal_orig, result, expected, *args, **kwargs) From 988d13aef1700a98db8e49f04fa4feee54958224 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Sun, 19 Jan 2025 17:28:45 +0100 Subject: [PATCH 08/12] Update dpnp.einsum_path to cast input to numpy array explicitly --- dpnp/dpnp_iface_linearalgebra.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dpnp/dpnp_iface_linearalgebra.py b/dpnp/dpnp_iface_linearalgebra.py index 54bc3f89941d..7e1b23a65892 100644 --- a/dpnp/dpnp_iface_linearalgebra.py +++ b/dpnp/dpnp_iface_linearalgebra.py @@ -548,6 +548,12 @@ def einsum_path(*operands, optimize="greedy", einsum_call=False): """ + # explicit casting to numpy array if applicable + operands = [ + dpnp.asnumpy(x) if dpnp.is_supported_array_type(x) else x + for x in operands + ] + return numpy.einsum_path( *operands, optimize=optimize, From 6df49b479d85695b7d8f7d371776eb48a88052a9 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Mon, 20 Jan 2025 12:32:25 +0100 Subject: [PATCH 09/12] Explicit cast shift to numpy array in dpnp.roll function --- dpnp/dpnp_iface_manipulation.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dpnp/dpnp_iface_manipulation.py b/dpnp/dpnp_iface_manipulation.py index a290abe90928..af5784ff526b 100644 --- a/dpnp/dpnp_iface_manipulation.py +++ b/dpnp/dpnp_iface_manipulation.py @@ -3207,10 +3207,14 @@ def roll(x, shift, axis=None): [3, 4, 0, 1, 2]]) """ - if axis is None: - return roll(x.reshape(-1), shift, 0).reshape(x.shape) usm_x = dpnp.get_usm_ndarray(x) + if dpnp.is_supported_array_type(shift): + shift = dpnp.asnumpy(shift) + + if axis is None: + return roll(dpt.reshape(usm_x, -1), shift, 0).reshape(x.shape) + usm_res = dpt.roll(usm_x, shift=shift, axis=axis) return dpnp_array._create_from_usm_ndarray(usm_res) From 6b9a918c4396a5f494b1e5e8eb81335060bc7d83 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Mon, 20 Jan 2025 12:58:28 +0100 Subject: [PATCH 10/12] Update tests for random state --- dpnp/tests/test_random_state.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dpnp/tests/test_random_state.py b/dpnp/tests/test_random_state.py index 7107a919c4e0..72afd5fcb241 100644 --- a/dpnp/tests/test_random_state.py +++ b/dpnp/tests/test_random_state.py @@ -15,7 +15,7 @@ from dpnp.dpnp_array import dpnp_array from dpnp.random import RandomState -from .helper import is_cpu_device +from .helper import get_array, is_cpu_device # aspects of default device: _def_device = dpctl.SyclQueue().sycl_device @@ -224,7 +224,7 @@ def test_fallback(self, loc, scale): # dpnp accepts only scalar as low and/or high, in other cases it will be a fallback to numpy actual = data.asnumpy() expected = numpy.random.RandomState(seed).normal( - loc=loc, scale=scale, size=size + loc=get_array(numpy, loc), scale=get_array(numpy, scale), size=size ) dtype = get_default_floating() @@ -557,7 +557,7 @@ def test_bounds_fallback(self, low, high): RandomState(seed).randint(low=low, high=high, size=size).asnumpy() ) expected = numpy.random.RandomState(seed).randint( - low=low, high=high, size=size + low=get_array(numpy, low), high=get_array(numpy, high), size=size ) assert_equal(actual, expected) @@ -1139,7 +1139,7 @@ def test_fallback(self, low, high): # dpnp accepts only scalar as low and/or high, in other cases it will be a fallback to numpy actual = data.asnumpy() expected = numpy.random.RandomState(seed).uniform( - low=low, high=high, size=size + low=get_array(numpy, low), high=get_array(numpy, high), size=size ) dtype = get_default_floating() From 8fe91668324e0d0c77df79fa41653436c580977e Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Mon, 20 Jan 2025 12:59:47 +0100 Subject: [PATCH 11/12] Fix a bug in test_search.py::TestWhere::test_ndim --- dpnp/tests/test_search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dpnp/tests/test_search.py b/dpnp/tests/test_search.py index 8578657baee6..693a3b86b9bd 100644 --- a/dpnp/tests/test_search.py +++ b/dpnp/tests/test_search.py @@ -272,7 +272,7 @@ def test_ndim(self): assert_array_equal(np_res, dpnp_res) np_res = numpy.where(c, a.T, b.T) - dpnp_res = numpy.where(ic, ia.T, ib.T) + dpnp_res = dpnp.where(ic, ia.T, ib.T) assert_array_equal(np_res, dpnp_res) def test_dtype_mix(self): From b131ba2361273e5535c90cbe738c6094197c29a9 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Mon, 20 Jan 2025 16:14:51 +0100 Subject: [PATCH 12/12] Update tests for dpnp.gradient --- dpnp/tests/third_party/cupy/math_tests/test_sumprod.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dpnp/tests/third_party/cupy/math_tests/test_sumprod.py b/dpnp/tests/third_party/cupy/math_tests/test_sumprod.py index cc58a8cc32a3..5817936784e7 100644 --- a/dpnp/tests/third_party/cupy/math_tests/test_sumprod.py +++ b/dpnp/tests/third_party/cupy/math_tests/test_sumprod.py @@ -1037,8 +1037,8 @@ def test_gradient_invalid_spacings1(self): def test_gradient_invalid_spacings2(self): # wrong length array in spacing shape = (32, 16) - spacing = (15, cupy.arange(shape[1] + 1)) for xp in [numpy, cupy]: + spacing = (15, xp.arange(shape[1] + 1)) x = testing.shaped_random(shape, xp) with pytest.raises(ValueError): xp.gradient(x, *spacing) @@ -1046,8 +1046,8 @@ def test_gradient_invalid_spacings2(self): def test_gradient_invalid_spacings3(self): # spacing array with ndim != 1 shape = (32, 16) - spacing = (15, cupy.arange(shape[0]).reshape(4, -1)) for xp in [numpy, cupy]: + spacing = (15, xp.arange(shape[0]).reshape(4, -1)) x = testing.shaped_random(shape, xp) with pytest.raises(ValueError): xp.gradient(x, *spacing)