Skip to content

Commit 1217be8

Browse files
authored
Merge branch 'master' into enable_dlpack_test
2 parents 605acbb + 865b53e commit 1217be8

26 files changed

+1503
-589
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,25 +44,29 @@ Also, that release drops support for Python 3.9, making Python 3.10 the minimum
4444
* Compile indexing extension with `-fno-sycl-id-queries-fit-in-int` to support huge arrays [#2721](https://github.com/IntelPython/dpnp/pull/2721)
4545
* Updated `dpnp.fix` to reuse `dpnp.trunc` internally [#2722](https://github.com/IntelPython/dpnp/pull/2722)
4646
* Changed the build scripts and documentation due to `python setup.py develop` deprecation notice [#2716](https://github.com/IntelPython/dpnp/pull/2716)
47+
* Clarified behavior on repeated `axes` in `dpnp.tensordot` and `dpnp.linalg.tensordot` functions [#2733](https://github.com/IntelPython/dpnp/pull/2733)
4748

4849
### Deprecated
4950

5051
* `dpnp.asfarray` is deprecated. Use `dpnp.asarray` with an appropriate dtype instead [#2650](https://github.com/IntelPython/dpnp/pull/2650)
5152
* 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)
5253
* `dpnp.ndarray.T` property is deprecated for not two-dimensional array to be compatible with the Python array API standard. To achieve a similar behavior when ``a.ndim != 2``, either ``a.transpose()``, or ``a.mT`` (swaps the last two axes only), or ``dpnp.permute_dims(a, range(a.ndim)[::-1])`` can be used [#2681](https://github.com/IntelPython/dpnp/pull/2681)
54+
* `dpnp.fix` is deprecated. Use `dpnp.trunc` instead, which provides identical functionality [#2730](https://github.com/IntelPython/dpnp/pull/2730)
5355

5456
### Removed
5557

5658
* Dropped support for Python 3.9 [#2626](https://github.com/IntelPython/dpnp/pull/2626)
5759
* Removed the obsolete interface from DPNP to Numba JIT [#2647](https://github.com/IntelPython/dpnp/pull/2647)
5860
* Removed the `newshape` parameter from `dpnp.reshape`, which has been deprecated since dpnp 0.17.0. Pass it positionally or use `shape=` on newer versions [#2670](https://github.com/IntelPython/dpnp/pull/2670)
61+
* Removed unused `pytest` configuration from `pyproject.toml` [#2729](https://github.com/IntelPython/dpnp/pull/2729)
5962

6063
### Fixed
6164

6265
* Suppressed a potential deprecation warning triggered during import of the `dpctl.tensor` module [#2709](https://github.com/IntelPython/dpnp/pull/2709)
6366
* Corrected a phonetic spelling issue due to incorrect using of `a nd` in docstrings [#2719](https://github.com/IntelPython/dpnp/pull/2719)
6467
* Resolved an issue causing `dpnp.linspace` to return an incorrect output shape when inputs were passed as arrays [#2712](https://github.com/IntelPython/dpnp/pull/2712)
6568
* Resolved an issue where `dpnp` always returns the base allocation pointer, when the view start is expected [#2651](https://github.com/IntelPython/dpnp/pull/2651)
69+
* Fixed an issue causing an exception in `dpnp.geomspace` and `dpnp.logspace` when called with explicit `device` keyword but any input array is allocated on another device [#2723](https://github.com/IntelPython/dpnp/pull/2723)
6670

6771
### Security
6872

conda-recipe/meta.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{% set max_compiler_and_mkl_version = environ.get("MAX_BUILD_CMPL_MKL_VERSION", "2026.0a0") %}
22
{% set required_compiler_and_mkl_version = "2025.0" %}
3-
{% set required_dpctl_version = "0.21.0" %}
3+
{% set required_dpctl_version = "0.22.0*" %}
44

55
{% set pyproject = load_file_data('pyproject.toml') %}
66
{% set py_build_deps = pyproject.get('build-system', {}).get('requires', []) %}

dpnp/dpnp_algo/dpnp_arraycreation.py

Lines changed: 81 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,46 @@
4646

4747

4848
def _as_usm_ndarray(a, usm_type, sycl_queue):
49+
"""Converts input object to `dpctl.tensor.usm_ndarray`"""
50+
4951
if isinstance(a, dpnp_array):
50-
return a.get_array()
52+
a = a.get_array()
5153
return dpt.asarray(a, usm_type=usm_type, sycl_queue=sycl_queue)
5254

5355

56+
def _check_has_zero_val(a):
57+
"""Check if any element in input object is equal to zero"""
58+
59+
if dpnp.isscalar(a):
60+
if a == 0:
61+
return True
62+
elif hasattr(a, "any"):
63+
if (a == 0).any():
64+
return True
65+
elif (numpy.array(a) == 0).any():
66+
return True
67+
return False
68+
69+
70+
def _get_usm_allocations(objs, device=None, usm_type=None, sycl_queue=None):
71+
"""
72+
Get common USM allocations based on a list of input objects and an explicit
73+
device, a SYCL queue, or a USM type if specified.
74+
75+
"""
76+
77+
alloc_usm_type, alloc_sycl_queue = get_usm_allocations(objs)
78+
79+
if sycl_queue is None and device is None:
80+
sycl_queue = alloc_sycl_queue
81+
82+
if usm_type is None:
83+
usm_type = alloc_usm_type or "device"
84+
return usm_type, dpnp.get_normalized_queue_device(
85+
sycl_queue=sycl_queue, device=device
86+
)
87+
88+
5489
def dpnp_geomspace(
5590
start,
5691
stop,
@@ -62,76 +97,57 @@ def dpnp_geomspace(
6297
endpoint=True,
6398
axis=0,
6499
):
65-
usm_type_alloc, sycl_queue_alloc = get_usm_allocations([start, stop])
66-
67-
if sycl_queue is None and device is None:
68-
sycl_queue = sycl_queue_alloc
69-
sycl_queue_normalized = dpnp.get_normalized_queue_device(
70-
sycl_queue=sycl_queue, device=device
100+
usm_type, sycl_queue = _get_usm_allocations(
101+
[start, stop], device=device, usm_type=usm_type, sycl_queue=sycl_queue
71102
)
72103

73-
if usm_type is None:
74-
_usm_type = "device" if usm_type_alloc is None else usm_type_alloc
75-
else:
76-
_usm_type = usm_type
104+
if _check_has_zero_val(start) or _check_has_zero_val(stop):
105+
raise ValueError("Geometric sequence cannot include zero")
77106

78-
start = _as_usm_ndarray(start, _usm_type, sycl_queue_normalized)
79-
stop = _as_usm_ndarray(stop, _usm_type, sycl_queue_normalized)
107+
start = dpnp.array(start, usm_type=usm_type, sycl_queue=sycl_queue)
108+
stop = dpnp.array(stop, usm_type=usm_type, sycl_queue=sycl_queue)
80109

81110
dt = numpy.result_type(start, stop, float(num))
82-
dt = map_dtype_to_device(dt, sycl_queue_normalized.sycl_device)
111+
dt = map_dtype_to_device(dt, sycl_queue.sycl_device)
83112
if dtype is None:
84113
dtype = dt
85114

86-
if dpnp.any(start == 0) or dpnp.any(stop == 0):
87-
raise ValueError("Geometric sequence cannot include zero")
115+
# promote both arguments to the same dtype
116+
start = start.astype(dt, copy=False)
117+
stop = stop.astype(dt, copy=False)
88118

89-
out_sign = dpt.ones(
90-
dpt.broadcast_arrays(start, stop)[0].shape,
91-
dtype=dt,
92-
usm_type=_usm_type,
93-
sycl_queue=sycl_queue_normalized,
94-
)
95-
# Avoid negligible real or imaginary parts in output by rotating to
96-
# positive real, calculating, then undoing rotation
97-
if dpnp.issubdtype(dt, dpnp.complexfloating):
98-
all_imag = (start.real == 0.0) & (stop.real == 0.0)
99-
if dpnp.any(all_imag):
100-
start[all_imag] = start[all_imag].imag
101-
stop[all_imag] = stop[all_imag].imag
102-
out_sign[all_imag] = 1j
103-
104-
both_negative = (dpt.sign(start) == -1) & (dpt.sign(stop) == -1)
105-
if dpnp.any(both_negative):
106-
dpt.negative(start[both_negative], out=start[both_negative])
107-
dpt.negative(stop[both_negative], out=stop[both_negative])
108-
dpt.negative(out_sign[both_negative], out=out_sign[both_negative])
109-
110-
log_start = dpt.log10(start)
111-
log_stop = dpt.log10(stop)
119+
# Allow negative real values and ensure a consistent result for complex
120+
# (including avoiding negligible real or imaginary parts in output) by
121+
# rotating start to positive real, calculating, then undoing rotation.
122+
out_sign = dpnp.sign(start)
123+
start = start / out_sign
124+
stop = stop / out_sign
125+
126+
log_start = dpnp.log10(start)
127+
log_stop = dpnp.log10(stop)
112128
res = dpnp_logspace(
113129
log_start,
114130
log_stop,
115131
num=num,
116132
endpoint=endpoint,
117133
base=10.0,
118-
dtype=dtype,
119-
usm_type=_usm_type,
120-
sycl_queue=sycl_queue_normalized,
121-
).get_array()
134+
dtype=dt,
135+
usm_type=usm_type,
136+
sycl_queue=sycl_queue,
137+
)
122138

139+
# Make sure the endpoints match the start and stop arguments. This is
140+
# necessary because np.exp(np.log(x)) is not necessarily equal to x.
123141
if num > 0:
124142
res[0] = start
125143
if num > 1 and endpoint:
126144
res[-1] = stop
127145

128-
res = out_sign * res
146+
res *= out_sign
129147

130148
if axis != 0:
131-
res = dpt.moveaxis(res, 0, axis)
132-
133-
res = dpt.astype(res, dtype, copy=False)
134-
return dpnp_array._create_from_usm_ndarray(res)
149+
res = dpnp.moveaxis(res, 0, axis)
150+
return res.astype(dtype, copy=False)
135151

136152

137153
def dpnp_linspace(
@@ -264,45 +280,36 @@ def dpnp_logspace(
264280
dtype=None,
265281
axis=0,
266282
):
267-
if not dpnp.isscalar(base):
268-
usm_type_alloc, sycl_queue_alloc = get_usm_allocations(
269-
[start, stop, base]
270-
)
271-
272-
if sycl_queue is None and device is None:
273-
sycl_queue = sycl_queue_alloc
274-
sycl_queue = dpnp.get_normalized_queue_device(
275-
sycl_queue=sycl_queue, device=device
276-
)
277-
278-
if usm_type is None:
279-
usm_type = "device" if usm_type_alloc is None else usm_type_alloc
280-
else:
281-
usm_type = usm_type
283+
usm_type, sycl_queue = _get_usm_allocations(
284+
[start, stop, base],
285+
device=device,
286+
usm_type=usm_type,
287+
sycl_queue=sycl_queue,
288+
)
282289

283-
start = _as_usm_ndarray(start, usm_type, sycl_queue)
284-
stop = _as_usm_ndarray(stop, usm_type, sycl_queue)
285-
base = _as_usm_ndarray(base, usm_type, sycl_queue)
290+
if not dpnp.isscalar(base):
291+
base = dpnp.array(base, usm_type=usm_type, sycl_queue=sycl_queue)
292+
start = dpnp.array(start, usm_type=usm_type, sycl_queue=sycl_queue)
293+
stop = dpnp.array(stop, usm_type=usm_type, sycl_queue=sycl_queue)
286294

287-
[start, stop, base] = dpt.broadcast_arrays(start, stop, base)
288-
base = dpt.expand_dims(base, axis=axis)
295+
start, stop, base = dpnp.broadcast_arrays(start, stop, base)
296+
base = dpnp.expand_dims(base, axis=axis)
289297

290-
# assume res as not a tuple, because retstep is False
298+
# assume `res` as not a tuple, because retstep is False
291299
res = dpnp_linspace(
292300
start,
293301
stop,
294302
num=num,
295-
device=device,
296303
usm_type=usm_type,
297304
sycl_queue=sycl_queue,
298305
endpoint=endpoint,
299306
axis=axis,
300-
).get_array()
307+
)
301308

302-
dpt.pow(base, res, out=res)
309+
dpnp.pow(base, res, out=res)
303310
if dtype is not None:
304-
res = dpt.astype(res, dtype, copy=False)
305-
return dpnp_array._create_from_usm_ndarray(res)
311+
res = res.astype(dtype, copy=False)
312+
return res
306313

307314

308315
class dpnp_nd_grid:

dpnp/dpnp_algo/dpnp_elementwise_common.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"DPNPBinaryFunc",
6161
"DPNPBinaryFuncOutKw",
6262
"DPNPBinaryTwoOutputsFunc",
63+
"DPNPDeprecatedUnaryFunc",
6364
"DPNPImag",
6465
"DPNPReal",
6566
"DPNPRound",
@@ -230,6 +231,32 @@ def _unpack_out_kw(self, out):
230231
return out
231232

232233

234+
class DPNPDeprecatedUnaryFunc(DPNPUnaryFunc):
235+
"""
236+
Class that implements a deprecated unary element-wise function.
237+
238+
Parameters
239+
----------
240+
deprecated_msg : {str, None}, optional
241+
Warning message to emit. If None, no warning is issued.
242+
243+
Default: ``None``.
244+
245+
"""
246+
247+
def __init__(self, *args, deprecated_msg=None, **kwargs):
248+
super().__init__(*args, **kwargs)
249+
self._deprecated_msg = deprecated_msg
250+
251+
@wraps(DPNPUnaryFunc.__call__)
252+
def __call__(self, *args, **kwargs):
253+
if self._deprecated_msg:
254+
warnings.warn(
255+
self._deprecated_msg, DeprecationWarning, stacklevel=2
256+
)
257+
return super().__call__(*args, **kwargs)
258+
259+
233260
class DPNPUnaryTwoOutputsFunc(UnaryElementwiseFunc):
234261
"""
235262
Class that implements unary element-wise functions with two output arrays.

dpnp/dpnp_iface_linearalgebra.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,7 +1121,7 @@ def outer(a, b, out=None):
11211121
return result
11221122

11231123

1124-
def tensordot(a, b, axes=2):
1124+
def tensordot(a, b, /, *, axes=2):
11251125
r"""
11261126
Compute tensor dot product along specified axes.
11271127
@@ -1148,7 +1148,10 @@ def tensordot(a, b, axes=2):
11481148
axes must match.
11491149
* (2,) array_like: A list of axes to be summed over, first sequence
11501150
applying to `a`, second to `b`. Both elements array_like must be of
1151-
the same length.
1151+
the same length. Each axis may appear at most once; repeated axes are
1152+
not allowed.
1153+
1154+
Default: ``2``.
11521155
11531156
Returns
11541157
-------
@@ -1178,6 +1181,13 @@ def tensordot(a, b, axes=2):
11781181
two sequences of the same length, with the first axis to sum over given
11791182
first in both sequences, the second axis second, and so forth.
11801183
1184+
For example, if ``a.shape == (2, 3, 4)`` and ``b.shape == (3, 4, 5)``, then
1185+
``axes=([1, 2], [0, 1])`` sums over the ``(3, 4)`` dimensions of both
1186+
arrays and produces an output of shape ``(2, 5)``.
1187+
1188+
Each summation axis corresponds to a distinct contraction index; repeating
1189+
an axis (for example ``axes=([1, 1], [0, 0])``) is invalid.
1190+
11811191
The shape of the result consists of the non-contracted axes of the
11821192
first tensor, followed by the non-contracted axes of the second.
11831193

dpnp/dpnp_iface_mathematical.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
DPNPBinaryFunc,
6767
DPNPBinaryFuncOutKw,
6868
DPNPBinaryTwoOutputsFunc,
69+
DPNPDeprecatedUnaryFunc,
6970
DPNPImag,
7071
DPNPReal,
7172
DPNPRound,
@@ -1853,6 +1854,12 @@ def ediff1d(ary, to_end=None, to_begin=None):
18531854
:obj:`dpnp.floor` : Return the floor of the input, element-wise.
18541855
:obj:`dpnp.ceil` : Return the ceiling of the input, element-wise.
18551856
1857+
Warning
1858+
-------
1859+
This function is deprecated. It is recommended to use
1860+
:func:`dpnp.trunc` instead, as it provides the same functionality of
1861+
truncating decimal values to their integer parts.
1862+
18561863
Examples
18571864
--------
18581865
>>> import dpnp as np
@@ -1867,13 +1874,14 @@ def ediff1d(ary, to_end=None, to_begin=None):
18671874
"""
18681875

18691876
# reuse trunc backend implementation for fix
1870-
fix = DPNPUnaryFunc(
1877+
fix = DPNPDeprecatedUnaryFunc(
18711878
"fix",
18721879
ti._trunc_result_type,
18731880
ti._trunc,
18741881
_FIX_DOCSTRING,
18751882
mkl_fn_to_call="_mkl_trunc_to_call",
18761883
mkl_impl_fn="_trunc",
1884+
deprecated_msg="dpnp.fix is deprecated in favor of dpnp.trunc",
18771885
)
18781886

18791887

dpnp/linalg/dpnp_iface_linalg.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1975,9 +1975,10 @@ def tensordot(a, b, /, *, axes=2):
19751975
axes must match.
19761976
* (2,) array_like: A list of axes to be summed over, first sequence
19771977
applying to `a`, second to `b`. Both elements array_like must be of
1978-
the same length.
1978+
the same length. Each axis may appear at most once; repeated axes are
1979+
not allowed.
19791980
1980-
Default: ``2``.
1981+
Default: ``2``.
19811982
19821983
Returns
19831984
-------
@@ -2007,6 +2008,13 @@ def tensordot(a, b, /, *, axes=2):
20072008
two sequences of the same length, with the first axis to sum over given
20082009
first in both sequences, the second axis second, and so forth.
20092010
2011+
For example, if ``a.shape == (2, 3, 4)`` and ``b.shape == (3, 4, 5)``, then
2012+
``axes=([1, 2], [0, 1])`` sums over the ``(3, 4)`` dimensions of both
2013+
arrays and produces an output of shape ``(2, 5)``.
2014+
2015+
Each summation axis corresponds to a distinct contraction index; repeating
2016+
an axis (for example ``axes=([1, 1], [0, 0])``) is invalid.
2017+
20102018
The shape of the result consists of the non-contracted axes of the
20112019
first tensor, followed by the non-contracted axes of the second.
20122020

0 commit comments

Comments
 (0)