Skip to content

Implemented dpnp.hstack() and dpnp.atleast_1d() functions. #1544

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

Merged
merged 10 commits into from
Sep 6, 2023
121 changes: 100 additions & 21 deletions dpnp/dpnp_iface_manipulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,52 @@ def atleast_1d(*arys):

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

Limitations
-----------
Input arrays is supported as :obj:`dpnp.ndarray`.
Parameters
----------
arys : {dpnp_array, usm_ndarray}
One or more input arrays.

Returns
-------
out : dpnp.ndarray
An array, or list of arrays, each with ``a.ndim >= 1``.
Copies are made only if necessary.

See Also
--------
atleast_2d, atleast_3d

Examples
--------
>>> import dpnp as np
>>> np.atleast_1d(1.0)
array([1.])

>>> x = np.arange(9.0).reshape(3,3)
>>> np.atleast_1d(x)
array([[0., 1., 2.],
[3., 4., 5.],
[6., 7., 8.]])
>>> np.atleast_1d(x) is x
True

>>> np.atleast_1d(1, [3, 4])
[array([1]), array([3, 4])]

"""

return call_origin(numpy.atleast_1d, *arys)
res = []
for ary in arys:
ary = dpnp.asanyarray(ary)
if ary.ndim == 0:
result = ary.reshape(1)
else:
result = ary
res.append(result)
if len(res) == 1:
return res[0]
else:
return res


def atleast_2d(*arys):
Expand Down Expand Up @@ -254,7 +293,9 @@ def broadcast_to(array, /, shape, subok=False):
return call_origin(numpy.broadcast_to, array, shape=shape, subok=subok)


def concatenate(arrays, /, *, axis=0, out=None, dtype=None, **kwargs):
def concatenate(
arrays, /, *, axis=0, out=None, dtype=None, casting="same_kind"
):
"""
Join a sequence of arrays along an existing axis.

Expand All @@ -271,7 +312,6 @@ def concatenate(arrays, /, *, axis=0, out=None, dtype=None, **kwargs):
or :class:`dpctl.tensor.usm_ndarray`. Otherwise ``TypeError`` exception
will be raised.
Parameters `out` and `dtype are supported with default value.
Keyword argument ``kwargs`` is currently unsupported.
Otherwise the function will be executed sequentially on CPU.

See Also
Expand Down Expand Up @@ -305,12 +345,12 @@ def concatenate(arrays, /, *, axis=0, out=None, dtype=None, **kwargs):

"""

if kwargs:
pass
elif out is not None:
if out is not None:
pass
elif dtype is not None:
pass
elif casting != "same_kind":
pass
else:
usm_arrays = [dpnp.get_usm_ndarray(x) for x in arrays]
usm_res = dpt.concat(usm_arrays, axis=axis)
Expand All @@ -322,7 +362,7 @@ def concatenate(arrays, /, *, axis=0, out=None, dtype=None, **kwargs):
axis=axis,
out=out,
dtype=dtype,
**kwargs,
casting=casting,
)


Expand Down Expand Up @@ -671,23 +711,62 @@ def flipud(m):
return m[::-1, ...]


def hstack(tup):
def hstack(tup, *, dtype=None, casting="same_kind"):
"""
Stack arrays in sequence horizontally (column wise).

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

"""
Returns
-------
out : dpnp.ndarray
The stacked array which has one more dimension than the input arrays.

# TODO:
# `call_origin` cannot convert sequence of array to sequence of
# nparrays
tup_new = []
for tp in tup:
tpx = dpnp.asnumpy(tp) if not isinstance(tp, numpy.ndarray) else tp
tup_new.append(tpx)
Limitations
-----------
Each array in `tup` is supported as either :class:`dpnp.ndarray`
or :class:`dpctl.tensor.usm_ndarray`. Otherwise ``TypeError`` exception
will be raised.
Parameters `dtype` and `casting` are supported with default value.
Otherwise the function will be executed sequentially on CPU.

See Also
--------
:obj:`dpnp.concatenate` : Join a sequence of arrays along an existing axis.
:obj:`dpnp.stack` : Join a sequence of arrays along a new axis.
:obj:`dpnp.vstack` : Stack arrays in sequence vertically (row wise).
:obj:`dpnp.block` : Assemble an nd-array from nested lists of blocks.
:obj:`dpnp.split` : Split array into a list of multiple sub-arrays of equal size.

Examples
--------
>>> import dpnp as np
>>> a = np.array((1,2,3))
>>> b = np.array((4,5,6))
>>> np.hstack((a,b))
array([1, 2, 3, 4, 5, 6])

>>> a = np.array([[1],[2],[3]])
>>> b = np.array([[4],[5],[6]])
>>> np.hstack((a,b))
array([[1, 4],
[2, 5],
[3, 6]])

"""

return call_origin(numpy.hstack, tup_new)
if not hasattr(tup, "__getitem__"):
raise TypeError(
"Arrays to stack must be passed as a sequence type such as list or tuple."
)
arrs = dpnp.atleast_1d(*tup)
if not isinstance(arrs, list):
arrs = [arrs]
# As a special case, dimension 0 of 1-dimensional arrays is "horizontal"
if arrs and arrs[0].ndim == 1:
return dpnp.concatenate(arrs, axis=0, dtype=dtype, casting=casting)
else:
return dpnp.concatenate(arrs, axis=1, dtype=dtype, casting=casting)


def moveaxis(a, source, destination):
Expand Down Expand Up @@ -1143,7 +1222,7 @@ def stack(arrays, /, *, axis=0, out=None, dtype=None, **kwargs):
Each array in `arrays` is supported as either :class:`dpnp.ndarray`
or :class:`dpctl.tensor.usm_ndarray`. Otherwise ``TypeError`` exception
will be raised.
Parameters `out` and `dtype are supported with default value.
Parameters `out` and `dtype` are supported with default value.
Keyword argument ``kwargs`` is currently unsupported.
Otherwise the function will be executed sequentially on CPU.

