Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

* Disallowed scalar conversion for non-0D `tensor.usm_ndarray` per Python Array API specification [gh-2223](https://github.com/IntelPython/dpctl/pull/2223)

### Fixed

### Maintenance
Expand Down
12 changes: 12 additions & 0 deletions dpctl/tensor/_usmarray.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,14 @@ cdef object _as_zero_dim_ndarray(object usm_ary):
return view


cdef inline void _check_0d_scalar_conversion(object usm_ary) except *:
"Raise TypeError if array cannot be converted to a Python scalar"
if (usm_ary.ndim != 0):
raise TypeError(
"only 0-dimensional arrays can be converted to Python scalars"
)


cdef int _copy_writable(int lhs_flags, int rhs_flags):
"Copy the WRITABLE flag to lhs_flags from rhs_flags"
return (lhs_flags & ~USM_ARRAY_WRITABLE) | (rhs_flags & USM_ARRAY_WRITABLE)
Expand Down Expand Up @@ -1132,6 +1140,7 @@ cdef class usm_ndarray:

def __bool__(self):
if self.size == 1:
_check_0d_scalar_conversion(self)
view = _as_zero_dim_ndarray(self)
return view.__bool__()

Expand All @@ -1147,6 +1156,7 @@ cdef class usm_ndarray:

def __float__(self):
if self.size == 1:
_check_0d_scalar_conversion(self)
view = _as_zero_dim_ndarray(self)
return view.__float__()

Expand All @@ -1156,6 +1166,7 @@ cdef class usm_ndarray:

def __complex__(self):
if self.size == 1:
_check_0d_scalar_conversion(self)
view = _as_zero_dim_ndarray(self)
return view.__complex__()

Expand All @@ -1165,6 +1176,7 @@ cdef class usm_ndarray:

def __int__(self):
if self.size == 1:
_check_0d_scalar_conversion(self)
view = _as_zero_dim_ndarray(self)
return view.__int__()

Expand Down
61 changes: 35 additions & 26 deletions dpctl/tests/test_usm_ndarray_ctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import numpy as np
import pytest
from numpy.testing import assert_raises_regex

import dpctl
import dpctl.memory as dpm
Expand Down Expand Up @@ -282,34 +283,42 @@ def test_properties(dt):
V.mT


@pytest.mark.parametrize("func", [bool, float, int, complex])
@pytest.mark.parametrize("shape", [tuple(), (1,), (1, 1), (1, 1, 1)])
@pytest.mark.parametrize("dtype", ["|b1", "|u2", "|f4", "|i8"])
def test_copy_scalar_with_func(func, shape, dtype):
try:
X = dpt.usm_ndarray(shape, dtype=dtype)
except dpctl.SyclDeviceCreationError:
pytest.skip("No SYCL devices available")
Y = np.arange(1, X.size + 1, dtype=dtype)
X.usm_data.copy_from_host(Y.view("|u1"))
Y.shape = tuple()
assert func(X) == func(Y)


@pytest.mark.parametrize(
"method", ["__bool__", "__float__", "__int__", "__complex__"]
)
@pytest.mark.parametrize("shape", [tuple(), (1,), (1, 1), (1, 1, 1)])
@pytest.mark.parametrize("dtype", ["|b1", "|u2", "|f4", "|i8"])
def test_copy_scalar_with_method(method, shape, dtype):
try:
X = dpt.usm_ndarray(shape, dtype=dtype)
except dpctl.SyclDeviceCreationError:
pytest.skip("No SYCL devices available")
Y = np.arange(1, X.size + 1, dtype=dtype)
X.usm_data.copy_from_host(Y.view("|u1"))
Y.shape = tuple()
assert getattr(X, method)() == getattr(Y, method)()
class TestCopyScalar:
@pytest.mark.parametrize("func", [bool, float, int, complex])
def test_copy_scalar_with_func(self, func, shape, dtype):
try:
X = dpt.usm_ndarray(shape, dtype=dtype)
except dpctl.SyclDeviceCreationError:
pytest.skip("No SYCL devices available")
Y = np.arange(1, X.size + 1, dtype=dtype)
X.usm_data.copy_from_host(Y.view("|u1"))
Y = Y.reshape(())
# Non-0D numeric arrays must not be convertible to Python scalars
if len(shape) != 0:
assert_raises_regex(TypeError, "only 0-dimensional arrays", func, X)
else:
# 0D arrays are allowed to convert
assert func(X) == func(Y)

@pytest.mark.parametrize(
"method", ["__bool__", "__float__", "__int__", "__complex__"]
)
def test_copy_scalar_with_method(self, method, shape, dtype):
try:
X = dpt.usm_ndarray(shape, dtype=dtype)
except dpctl.SyclDeviceCreationError:
pytest.skip("No SYCL devices available")
Y = np.arange(1, X.size + 1, dtype=dtype)
X.usm_data.copy_from_host(Y.view("|u1"))
Y = Y.reshape(())
if len(shape) != 0:
assert_raises_regex(
TypeError, "only 0-dimensional arrays", getattr(X, method)
)
else:
assert getattr(X, method)() == getattr(Y, method)()


@pytest.mark.parametrize("func", [bool, float, int, complex])
Expand Down
18 changes: 10 additions & 8 deletions dpctl/tests/test_usm_ndarray_indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1430,11 +1430,12 @@ def test_nonzero_f_contig():
mask = dpt.zeros((5, 5), dtype="?", order="F")
mask[2, 3] = True

expected_res = (2, 3)
res = dpt.nonzero(mask)
expected_res = np.nonzero(dpt.asnumpy(mask))
result = dpt.nonzero(mask)

assert expected_res == res
assert mask[res]
for exp, res in zip(expected_res, result):
assert_array_equal(dpt.asnumpy(res), exp)
assert dpt.asnumpy(mask[result]).all()


def test_nonzero_compacting():
Expand All @@ -1448,11 +1449,12 @@ def test_nonzero_compacting():
mask[3, 2, 1] = True
mask_view = mask[..., :3]

expected_res = (3, 2, 1)
res = dpt.nonzero(mask_view)
expected_res = np.nonzero(dpt.asnumpy(mask_view))
result = dpt.nonzero(mask_view)

assert expected_res == res
assert mask_view[res]
for exp, res in zip(expected_res, result):
assert_array_equal(dpt.asnumpy(res), exp)
assert dpt.asnumpy(mask_view[result]).all()


def test_assign_scalar():
Expand Down
4 changes: 2 additions & 2 deletions dpctl/tests/test_usm_ndarray_manipulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1438,11 +1438,11 @@ def test_tile_size_1():
# test for gh-1627 behavior
res = dpt.tile(x1, reps)
assert x1.shape == res.shape
assert x1 == res
assert_array_equal(dpt.asnumpy(x1), dpt.asnumpy(res))

res = dpt.tile(x2, reps)
assert x2.shape == res.shape
assert x2 == res
assert_array_equal(dpt.asnumpy(x2), dpt.asnumpy(res))


def test_tile_prepends_axes():
Expand Down
2 changes: 1 addition & 1 deletion dpctl/tests/test_usm_ndarray_operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def test_mat_ops(namespace):
@pytest.mark.parametrize("namespace", [dpt, Dummy()])
def test_comp_ops(namespace):
try:
X = dpt.ones(1, dtype="u8")
X = dpt.asarray(1, dtype="u8")
except dpctl.SyclDeviceCreationError:
pytest.skip("No SYCL devices available")
X._set_namespace(namespace)
Expand Down
15 changes: 8 additions & 7 deletions dpctl/tests/test_usm_ndarray_sorting.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import numpy as np
import pytest
from numpy.testing import assert_array_equal

import dpctl.tensor as dpt
from dpctl.tests.helper import get_queue_or_skip, skip_if_dtype_not_supported
Expand Down Expand Up @@ -345,19 +346,19 @@ def test_radix_sort_size_1_axis():

x1 = dpt.ones((), dtype="i1")
r1 = dpt.sort(x1, kind="radixsort")
assert r1 == x1
assert_array_equal(dpt.asnumpy(r1), dpt.asnumpy(x1))

x2 = dpt.ones([1], dtype="i1")
r2 = dpt.sort(x2, kind="radixsort")
assert r2 == x2
assert_array_equal(dpt.asnumpy(r2), dpt.asnumpy(x2))

x3 = dpt.reshape(dpt.arange(10, dtype="i1"), (10, 1))
r3 = dpt.sort(x3, kind="radixsort")
assert dpt.all(r3 == x3)
assert dpt.asnumpy(r3 == x3).all()

x4 = dpt.reshape(dpt.arange(10, dtype="i1"), (1, 10))
r4 = dpt.sort(x4, axis=0, kind="radixsort")
assert dpt.all(r4 == x4)
assert dpt.asnumpy(r4 == x4).all()


def test_radix_argsort_size_1_axis():
Expand All @@ -369,12 +370,12 @@ def test_radix_argsort_size_1_axis():

x2 = dpt.ones([1], dtype="i1")
r2 = dpt.argsort(x2, kind="radixsort")
assert r2 == 0
assert dpt.asnumpy(r2 == 0).all()

x3 = dpt.reshape(dpt.arange(10, dtype="i1"), (10, 1))
r3 = dpt.argsort(x3, kind="radixsort")
assert dpt.all(r3 == 0)
assert dpt.asnumpy(r3 == 0).all()

x4 = dpt.reshape(dpt.arange(10, dtype="i1"), (1, 10))
r4 = dpt.argsort(x4, axis=0, kind="radixsort")
assert dpt.all(r4 == 0)
assert dpt.asnumpy(r4 == 0).all()
Loading