diff --git a/dpnp/dpnp_iface_manipulation.py b/dpnp/dpnp_iface_manipulation.py index a747719e1c68..f71aa751cb2c 100644 --- a/dpnp/dpnp_iface_manipulation.py +++ b/dpnp/dpnp_iface_manipulation.py @@ -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): @@ -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. @@ -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 @@ -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) @@ -322,7 +362,7 @@ def concatenate(arrays, /, *, axis=0, out=None, dtype=None, **kwargs): axis=axis, out=out, dtype=dtype, - **kwargs, + casting=casting, ) @@ -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): @@ -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. diff --git a/tests/skipped_tests.tbl b/tests/skipped_tests.tbl index 9c5b855f3631..10f167653eab 100644 --- a/tests/skipped_tests.tbl +++ b/tests/skipped_tests.tbl @@ -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]]] diff --git a/tests/skipped_tests_gpu.tbl b/tests/skipped_tests_gpu.tbl index f82c6107a4c6..081f0b72350b 100644 --- a/tests/skipped_tests_gpu.tbl +++ b/tests/skipped_tests_gpu.tbl @@ -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]]] diff --git a/tests/test_arraymanipulation.py b/tests/test_arraymanipulation.py index 510b347ab3b5..1c98c487dfc2 100644 --- a/tests/test_arraymanipulation.py +++ b/tests/test_arraymanipulation.py @@ -329,16 +329,31 @@ 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) @@ -346,7 +361,6 @@ def test_0D_array(self): 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]) @@ -354,7 +368,6 @@ def test_1D_array(self): 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]]) @@ -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: @@ -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), diff --git a/tests/third_party/cupy/manipulation_tests/test_dims.py b/tests/third_party/cupy/manipulation_tests/test_dims.py index 6d59f0640512..461c0bb2ec2b 100644 --- a/tests/third_party/cupy/manipulation_tests/test_dims.py +++ b/tests/third_party/cupy/manipulation_tests/test_dims.py @@ -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) diff --git a/tests/third_party/cupy/manipulation_tests/test_join.py b/tests/third_party/cupy/manipulation_tests/test_join.py index 054ada455aee..8bffce98d875 100644 --- a/tests/third_party/cupy/manipulation_tests/test_join.py +++ b/tests/third_party/cupy/manipulation_tests/test_join.py @@ -270,14 +270,12 @@ 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) @@ -285,7 +283,6 @@ def test_hstack_scalars(self, 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) @@ -293,7 +290,7 @@ def test_hstack(self, 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) @@ -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)