Skip to content

Commit 356184a

Browse files
authored
Raise TypeError in dpnp.ndarray.__array__ (#2260)
The PR implements `dpnp.ndarray.__array__` method that raises `TypeError` to disallow implicit conversion of `dpnp.ndarray` to `numpy.ndarray`. While explicit conversion using `dpnp.ndarray.asnumpy` method is advised. Disallowing implicit conversion prevents `numpy.asarray(dpnp_arr)` from creating an array of 0D `dpnp.ndarray` instances, because using it is very costly due to multitude of short-array transfers from GPU to host.
1 parent 13816bd commit 356184a

File tree

14 files changed

+65
-28
lines changed

14 files changed

+65
-28
lines changed

dpnp/dpnp_array.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,12 @@ def __and__(self, other):
185185
"""Return ``self&value``."""
186186
return dpnp.bitwise_and(self, other)
187187

188-
# '__array__',
188+
def __array__(self, dtype=None, /, *, copy=None):
189+
raise TypeError(
190+
"Implicit conversion to a NumPy array is not allowed. "
191+
"Please use `.asnumpy()` to construct a NumPy array explicitly."
192+
)
193+
189194
# '__array_finalize__',
190195
# '__array_function__',
191196
# '__array_interface__',

dpnp/dpnp_iface_linearalgebra.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,12 @@ def einsum_path(*operands, optimize="greedy", einsum_call=False):
548548
549549
"""
550550

551+
# explicit casting to numpy array if applicable
552+
operands = [
553+
dpnp.asnumpy(x) if dpnp.is_supported_array_type(x) else x
554+
for x in operands
555+
]
556+
551557
return numpy.einsum_path(
552558
*operands,
553559
optimize=optimize,

dpnp/dpnp_iface_manipulation.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3207,10 +3207,14 @@ def roll(x, shift, axis=None):
32073207
[3, 4, 0, 1, 2]])
32083208
32093209
"""
3210-
if axis is None:
3211-
return roll(x.reshape(-1), shift, 0).reshape(x.shape)
32123210

32133211
usm_x = dpnp.get_usm_ndarray(x)
3212+
if dpnp.is_supported_array_type(shift):
3213+
shift = dpnp.asnumpy(shift)
3214+
3215+
if axis is None:
3216+
return roll(dpt.reshape(usm_x, -1), shift, 0).reshape(x.shape)
3217+
32143218
usm_res = dpt.roll(usm_x, shift=shift, axis=axis)
32153219
return dpnp_array._create_from_usm_ndarray(usm_res)
32163220

dpnp/dpnp_utils/dpnp_utils_pad.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,12 @@ def _as_pairs(x, ndim, as_index=False):
7474
x = round(x)
7575
return ((x, x),) * ndim
7676

77-
x = numpy.array(x)
77+
# explicitly cast input "x" to NumPy array
78+
if dpnp.is_supported_array_type(x):
79+
x = dpnp.asnumpy(x)
80+
else:
81+
x = numpy.array(x)
82+
7883
if as_index:
7984
x = numpy.asarray(numpy.round(x), dtype=numpy.intp)
8085

dpnp/tests/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@
33
from . import testing
44

55
numpy.testing.assert_allclose = testing.assert_allclose
6+
numpy.testing.assert_almost_equal = testing.assert_almost_equal
7+
numpy.testing.assert_array_almost_equal = testing.assert_array_almost_equal
68
numpy.testing.assert_array_equal = testing.assert_array_equal
79
numpy.testing.assert_equal = testing.assert_equal

dpnp/tests/test_indexing.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@
1717
import dpnp
1818
from dpnp.dpnp_array import dpnp_array
1919

20-
from .helper import get_all_dtypes, get_integer_dtypes, has_support_aspect64
20+
from .helper import (
21+
get_all_dtypes,
22+
get_array,
23+
get_integer_dtypes,
24+
has_support_aspect64,
25+
)
2126
from .third_party.cupy import testing
2227

2328

@@ -441,16 +446,15 @@ class TestPut:
441446
)
442447
@pytest.mark.parametrize("ind_dt", get_all_dtypes(no_none=True))
443448
@pytest.mark.parametrize(
444-
"vals",
449+
"ivals",
445450
[0, [1, 2], (2, 2), dpnp.array([1, 2])],
446451
ids=["0", "[1, 2]", "(2, 2)", "dpnp.array([1,2])"],
447452
)
448453
@pytest.mark.parametrize("mode", ["clip", "wrap"])
449-
def test_input_1d(self, a_dt, indices, ind_dt, vals, mode):
454+
def test_input_1d(self, a_dt, indices, ind_dt, ivals, mode):
450455
a = numpy.array([-2, -1, 0, 1, 2], dtype=a_dt)
451-
b = numpy.copy(a)
452-
ia = dpnp.array(a)
453-
ib = dpnp.array(b)
456+
b, vals = numpy.copy(a), get_array(numpy, ivals)
457+
ia, ib = dpnp.array(a), dpnp.array(b)
454458

