Skip to content
Merged
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
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [0.21.0] - 2026-MM-DD

This release is compatible with NumPy 2.4.5.
This release is compatible with NumPy 2.5.

### Added

Expand All @@ -27,6 +27,8 @@ This release is compatible with NumPy 2.4.5.

### Removed

* 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)

### Fixed

* 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)
Expand Down
79 changes: 20 additions & 59 deletions dpnp/dpnp_iface_mathematical.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@


import builtins
import warnings

import dpctl.utils as dpu
import numpy
Expand Down Expand Up @@ -874,11 +873,8 @@ def cross(a, b, axisa=-1, axisb=-1, axisc=-1, axis=None):

The cross product of `a` and `b` in :math:`R^3` is a vector perpendicular
to both `a` and `b`. If `a` and `b` are arrays of vectors, the vectors
are defined by the last axis of `a` and `b` by default, and these axes
can have dimensions 2 or 3. Where the dimension of either `a` or `b` is
2, the third component of the input vector is assumed to be zero and the
cross product calculated accordingly. In cases where both input vectors
have dimension 2, the z-component of the cross product is returned.
are defined by the last axis of `a` and `b` by default, and these axes must
have 3 dimensions.

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

Expand All @@ -897,8 +893,7 @@ def cross(a, b, axisa=-1, axisb=-1, axisc=-1, axis=None):

Default: ``-1``.
axisc : int, optional
Axis of `c` containing the cross product vector(s). Ignored if both
input vectors have dimension ``2``, as the return is scalar. By default,
Axis of `c` containing the cross product vector(s). By default,
the last axis.

Default: ``-1``.
Expand All @@ -913,9 +908,14 @@ def cross(a, b, axisa=-1, axisb=-1, axisc=-1, axis=None):
out : dpnp.ndarray
Vector cross product(s).

Raises
------
ValueError
When the dimension of the vector(s) in `a` or `b` does not equal 3.

See Also
--------
:obj:`dpnp.linalg.cross` : Array API compatible version.
:obj:`dpnp.linalg.cross` : Array API compatible variation.
:obj:`dpnp.inner` : Inner product.
:obj:`dpnp.outer` : Outer product.

Expand All @@ -929,27 +929,6 @@ def cross(a, b, axisa=-1, axisb=-1, axisc=-1, axis=None):
>>> np.cross(x, y)
array([-3, 6, -3])

One vector with dimension 2.

>>> x = np.array([1, 2])
>>> y = np.array([4, 5, 6])
>>> np.cross(x, y)
array([12, -6, -3])

Equivalently:

>>> x = np.array([1, 2, 0])
>>> y = np.array([4, 5, 6])
>>> np.cross(x, y)
array([12, -6, -3])

Both vectors with dimension 2.

>>> x = np.array([1, 2])
>>> y = np.array([4, 5])
>>> np.cross(x, y)
array(-3)

Multiple vector cross-products. Note that the direction of the cross
product vector is defined by the *right-hand rule*.

Expand Down Expand Up @@ -992,43 +971,28 @@ def cross(a, b, axisa=-1, axisb=-1, axisc=-1, axis=None):
"Input arrays with boolean data type are not supported."
)

if (a.ndim < 1) or (b.ndim < 1):
raise ValueError("At least one array has zero dimension")

# Check axisa and axisb are within bounds
axisa = normalize_axis_index(axisa, a.ndim, msg_prefix="axisa")
axisb = normalize_axis_index(axisb, b.ndim, msg_prefix="axisb")

# Move working axis to the end of the shape
a = dpnp.moveaxis(a, axisa, -1)
b = dpnp.moveaxis(b, axisb, -1)
if a.shape[-1] not in (2, 3) or b.shape[-1] not in (2, 3):
raise ValueError(
"Incompatible vector dimensions for cross product\n"
"(the dimension of vector used in cross product must be 2 or 3)"
)

if a.shape[-1] == 2 or b.shape[-1] == 2:
warnings.warn(
"Arrays of 2-dimensional vectors are deprecated. Use arrays of "
"3-dimensional vectors instead. (deprecated in dpnp 0.17.0)",
DeprecationWarning,
stacklevel=2,
)

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

res_shape = dpnp.broadcast_shapes(a_shape[:-1], b_shape[:-1])
if a_shape[:-1] != res_shape:
a = dpnp.broadcast_to(a, res_shape + (a_shape[-1],))
a_shape = a.shape
if b_shape[:-1] != res_shape:
b = dpnp.broadcast_to(b, res_shape + (b_shape[-1],))
b_shape = b.shape

