Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implementation of dpnp.require #2036

Merged
merged 11 commits into from
Sep 16, 2024
118 changes: 103 additions & 15 deletions dpnp/dpnp_iface_manipulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"permute_dims",
"ravel",
"repeat",
"require",
"reshape",
"result_type",
"roll",
Expand Down Expand Up @@ -646,12 +647,8 @@ def atleast_1d(*arys):
"""

res = []
dpnp.check_supported_arrays_type(*arys)
for ary in arys:
if not dpnp.is_supported_array_type(ary):
raise TypeError(
"Each input array must be any of supported type, "
f"but got {type(ary)}"
)
if ary.ndim == 0:
result = ary.reshape(1)
else:
Expand Down Expand Up @@ -704,12 +701,8 @@ def atleast_2d(*arys):
"""

res = []
dpnp.check_supported_arrays_type(*arys)
for ary in arys:
if not dpnp.is_supported_array_type(ary):
raise TypeError(
"Each input array must be any of supported type, "
f"but got {type(ary)}"
)
if ary.ndim == 0:
result = ary.reshape(1, 1)
elif ary.ndim == 1:
Expand Down Expand Up @@ -768,12 +761,8 @@ def atleast_3d(*arys):
"""

res = []
dpnp.check_supported_arrays_type(*arys)
for ary in arys:
if not dpnp.is_supported_array_type(ary):
raise TypeError(
"Each input array must be any of supported type, "
f"but got {type(ary)}"
)
if ary.ndim == 0:
result = ary.reshape(1, 1, 1)
elif ary.ndim == 1:
Expand Down Expand Up @@ -1954,6 +1943,105 @@ def repeat(a, repeats, axis=None):
return dpnp_array._create_from_usm_ndarray(usm_res)


def require(a, dtype=None, requirements=None, *, like=None):
"""
Return an dpnp.ndarray of the provided type that satisfies requirements.
vtavana marked this conversation as resolved.
Show resolved Hide resolved

This function is useful to be sure that an array with the correct flags
is returned for passing to compiled code (perhaps through ctypes).

For full documentation refer to :obj:`numpy.require`.

Parameters
----------
a : array_like
vtavana marked this conversation as resolved.
Show resolved Hide resolved
The object to be converted to a type-and-requirement-satisfying array.
dtype : data-type, optional
vtavana marked this conversation as resolved.
Show resolved Hide resolved
The required data-type. If None preserve the current dtype. If your
application requires the data to be in native byteorder, include
a byteorder specification as a part of the dtype specification.
vtavana marked this conversation as resolved.
Show resolved Hide resolved
requirements : {str, sequence of str}, , optional
vtavana marked this conversation as resolved.
Show resolved Hide resolved
The requirements list can be any of the following:

* 'F_CONTIGUOUS' ('F') - ensure a Fortran-contiguous array
* 'C_CONTIGUOUS' ('C') - ensure a C-contiguous array
* 'WRITABLE' ('W') - ensure a writable array

Returns
-------
out : dpnp.ndarray
Array with specified requirements and type if given.

Limitations
-----------
Parameter `like` is supported only with default value ``None``.
Otherwise, the function raises `NotImplementedError` exception.

See Also
--------
:obj:`dpnp.asarray` : Convert input to an ndarray.
:obj:`dpnp.asanyarray ` : Convert to an ndarray, but pass through
vtavana marked this conversation as resolved.
Show resolved Hide resolved
ndarray subclasses.
:obj:`dpnp.ascontiguousarray` : Convert input to a contiguous array.
:obj:`dpnp.asfortranarray` : Convert input to an ndarray with
column-major memory order.
:obj:`dpnp.ndarray.flags` : Information about the memory layout
of the array.

Notes
-----
The returned array will be guaranteed to have the listed requirements
by making a copy if needed.

Examples
--------
>>> import dpnp as np
>>> x = np.arange(6).reshape(2,3)
vtavana marked this conversation as resolved.
Show resolved Hide resolved
>>> x.flags
C_CONTIGUOUS : True
F_CONTIGUOUS : False
WRITEABLE : True

>>> y = np.require(x, dtype=np.float32, requirements=['W', 'F'])
>>> y.flags
C_CONTIGUOUS : False
F_CONTIGUOUS : True
WRITEABLE : True

