From 2bcbdc236e9d811b758d037d8685e3e45ab9cc3f Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Wed, 14 Jun 2023 20:26:14 +0200 Subject: [PATCH 1/2] Add a new implementation of dpnp.outer --- dpnp/dpnp_algo/dpnp_algo_linearalgebra.pxi | 25 -------------- dpnp/dpnp_iface_linearalgebra.py | 38 ++++++++++++++-------- tests/skipped_tests_gpu.tbl | 1 - tests/test_outer.py | 11 +++---- 4 files changed, 29 insertions(+), 46 deletions(-) diff --git a/dpnp/dpnp_algo/dpnp_algo_linearalgebra.pxi b/dpnp/dpnp_algo/dpnp_algo_linearalgebra.pxi index f9eac4ffd35b..407d3466c9b8 100644 --- a/dpnp/dpnp_algo/dpnp_algo_linearalgebra.pxi +++ b/dpnp/dpnp_algo/dpnp_algo_linearalgebra.pxi @@ -40,7 +40,6 @@ __all__ += [ "dpnp_inner", "dpnp_kron", "dpnp_matmul", - "dpnp_outer" ] @@ -378,27 +377,3 @@ cpdef utils.dpnp_descriptor dpnp_matmul(utils.dpnp_descriptor in_array1, utils.d c_dpctl.DPCTLEvent_Delete(event_ref) return result - - -cpdef utils.dpnp_descriptor dpnp_outer(utils.dpnp_descriptor array1, utils.dpnp_descriptor array2): - cdef shape_type_c result_shape = (array1.size, array2.size) - result_type = numpy.promote_types(array1.dtype, array1.dtype) - - result_sycl_device, result_usm_type, result_sycl_queue = utils.get_common_usm_allocation(array1, array2) - - cdef utils.dpnp_descriptor result = utils_py.create_output_descriptor_py(result_shape, - result_type, - None, - device=result_sycl_device, - usm_type=result_usm_type, - sycl_queue=result_sycl_queue) - - result_flatiter = result.get_pyobj().flat - array1_flatiter = array1.get_pyobj().flat - array2_flatiter = array2.get_pyobj().flat - - for idx1 in range(array1.size): - for idx2 in range(array2.size): - result_flatiter[idx1 * array2.size + idx2] = array1_flatiter[idx1] * array2_flatiter[idx2] - - return result diff --git a/dpnp/dpnp_iface_linearalgebra.py b/dpnp/dpnp_iface_linearalgebra.py index 1b9e2d28cfb4..a3c656077f6c 100644 --- a/dpnp/dpnp_iface_linearalgebra.py +++ b/dpnp/dpnp_iface_linearalgebra.py @@ -73,7 +73,7 @@ def dot(x1, x2, out=None, **kwargs): y : dpnp.ndarray Returns the dot product of `x1` and `x2`. If `out` is given, then it is returned. - + Limitations ----------- Parameters `x1` and `x2` are supported as either scalar, :class:`dpnp.ndarray` @@ -298,7 +298,7 @@ def matmul(x1, x2, out=None, **kwargs): return call_origin(numpy.matmul, x1, x2, out=out, **kwargs) -def outer(x1, x2, **kwargs): +def outer(x1, x2, out=None): """ Returns the outer product of two arrays. @@ -306,8 +306,8 @@ def outer(x1, x2, **kwargs): Limitations ----------- - Parameters ``x1`` and ``x2`` are supported as :obj:`dpnp.ndarray`. - Keyword arguments ``kwargs`` are currently unsupported. + Parameters `x1` and `x2` are supported as either scalar, :class:`dpnp.ndarray` + or :class:`dpctl.tensor.usm_ndarray`, but both `x1` and `x2` can not be scalars at the same time. Otherwise the functions will be executed sequentially on CPU. Input array data types are limited by supported DPNP :ref:`Data types`. @@ -323,21 +323,31 @@ def outer(x1, x2, **kwargs): >>> b = np.array([1, 2, 3]) >>> result = np.outer(a, b) >>> [x for x in result] - [1, 2, 3, 1, 2, 3, 1, 2, 3] + array([[1, 2, 3], + [1, 2, 3], + [1, 2, 3]]) """ + check_input_type = lambda x: isinstance(x, (dpnp_array, dpt.usm_ndarray)) + + if dpnp.isscalar(x1) and dpnp.isscalar(x2): + pass + elif not check_input_type(x1) and not check_input_type(x2): + pass + else: + if check_input_type(x1): + x1_in = (x1.reshape(-1) if x1.ndim > 1 else x1)[:, None] + else: + x1_in = x1 - if not kwargs: - if isinstance(x1, dpnp_array) and isinstance(x2, dpnp_array): - ravel = lambda x: x.flatten() if x.ndim > 1 else x - return ravel(x1)[:, None] * ravel(x2)[None, :] + if check_input_type(x2): + x2_in = (x2.reshape(-1) if x2.ndim > 1 else x2)[None, :] + else: + x2_in = x2 - x1_desc = dpnp.get_dpnp_descriptor(x1, copy_when_nondefault_queue=False) - x2_desc = dpnp.get_dpnp_descriptor(x2, copy_when_nondefault_queue=False) - if x1_desc and x2_desc: - return dpnp_outer(x1_desc, x2_desc).get_pyobj() + return dpnp.multiply(x1_in, x2_in, out=out) - return call_origin(numpy.outer, x1, x2, **kwargs) + return call_origin(numpy.outer, x1, x2, out=out) def tensordot(x1, x2, axes=2): diff --git a/tests/skipped_tests_gpu.tbl b/tests/skipped_tests_gpu.tbl index 93ee67dda238..1e17892fea7d 100644 --- a/tests/skipped_tests_gpu.tbl +++ b/tests/skipped_tests_gpu.tbl @@ -206,7 +206,6 @@ tests/third_party/cupy/linalg_tests/test_einsum.py::TestEinSumError::test_too_ma tests/third_party/cupy/linalg_tests/test_einsum.py::TestListArgEinSumError::test_dim_mismatch3 tests/third_party/cupy/linalg_tests/test_einsum.py::TestListArgEinSumError::test_too_many_dims3 -tests/third_party/cupy/linalg_tests/test_product.py::TestProduct::test_reversed_outer tests/third_party/cupy/linalg_tests/test_product.py::TestProduct::test_reversed_vdot tests/third_party/cupy/manipulation_tests/test_basic.py::TestCopytoFromScalar_param_7_{dst_shape=(0,), src=3.2}::test_copyto_where tests/third_party/cupy/manipulation_tests/test_basic.py::TestCopytoFromScalar_param_8_{dst_shape=(0,), src=0}::test_copyto_where diff --git a/tests/test_outer.py b/tests/test_outer.py index 6c91fad45df4..8cbee99e4377 100644 --- a/tests/test_outer.py +++ b/tests/test_outer.py @@ -3,6 +3,7 @@ import dpnp as dp import numpy as np +import pytest from numpy.testing import assert_raises @@ -40,23 +41,21 @@ def test_the_same_matrix(self, xp, dtype): class TestScalarOuter(unittest.TestCase): - @unittest.skip("A scalar isn't currently supported as input") @testing.for_all_dtypes() - @testing.numpy_cupy_allclose() + @testing.numpy_cupy_allclose(type_check=False) def test_first_is_scalar(self, xp, dtype): scalar = xp.int64(4) a = xp.arange(5**3, dtype=dtype).reshape(5, 5, 5) return xp.outer(scalar, a) - @unittest.skip("A scalar isn't currently supported as input") @testing.for_all_dtypes() - @testing.numpy_cupy_allclose() + @testing.numpy_cupy_allclose(type_check=False) def test_second_is_scalar(self, xp, dtype): scalar = xp.int32(7) a = xp.arange(5**3, dtype=dtype).reshape(5, 5, 5) return xp.outer(a, scalar) - @unittest.skip("A scalar isn't currently supported as input") + @pytest.mark.usefixtures("allow_fall_back_on_numpy") @testing.numpy_cupy_array_equal() def test_both_inputs_as_scalar(self, xp): a = xp.int64(4) @@ -71,7 +70,7 @@ def test_list(self): b: list[list[list[int]]] = a.tolist() dp_a = dp.array(a) - with assert_raises(NotImplementedError): + with assert_raises(TypeError): dp.outer(b, dp_a) dp.outer(dp_a, b) dp.outer(b, b) From 7d021a2f3c0b6fbcd5a5f8e1813453517c96b89e Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Wed, 14 Jun 2023 21:25:24 +0200 Subject: [PATCH 2/2] Update dpnp.outer implementation --- dpnp/dpnp_iface_linearalgebra.py | 21 ++++++++------------- tests/test_outer.py | 2 +- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/dpnp/dpnp_iface_linearalgebra.py b/dpnp/dpnp_iface_linearalgebra.py index a3c656077f6c..f26f9648b24f 100644 --- a/dpnp/dpnp_iface_linearalgebra.py +++ b/dpnp/dpnp_iface_linearalgebra.py @@ -328,23 +328,18 @@ def outer(x1, x2, out=None): [1, 2, 3]]) """ - check_input_type = lambda x: isinstance(x, (dpnp_array, dpt.usm_ndarray)) + x1_is_scalar = dpnp.isscalar(x1) + x2_is_scalar = dpnp.isscalar(x2) - if dpnp.isscalar(x1) and dpnp.isscalar(x2): + if x1_is_scalar and x2_is_scalar: pass - elif not check_input_type(x1) and not check_input_type(x2): + elif not (x1_is_scalar or dpnp.is_supported_array_type(x1)): + pass + elif not (x2_is_scalar or dpnp.is_supported_array_type(x2)): pass else: - if check_input_type(x1): - x1_in = (x1.reshape(-1) if x1.ndim > 1 else x1)[:, None] - else: - x1_in = x1 - - if check_input_type(x2): - x2_in = (x2.reshape(-1) if x2.ndim > 1 else x2)[None, :] - else: - x2_in = x2 - + x1_in = x1 if x1_is_scalar else (x1.reshape(-1) if x1.ndim > 1 else x1)[:, None] + x2_in = x2 if x2_is_scalar else (x2.reshape(-1) if x2.ndim > 1 else x2)[None, :] return dpnp.multiply(x1_in, x2_in, out=out) return call_origin(numpy.outer, x1, x2, out=out) diff --git a/tests/test_outer.py b/tests/test_outer.py index 8cbee99e4377..3ac751b2b280 100644 --- a/tests/test_outer.py +++ b/tests/test_outer.py @@ -70,7 +70,7 @@ def test_list(self): b: list[list[list[int]]] = a.tolist() dp_a = dp.array(a) - with assert_raises(TypeError): + with assert_raises(NotImplementedError): dp.outer(b, dp_a) dp.outer(dp_a, b) dp.outer(b, b)