Skip to content
Open
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,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`, and `dpnp.ogrid` to return a tuple of arrays instead of a list [#2965](https://github.com/IntelPython/dpnp/pull/2965)

### Deprecated

Expand Down
3 changes: 2 additions & 1 deletion dpnp/dpnp_algo/dpnp_arraycreation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
34 changes: 24 additions & 10 deletions dpnp/dpnp_iface_arraycreation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
--------
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion dpnp/dpnp_iface_histograms.py
Original file line number Diff line number Diff line change
Expand Up @@ -1151,7 +1151,7 @@ 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}
edges : list of dpnp.ndarray
A list of D arrays describing the bin edges for each dimension.
See Also
Expand Down
26 changes: 13 additions & 13 deletions dpnp/dpnp_iface_manipulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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):
Expand All @@ -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.

Expand Down Expand Up @@ -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):
Expand All @@ -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.

Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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)


Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
22 changes: 14 additions & 8 deletions dpnp/tests/test_arraycreation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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(
Expand All @@ -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)


Expand Down
14 changes: 11 additions & 3 deletions dpnp/tests/test_arraymanipulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ 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])]
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)


Expand Down Expand Up @@ -117,7 +119,9 @@ 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]])]
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)


Expand Down Expand Up @@ -156,7 +160,9 @@ 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]]])]
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)


Expand Down Expand Up @@ -1067,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)
Expand Down
3 changes: 3 additions & 0 deletions dpnp/tests/test_histogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

assert isinstance(result_edges, list)
assert isinstance(expected_edges, list)
for x, y in zip(result_edges, expected_edges):
assert_allclose(x, y)

Expand Down
5 changes: 5 additions & 0 deletions dpnp/tests/test_indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])

Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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)


Expand Down Expand Up @@ -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)


Expand Down Expand Up @@ -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):
Expand Down
4 changes: 3 additions & 1 deletion dpnp/tests/test_manipulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@


def _compare_results(result, expected):
"""Compare lists of arrays."""
"""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")

Expand Down
8 changes: 6 additions & 2 deletions dpnp/tests/test_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Loading