Skip to content

Commit d0883bd

Browse files
Merge remote-tracking branch 'origin/master' into fix_fft_logic
2 parents a9419ae + 1f35e57 commit d0883bd

13 files changed

Lines changed: 249 additions & 186 deletions

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [0.21.0] - 2026-MM-DD
88

9-
This release is compatible with NumPy 2.4.5.
9+
This release is compatible with NumPy 2.5.
1010

1111
### Added
1212

@@ -22,12 +22,16 @@ This release is compatible with NumPy 2.4.5.
2222
* Replaced `.pxi` includes in `dpnp.tensor` with modular `.pxd`/`.pyx` Cython imports [#2913](https://github.com/IntelPython/dpnp/pull/2913)
2323
* Reimplemented `dpnp.eye` and `dpnp.tensor.eye` with a branchless kernel [#2937](https://github.com/IntelPython/dpnp/pull/2937)
2424
* Cleaned up Python bindings for indexing functions, renaming `usm_ndarray_take` and `usm_ndarray_put` to `py_take` and `py_put` and refactoring validation [#2935](https://github.com/IntelPython/dpnp/pull/2935)
25+
* Updated `dpnp.linalg.eig` and `dpnp.linalg.eigvals` documentation to reflect NumPy's always-complex eigenvalue output for general matrices [#2953](https://github.com/IntelPython/dpnp/pull/2953)
26+
* Clarified support for negative axes in `dpnp.transpose`/`dpnp.permute_dims` documentation [#2940](https://github.com/IntelPython/dpnp/pull/2940)
2527
* Improved performance of `dpnp.fft` functions for complex strided input by avoiding oversized allocations and extra copies [#2939](https://github.com/IntelPython/dpnp/pull/2939)
2628

2729
### Deprecated
2830

2931
### Removed
3032

33+
* Removed support for arrays of 2-dimensional vectors in `dpnp.cross`, which now requires (arrays of) 3-dimensional vectors and raises `ValueError` otherwise [#2950](https://github.com/IntelPython/dpnp/pull/2950)
34+
3135
### Fixed
3236

3337
* Fixed incorrect in-place advanced indexing for 4D arrays when using `range` or `list` as index keys [#2872](https://github.com/IntelPython/dpnp/pull/2872)
@@ -39,6 +43,8 @@ This release is compatible with NumPy 2.4.5.
3943
* Fixed a bug in `astype` where casting floating point types to unsigned integral types could cause an intermediate signed integral type to overflow, leading to incorrect results [#2930](https://github.com/IntelPython/dpnp/pull/2930)
4044
* Fixed incorrect `dpnp.tensor.expm1` result for `complex(±0, 0)` special case on CPU to match the Python Array API specification [#2926](https://github.com/IntelPython/dpnp/pull/2926)
4145
* Fixed tests which expected lists from `dpctl` functions which now return tuples (i.e., `dpctl.SyclDevice.create_sub_devices`) [#2945](https://github.com/IntelPython/dpnp/pull/2945)
46+
* Fixed `PytestRemovedIn10Warning` raised by `pytest` 9.1.0 by converting class-scoped fixtures to class methods [#2952](https://github.com/IntelPython/dpnp/pull/2952)
47+
* Fixed `dpnp.linalg.svd(..., hermitian=True)` returning a non-unitary `vh` for singular input arrays due to a zero sign appearing [#2954](https://github.com/IntelPython/dpnp/pull/2954)
4248

4349
### Security
4450

dpnp/dpnp_array.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2384,6 +2384,7 @@ def transpose(self, *axes):
23842384
* ``tuple or list of ints``: `i` in the `j`-th place in the
23852385
tuple/list means that the array’s `i`-th axis becomes the
23862386
transposed array’s `j`-th axis.
2387+
Negative indices can also be used to specify axes.
23872388
* ``n ints``: same as an n-tuple/n-list of the same integers (this
23882389
form is intended simply as a “convenience” alternative to the
23892390
tuple form).

dpnp/dpnp_iface_manipulation.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3908,6 +3908,7 @@ def transpose(a, axes=None):
39083908
axes : {None, tuple or list of ints}, optional
39093909
If specified, it must be a tuple or list which contains a permutation
39103910
of [0, 1, ..., N-1] where N is the number of axes of `a`.
3911+
Negative indices can also be used to specify axes.
39113912
The `i`'th axis of the returned array will correspond to the axis
39123913
numbered ``axes[i]`` of the input. If not specified or ``None``,
39133914
defaults to ``range(a.ndim)[::-1]``, which reverses the order of
@@ -3951,6 +3952,10 @@ def transpose(a, axes=None):
39513952
>>> np.transpose(a).shape
39523953
(5, 4, 3, 2)
39533954
3955+
>>> a = np.arange(3*4*5).reshape((3, 4, 5))
3956+
>>> np.transpose(a, (-1, 0, -2)).shape
3957+
(5, 3, 4)
3958+
39543959
"""
39553960

39563961
dpnp.check_supported_arrays_type(a)

dpnp/dpnp_iface_mathematical.py

Lines changed: 20 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545

4646

4747
import builtins
48-
import warnings
4948

5049
import dpctl.utils as dpu
5150
import numpy
@@ -874,11 +873,8 @@ def cross(a, b, axisa=-1, axisb=-1, axisc=-1, axis=None):
874873
875874
The cross product of `a` and `b` in :math:`R^3` is a vector perpendicular
876875
to both `a` and `b`. If `a` and `b` are arrays of vectors, the vectors
877-
are defined by the last axis of `a` and `b` by default, and these axes
878-
can have dimensions 2 or 3. Where the dimension of either `a` or `b` is
879-
2, the third component of the input vector is assumed to be zero and the
880-
cross product calculated accordingly. In cases where both input vectors
881-
have dimension 2, the z-component of the cross product is returned.
876+
are defined by the last axis of `a` and `b` by default, and these axes must
877+
have 3 dimensions.
882878
883879
For full documentation refer to :obj:`numpy.cross`.
884880
@@ -897,8 +893,7 @@ def cross(a, b, axisa=-1, axisb=-1, axisc=-1, axis=None):
897893
898894
Default: ``-1``.
899895
axisc : int, optional
900-
Axis of `c` containing the cross product vector(s). Ignored if both
901-
input vectors have dimension ``2``, as the return is scalar. By default,
896+
Axis of `c` containing the cross product vector(s). By default,
902897
the last axis.
903898
904899
Default: ``-1``.
@@ -913,9 +908,14 @@ def cross(a, b, axisa=-1, axisb=-1, axisc=-1, axis=None):
913908
out : dpnp.ndarray
914909
Vector cross product(s).
915910
911+
Raises
912+
------
913+
ValueError
914+
When the dimension of the vector(s) in `a` or `b` does not equal 3.
915+
916916
See Also
917917
--------
918-
:obj:`dpnp.linalg.cross` : Array API compatible version.
918+
:obj:`dpnp.linalg.cross` : Array API compatible variation.
919919
:obj:`dpnp.inner` : Inner product.
920920
:obj:`dpnp.outer` : Outer product.
921921
@@ -929,27 +929,6 @@ def cross(a, b, axisa=-1, axisb=-1, axisc=-1, axis=None):
929929
>>> np.cross(x, y)
930930
array([-3, 6, -3])
931931
932-
One vector with dimension 2.
933-
934-
>>> x = np.array([1, 2])
935-
>>> y = np.array([4, 5, 6])
936-
>>> np.cross(x, y)
937-
array([12, -6, -3])
938-
939-
Equivalently:
940-
941-
>>> x = np.array([1, 2, 0])
942-
>>> y = np.array([4, 5, 6])
943-
>>> np.cross(x, y)
944-
array([12, -6, -3])
945-
946-
Both vectors with dimension 2.
947-
948-
>>> x = np.array([1, 2])
949-
>>> y = np.array([4, 5])
950-
>>> np.cross(x, y)
951-
array(-3)
952-
953932
Multiple vector cross-products. Note that the direction of the cross
954933
product vector is defined by the *right-hand rule*.
955934
@@ -992,43 +971,28 @@ def cross(a, b, axisa=-1, axisb=-1, axisc=-1, axis=None):
992971
"Input arrays with boolean data type are not supported."
993972
)
994973

974+
if (a.ndim < 1) or (b.ndim < 1):
975+
raise ValueError("At least one array has zero dimension")
976+
995977
# Check axisa and axisb are within bounds
996978
axisa = normalize_axis_index(axisa, a.ndim, msg_prefix="axisa")
997979
axisb = normalize_axis_index(axisb, b.ndim, msg_prefix="axisb")
998980

999981
# Move working axis to the end of the shape
1000982
a = dpnp.moveaxis(a, axisa, -1)
1001983
b = dpnp.moveaxis(b, axisb, -1)
1002-
if a.shape[-1] not in (2, 3) or b.shape[-1] not in (2, 3):
1003-
raise ValueError(
1004-
"Incompatible vector dimensions for cross product\n"
1005-
"(the dimension of vector used in cross product must be 2 or 3)"
1006-
)
1007-
1008-
if a.shape[-1] == 2 or b.shape[-1] == 2:
1009-
warnings.warn(
1010-
"Arrays of 2-dimensional vectors are deprecated. Use arrays of "
1011-
"3-dimensional vectors instead. (deprecated in dpnp 0.17.0)",
1012-
DeprecationWarning,
1013-
stacklevel=2,
1014-
)
1015984

1016-
# Modify the shape of input arrays if necessary
1017985
a_shape = a.shape
1018986
b_shape = b.shape
987+
if a_shape[-1] != 3 or b_shape[-1] != 3:
988+
raise ValueError(
989+
"Both input arrays must be (arrays of) 3-dimensional vectors, "
990+
f"but they are {a_shape[-1]} and {b_shape[-1]} dimensional instead."
991+
)
1019992

1020-
res_shape = dpnp.broadcast_shapes(a_shape[:-1], b_shape[:-1])
1021-
if a_shape[:-1] != res_shape:
1022-
a = dpnp.broadcast_to(a, res_shape + (a_shape[-1],))
1023-
a_shape = a.shape
1024-
if b_shape[:-1] != res_shape:
1025-
b = dpnp.broadcast_to(b, res_shape + (b_shape[-1],))
1026-
b_shape = b.shape
1027-
1028-
if a_shape[-1] == 3 or b_shape[-1] == 3:
1029-
res_shape += (3,)
1030-
# Check axisc is within bounds
1031-
axisc = normalize_axis_index(axisc, len(res_shape), msg_prefix="axisc")
993+
# Check axisc is within bounds
994+
res_shape = *dpnp.broadcast_shapes(a_shape[:-1], b_shape[:-1]), 3
995+
axisc = normalize_axis_index(axisc, len(res_shape), msg_prefix="axisc")
1032996

1033997
# Create the output array
1034998
dtype = dpnp.result_type(a, b)
@@ -1042,9 +1006,6 @@ def cross(a, b, axisa=-1, axisb=-1, axisc=-1, axis=None):
10421006
b = b.astype(dtype, copy=False)
10431007

10441008
cp = dpnp_cross(a, b, cp)
1045-
if a_shape[-1] == 2 and b_shape[-1] == 2:
1046-
return cp
1047-
10481009
return dpnp.moveaxis(cp, -1, axisc)
10491010

10501011

dpnp/dpnp_utils/dpnp_utils_linearalgebra.py

Lines changed: 16 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -700,67 +700,33 @@ def _validate_out_array(out, exec_q):
700700

701701

702702
def dpnp_cross(a, b, cp):
703-
"""Return the cross product of two (arrays of) vectors."""
703+
"""Return the cross product of two (arrays of) 3-dimensional vectors."""
704704

705705
# create local aliases for readability
706706
a0 = a[..., 0]
707707
a1 = a[..., 1]
708-
if a.shape[-1] == 3:
709-
a2 = a[..., 2]
708+
a2 = a[..., 2]
710709

711710
b0 = b[..., 0]
712711
b1 = b[..., 1]
713-
if b.shape[-1] == 3:
714-
b2 = b[..., 2]
715-
716-
if cp.ndim != 0 and cp.shape[-1] == 3:
717-
cp0 = cp[..., 0]
718-
cp1 = cp[..., 1]
719-
cp2 = cp[..., 2]
720-
721-
if a.shape[-1] == 2:
722-
if b.shape[-1] == 2:
723-
# a0 * b1 - a1 * b0
724-
cp = dpnp.multiply(a0, b1, out=cp)
725-
cp -= a1 * b0
726-
else:
727-
assert b.shape[-1] == 3
728-
# cp0 = a1 * b2 - 0 (a2 = 0)
729-
cp0 = dpnp.multiply(a1, b2, out=cp0)
712+
b2 = b[..., 2]
730713

731-
# cp1 = 0 - a0 * b2 (a2 = 0)
732-
cp1 = dpnp.multiply(a0, b2, out=cp1)
733-
cp1 = dpnp.negative(cp1, out=cp1)
714+
cp0 = cp[..., 0]
715+
cp1 = cp[..., 1]
716+
cp2 = cp[..., 2]
734717

735-
# cp2 = a0 * b1 - a1 * b0
736-
cp2 = dpnp.multiply(a0, b1, out=cp2)
737-
cp2 -= a1 * b0
738-
else:
739-
assert a.shape[-1] == 3
740-
if b.shape[-1] == 3:
741-
# cp0 = a1 * b2 - a2 * b1
742-
cp0 = dpnp.multiply(a1, b2, out=cp0)
743-
cp0 -= a2 * b1
744-
745-
# cp1 = a2 * b0 - a0 * b2
746-
cp1 = dpnp.multiply(a2, b0, out=cp1)
747-
cp1 -= a0 * b2
748-
749-
# cp2 = a0 * b1 - a1 * b0
750-
cp2 = dpnp.multiply(a0, b1, out=cp2)
751-
cp2 -= a1 * b0
752-
else:
753-
assert b.shape[-1] == 2
754-
# cp0 = 0 - a2 * b1 (b2 = 0)
755-
cp0 = dpnp.multiply(a2, b1, out=cp0)
756-
cp0 = dpnp.negative(cp0, out=cp0)
718+
# cp0 = a1 * b2 - a2 * b1
719+
cp0 = dpnp.multiply(a1, b2, out=cp0)
720+
cp0 -= a2 * b1
721+
722+
# cp1 = a2 * b0 - a0 * b2
723+
cp1 = dpnp.multiply(a2, b0, out=cp1)
724+
cp1 -= a0 * b2
757725

758-
# cp1 = a2 * b0 - 0 (b2 = 0)
759-
cp1 = dpnp.multiply(a2, b0, out=cp1)
726+
# cp2 = a0 * b1 - a1 * b0
727+
cp2 = dpnp.multiply(a0, b1, out=cp2)
728+
cp2 -= a1 * b0
760729

761-
# cp2 = a0 * b1 - a1 * b0
762-
cp2 = dpnp.multiply(a0, b1, out=cp2)
763-
cp2 -= a1 * b0
764730
return cp
765731

766732

dpnp/linalg/dpnp_iface_linalg.py

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -309,14 +309,6 @@ def cross(x1, x2, /, *, axis=-1):
309309
310310
"""
311311

312-
dpnp.check_supported_arrays_type(x1, x2)
313-
if x1.shape[axis] != 3 or x2.shape[axis] != 3:
314-
raise ValueError(
315-
"Both input arrays must be (arrays of) 3-dimensional vectors, "
316-
f"but they are {x1.shape[axis]} and {x2.shape[axis]} "
317-
"dimensional instead."
318-
)
319-
320312
return dpnp.cross(x1, x2, axis=axis)
321313

322314

@@ -480,10 +472,10 @@ def eig(a):
480472
481473
eigenvalues : (..., M) dpnp.ndarray
482474
The eigenvalues, each repeated according to its multiplicity.
483-
The eigenvalues are not necessarily ordered. The resulting array will
484-
be of complex type, unless the imaginary part is zero in which case it
485-
will be cast to a real type. When `a` is real the resulting eigenvalues
486-
will be real (zero imaginary part) or occur in conjugate pairs.
475+
The eigenvalues are not necessarily ordered. The resulting array is
476+
always of complex type, even when `a` is real-valued. In that case the
477+
eigenvalues either have a zero imaginary part or occur in conjugate
478+
pairs.
487479
eigenvectors : (..., M, M) dpnp.ndarray
488480
The normalized (unit "length") eigenvectors, such that the column
489481
``eigenvectors[:,i]`` is the eigenvector corresponding to the
@@ -511,8 +503,8 @@ def eig(a):
511503
(Almost) trivial example with real eigenvalues and eigenvectors.
512504
513505
>>> w, v = LA.eig(np.diag((1, 2, 3)))
514-
>>> w, v
515-
(array([1., 2., 3.]),
506+
>>> w, v.real
507+
(array([1.+0.j, 2.+0.j, 3.+0.j]),
516508
array([[1., 0., 0.],
517509
[0., 1., 0.],
518510
[0., 0., 1.]]))
@@ -541,8 +533,8 @@ def eig(a):
541533
>>> a = np.array([[1 + 1e-9, 0], [0, 1 - 1e-9]])
542534
>>> # Theor. eigenvalues are 1 +/- 1e-9
543535
>>> w, v = LA.eig(a)
544-
>>> w, v
545-
(array([1., 1.]),
536+
>>> w, v.real
537+
(array([1.+0.j, 1.+0.j]),
546538
array([[1., 0.],
547539
[0., 1.]]))
548540
@@ -689,11 +681,11 @@ def eigvals(a):
689681
690682
>>> D = np.diag((-1, 1))
691683
>>> LA.eigvals(D)
692-
array([-1., 1.])
684+
array([-1.+0.j, 1.+0.j])
693685
>>> A = np.dot(Q, D)
694686
>>> A = np.dot(A, Q.T)
695687
>>> LA.eigvals(A)
696-
array([-1., 1.]) # random
688+
array([-1.+0.j, 1.+0.j]) # random
697689
698690
"""
699691

dpnp/linalg/dpnp_utils_linalg.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -914,7 +914,8 @@ def _hermitian_svd(a, compute_uv):
914914
# the eigenvalues and related arrays to have the correct order
915915
if compute_uv:
916916
s, u = dpnp_eigh(a, eigen_mode="V")
917-
sgn = dpnp.sign(s)
917+
# avoid zero sign
918+
sgn = dpnp.copysign(1.0, s)
918919
s = dpnp.abs(s, out=s)
919920
sidx = dpnp.argsort(s)[..., ::-1]
920921
# Rearrange the signs according to sorted indices

0 commit comments

Comments
 (0)