if a_shape[-1] == 3 or b_shape[-1] == 3:
res_shape += (3,)
# Check axisc is within bounds
axisc = normalize_axis_index(axisc, len(res_shape), msg_prefix="axisc")
# Check axisc is within bounds
res_shape = *dpnp.broadcast_shapes(a_shape[:-1], b_shape[:-1]), 3
axisc = normalize_axis_index(axisc, len(res_shape), msg_prefix="axisc")

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

cp = dpnp_cross(a, b, cp)
if a_shape[-1] == 2 and b_shape[-1] == 2:
return cp

return dpnp.moveaxis(cp, -1, axisc)


Expand Down
66 changes: 16 additions & 50 deletions dpnp/dpnp_utils/dpnp_utils_linearalgebra.py
Original file line number Diff line number Diff line change
Expand Up @@ -700,67 +700,33 @@ def _validate_out_array(out, exec_q):


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

# create local aliases for readability
a0 = a[..., 0]
a1 = a[..., 1]
if a.shape[-1] == 3:
a2 = a[..., 2]
a2 = a[..., 2]

b0 = b[..., 0]
b1 = b[..., 1]
if b.shape[-1] == 3:
b2 = b[..., 2]

if cp.ndim != 0 and cp.shape[-1] == 3:
cp0 = cp[..., 0]
cp1 = cp[..., 1]
cp2 = cp[..., 2]

if a.shape[-1] == 2:
if b.shape[-1] == 2:
# a0 * b1 - a1 * b0
cp = dpnp.multiply(a0, b1, out=cp)
cp -= a1 * b0
else:
assert b.shape[-1] == 3
# cp0 = a1 * b2 - 0 (a2 = 0)
cp0 = dpnp.multiply(a1, b2, out=cp0)
b2 = b[..., 2]

# cp1 = 0 - a0 * b2 (a2 = 0)
cp1 = dpnp.multiply(a0, b2, out=cp1)
cp1 = dpnp.negative(cp1, out=cp1)
cp0 = cp[..., 0]
cp1 = cp[..., 1]
cp2 = cp[..., 2]

# cp2 = a0 * b1 - a1 * b0
cp2 = dpnp.multiply(a0, b1, out=cp2)
cp2 -= a1 * b0
else:
assert a.shape[-1] == 3
if b.shape[-1] == 3:
# cp0 = a1 * b2 - a2 * b1
cp0 = dpnp.multiply(a1, b2, out=cp0)
cp0 -= a2 * b1

# cp1 = a2 * b0 - a0 * b2
cp1 = dpnp.multiply(a2, b0, out=cp1)
cp1 -= a0 * b2

# cp2 = a0 * b1 - a1 * b0
cp2 = dpnp.multiply(a0, b1, out=cp2)
cp2 -= a1 * b0
else:
assert b.shape[-1] == 2
# cp0 = 0 - a2 * b1 (b2 = 0)
cp0 = dpnp.multiply(a2, b1, out=cp0)
cp0 = dpnp.negative(cp0, out=cp0)
# cp0 = a1 * b2 - a2 * b1
cp0 = dpnp.multiply(a1, b2, out=cp0)
cp0 -= a2 * b1

# cp1 = a2 * b0 - a0 * b2
cp1 = dpnp.multiply(a2, b0, out=cp1)
cp1 -= a0 * b2

# cp1 = a2 * b0 - 0 (b2 = 0)
cp1 = dpnp.multiply(a2, b0, out=cp1)
# cp2 = a0 * b1 - a1 * b0
cp2 = dpnp.multiply(a0, b1, out=cp2)
cp2 -= a1 * b0

# cp2 = a0 * b1 - a1 * b0
cp2 = dpnp.multiply(a0, b1, out=cp2)
cp2 -= a1 * b0
return cp


Expand Down
8 changes: 0 additions & 8 deletions dpnp/linalg/dpnp_iface_linalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,14 +309,6 @@ def cross(x1, x2, /, *, axis=-1):

"""

dpnp.check_supported_arrays_type(x1, x2)
if x1.shape[axis] != 3 or x2.shape[axis] != 3:
raise ValueError(
"Both input arrays must be (arrays of) 3-dimensional vectors, "
f"but they are {x1.shape[axis]} and {x2.shape[axis]} "
"dimensional instead."
)

return dpnp.cross(x1, x2, axis=axis)


Expand Down
Loading
Loading