"""

dpnp.check_limitations(like=like)

possible_flags = {
"C": "C",
"C_CONTIGUOUS": "C",
antonwolfy marked this conversation as resolved.
Show resolved Hide resolved
"F": "F",
"F_CONTIGUOUS": "F",
"W": "W",
"WRITEABLE": "W",
}

if not requirements:
return dpnp.asanyarray(a, dtype=dtype)

requirements = {possible_flags[x.upper()] for x in requirements}
vtavana marked this conversation as resolved.
Show resolved Hide resolved
order = "A"
if requirements.issuperset({"C", "F"}):
raise ValueError("Cannot specify both 'C' and 'F' order")
if "F" in requirements:
order = "F"
requirements.remove("F")
elif "C" in requirements:
order = "C"
requirements.remove("C")

arr = dpnp.array(a, dtype=dtype, order=order, copy=None)
if not arr.flags["W"]:
return arr.copy(order)

return arr


def reshape(a, /, newshape, order="C", copy=None):
"""
Gives a new shape to an array without changing its data.
Expand Down
22 changes: 22 additions & 0 deletions tests/test_arraymanipulation.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import dpctl.tensor as dpt
import numpy
import pytest
from dpctl.tensor._numpy_helper import AxisError
Expand Down Expand Up @@ -45,6 +46,13 @@ def test_3D_array(self):
desired = [a, b]
assert_array_equal(res, desired)

def test_dpnp_dpt_array(self):
a = dpnp.array([1, 2])
b = dpt.asarray([2, 3])
res = dpnp.atleast_1d(a, b)
desired = [dpnp.array([1, 2]), dpnp.array([2, 3])]
assert_array_equal(res, desired)


class TestAtleast2d:
def test_0D_array(self):
Expand Down Expand Up @@ -77,6 +85,13 @@ def test_3D_array(self):
desired = [a, b]
assert_array_equal(res, desired)

def test_dpnp_dpt_array(self):
a = dpnp.array([1, 2])
b = dpt.asarray([2, 3])
res = dpnp.atleast_2d(a, b)
desired = [dpnp.array([[1, 2]]), dpnp.array([[2, 3]])]
assert_array_equal(res, desired)


class TestAtleast3d:
def test_0D_array(self):
Expand Down Expand Up @@ -109,6 +124,13 @@ def test_3D_array(self):
desired = [a, b]
assert_array_equal(res, desired)

def test_dpnp_dpt_array(self):
a = dpnp.array([1, 2])
b = dpt.asarray([2, 3])
res = dpnp.atleast_3d(a, b)
desired = [dpnp.array([[[1], [2]]]), dpnp.array([[[2], [3]]])]
assert_array_equal(res, desired)


class TestColumnStack:
def test_non_iterable(self):
Expand Down
67 changes: 67 additions & 0 deletions tests/test_manipulation.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import itertools

import dpctl.tensor as dpt
import numpy
import pytest
Expand Down Expand Up @@ -665,6 +667,71 @@ def test_minimum_signed_integers(self, data, dtype):
assert_array_equal(result, expected)


class TestRequire:
flag_names = ["C", "C_CONTIGUOUS", "F", "F_CONTIGUOUS", "W"]
vtavana marked this conversation as resolved.
Show resolved Hide resolved

def generate_all_false(self, dtype):
vtavana marked this conversation as resolved.
Show resolved Hide resolved
a_np = numpy.zeros((10, 10))
a_dp = dpnp.zeros((10, 10))
a_np = a_np[::2, ::2]
a_dp = a_dp[::2, ::2]
a_np.flags["W"] = False
a_dp.flags["W"] = False
assert not a_dp.flags["C"]
assert not a_dp.flags["F"]
assert not a_dp.flags["W"]
return a_np, a_dp

def set_and_check_flag(self, flag, dtype, arr):
if dtype is None:
dtype = arr[1].dtype
result = numpy.require(arr[0], dtype, [flag])
expected = dpnp.require(arr[1], dtype, [flag])
assert result.flags[flag] == expected.flags[flag]
assert result.dtype == expected.dtype

