Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

* Added support for boolean input in `dpnp.positive` and the `+` operator of `dpnp.ndarray` [#3128](https://github.com/IntelPython/dpnp/pull/3128)
* Added `dpnp.top_k` to return the `k` largest or smallest elements and their indices along an axis, with a `sorted` keyword to return them in sorted order [#3133](https://github.com/IntelPython/dpnp/pull/3133)
* Added implementation of `dpnp.minmax`, computing the minimum and the maximum of an array in a single pass by a new fused reduction kernel [#3135](https://github.com/IntelPython/dpnp/pull/3135)

### Changed

Expand Down
2 changes: 2 additions & 0 deletions doc/reference/math.rst
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ Extrema finding
fmin
nanmin

minmax


Miscellaneous
-------------
Expand Down
2 changes: 2 additions & 0 deletions dpnp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,7 @@
mean,
median,
min,
minmax,
ptp,
std,
var,
Expand Down Expand Up @@ -1018,6 +1019,7 @@
"mean",
"median",
"min",
"minmax",
"nanmax",
"nanmean",
"nanmedian",
Expand Down
138 changes: 137 additions & 1 deletion dpnp/dpnp_iface_statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

"""

# pylint: disable=protected-access
# pylint: disable=no-name-in-module

import math
Expand All @@ -49,6 +50,7 @@
import dpnp
import dpnp.libtensor._statistics_impl as statistics_ext
import dpnp.libtensor._tensor_elementwise_impl as ti
import dpnp.libtensor._tensor_impl as dti
import dpnp.tensor as dpt

from .dpnp_utils import get_usm_allocations
Expand All @@ -59,6 +61,7 @@
from .dpnp_utils.dpnp_utils_reduction import dpnp_wrap_reduction_call
from .dpnp_utils.dpnp_utils_statistics import dpnp_cov, dpnp_median
from .tensor._numpy_helper import normalize_axis_index
from .tensor._reduction import _minmax as dpt_minmax


def _count_reduce_items(arr, axis, where=True):
Expand Down Expand Up @@ -133,7 +136,6 @@ def _divide_by_scalar(a, v):
_manager = dpu.SequentialOrderManager[queue]
dep_evs = _manager.submitted_events

# pylint: disable=protected-access
ht_ev, div_ev = ti._divide_by_scalar(
src=usm_a, scalar=v, dst=usm_a, sycl_queue=queue, depends=dep_evs
)
Expand Down Expand Up @@ -1094,6 +1096,7 @@ def max(a, axis=None, out=None, keepdims=False, initial=None, where=True):
See Also
--------
:obj:`dpnp.min` : Return the minimum of an array.
:obj:`dpnp.minmax` : Return the minimum and maximum of an array.
:obj:`dpnp.maximum` : Element-wise maximum of two arrays, propagates NaNs.
:obj:`dpnp.fmax` : Element-wise maximum of two arrays, ignores NaNs.
:obj:`dpnp.nanmax` : The maximum value of an array along a given axis,
Expand Down Expand Up @@ -1369,6 +1372,7 @@ def min(a, axis=None, out=None, keepdims=False, initial=None, where=True):
See Also
--------
:obj:`dpnp.max` : Return the maximum of an array.
:obj:`dpnp.minmax` : Return the minimum and maximum of an array.
:obj:`dpnp.minimum` : Element-wise minimum of two arrays, propagates NaNs.
:obj:`dpnp.fmin` : Element-wise minimum of two arrays, ignores NaNs.
:obj:`dpnp.nanmin` : The minimum value of an array along a given axis,
Expand Down Expand Up @@ -1409,6 +1413,138 @@ def min(a, axis=None, out=None, keepdims=False, initial=None, where=True):
)


def minmax(a, axis=None, out=None, keepdims=False, initial=None, where=True):
"""
Return the minimum and maximum of an array or along an axis.

This is equivalent to ``(dpnp.min(a, ...), dpnp.max(a, ...))`` but computes
both the minimum and the maximum in a single pass over `a`.

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

Parameters
----------
a : {dpnp.ndarray, usm_ndarray}
Input array.
axis : {None, int or tuple of ints}, optional
Axis or axes along which to operate. By default, flattened input is
used. If this is a tuple of integers, the minimum and the maximum are
selected over multiple axes, instead of a single axis or all the axes
as before.

Default: ``None``.
out : {None, tuple of None, dpnp.ndarray or usm_ndarray}, optional
A tuple ``(min, max)`` of two arrays in which to place the result.
Each array must be of the same shape and buffer length as the expected
output. A ``None`` entry means that a new array is allocated for that
result.

Default: ``None``.
keepdims : {None, bool}, optional
If this is set to ``True``, the axes which are reduced are left in the
result as dimensions with size one. With this option, the result will
broadcast correctly against the input array.

Default: ``False``.

Returns
-------
out : tuple of dpnp.ndarray
A tuple ``(min, max)`` holding the minimum and maximum of `a`. If
`axis` is ``None``, the results are zero-dimensional arrays. If `axis`
is an integer, the results are arrays of dimension ``a.ndim - 1``. If
`axis` is a tuple, the results are arrays of dimension
``a.ndim - len(axis)``.

Limitations
-----------
Parameters `where`, and `initial` are only supported with their default
values. Otherwise ``NotImplementedError`` exception will be raised.

If an array in `out` has a data type other than that of `a`, the result is
computed in the data type of `a` and then cast, while NumPy casts `a` to
the data type of `out` first. The results differ when the cast does not
preserve the order of values, e.g. for a narrowing integer cast.

The arrays in `out` must not overlap in memory. Interleaved views, such as
``(r[:, 0], r[:, 1])``, are rejected as well, while NumPy accepts them.

See Also
--------
:obj:`dpnp.min` : Return the minimum of an array.
:obj:`dpnp.max` : Return the maximum of an array.
:obj:`dpnp.ptp` : Range of values (maximum - minimum) along an axis.
:obj:`dpnp.nanmin` : The minimum value of an array along a given axis,
ignores NaNs.
:obj:`dpnp.nanmax` : The maximum value of an array along a given axis,
ignores NaNs.

Notes
-----
NaN values are propagated, that is if at least one item is NaN, the
corresponding output value will be NaN as well. To ignore NaN values
use :obj:`dpnp.nanmin` and :obj:`dpnp.nanmax`.

Examples
--------
>>> import dpnp as np
>>> a = np.arange(4).reshape((2, 2))
>>> a
array([[0, 1],
[2, 3]])
>>> np.minmax(a) # min and max of the flattened array
(array(0), array(3))

>>> np.minmax(a, axis=0) # along the first axis
(array([0, 1]), array([2, 3]))
>>> np.minmax(a, axis=1) # along the second axis
(array([0, 2]), array([1, 3]))

>>> b = np.arange(5, dtype=float)
>>> b[2] = np.nan
>>> np.minmax(b)
(array(nan), array(nan))

"""

dpnp.check_limitations(initial=initial, where=where)
usm_a = dpnp.get_usm_ndarray(a)

if out is None:
out = (None, None)
elif not isinstance(out, tuple):
raise TypeError("'out' must be a tuple of arrays")
elif len(out) != 2:
raise ValueError("'out' tuple must have exactly two entries")

out_arrays = [res for res in out if res is not None]
dpnp.check_supported_arrays_type(*out_arrays)
if len(out_arrays) == 2 and dti._array_overlap(
*map(dpnp.get_usm_ndarray, out_arrays)
):
raise ValueError("Output arrays cannot overlap")

usm_out = []
for res in out:
if res is not None:
# the result is computed in the data type of `a`
if res.dtype != a.dtype:
# a writable temporary would hide the flag
if not res.flags.writable:
raise ValueError("provided `out` array is read-only")
res = dpnp.empty_like(res, dtype=a.dtype)
res = dpnp.get_usm_ndarray(res)
usm_out.append(res)

usm_res = dpt_minmax(
usm_a, axis=axis, keepdims=keepdims, out=tuple(usm_out)
)
return tuple(
dpnp.get_result_array(x, res, casting="unsafe")
for x, res in zip(usm_res, out)
)


def ptp(
a,
/,
Expand Down
Loading
Loading