455459
ind = numpy.array(indices, dtype=ind_dt)
456460
if ind_dt == dpnp.bool and ind.all():
@@ -459,18 +463,18 @@ def test_input_1d(self, a_dt, indices, ind_dt, vals, mode):
459463

460464
if numpy.can_cast(ind_dt, numpy.intp, casting="safe"):
461465
numpy.put(a, ind, vals, mode=mode)
462-
dpnp.put(ia, iind, vals, mode=mode)
466+
dpnp.put(ia, iind, ivals, mode=mode)
463467
assert_array_equal(ia, a)
464468

465469
b.put(ind, vals, mode=mode)
466-
ib.put(iind, vals, mode=mode)
470+
ib.put(iind, ivals, mode=mode)
467471
assert_array_equal(ib, b)
468472
else:
469473
assert_raises(TypeError, numpy.put, a, ind, vals, mode=mode)
470-
assert_raises(TypeError, dpnp.put, ia, iind, vals, mode=mode)
474+
assert_raises(TypeError, dpnp.put, ia, iind, ivals, mode=mode)
471475

472476
assert_raises(TypeError, b.put, ind, vals, mode=mode)
473-
assert_raises(TypeError, ib.put, iind, vals, mode=mode)
477+
assert_raises(TypeError, ib.put, iind, ivals, mode=mode)
474478

475479
@pytest.mark.parametrize("a_dt", get_all_dtypes(no_none=True))
476480
@pytest.mark.parametrize(
@@ -637,7 +641,7 @@ def test_values(self, arr_dt, idx_dt, ndim, values):
637641
ia, iind = dpnp.array(a), dpnp.array(ind)
638642

639643
for axis in range(ndim):
640-
numpy.put_along_axis(a, ind, values, axis)
644+
numpy.put_along_axis(a, ind, get_array(numpy, values), axis)
641645
dpnp.put_along_axis(ia, iind, values, axis)
642646
assert_array_equal(ia, a)
643647

dpnp/tests/test_random_state.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from dpnp.dpnp_array import dpnp_array
1616
from dpnp.random import RandomState
1717

18-
from .helper import is_cpu_device
18+
from .helper import get_array, is_cpu_device
1919

2020
# aspects of default device:
2121
_def_device = dpctl.SyclQueue().sycl_device
@@ -224,7 +224,7 @@ def test_fallback(self, loc, scale):
224224
# dpnp accepts only scalar as low and/or high, in other cases it will be a fallback to numpy
225225
actual = data.asnumpy()
226226
expected = numpy.random.RandomState(seed).normal(
227-
loc=loc, scale=scale, size=size
227+
loc=get_array(numpy, loc), scale=get_array(numpy, scale), size=size
228228
)
229229

230230
dtype = get_default_floating()
@@ -557,7 +557,7 @@ def test_bounds_fallback(self, low, high):
557557
RandomState(seed).randint(low=low, high=high, size=size).asnumpy()
558558
)
559559
expected = numpy.random.RandomState(seed).randint(
560-
low=low, high=high, size=size
560+
low=get_array(numpy, low), high=get_array(numpy, high), size=size
561561
)
562562
assert_equal(actual, expected)
563563

@@ -1139,7 +1139,7 @@ def test_fallback(self, low, high):
11391139
# dpnp accepts only scalar as low and/or high, in other cases it will be a fallback to numpy
11401140
actual = data.asnumpy()
11411141
expected = numpy.random.RandomState(seed).uniform(
1142-
low=low, high=high, size=size
1142+
low=get_array(numpy, low), high=get_array(numpy, high), size=size
11431143
)
11441144

11451145
dtype = get_default_floating()

dpnp/tests/test_search.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ def test_ndim(self):
272272
assert_array_equal(np_res, dpnp_res)
273273

274274
np_res = numpy.where(c, a.T, b.T)
275-
dpnp_res = numpy.where(ic, ia.T, ib.T)
275+
dpnp_res = dpnp.where(ic, ia.T, ib.T)
276276
assert_array_equal(np_res, dpnp_res)
277277

278278
def test_dtype_mix(self):

dpnp/tests/testing/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from .array import (
22
assert_allclose,
3+
assert_almost_equal,
4+
assert_array_almost_equal,
35
assert_array_equal,
46
assert_equal,
57
)

dpnp/tests/testing/array.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
from dpnp.dpnp_utils import convert_item
3030

3131
assert_allclose_orig = numpy.testing.assert_allclose
32+
assert_almost_equal_orig = numpy.testing.assert_almost_equal
33+
assert_array_almost_equal_orig = numpy.testing.assert_array_almost_equal
3234
assert_array_equal_orig = numpy.testing.assert_array_equal
3335
assert_equal_orig = numpy.testing.assert_equal
3436