# a further call to dpnp.require ought to return the same array
c = dpnp.require(expected, None, [flag])
assert c is expected

def test_require_each(self):
id = ["f4", "i4"]
fd = [None, "f4", "c8"]
for idtype, fdtype, flag in itertools.product(id, fd, self.flag_names):
a = self.generate_all_false(idtype)
self.set_and_check_flag(flag, fdtype, a)

@pytest.mark.parametrize("xp", [numpy, dpnp])
def test_unknown_requirement(self, xp):
a = self.generate_all_false("f4")
assert_raises(KeyError, xp.require, a, None, "Q")

def test_non_array_input(self):
expected = numpy.require([1, 2, 3, 4], "i4", ["C", "W"])
result = dpnp.require([1, 2, 3, 4], "i4", ["C", "W"])
assert expected.flags["C"] == result.flags["C"]
assert expected.flags["F"] == result.flags["F"]
assert expected.flags["W"] == result.flags["W"]
assert expected.dtype == result.dtype
assert_array_equal(expected, result)

@pytest.mark.parametrize("xp", [numpy, dpnp])
def test_C_and_F_simul(self, xp):
a = self.generate_all_false("f4")
assert_raises(ValueError, xp.require, a, None, ["C", "F"])

def test_copy(self):
a_np = numpy.arange(6).reshape(2, 3)
a_dp = dpnp.arange(6).reshape(2, 3)
a_np.flags["W"] = False
a_dp.flags["W"] = False
expected = numpy.require(a_np, requirements=["W", "C"])
result = dpnp.require(a_dp, requirements=["W", "C"])
# copy is done
assert result is not a_dp
assert_array_equal(expected, result)
vtavana marked this conversation as resolved.
Show resolved Hide resolved


class TestTranspose:
@pytest.mark.parametrize("axes", [(0, 1), (1, 0), [0, 1]])
def test_2d_with_axes(self, axes):
Expand Down
10 changes: 3 additions & 7 deletions tests/third_party/cupy/manipulation_tests/test_kind.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ def func(xp):

assert func(numpy) == func(cupy)

@pytest.mark.skip("dpnp.require() is not implemented yet")
@testing.for_all_dtypes()
def test_require_flag_check(self, dtype):
possible_flags = [["C_CONTIGUOUS"], ["F_CONTIGUOUS"]]
Expand All @@ -105,36 +104,33 @@ def test_require_flag_check(self, dtype):
assert arr.flags[parameter]
assert arr.dtype == dtype

@pytest.mark.skip("dpnp.require() is not implemented yet")
@pytest.mark.skip("dpnp.require() does not support requirement ['O']")
@testing.for_all_dtypes()
def test_require_owndata(self, dtype):
x = cupy.zeros((2, 3, 4), dtype=dtype)
arr = x.view()
arr = cupy.require(arr, dtype, ["O"])
assert arr.flags["OWNDATA"]

@pytest.mark.skip("dpnp.require() is not implemented yet")
@testing.for_all_dtypes()
def test_require_C_and_F_flags(self, dtype):
x = cupy.zeros((2, 3, 4), dtype=dtype)
with pytest.raises(ValueError):
cupy.require(x, dtype, ["C", "F"])

@pytest.mark.skip("dpnp.require() is not implemented yet")
@pytest.mark.skip("dpnp.require() does support requirement ['W']")
@testing.for_all_dtypes()
def test_require_incorrect_requirments(self, dtype):
x = cupy.zeros((2, 3, 4), dtype=dtype)
with pytest.raises(ValueError):
cupy.require(x, dtype, ["W"])
vtavana marked this conversation as resolved.
Show resolved Hide resolved

@pytest.mark.skip("dpnp.require() is not implemented yet")
@testing.for_all_dtypes()
def test_require_incorrect_dtype(self, dtype):
x = cupy.zeros((2, 3, 4), dtype=dtype)
with pytest.raises(ValueError):
with pytest.raises((ValueError, TypeError)):
cupy.require(x, "random", "C")

@pytest.mark.skip("dpnp.require() is not implemented yet")
@testing.for_all_dtypes()
def test_require_empty_requirements(self, dtype):
x = cupy.zeros((2, 3, 4), dtype=dtype)
Expand Down
Loading