Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Also, that release drops support for Python 3.9, making Python 3.10 the minimum
* Added support for the `out` keyword to accept a tuple, bringing ufunc signatures into alignment with those in NumPy [#2664](https://github.com/IntelPython/dpnp/pull/2664)
* Unified public API definitions in `dpnp.linalg` and `dpnp.scipy` submodules [#2663](https://github.com/IntelPython/dpnp/pull/2663)
* Aligned the signature of `dpnp.reshape` function with Python array API by making `shape` a required argument [#2673](https://github.com/IntelPython/dpnp/pull/2673)
* Disallowed conversion of `dpnp.ndarray` with more than one dimension to Python scalars (`int`, `float`, `complex`) to align with NumPy 2.4.0 [#2694](https://github.com/IntelPython/dpnp/pull/2694)

### Deprecated

Expand Down
11 changes: 11 additions & 0 deletions dpnp/dpnp_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ def __bytes__(self):

def __complex__(self, /):
"""Convert a zero-dimensional array to a Python complex object."""
self._check_scalar_convertible()
return self._array_obj.__complex__()

def __contains__(self, value, /):
Expand Down Expand Up @@ -302,6 +303,7 @@ def __eq__(self, other, /):

def __float__(self, /):
"""Convert a zero-dimensional array to a Python float object."""
self._check_scalar_convertible()
return self._array_obj.__float__()

def __floordiv__(self, other, /):
Expand Down Expand Up @@ -393,6 +395,7 @@ def __index__(self, /):

def __int__(self, /):
"""Convert a zero-dimensional array to a Python int object."""
self._check_scalar_convertible()
Comment thread
vlad-perevezentsev marked this conversation as resolved.
Outdated
return self._array_obj.__int__()

def __invert__(self, /):
Expand Down Expand Up @@ -610,6 +613,14 @@ def __xor__(self, other, /):
r"""Return :math:`\text{self ^ value}`."""
return dpnp.bitwise_xor(self, other)

def _check_scalar_convertible(self):
"""Raise if array cannot be converted to a Python scalar."""
if self.ndim != 0:
raise TypeError(
"Only 0-dimensional dpnp.ndarray can be converted "
"to a Python scalar"
)

@staticmethod
def _create_from_usm_ndarray(usm_ary: dpt.usm_ndarray):
"""
Expand Down
76 changes: 50 additions & 26 deletions dpnp/tests/test_ndarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
assert_allclose,
assert_array_equal,
assert_equal,
assert_raises,
assert_raises_regex,
)

Expand All @@ -17,6 +18,7 @@
get_complex_dtypes,
get_float_dtypes,
has_support_aspect64,
numpy_version,
Comment thread
vlad-perevezentsev marked this conversation as resolved.
Outdated
)
from .third_party.cupy import testing

Expand Down Expand Up @@ -530,34 +532,56 @@ def test_print_dpnp_zero_shape():
assert result == expected


# Numpy will raise an error when converting a.ndim > 0 to a scalar
# TODO: Discuss dpnp behavior according to these future changes
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
@pytest.mark.parametrize("func", [bool, float, int, complex])
@pytest.mark.parametrize("shape", [tuple(), (1,), (1, 1), (1, 1, 1)])
@pytest.mark.parametrize(
"dtype", get_all_dtypes(no_float16=False, no_complex=True)
)
def test_scalar_type_casting(func, shape, dtype):
a = numpy.full(shape, 5, dtype=dtype)
ia = dpnp.full(shape, 5, dtype=dtype)
assert func(a) == func(ia)
@pytest.mark.parametrize("xp", [dpnp, numpy])
class TestPythonScalarConversion:
@pytest.mark.parametrize("shape", [tuple(), (1,), (1, 1), (1, 1, 1)])
Comment thread
vlad-perevezentsev marked this conversation as resolved.
Outdated
@pytest.mark.parametrize(
"dtype", get_all_dtypes(no_none=True, no_float16=False, no_complex=True)
)
def test_bool_conversion(self, xp, shape, dtype):
a = xp.full(shape, 5, dtype=dtype)
assert bool(a) == True

@pytest.mark.parametrize("shape", [tuple(), (1,), (1, 1), (1, 1, 1)])
@pytest.mark.parametrize(
"dtype", get_all_dtypes(no_none=True, no_float16=False, no_complex=True)
)
def test_bool_method_conversion(self, xp, shape, dtype):
a = xp.full(shape, 5, dtype=dtype)
assert a.__bool__() == True

# Numpy will raise an error when converting a.ndim > 0 to a scalar
# TODO: Discuss dpnp behavior according to these future changes
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
@pytest.mark.parametrize(
"method", ["__bool__", "__float__", "__int__", "__complex__"]
)
@pytest.mark.parametrize("shape", [tuple(), (1,), (1, 1), (1, 1, 1)])
@pytest.mark.parametrize(
"dtype", get_all_dtypes(no_float16=False, no_complex=True)
)
def test_scalar_type_casting_by_method(method, shape, dtype):
a = numpy.full(shape, 4.7, dtype=dtype)
ia = dpnp.full(shape, 4.7, dtype=dtype)
assert_allclose(getattr(a, method)(), getattr(ia, method)(), rtol=1e-06)
@testing.with_requires("numpy>=2.4")
@pytest.mark.parametrize("func", [float, int, complex])
@pytest.mark.parametrize("shape", [tuple(), (1,), (1, 1), (1, 1, 1)])
@pytest.mark.parametrize(
"dtype", get_all_dtypes(no_none=True, no_float16=False, no_complex=True)
)
def test_non_bool_conversion(self, xp, func, shape, dtype):
a = xp.full(shape, 5, dtype=dtype)
if len(shape) > 0:
# Non-0D arrays must not be convertible to Python numeric scalars
assert_raises(TypeError, func, a)
else:
# 0D arrays are allowed to convert
expected = 1 if xp.issubdtype(dtype, xp.bool) else 5
Comment thread
vlad-perevezentsev marked this conversation as resolved.
Outdated
assert func(a) == func(expected)

@testing.with_requires("numpy>=2.4")
@pytest.mark.parametrize("method", ["__float__", "__int__", "__complex__"])
@pytest.mark.parametrize("shape", [tuple(), (1,), (1, 1), (1, 1, 1)])
@pytest.mark.parametrize(
"dtype", get_all_dtypes(no_none=True, no_float16=False, no_complex=True)
)
def test_non_bool_method_conversion(self, xp, method, shape, dtype):
a = xp.full(shape, 5, dtype=dtype)
if len(shape) > 0:
assert_raises(TypeError, getattr(a, method))
else:
expected = 1 if xp.issubdtype(dtype, xp.bool) else 5
func = {"__float__": float, "__int__": int, "__complex__": complex}[
Comment thread
vlad-perevezentsev marked this conversation as resolved.
method
]
assert getattr(a, method)() == func(expected)


@pytest.mark.parametrize("shape", [(1,), (1, 1), (1, 1, 1)])
Expand Down
Loading