@@ -44,6 +46,14 @@ def assert_allclose(result, expected, *args, **kwargs):
4446
_assert(assert_allclose_orig, result, expected, *args, **kwargs)
4547

4648

49+
def assert_almost_equal(result, expected, *args, **kwargs):
50+
_assert(assert_almost_equal_orig, result, expected, *args, **kwargs)
51+
52+
53+
def assert_array_almost_equal(result, expected, *args, **kwargs):
54+
_assert(assert_array_almost_equal_orig, result, expected, *args, **kwargs)
55+
56+
4757
def assert_array_equal(result, expected, *args, **kwargs):
4858
_assert(assert_array_equal_orig, result, expected, *args, **kwargs)
4959

dpnp/tests/third_party/cupy/core_tests/test_ndarray.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -691,7 +691,6 @@ def test_format(self, xp):
691691
return format(x, ".2f")
692692

693693

694-
@pytest.mark.skip("implicit conversation to numpy does not raise an exception")
695694
class TestNdarrayImplicitConversion(unittest.TestCase):
696695

697696
def test_array(self):

dpnp/tests/third_party/cupy/math_tests/test_sumprod.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,17 +1037,17 @@ def test_gradient_invalid_spacings1(self):
10371037
def test_gradient_invalid_spacings2(self):
10381038
# wrong length array in spacing
10391039
shape = (32, 16)
1040-
spacing = (15, cupy.arange(shape[1] + 1))
10411040
for xp in [numpy, cupy]:
1041+
spacing = (15, xp.arange(shape[1] + 1))
10421042
x = testing.shaped_random(shape, xp)
10431043
with pytest.raises(ValueError):
10441044
xp.gradient(x, *spacing)
10451045

10461046
def test_gradient_invalid_spacings3(self):
10471047
# spacing array with ndim != 1
10481048
shape = (32, 16)
1049-
spacing = (15, cupy.arange(shape[0]).reshape(4, -1))
10501049
for xp in [numpy, cupy]:
1050+
spacing = (15, xp.arange(shape[0]).reshape(4, -1))
10511051
x = testing.shaped_random(shape, xp)
10521052
with pytest.raises(ValueError):
10531053
xp.gradient(x, *spacing)

dpnp/tests/third_party/cupy/random_tests/test_permutations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ class TestPermutationSoundness(unittest.TestCase):
129129

130130
def setUp(self):
131131
a = cupy.random.permutation(self.num)
132-
self.a = a
132+
self.a = a.asnumpy()
133133

134134
# Test soundness
135135

dpnp/tests/third_party/cupy/random_tests/test_sample.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,15 @@ def test_bound_float2(self):
8989
def test_goodness_of_fit(self):
9090
mx = 5
9191
trial = 100
92-
vals = [random.randint(mx) for _ in range(trial)]
92+
vals = [random.randint(mx).asnumpy() for _ in range(trial)]
9393
counts = numpy.histogram(vals, bins=numpy.arange(mx + 1))[0]
9494
expected = numpy.array([float(trial) / mx] * mx)
9595
assert _hypothesis.chi_square_test(counts, expected)
9696

9797
@_condition.repeat(3, 10)
9898
def test_goodness_of_fit_2(self):
9999
mx = 5
100-
vals = random.randint(mx, size=(5, 20))
100+
vals = random.randint(mx, size=(5, 20)).asnumpy()
101101
counts = numpy.histogram(vals, bins=numpy.arange(mx + 1))[0]
102102
expected = numpy.array([float(vals.size) / mx] * mx)
103103
assert _hypothesis.chi_square_test(counts, expected)
@@ -191,15 +191,15 @@ def test_bound_2(self):
191191
def test_goodness_of_fit(self):
192192
mx = 5
193193
trial = 100
194-
vals = [random.randint(0, mx) for _ in range(trial)]
194+
vals = [random.randint(0, mx).asnumpy() for _ in range(trial)]
195195
counts = numpy.histogram(vals, bins=numpy.arange(mx + 1))[0]
196196
expected = numpy.array([float(trial) / mx] * mx)
197197
assert _hypothesis.chi_square_test(counts, expected)
198198

199199
@_condition.repeat(3, 10)
200200
def test_goodness_of_fit_2(self):
201201
mx = 5
202-
vals = random.randint(0, mx, (5, 20))
202+
vals = random.randint(0, mx, (5, 20)).asnumpy()
203203
counts = numpy.histogram(vals, bins=numpy.arange(mx + 1))[0]
204204
expected = numpy.array([float(vals.size) / mx] * mx)
205205
assert _hypothesis.chi_square_test(counts, expected)

0 commit comments

Comments
 (0)