Expand Down
1 change: 0 additions & 1 deletion tests/skipped_tests.tbl
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ tests/third_party/cupy/fft_tests/test_fft.py::TestFftn_param_23_{axes=None, norm

tests/third_party/intel/test_zero_copy_test1.py::test_dpnp_interaction_with_dpctl_memory

tests/test_arraymanipulation.py::TestHstack::test_generator
tests/test_arraymanipulation.py::TestVstack::test_generator

tests/test_linalg.py::test_cond[-1-[[1, 2, 3], [4, 5, 6], [7, 8, 9]]]
Expand Down
1 change: 0 additions & 1 deletion tests/skipped_tests_gpu.tbl
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,6 @@ tests/third_party/cupy/random_tests/test_distributions.py::TestDistributionsMult
tests/third_party/cupy/random_tests/test_distributions.py::TestDistributionsMultivariateNormal_param_3_{d=4, shape=(3, 2)}::test_normal

tests/third_party/intel/test_zero_copy_test1.py::test_dpnp_interaction_with_dpctl_memory
tests/test_arraymanipulation.py::TestHstack::test_generator
tests/test_arraymanipulation.py::TestVstack::test_generator

tests/test_linalg.py::test_cond[-1-[[1, 2, 3], [4, 5, 6], [7, 8, 9]]]
Expand Down
74 changes: 65 additions & 9 deletions tests/test_arraymanipulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,32 +329,45 @@ def test_concatenate_out(self, dtype):
assert_array_equal(dp_out.asnumpy(), np_out)
assert_array_equal(dp_res.asnumpy(), np_res)

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
@pytest.mark.parametrize(
"dtype", get_all_dtypes(no_bool=True, no_none=True)
)
@pytest.mark.parametrize(
"casting", ["no", "equiv", "safe", "same_kind", "unsafe"]
)
def test_concatenate_casting(self, dtype, casting):
np_a = numpy.arange(2 * 3 * 7, dtype=dtype).reshape((2, 3, 7))

dp_a = dpnp.arange(2 * 3 * 7, dtype=dtype).reshape((2, 3, 7))

np_res = numpy.concatenate((np_a, np_a), axis=2, casting=casting)
dp_res = dpnp.concatenate((dp_a, dp_a), axis=2, casting=casting)

assert_array_equal(dp_res.asnumpy(), np_res)


class TestHstack:
def test_non_iterable(self):
assert_raises(TypeError, dpnp.hstack, 1)

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_empty_input(self):
assert_raises(ValueError, dpnp.hstack, ())
assert_raises(TypeError, dpnp.hstack, ())

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_0D_array(self):
b = dpnp.array(2)
a = dpnp.array(1)
res = dpnp.hstack([a, b])
desired = dpnp.array([1, 2])
assert_array_equal(res, desired)

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_1D_array(self):
a = dpnp.array([1])
b = dpnp.array([2])
res = dpnp.hstack([a, b])
desired = dpnp.array([1, 2])
assert_array_equal(res, desired)

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
def test_2D_array(self):
a = dpnp.array([[1], [2]])
b = dpnp.array([[1], [2]])
Expand All @@ -363,10 +376,15 @@ def test_2D_array(self):
assert_array_equal(res, desired)

def test_generator(self):
with assert_warns(FutureWarning):
dpnp.hstack((numpy.arange(3) for _ in range(2)))
with assert_warns(FutureWarning):
dpnp.hstack(map(lambda x: x, numpy.ones((3, 2))))
with pytest.raises(TypeError):
dpnp.hstack((dpnp.arange(3) for _ in range(2)))
with pytest.raises(TypeError):
dpnp.hstack(map(lambda x: x, dpnp.ones((3, 2))))

def test_one_element(self):
a = dpnp.array([1])
res = dpnp.hstack(a)
assert_array_equal(res, a)


class TestStack:
Expand Down Expand Up @@ -606,6 +624,44 @@ def test_generator(self):
dpnp.vstack((numpy.arange(3) for _ in range(2)))


class TestAtleast1d:
def test_0D_array(self):
a = dpnp.array(1)
b = dpnp.array(2)
res = [dpnp.atleast_1d(a), dpnp.atleast_1d(b)]
desired = [dpnp.array([1]), dpnp.array([2])]
assert_array_equal(res, desired)

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

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

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

def test_r1array(self):
assert dpnp.atleast_1d(3).shape == (1,)
assert dpnp.atleast_1d(3j).shape == (1,)
assert dpnp.atleast_1d(3.0).shape == (1,)
assert dpnp.atleast_1d([[2, 3], [4, 5]]).shape == (2, 2)


class TestRollaxis:
data = [
(0, 0),
Expand Down
2 changes: 0 additions & 2 deletions tests/third_party/cupy/manipulation_tests/test_dims.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@ def check_atleast(self, func, xp):
f = numpy.float32(1)
return func(a, b, c, d, e, f)

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
@testing.numpy_cupy_array_equal()
def test_atleast_1d1(self, xp):
return self.check_atleast(xp.atleast_1d, xp)

@pytest.mark.usefixtures("allow_fall_back_on_numpy")
@testing.numpy_cupy_array_equal()
def test_atleast_1d2(self, xp):
a = testing.shaped_arange((1, 3, 2), xp)
Expand Down
18 changes: 3 additions & 15 deletions tests/third_party/cupy/manipulation_tests/test_join.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,30 +270,27 @@ def test_dstack_single_element_3(self, xp):
a = testing.shaped_arange((1,), xp)
return xp.dstack((a,))

@pytest.mark.skip("dpnp.hstack() is not implemented yet")
@testing.numpy_cupy_array_equal()
def test_hstack_vectors(self, xp):
a = xp.arange(3)
b = xp.arange(2, -1, -1)
return xp.hstack((a, b))

@pytest.mark.skip("dpnp.hstack() is not implemented yet")
@testing.numpy_cupy_array_equal()
def test_hstack_scalars(self, xp):
a = testing.shaped_arange((), xp)
b = testing.shaped_arange((), xp)
c = testing.shaped_arange((), xp)
return xp.hstack((a, b, c))

@pytest.mark.skip("dpnp.hstack() is not implemented yet")
@testing.numpy_cupy_array_equal()
def test_hstack(self, xp):
a = testing.shaped_arange((2, 1), xp)
b = testing.shaped_arange((2, 2), xp)
c = testing.shaped_arange((2, 3), xp)
return xp.hstack((a, b, c))

@pytest.mark.skip("dpnp.hstack() is not implemented yet")
@pytest.mark.usefixtures("allow_fall_back_on_numpy")
@testing.with_requires("numpy>=1.24.0")
@testing.for_all_dtypes_combination(names=["dtype1", "dtype2"])
@testing.numpy_cupy_array_equal(accept_error=TypeError)
Expand All @@ -302,18 +299,9 @@ def test_hstack_dtype(self, xp, dtype1, dtype2):
b = testing.shaped_arange((3, 4), xp, dtype1)
return xp.hstack((a, b), dtype=dtype2)

@pytest.mark.skip("dpnp.hstack() is not implemented yet")
@pytest.mark.usefixtures("allow_fall_back_on_numpy")
@testing.with_requires("numpy>=1.24.0")
@pytest.mark.parametrize(
"casting",
[
"no",
"equiv",
"safe",
"same_kind",
"unsafe",
],
)
@testing.for_castings()
@testing.for_all_dtypes_combination(names=["dtype1", "dtype2"])
@testing.numpy_cupy_array_equal(
accept_error=(TypeError, numpy.ComplexWarning)
Expand Down