Skip to content

Commit 2c60677

Browse files
Merge branch 'master' into refactor_submodule_exports
2 parents 3a06a3a + f0eb0a8 commit 2c60677

13 files changed

+468
-158
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@ Also, that release drops support for Python 3.9, making Python 3.10 the minimum
3333
* Added the missing positional-only and keyword-only parameter markers to bring the ufunc signatures into alignment with NumPy [#2660](https://github.com/IntelPython/dpnp/pull/2660)
3434
* Redesigned `dpnp.modf` function to be a part of `ufunc` and `vm` pybind11 extensions [#2654](https://github.com/IntelPython/dpnp/pull/2654)
3535
* Refactored `dpnp.fft` and `dpnp.random` submodules by removing wildcard imports and defining explicit public exports [#2649](https://github.com/IntelPython/dpnp/pull/2649)
36+
* Added support for the `out` keyword to accept a tuple, bringing ufunc signatures into alignment with those in NumPy [#2664](https://github.com/IntelPython/dpnp/pull/2664)
3637
* Unified public API definitions in `dpnp.linalg` and `dpnp.scipy` submodules [#2663](https://github.com/IntelPython/dpnp/pull/2663)
3738

3839
### Deprecated
3940

4041
* `dpnp.asfarray` is deprecated. Use `dpnp.asarray` with an appropriate dtype instead [#2650](https://github.com/IntelPython/dpnp/pull/2650)
42+
* Passing the output array ``out`` positionally to `dpnp.minimum` and `dpnp.maximum` is deprecated. Pass the output with the keyword form, e.g. ``dpnp.minimum(a, b, out=c)`` [#2659](https://github.com/IntelPython/dpnp/pull/2659)
4143

4244
### Removed
4345

doc/conf.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from dpnp.dpnp_algo.dpnp_elementwise_common import (
1414
DPNPBinaryFunc,
15+
DPNPBinaryFuncOutKw,
1516
DPNPUnaryFunc,
1617
DPNPUnaryTwoOutputsFunc,
1718
)
@@ -210,7 +211,13 @@
210211
# -- Options for todo extension ----------------------------------------------
211212
def _can_document_member(member, *args, **kwargs):
212213
if isinstance(
213-
member, (DPNPBinaryFunc, DPNPUnaryFunc, DPNPUnaryTwoOutputsFunc)
214+
member,
215+
(
216+
DPNPBinaryFunc,
217+
DPNPBinaryFuncOutKw,
218+
DPNPUnaryFunc,
219+
DPNPUnaryTwoOutputsFunc,
220+
),
214221
):
215222
return True
216223
return orig(member, *args, **kwargs)

dpnp/dpnp_algo/dpnp_elementwise_common.py

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
# THE POSSIBILITY OF SUCH DAMAGE.
2727
# *****************************************************************************
2828

29+
import warnings
30+
from functools import wraps
31+
2932
import dpctl.tensor as dpt
3033
import dpctl.tensor._copy_utils as dtc
3134
import dpctl.tensor._tensor_impl as dti
@@ -48,6 +51,7 @@
4851
"DPNPI0",
4952
"DPNPAngle",
5053
"DPNPBinaryFunc",
54+
"DPNPBinaryFuncOutKw",
5155
"DPNPFix",
5256
"DPNPImag",
5357
"DPNPReal",
@@ -199,13 +203,25 @@ def __call__(
199203
if dtype is not None:
200204
x_usm = dpt.astype(x_usm, dtype, copy=False)
201205

206+
out = self._unpack_out_kw(out)
202207
out_usm = None if out is None else dpnp.get_usm_ndarray(out)
203-
res_usm = super().__call__(x_usm, out=out_usm, order=order)
204208

209+
res_usm = super().__call__(x_usm, out=out_usm, order=order)
205210
if out is not None and isinstance(out, dpnp_array):
206211
return out
207212
return dpnp_array._create_from_usm_ndarray(res_usm)
208213

214+
def _unpack_out_kw(self, out):
215+
"""Unpack `out` keyword if passed as a tuple."""
216+
217+
if isinstance(out, tuple):
218+
if len(out) != self.nout:
219+
raise ValueError(
220+
"'out' tuple must have exactly one entry per ufunc output"
221+
)
222+
return out[0]
223+
return out
224+
209225

210226
class DPNPUnaryTwoOutputsFunc(UnaryElementwiseFunc):
211227
"""
@@ -361,7 +377,7 @@ def __call__(
361377
orig_out, out = list(out), list(out)
362378
res_dts = [res1_dt, res2_dt]
363379

364-
for i in range(2):
380+
for i in range(self.nout):
365381
if out[i] is None:
366382
continue
367383

@@ -419,7 +435,7 @@ def __call__(
419435
dep_evs = copy_ev
420436

421437
# Allocate a buffer for the output arrays if needed
422-
for i in range(2):
438+
for i in range(self.nout):
423439
if out[i] is None:
424440
res_dt = res_dts[i]
425441
if order == "K":
@@ -438,7 +454,7 @@ def __call__(
438454
)
439455
_manager.add_event_pair(ht_unary_ev, unary_ev)
440456

441-
for i in range(2):
457+
for i in range(self.nout):
442458
orig_res, res = orig_out[i], out[i]
443459
if not (orig_res is None or orig_res is res):
444460
# Copy the out data from temporary buffer to original memory
@@ -606,6 +622,13 @@ def __call__(
606622

607623
x1_usm = dpnp.get_usm_ndarray_or_scalar(x1)
608624
x2_usm = dpnp.get_usm_ndarray_or_scalar(x2)
625+
626+
if isinstance(out, tuple):
627+
if len(out) != self.nout:
628+
raise ValueError(
629+
"'out' tuple must have exactly one entry per ufunc output"
630+
)
631+
out = out[0]
609632
out_usm = None if out is None else dpnp.get_usm_ndarray(out)
610633

611634
if (
@@ -756,6 +779,22 @@ def outer(
756779
)
757780

758781

782+
class DPNPBinaryFuncOutKw(DPNPBinaryFunc):
783+
"""DPNPBinaryFunc that deprecates positional `out` argument."""
784+
785+
@wraps(DPNPBinaryFunc.__call__)
786+
def __call__(self, *args, **kwargs):
787+
if len(args) > self.nin:
788+
warnings.warn(
789+
"Passing more than 2 positional arguments is deprecated. "
790+
"If you meant to use the third argument as an output, "
791+
"use the `out` keyword argument instead.",
792+
DeprecationWarning,
793+
stacklevel=2,
794+
)
795+
return super().__call__(*args, **kwargs)
796+
797+
759798
class DPNPAngle(DPNPUnaryFunc):
760799
"""Class that implements dpnp.angle unary element-wise functions."""
761800

@@ -806,15 +845,22 @@ def __call__(self, x, /, out=None, *, order="K"):
806845
pass # pass to raise error in main implementation
807846
elif dpnp.issubdtype(x.dtype, dpnp.inexact):
808847
pass # for inexact types, pass to calculate in the backend
809-
elif out is not None and not dpnp.is_supported_array_type(out):
848+
elif not (
849+
out is None
850+
or isinstance(out, tuple)
851+
or dpnp.is_supported_array_type(out)
852+
):
810853
pass # pass to raise error in main implementation
811-
elif out is not None and out.dtype != x.dtype:
854+
elif not (
855+
out is None or isinstance(out, tuple) or out.dtype == x.dtype
856+
):
812857
# passing will raise an error but with incorrect needed dtype
813858
raise ValueError(
814859
f"Output array of type {x.dtype} is needed, got {out.dtype}"
815860
)
816861
else:
817862
# for exact types, return the input
863+
out = self._unpack_out_kw(out)
818864
if out is None:
819865
return dpnp.copy(x, order=order)
820866

@@ -919,6 +965,7 @@ def __init__(
919965
def __call__(self, x, /, decimals=0, out=None, *, dtype=None):
920966
if decimals != 0:
921967
x_usm = dpnp.get_usm_ndarray(x)
968+
out = self._unpack_out_kw(out)
922969
out_usm = None if out is None else dpnp.get_usm_ndarray(out)
923970

924971
if dpnp.issubdtype(x_usm.dtype, dpnp.integer):

dpnp/dpnp_iface_bitwise.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,11 @@ def binary_repr(num, width=None):
144144
First input array, expected to have an integer or boolean data type.
145145
x2 : {dpnp.ndarray, usm_ndarray, scalar}
146146
Second input array, also expected to have an integer or boolean data type.
147-
out : {None, dpnp.ndarray, usm_ndarray}, optional
147+
out : {None, dpnp.ndarray, usm_ndarray, tuple of ndarray}, optional
148148
Output array to populate.
149149
Array must have the correct shape and the expected data type.
150+
A tuple (possible only as a keyword argument) must have length equal to the
151+
number of outputs.
150152
151153
Default: ``None``.
152154
order : {None, "C", "F", "A", "K"}, optional
@@ -233,9 +235,11 @@ def binary_repr(num, width=None):
233235
----------
234236
x : {dpnp.ndarray, usm_ndarray}
235237
Input array, expected to have an integer data type.
236-
out : {None, dpnp.ndarray, usm_ndarray}, optional
238+
out : {None, dpnp.ndarray, usm_ndarray, tuple of ndarray}, optional
237239
Output array to populate.
238240
Array must have the correct shape and the expected data type.
241+
A tuple (possible only as a keyword argument) must have length equal to the
242+
number of outputs.
239243
240244
Default: ``None``.
241245
order : {None, "C", "F", "A", "K"}, optional
@@ -290,9 +294,11 @@ def binary_repr(num, width=None):
290294
First input array, expected to have an integer or boolean data type.
291295
x2 : {dpnp.ndarray, usm_ndarray, scalar}
292296
Second input array, also expected to have an integer or boolean data type.
293-
out : {None, dpnp.ndarray, usm_ndarray}, optional
297+
out : {None, dpnp.ndarray, usm_ndarray, tuple of ndarray}, optional
294298
Output array to populate.
295299
Array must have the correct shape and the expected data type.
300+
A tuple (possible only as a keyword argument) must have length equal to the
301+
number of outputs.
296302
297303
Default: ``None``.
298304
order : {None, "C", "F", "A", "K"}, optional
@@ -374,9 +380,11 @@ def binary_repr(num, width=None):
374380
First input array, expected to have an integer or boolean data type.
375381
x2 : {dpnp.ndarray, usm_ndarray, scalar}
376382
Second input array, also expected to have an integer or boolean data type.
377-
out : {None, dpnp.ndarray, usm_ndarray}, optional
383+
out : {None, dpnp.ndarray, usm_ndarray, tuple of ndarray}, optional
378384
Output array to populate.
379385
Array must have the correct shape and the expected data type.
386+
A tuple (possible only as a keyword argument) must have length equal to the
387+
number of outputs.
380388
381389
Default: ``None``.
382390
order : {None, "C", "F", "A", "K"}, optional
@@ -460,9 +468,11 @@ def binary_repr(num, width=None):
460468
----------
461469
x : {dpnp.ndarray, usm_ndarray}
462470
Input array, expected to have an integer or boolean data type.
463-
out : {None, dpnp.ndarray, usm_ndarray}, optional
471+
out : {None, dpnp.ndarray, usm_ndarray, tuple of ndarray}, optional
464472
Output array to populate.
465473
Array must have the correct shape and the expected data type.
474+
A tuple (possible only as a keyword argument) must have length equal to the
475+
number of outputs.
466476
467477
Default: ``None``.
468478
order : {None, "C", "F", "A", "K"}, optional
@@ -544,9 +554,11 @@ def binary_repr(num, width=None):
544554
x2 : {dpnp.ndarray, usm_ndarray, scalar}
545555
Second input array, also expected to have an integer data type.
546556
Each element must be greater than or equal to ``0``.
547-
out : {None, dpnp.ndarray, usm_ndarray}, optional
557+
out : {None, dpnp.ndarray, usm_ndarray, tuple of ndarray}, optional
548558
Output array to populate.
549559
Array must have the correct shape and the expected data type.
560+
A tuple (possible only as a keyword argument) must have length equal to the
561+
number of outputs.
550562
551563
Default: ``None``.
552564
order : {None, "C", "F", "A", "K"}, optional
@@ -627,9 +639,11 @@ def binary_repr(num, width=None):
627639
x2 : {dpnp.ndarray, usm_ndarray, scalar}
628640
Second input array, also expected to have an integer data type.
629641
Each element must be greater than or equal to ``0``.
630-
out : {None, dpnp.ndarray, usm_ndarray}, optional
642+
out : {None, dpnp.ndarray, usm_ndarray, tuple of ndarray}, optional
631643
Output array to populate.
632644
Array must have the correct shape and the expected data type.
645+
A tuple (possible only as a keyword argument) must have length equal to the
646+
number of outputs.
633647
634648
Default: ``None``.
635649
order : {None, "C", "F", "A", "K"}, optional

0 commit comments

Comments
 (0)