From bf288536f3e86602db4cce83ecc6839fcfb02b7b Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Mon, 22 Jun 2026 15:15:09 +0200 Subject: [PATCH 1/9] Update grid-like functions --- dpnp/dpnp_iface_arraycreation.py | 34 ++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/dpnp/dpnp_iface_arraycreation.py b/dpnp/dpnp_iface_arraycreation.py index 5a245c69037..560ad16e1d5 100644 --- a/dpnp/dpnp_iface_arraycreation.py +++ b/dpnp/dpnp_iface_arraycreation.py @@ -3036,7 +3036,7 @@ def logspace( # pylint: disable=redefined-outer-name def meshgrid(*xi, copy=True, sparse=False, indexing="xy"): """ - Return coordinate matrices from coordinate vectors. + Return a tuple of coordinate matrices from coordinate vectors. Make N-D coordinate arrays for vectorized evaluations of N-D scalar/vector fields over N-D grids, given @@ -3069,6 +3069,13 @@ def meshgrid(*xi, copy=True, sparse=False, indexing="xy"): the elements of `xi` repeated to fill the matrix along the first dimension for `x1`, the second for `x2` and so on. + See Also + -------- + :obj:`dpnp.mgrid` : Construct a multi-dimensional "meshgrid" using + indexing notation. + :obj:`dpnp.ogrid` : Construct an open multi-dimensional "meshgrid" using + indexing notation. + Examples -------- >>> import dpnp as np @@ -3164,9 +3171,9 @@ class MGridClass: Returns ------- - out : one dpnp.ndarray or tuple of dpnp.ndarray - Returns one array of grid indices, - ``grid.shape = (len(dimensions),) + tuple(dimensions)``. + out : dpnp.ndarray + A single array, containing a set of arrays all of the same dimensions, + stacked along the first axis. Examples -------- @@ -3183,6 +3190,13 @@ class MGridClass: [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]]]) + >>> np.mgrid[0:4].shape + (4,) + >>> np.mgrid[0:4, 0:5].shape + (2, 4, 5) + >>> np.mgrid[0:4, 0:5, 0:6].shape + (3, 4, 5, 6) + Creating an array on a different device or with a specified usm_type >>> x = np.mgrid[-1:1:5j] # default case @@ -3243,20 +3257,20 @@ class OGridClass: Returns ------- - out : one dpnp.ndarray or tuple of dpnp.ndarray - Returns a tuple of arrays, - with grid[i].shape = (1, ..., 1, dimensions[i], 1, ..., 1) - with dimensions[i] in the i-th place. + out : dpnp.ndarray or tuple of dpnp.ndarray + If the input is a single slice, returns an array. + If the input is multiple slices, returns a tuple of arrays, with + only one dimension not equal to 1. Examples -------- >>> import dpnp as np >>> np.ogrid[0:5, 0:5] - [array([[0], + (array([[0], [1], [2], [3], - [4]]), array([[0, 1, 2, 3, 4]])] + [4]]), array([[0, 1, 2, 3, 4]])) Creating an array on a different device or with a specified usm_type From 6ea41a5e0d2663c141e3ee26a8a3a6ccef4051c8 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Mon, 22 Jun 2026 15:16:17 +0200 Subject: [PATCH 2/9] Update histogramdd function --- dpnp/dpnp_iface_histograms.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dpnp/dpnp_iface_histograms.py b/dpnp/dpnp_iface_histograms.py index 7e91968926f..bb1dd2291b0 100644 --- a/dpnp/dpnp_iface_histograms.py +++ b/dpnp/dpnp_iface_histograms.py @@ -1151,8 +1151,8 @@ def histogramdd(sample, bins=10, range=None, density=None, weights=None): H : dpnp.ndarray The multidimensional histogram of sample x. See density and weights for the different possible semantics. - edges : list of {dpnp.ndarray or usm_ndarray} - A list of D arrays describing the bin edges for each dimension. + edges : tuple of dpnp.ndarray + A tuple of D arrays describing the bin edges for each dimension. See Also -------- @@ -1228,4 +1228,4 @@ def histogramdd(sample, bins=10, range=None, density=None, weights=None): if dpnp.is_supported_array_type(b): bin_edges_view_list[i] = b - return n, bin_edges_view_list + return n, tuple(bin_edges_view_list) From 1dbe497feacab3d480961b8330fb84c5df65faf8 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Mon, 22 Jun 2026 15:17:22 +0200 Subject: [PATCH 3/9] Update manipulation functions --- dpnp/dpnp_algo/dpnp_arraycreation.py | 3 +- dpnp/dpnp_iface_manipulation.py | 92 ++++++++++++++-------------- 2 files changed, 48 insertions(+), 47 deletions(-) diff --git a/dpnp/dpnp_algo/dpnp_arraycreation.py b/dpnp/dpnp_algo/dpnp_arraycreation.py index 9c9110b8538..6052ad46727 100644 --- a/dpnp/dpnp_algo/dpnp_arraycreation.py +++ b/dpnp/dpnp_algo/dpnp_arraycreation.py @@ -439,4 +439,5 @@ def __getitem__(self, key): slobj[k] = slice(None, None) nn[k] = nn[k][tuple(slobj)] slobj[k] = dpnp.newaxis - return nn + return tuple(nn) # ogrid -> tuple of arrays + return nn # mgrid -> ndarray diff --git a/dpnp/dpnp_iface_manipulation.py b/dpnp/dpnp_iface_manipulation.py index 6ef4400562f..e613089b8ee 100644 --- a/dpnp/dpnp_iface_manipulation.py +++ b/dpnp/dpnp_iface_manipulation.py @@ -612,8 +612,8 @@ def array_split(ary, indices_or_sections, axis=0): Returns ------- - sub-arrays : list of dpnp.ndarray - A list of sub arrays. Each array is a view of the corresponding input + sub-arrays : tuple of dpnp.ndarray + A tuple of sub arrays. Each array is a view of the corresponding input array. See Also @@ -625,11 +625,11 @@ def array_split(ary, indices_or_sections, axis=0): >>> import dpnp as np >>> x = np.arange(8.0) >>> np.array_split(x, 3) - [array([0., 1., 2.]), array([3., 4., 5.]), array([6., 7.])] + (array([0., 1., 2.]), array([3., 4., 5.]), array([6., 7.])) >>> x = np.arange(9) >>> np.array_split(x, 4) - [array([0, 1, 2]), array([3, 4]), array([5, 6]), array([7, 8])] + (array([0, 1, 2]), array([3, 4]), array([5, 6]), array([7, 8])) """ @@ -662,7 +662,7 @@ def array_split(ary, indices_or_sections, axis=0): end = div_points[i + 1] sub_arys.append(dpnp.swapaxes(sary[st:end], axis, 0)) - return sub_arys + return tuple(sub_arys) def asarray_chkfinite( @@ -881,7 +881,7 @@ def atleast_1d(*arys): Returns ------- out : dpnp.ndarray - An array, or list of arrays, each with ``a.ndim >= 1``. + An array, or tuple of arrays, each with ``a.ndim >= 1``. Copies are made only if necessary. See Also @@ -899,7 +899,7 @@ def atleast_1d(*arys): >>> y = np.array([3, 4]) >>> np.atleast_1d(x, y) - [array([1.]), array([3, 4])] + (array([1.]), array([3, 4])) >>> x = np.arange(9.0).reshape(3, 3) >>> np.atleast_1d(x) @@ -926,7 +926,7 @@ def atleast_1d(*arys): res.append(result) if len(res) == 1: return res[0] - return res + return tuple(res) def atleast_2d(*arys): @@ -944,7 +944,7 @@ def atleast_2d(*arys): Returns ------- out : dpnp.ndarray - An array, or list of arrays, each with ``a.ndim >= 2``. + An array, or tuple of arrays, each with ``a.ndim >= 2``. Copies are avoided where possible, and views with two or more dimensions are returned. @@ -982,7 +982,7 @@ def atleast_2d(*arys): res.append(result) if len(res) == 1: return res[0] - return res + return tuple(res) def atleast_3d(*arys): @@ -1000,7 +1000,7 @@ def atleast_3d(*arys): Returns ------- out : dpnp.ndarray - An array, or list of arrays, each with ``a.ndim >= 3``. Copies are + An array, or tuple of arrays, each with ``a.ndim >= 3``. Copies are avoided where possible, and views with three or more dimensions are returned. @@ -1044,7 +1044,7 @@ def atleast_3d(*arys): res.append(result) if len(res) == 1: return res[0] - return res + return tuple(res) def broadcast_arrays(*args, subok=False): @@ -1653,8 +1653,8 @@ def dsplit(ary, indices_or_sections): Returns ------- - sub-arrays : list of dpnp.ndarray - A list of sub arrays. Each array is a view of the corresponding input + sub-arrays : tuple of dpnp.ndarray + A tuple of sub arrays. Each array is a view of the corresponding input array. See Also @@ -1671,16 +1671,16 @@ def dsplit(ary, indices_or_sections): [[ 8., 9., 10., 11.], [12., 13., 14., 15.]]]) >>> np.dsplit(x, 2) - [array([[[ 0., 1.], + (array([[[ 0., 1.], [ 4., 5.]], [[ 8., 9.], [12., 13.]]]), array([[[ 2., 3.], [ 6., 7.]], [[10., 11.], - [14., 15.]]])] + [14., 15.]]])) >>> np.dsplit(x, np.array([3, 6])) - [array([[[ 0., 1., 2.], + (array([[[ 0., 1., 2.], [ 4., 5., 6.]], [[ 8., 9., 10.], [12., 13., 14.]]]), @@ -1688,7 +1688,7 @@ def dsplit(ary, indices_or_sections): [ 7.]], [[11.], [15.]]]), - array([])] + array([])) """ @@ -1753,8 +1753,8 @@ def dstack(tup): _check_stack_arrays(tup) arrs = atleast_3d(*tup) - if not isinstance(arrs, list): - arrs = [arrs] + if not isinstance(arrs, tuple): + arrs = (arrs,) return dpnp.concatenate(arrs, axis=2) @@ -2052,8 +2052,8 @@ def hsplit(ary, indices_or_sections): Returns ------- - sub-arrays : list of dpnp.ndarray - A list of sub arrays. Each array is a view of the corresponding input + sub-arrays : tuple of dpnp.ndarray + A tuple of sub arrays. Each array is a view of the corresponding input array. See Also @@ -2070,16 +2070,16 @@ def hsplit(ary, indices_or_sections): [ 8., 9., 10., 11.], [12., 13., 14., 15.]]) >>> np.hsplit(x, 2) - [array([[ 0., 1.], + (array([[ 0., 1.], [ 4., 5.], [ 8., 9.], [12., 13.]]), array([[ 2., 3.], [ 6., 7.], [10., 11.], - [14., 15.]])] + [14., 15.]])) >>> np.hsplit(x, np.array([3, 6])) - [array([[ 0., 1., 2.], + (array([[ 0., 1., 2.], [ 4., 5., 6.], [ 8., 9., 10.], [12., 13., 14.]]), @@ -2087,7 +2087,7 @@ def hsplit(ary, indices_or_sections): [ 7.], [11.], [15.]]), - array([])] + array([])) With a higher dimensional array the split is still along the second axis. @@ -2098,16 +2098,16 @@ def hsplit(ary, indices_or_sections): [[4., 5.], [6., 7.]]]) >>> np.hsplit(x, 2) - [array([[[0., 1.]], + (array([[[0., 1.]], [[4., 5.]]]), array([[[2., 3.]], - [[6., 7.]]])] + [[6., 7.]]])) With a 1-D array, the split is along axis 0. >>> x = np.array([0, 1, 2, 3, 4, 5]) >>> np.hsplit(x, 2) - [array([0, 1, 2]), array([3, 4, 5])] + (array([0, 1, 2]), array([3, 4, 5])) """ @@ -2178,8 +2178,8 @@ def hstack(tup, *, dtype=None, casting="same_kind"): _check_stack_arrays(tup) arrs = dpnp.atleast_1d(*tup) - if not isinstance(arrs, list): - arrs = [arrs] + if not isinstance(arrs, tuple): + arrs = (arrs,) # As a special case, dimension 0 of 1-dimensional arrays is "horizontal" if arrs and arrs[0].ndim == 1: @@ -3555,8 +3555,8 @@ def split(ary, indices_or_sections, axis=0): Returns ------- - sub-arrays : list of dpnp.ndarray - A list of sub arrays. Each array is a view of the corresponding input + sub-arrays : tuple of dpnp.ndarray + A tuple of sub arrays. Each array is a view of the corresponding input array. Raises @@ -3588,12 +3588,12 @@ def split(ary, indices_or_sections, axis=0): >>> import dpnp as np >>> x = np.arange(9.0) >>> np.split(x, 3) - [array([0., 1., 2.]), array([3., 4., 5.]), array([6., 7., 8.])] + (array([0., 1., 2.]), array([3., 4., 5.]), array([6., 7., 8.])) >>> x = np.arange(8.0) >>> np.split(x, [3, 5, 6, 10]) - [array([0., 1., 2.]), array([3., 4.]), array([5.]), array([6., 7.]), \ - array([])] + (array([0., 1., 2.]), array([3., 4.]), array([5.]), array([6., 7.]), \ + array([])) """ @@ -4551,8 +4551,8 @@ def vsplit(ary, indices_or_sections): Returns ------- - sub-arrays : list of dpnp.ndarray - A list of sub arrays. Each array is a view of the corresponding input + sub-arrays : tuple of dpnp.ndarray + A tuple of sub arrays. Each array is a view of the corresponding input array. See Also @@ -4569,16 +4569,16 @@ def vsplit(ary, indices_or_sections): [ 8., 9., 10., 11.], [12., 13., 14., 15.]]) >>> np.vsplit(x, 2) - [array([[0., 1., 2., 3.], + (array([[0., 1., 2., 3.], [4., 5., 6., 7.]]), array([[ 8., 9., 10., 11.], - [12., 13., 14., 15.]])] + [12., 13., 14., 15.]])) >>> np.vsplit(x, np.array([3, 6])) - [array([[ 0., 1., 2., 3.], + (array([[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.]]), array([[12., 13., 14., 15.]]), - array([], shape=(0, 4), dtype=float64)] + array([], shape=(0, 4), dtype=float64)) With a higher dimensional array the split is still along the first axis. @@ -4589,10 +4589,10 @@ def vsplit(ary, indices_or_sections): [[4., 5.], [6., 7.]]]) >>> np.vsplit(x, 2) - [array([[[0., 1.], + (array([[[0., 1.], [2., 3.]]]), array([[[4., 5.], - [6., 7.]]])] + [6., 7.]]])) """ @@ -4664,6 +4664,6 @@ def vstack(tup, *, dtype=None, casting="same_kind"): _check_stack_arrays(tup) arrs = dpnp.atleast_2d(*tup) - if not isinstance(arrs, list): - arrs = [arrs] + if not isinstance(arrs, tuple): + arrs = (arrs,) return dpnp.concatenate(arrs, axis=0, dtype=dtype, casting=casting) From 47309d615d3d2a6842e0c80816f3e0aecaa34a3b Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Mon, 22 Jun 2026 15:19:33 +0200 Subject: [PATCH 4/9] Update tests to improve coverage and check the result is a tuple for the affected functions --- dpnp/tests/test_arraycreation.py | 22 ++++++++++++++-------- dpnp/tests/test_arraymanipulation.py | 6 ++++++ dpnp/tests/test_histogram.py | 3 +++ dpnp/tests/test_manipulation.py | 3 ++- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/dpnp/tests/test_arraycreation.py b/dpnp/tests/test_arraycreation.py index 5a9a1c73c6e..d6e72672636 100644 --- a/dpnp/tests/test_arraycreation.py +++ b/dpnp/tests/test_arraycreation.py @@ -1050,13 +1050,19 @@ def test_meshgrid_raise_error(): dpnp.meshgrid(b, indexing="ab") -class TestMgrid: +@pytest.mark.parametrize("grid", ["mgrid", "ogrid"]) +class TestGrid: def check_results(self, result, expected): - if isinstance(result, (list, tuple)): + # mgrid always returns a single array; ogrid returns a single array for + # a single slice and a tuple of arrays for multiple slices. In every + # case the container kind (tuple vs. bare array) must match NumPy. + if isinstance(expected, tuple): + assert isinstance(result, tuple) assert len(result) == len(expected) for dp_arr, np_arr in zip(result, expected): assert_allclose(dp_arr, np_arr) else: + assert not isinstance(result, tuple) assert_allclose(result, expected) @pytest.mark.parametrize( @@ -1069,9 +1075,9 @@ def check_results(self, result, expected): slice(0, 5, None), # no step ], ) - def test_single_slice(self, slice): - dpnp_result = dpnp.mgrid[slice] - numpy_result = numpy.mgrid[slice] + def test_single_slice(self, grid, slice): + dpnp_result = getattr(dpnp, grid)[slice] + numpy_result = getattr(numpy, grid)[slice] self.check_results(dpnp_result, numpy_result) @pytest.mark.parametrize( @@ -1086,9 +1092,9 @@ def test_single_slice(self, slice): ), # float start and complex step ], ) - def test_md_slice(self, slices): - dpnp_result = dpnp.mgrid[slices] - numpy_result = numpy.mgrid[slices] + def test_md_slice(self, grid, slices): + dpnp_result = getattr(dpnp, grid)[slices] + numpy_result = getattr(numpy, grid)[slices] self.check_results(dpnp_result, numpy_result) diff --git a/dpnp/tests/test_arraymanipulation.py b/dpnp/tests/test_arraymanipulation.py index 25c454b9761..70ce4e440d1 100644 --- a/dpnp/tests/test_arraymanipulation.py +++ b/dpnp/tests/test_arraymanipulation.py @@ -78,6 +78,8 @@ def test_dpnp_dpt_array(self): a = dpnp.array([1, 2]) b = dpt.asarray([2, 3]) res = dpnp.atleast_1d(a, b) + # multiple inputs are returned as a tuple of arrays + assert isinstance(res, tuple) desired = [dpnp.array([1, 2]), dpnp.array([2, 3])] assert_array_equal(res, desired) @@ -117,6 +119,8 @@ def test_dpnp_dpt_array(self): a = dpnp.array([1, 2]) b = dpt.asarray([2, 3]) res = dpnp.atleast_2d(a, b) + # multiple inputs are returned as a tuple of arrays + assert isinstance(res, tuple) desired = [dpnp.array([[1, 2]]), dpnp.array([[2, 3]])] assert_array_equal(res, desired) @@ -156,6 +160,8 @@ def test_dpnp_dpt_array(self): a = dpnp.array([1, 2]) b = dpt.asarray([2, 3]) res = dpnp.atleast_3d(a, b) + # multiple inputs are returned as a tuple of arrays + assert isinstance(res, tuple) desired = [dpnp.array([[[1], [2]]]), dpnp.array([[[2], [3]]])] assert_array_equal(res, desired) diff --git a/dpnp/tests/test_histogram.py b/dpnp/tests/test_histogram.py index b0988609d2c..9c915e10a24 100644 --- a/dpnp/tests/test_histogram.py +++ b/dpnp/tests/test_histogram.py @@ -738,6 +738,9 @@ def test_bins(self, bins): expected_hist, expected_edges = numpy.histogramdd(v, bins) result_hist, result_edges = dpnp.histogramdd(iv, bins_dpnp) assert_allclose(result_hist, expected_hist) + + # bin edges are returned as a tuple of arrays + assert isinstance(result_edges, tuple) for x, y in zip(result_edges, expected_edges): assert_allclose(x, y) diff --git a/dpnp/tests/test_manipulation.py b/dpnp/tests/test_manipulation.py index d09f19f1d77..67099cc2c38 100644 --- a/dpnp/tests/test_manipulation.py +++ b/dpnp/tests/test_manipulation.py @@ -28,7 +28,8 @@ def _compare_results(result, expected): - """Compare lists of arrays.""" + """Compare tuples of arrays returned by the split family of functions.""" + assert isinstance(result, tuple) if len(result) != len(expected): raise ValueError("Iterables have different lengths") From 35e949cd1bd6876f40c5ec58791cdfdbf1bbe8f2 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Mon, 22 Jun 2026 15:21:13 +0200 Subject: [PATCH 5/9] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 885cc73bab4..37340547f14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ This release is compatible with NumPy 2.5. * Clarified support for negative axes in `dpnp.transpose`/`dpnp.permute_dims` documentation [#2940](https://github.com/IntelPython/dpnp/pull/2940) * Allowed `dpnp.take` and `dpnp.compress` to cast the result into an `out` array of a different but same-kind dtype [#2959](https://github.com/IntelPython/dpnp/pull/2959) * Clarified the summary in `dpnp.reshape` and `dpnp.ndarray.reshape` docstrings [#2964](https://github.com/IntelPython/dpnp/pull/2964) +* Changed `dpnp.atleast_1d`, `dpnp.atleast_2d`, `dpnp.atleast_3d`, `dpnp.array_split`, `dpnp.split`, `dpnp.hsplit`, `dpnp.vsplit`, `dpnp.dsplit`, `dpnp.ogrid`, and `dpnp.histogramdd` to return a tuple of arrays instead of a list [#2965](https://github.com/IntelPython/dpnp/pull/2965) ### Deprecated From fed9658d9101ee51a98c33efe791c0f0948f6569 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Mon, 22 Jun 2026 16:12:51 +0200 Subject: [PATCH 6/9] Reverting changes for histogramm and split functions, since latest NumPy still use list for the result from them --- dpnp/dpnp_iface_histograms.py | 6 +-- dpnp/dpnp_iface_manipulation.py | 66 ++++++++++++++++----------------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/dpnp/dpnp_iface_histograms.py b/dpnp/dpnp_iface_histograms.py index bb1dd2291b0..b60ec12cc48 100644 --- a/dpnp/dpnp_iface_histograms.py +++ b/dpnp/dpnp_iface_histograms.py @@ -1151,8 +1151,8 @@ def histogramdd(sample, bins=10, range=None, density=None, weights=None): H : dpnp.ndarray The multidimensional histogram of sample x. See density and weights for the different possible semantics. - edges : tuple of dpnp.ndarray - A tuple of D arrays describing the bin edges for each dimension. + edges : list of dpnp.ndarray + A list of D arrays describing the bin edges for each dimension. See Also -------- @@ -1228,4 +1228,4 @@ def histogramdd(sample, bins=10, range=None, density=None, weights=None): if dpnp.is_supported_array_type(b): bin_edges_view_list[i] = b - return n, tuple(bin_edges_view_list) + return n, bin_edges_view_list diff --git a/dpnp/dpnp_iface_manipulation.py b/dpnp/dpnp_iface_manipulation.py index e613089b8ee..a8b2fc931de 100644 --- a/dpnp/dpnp_iface_manipulation.py +++ b/dpnp/dpnp_iface_manipulation.py @@ -612,8 +612,8 @@ def array_split(ary, indices_or_sections, axis=0): Returns ------- - sub-arrays : tuple of dpnp.ndarray - A tuple of sub arrays. Each array is a view of the corresponding input + sub-arrays : list of dpnp.ndarray + A list of sub arrays. Each array is a view of the corresponding input array. See Also @@ -625,11 +625,11 @@ def array_split(ary, indices_or_sections, axis=0): >>> import dpnp as np >>> x = np.arange(8.0) >>> np.array_split(x, 3) - (array([0., 1., 2.]), array([3., 4., 5.]), array([6., 7.])) + [array([0., 1., 2.]), array([3., 4., 5.]), array([6., 7.])] >>> x = np.arange(9) >>> np.array_split(x, 4) - (array([0, 1, 2]), array([3, 4]), array([5, 6]), array([7, 8])) + [array([0, 1, 2]), array([3, 4]), array([5, 6]), array([7, 8])] """ @@ -662,7 +662,7 @@ def array_split(ary, indices_or_sections, axis=0): end = div_points[i + 1] sub_arys.append(dpnp.swapaxes(sary[st:end], axis, 0)) - return tuple(sub_arys) + return sub_arys def asarray_chkfinite( @@ -1653,8 +1653,8 @@ def dsplit(ary, indices_or_sections): Returns ------- - sub-arrays : tuple of dpnp.ndarray - A tuple of sub arrays. Each array is a view of the corresponding input + sub-arrays : list of dpnp.ndarray + A list of sub arrays. Each array is a view of the corresponding input array. See Also @@ -1671,16 +1671,16 @@ def dsplit(ary, indices_or_sections): [[ 8., 9., 10., 11.], [12., 13., 14., 15.]]]) >>> np.dsplit(x, 2) - (array([[[ 0., 1.], + [array([[[ 0., 1.], [ 4., 5.]], [[ 8., 9.], [12., 13.]]]), array([[[ 2., 3.], [ 6., 7.]], [[10., 11.], - [14., 15.]]])) + [14., 15.]]])] >>> np.dsplit(x, np.array([3, 6])) - (array([[[ 0., 1., 2.], + [array([[[ 0., 1., 2.], [ 4., 5., 6.]], [[ 8., 9., 10.], [12., 13., 14.]]]), @@ -1688,7 +1688,7 @@ def dsplit(ary, indices_or_sections): [ 7.]], [[11.], [15.]]]), - array([])) + array([])] """ @@ -2052,8 +2052,8 @@ def hsplit(ary, indices_or_sections): Returns ------- - sub-arrays : tuple of dpnp.ndarray - A tuple of sub arrays. Each array is a view of the corresponding input + sub-arrays : list of dpnp.ndarray + A list of sub arrays. Each array is a view of the corresponding input array. See Also @@ -2070,16 +2070,16 @@ def hsplit(ary, indices_or_sections): [ 8., 9., 10., 11.], [12., 13., 14., 15.]]) >>> np.hsplit(x, 2) - (array([[ 0., 1.], + [array([[ 0., 1.], [ 4., 5.], [ 8., 9.], [12., 13.]]), array([[ 2., 3.], [ 6., 7.], [10., 11.], - [14., 15.]])) + [14., 15.]])] >>> np.hsplit(x, np.array([3, 6])) - (array([[ 0., 1., 2.], + [array([[ 0., 1., 2.], [ 4., 5., 6.], [ 8., 9., 10.], [12., 13., 14.]]), @@ -2087,7 +2087,7 @@ def hsplit(ary, indices_or_sections): [ 7.], [11.], [15.]]), - array([])) + array([])] With a higher dimensional array the split is still along the second axis. @@ -2098,16 +2098,16 @@ def hsplit(ary, indices_or_sections): [[4., 5.], [6., 7.]]]) >>> np.hsplit(x, 2) - (array([[[0., 1.]], + [array([[[0., 1.]], [[4., 5.]]]), array([[[2., 3.]], - [[6., 7.]]])) + [[6., 7.]]])] With a 1-D array, the split is along axis 0. >>> x = np.array([0, 1, 2, 3, 4, 5]) >>> np.hsplit(x, 2) - (array([0, 1, 2]), array([3, 4, 5])) + [array([0, 1, 2]), array([3, 4, 5])] """ @@ -3555,8 +3555,8 @@ def split(ary, indices_or_sections, axis=0): Returns ------- - sub-arrays : tuple of dpnp.ndarray - A tuple of sub arrays. Each array is a view of the corresponding input + sub-arrays : list of dpnp.ndarray + A list of sub arrays. Each array is a view of the corresponding input array. Raises @@ -3588,12 +3588,12 @@ def split(ary, indices_or_sections, axis=0): >>> import dpnp as np >>> x = np.arange(9.0) >>> np.split(x, 3) - (array([0., 1., 2.]), array([3., 4., 5.]), array([6., 7., 8.])) + [array([0., 1., 2.]), array([3., 4., 5.]), array([6., 7., 8.])] >>> x = np.arange(8.0) >>> np.split(x, [3, 5, 6, 10]) - (array([0., 1., 2.]), array([3., 4.]), array([5.]), array([6., 7.]), \ - array([])) + [array([0., 1., 2.]), array([3., 4.]), array([5.]), array([6., 7.]), \ + array([])] """ @@ -4551,8 +4551,8 @@ def vsplit(ary, indices_or_sections): Returns ------- - sub-arrays : tuple of dpnp.ndarray - A tuple of sub arrays. Each array is a view of the corresponding input + sub-arrays : list of dpnp.ndarray + A list of sub arrays. Each array is a view of the corresponding input array. See Also @@ -4569,16 +4569,16 @@ def vsplit(ary, indices_or_sections): [ 8., 9., 10., 11.], [12., 13., 14., 15.]]) >>> np.vsplit(x, 2) - (array([[0., 1., 2., 3.], + [array([[0., 1., 2., 3.], [4., 5., 6., 7.]]), array([[ 8., 9., 10., 11.], - [12., 13., 14., 15.]])) + [12., 13., 14., 15.]])] >>> np.vsplit(x, np.array([3, 6])) - (array([[ 0., 1., 2., 3.], + [array([[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.]]), array([[12., 13., 14., 15.]]), - array([], shape=(0, 4), dtype=float64)) + array([], shape=(0, 4), dtype=float64)] With a higher dimensional array the split is still along the first axis. @@ -4589,10 +4589,10 @@ def vsplit(ary, indices_or_sections): [[4., 5.], [6., 7.]]]) >>> np.vsplit(x, 2) - (array([[[0., 1.], + [array([[[0., 1.], [2., 3.]]]), array([[[4., 5.], - [6., 7.]]])) + [6., 7.]]])] """ From 00ae59bcee0f58810e8ccc6bce0499fc9256f85e Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Mon, 22 Jun 2026 16:16:51 +0200 Subject: [PATCH 7/9] Update the check in tests to validate result type matches NumPy --- dpnp/tests/test_arraymanipulation.py | 20 +++++++++++--------- dpnp/tests/test_histogram.py | 4 ++-- dpnp/tests/test_manipulation.py | 5 +++-- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/dpnp/tests/test_arraymanipulation.py b/dpnp/tests/test_arraymanipulation.py index 70ce4e440d1..d1587b9a50c 100644 --- a/dpnp/tests/test_arraymanipulation.py +++ b/dpnp/tests/test_arraymanipulation.py @@ -78,9 +78,9 @@ def test_dpnp_dpt_array(self): a = dpnp.array([1, 2]) b = dpt.asarray([2, 3]) res = dpnp.atleast_1d(a, b) - # multiple inputs are returned as a tuple of arrays - assert isinstance(res, tuple) - desired = [dpnp.array([1, 2]), dpnp.array([2, 3])] + desired = numpy.atleast_1d(numpy.array([1, 2]), numpy.array([2, 3])) + # multiple inputs are returned as a tuple of arrays, matching NumPy + assert type(res) is type(desired) is tuple assert_array_equal(res, desired) @@ -119,9 +119,9 @@ def test_dpnp_dpt_array(self): a = dpnp.array([1, 2]) b = dpt.asarray([2, 3]) res = dpnp.atleast_2d(a, b) - # multiple inputs are returned as a tuple of arrays - assert isinstance(res, tuple) - desired = [dpnp.array([[1, 2]]), dpnp.array([[2, 3]])] + desired = numpy.atleast_2d(numpy.array([1, 2]), numpy.array([2, 3])) + # multiple inputs are returned as a tuple of arrays, matching NumPy + assert type(res) is type(desired) is tuple assert_array_equal(res, desired) @@ -160,9 +160,9 @@ def test_dpnp_dpt_array(self): a = dpnp.array([1, 2]) b = dpt.asarray([2, 3]) res = dpnp.atleast_3d(a, b) - # multiple inputs are returned as a tuple of arrays - assert isinstance(res, tuple) - desired = [dpnp.array([[[1], [2]]]), dpnp.array([[[2], [3]]])] + desired = numpy.atleast_3d(numpy.array([1, 2]), numpy.array([2, 3])) + # multiple inputs are returned as a tuple of arrays, matching NumPy + assert type(res) is type(desired) is tuple assert_array_equal(res, desired) @@ -1073,6 +1073,8 @@ def test_1d_array(self, dtype): np_res = numpy.unstack(np_a) dp_res = dpnp.unstack(dp_a) + # the result is a tuple of arrays, matching NumPy + assert type(dp_res) is type(np_res) is tuple assert len(dp_res) == len(np_res) for dp_arr, np_arr in zip(dp_res, np_res): assert_array_equal(dp_arr, np_arr) diff --git a/dpnp/tests/test_histogram.py b/dpnp/tests/test_histogram.py index 9c915e10a24..9fbf18bdc32 100644 --- a/dpnp/tests/test_histogram.py +++ b/dpnp/tests/test_histogram.py @@ -739,8 +739,8 @@ def test_bins(self, bins): result_hist, result_edges = dpnp.histogramdd(iv, bins_dpnp) assert_allclose(result_hist, expected_hist) - # bin edges are returned as a tuple of arrays - assert isinstance(result_edges, tuple) + assert isinstance(result_edges, list) + assert isinstance(expected_edges, list) for x, y in zip(result_edges, expected_edges): assert_allclose(x, y) diff --git a/dpnp/tests/test_manipulation.py b/dpnp/tests/test_manipulation.py index 67099cc2c38..9eedcfe275d 100644 --- a/dpnp/tests/test_manipulation.py +++ b/dpnp/tests/test_manipulation.py @@ -28,8 +28,9 @@ def _compare_results(result, expected): - """Compare tuples of arrays returned by the split family of functions.""" - assert isinstance(result, tuple) + """Compare lists of arrays returned by the split family of functions.""" + # split/array_split/{h,v,d}split return a list of arrays + assert type(result) is type(expected) is list if len(result) != len(expected): raise ValueError("Iterables have different lengths") From d98fa28ac0e552193d6bd5e6f95980586e71ed16 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Mon, 22 Jun 2026 16:20:26 +0200 Subject: [PATCH 8/9] Update tests for other functions returning a collection --- dpnp/tests/test_indexing.py | 5 +++++ dpnp/tests/test_search.py | 8 ++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/dpnp/tests/test_indexing.py b/dpnp/tests/test_indexing.py index b7dc5f791b7..84bf62d0356 100644 --- a/dpnp/tests/test_indexing.py +++ b/dpnp/tests/test_indexing.py @@ -480,6 +480,7 @@ def test_ix(self, x0, x1): expected = dpnp.ix_(dpnp.array(x0), dpnp.array(x1)) result = numpy.ix_(numpy.array(x0), numpy.array(x1)) + assert type(expected) is type(result) is tuple assert_array_equal(result[0], expected[0]) assert_array_equal(result[1], expected[1]) @@ -571,6 +572,7 @@ def test_1d(self, dtype): np_res = numpy.nonzero(a) dpnp_res = dpnp.nonzero(ia) + assert type(dpnp_res) is type(np_res) is tuple assert_array_equal(np_res, dpnp_res) @pytest.mark.parametrize("dtype", get_all_dtypes(no_none=True)) @@ -1295,6 +1297,7 @@ def test_putmask3(arr, mask, vals): def test_tril_indices(n, k, m): result = dpnp.tril_indices(n, k, m) expected = numpy.tril_indices(n, k, m) + assert type(result) is type(expected) is tuple assert_array_equal(expected, result) @@ -1322,6 +1325,7 @@ def test_tril_indices_from(array, k): def test_triu_indices(n, k, m): result = dpnp.triu_indices(n, k, m) expected = numpy.triu_indices(n, k, m) + assert type(result) is type(expected) is tuple assert_array_equal(expected, result) @@ -1451,6 +1455,7 @@ def test_basic(self): expected = numpy.unravel_index(x_np, (7, 6)) result = dpnp.unravel_index(x_dp, (7, 6)) + assert type(result) is type(expected) is tuple assert_equal(expected, result) def test_order_f(self): diff --git a/dpnp/tests/test_search.py b/dpnp/tests/test_search.py index 493c1edce2d..4c6e3fd6595 100644 --- a/dpnp/tests/test_search.py +++ b/dpnp/tests/test_search.py @@ -320,8 +320,12 @@ def test_empty_result(self): a = numpy.zeros((1, 1)) ia = dpnp.array(a) - expected = numpy.vstack(numpy.where(a == 99.0)) - result = dpnp.vstack(dpnp.where(ia == 99.0)) + np_res = numpy.where(a == 99.0) + dp_res = dpnp.where(ia == 99.0) + assert type(dp_res) is type(np_res) is tuple + + expected = numpy.vstack(np_res) + result = dpnp.vstack(dp_res) assert_array_equal(result, expected) @testing.with_requires("numpy>=2.5") From 5378d28b875ca156208ca155650c776b12a4016e Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Mon, 22 Jun 2026 16:25:30 +0200 Subject: [PATCH 9/9] Revert changes in CHANGELOG.md lost after the rebase --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37340547f14..616f10e16b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ This release is compatible with NumPy 2.5. * Clarified support for negative axes in `dpnp.transpose`/`dpnp.permute_dims` documentation [#2940](https://github.com/IntelPython/dpnp/pull/2940) * Allowed `dpnp.take` and `dpnp.compress` to cast the result into an `out` array of a different but same-kind dtype [#2959](https://github.com/IntelPython/dpnp/pull/2959) * Clarified the summary in `dpnp.reshape` and `dpnp.ndarray.reshape` docstrings [#2964](https://github.com/IntelPython/dpnp/pull/2964) -* Changed `dpnp.atleast_1d`, `dpnp.atleast_2d`, `dpnp.atleast_3d`, `dpnp.array_split`, `dpnp.split`, `dpnp.hsplit`, `dpnp.vsplit`, `dpnp.dsplit`, `dpnp.ogrid`, and `dpnp.histogramdd` to return a tuple of arrays instead of a list [#2965](https://github.com/IntelPython/dpnp/pull/2965) +* Changed `dpnp.atleast_1d`, `dpnp.atleast_2d`, `dpnp.atleast_3d`, and `dpnp.ogrid` to return a tuple of arrays instead of a list [#2965](https://github.com/IntelPython/dpnp/pull/2965) ### Deprecated