Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix_issue-2319 #2371

Merged
merged 11 commits into from
Mar 20, 2025
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

* Allowed input array of `uint64` dtype in `dpnp.bincount` [#2361](https://github.com/IntelPython/dpnp/pull/2361)
* The vector norms `ord={None, 1, 2, inf}` and the matrix norms `ord={None, 1, 2, inf, "fro", "nuc"}` now consistently return zero for empty arrays, which are arrays with at least one axis of size zero. This change affects `dpnp.linalg.norm`, `dpnp.linalg.vector_norm`, and `dpnp.linalg.matrix_norm`. Previously, dpnp would either raise errors or return zero depending on the parameters provided [#2371](https://github.com/IntelPython/dpnp/pull/2371)

### Fixed

Expand Down
7 changes: 7 additions & 0 deletions dpnp/linalg/dpnp_utils_linalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -1185,6 +1185,9 @@ def _norm_int_axis(x, ord, axis, keepdims):
"""

if ord == dpnp.inf:
if x.shape[axis] == 0:
x = dpnp.moveaxis(x, axis, -1)
return dpnp.zeros_like(x, shape=x.shape[:-1])
return dpnp.abs(x).max(axis=axis, keepdims=keepdims)
if ord == -dpnp.inf:
return dpnp.abs(x).min(axis=axis, keepdims=keepdims)
Expand Down Expand Up @@ -1220,6 +1223,10 @@ def _norm_tuple_axis(x, ord, row_axis, col_axis, keepdims):
"""

axis = (row_axis, col_axis)
flag = x.shape[row_axis] == 0 or x.shape[col_axis] == 0
if flag and ord in [1, 2, dpnp.inf]:
x = dpnp.moveaxis(x, axis, (-2, -1))
return dpnp.zeros_like(x, shape=x.shape[:-2])
if row_axis == col_axis:
raise ValueError("Duplicate axes given.")
if ord == 2:
Expand Down
172 changes: 105 additions & 67 deletions dpnp/tests/test_linalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
assert_allclose,
assert_almost_equal,
assert_array_equal,
assert_equal,
assert_raises,
assert_raises_regex,
suppress_warnings,
Expand All @@ -24,6 +25,7 @@
has_support_aspect64,
is_cpu_device,
is_cuda_device,
numpy_version,
)
from .third_party.cupy import testing

Expand Down Expand Up @@ -2074,9 +2076,6 @@ def test_matrix_transpose():


class TestNorm:
def setup_method(self):
numpy.random.seed(42)

@pytest.mark.usefixtures("suppress_divide_numpy_warnings")
@pytest.mark.parametrize(
"shape", [(0,), (5, 0), (2, 0, 3)], ids=["(0,)", "(5, 0)", "(2, 0, 3)"]
Expand All @@ -2087,29 +2086,36 @@ def setup_method(self):
def test_empty(self, shape, ord, axis, keepdims):
a = numpy.empty(shape)
ia = dpnp.array(a)
kwarg = {"ord": ord, "axis": axis, "keepdims": keepdims}

if axis is None and a.ndim > 1 and ord in [0, 3]:
# Invalid norm order for matrices (a.ndim == 2) or
# Improper number of dimensions to norm (a.ndim>2)
with pytest.raises(ValueError):
dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
assert_raises(ValueError, dpnp.linalg.norm, ia, **kwarg)
assert_raises(ValueError, numpy.linalg.norm, a, **kwarg)
elif axis is None and a.ndim > 2 and ord is not None:
# Improper number of dimensions to norm
with pytest.raises(ValueError):
dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
assert_raises(ValueError, dpnp.linalg.norm, ia, **kwarg)
assert_raises(ValueError, numpy.linalg.norm, a, **kwarg)
elif (
axis is None
and ord is not None
and a.ndim != 1
and a.shape[-1] == 0
):
# reduction cannot be performed over zero-size axes
with pytest.raises(ValueError):
dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
if ord in [-2, -1, 0, 3]:
# reduction cannot be performed over zero-size axes
assert_raises(ValueError, dpnp.linalg.norm, ia, **kwarg)
assert_raises(ValueError, numpy.linalg.norm, a, **kwarg)
else:
# TODO: when similar changes in numpy are available, instead
# of assert_equal with zero, we should compare with numpy
# ord in [None, 1, 2]
assert_equal(dpnp.linalg.norm(ia, **kwarg), 0)
assert_raises(ValueError, numpy.linalg.norm, a, **kwarg)
else:
result = dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
expected = numpy.linalg.norm(
a, ord=ord, axis=axis, keepdims=keepdims
)
result = dpnp.linalg.norm(ia, **kwarg)
expected = numpy.linalg.norm(a, **kwarg)
assert_dtype_allclose(result, expected)

@pytest.mark.parametrize(
Expand All @@ -2121,11 +2127,11 @@ def test_0D(self, ord, axis):
ia = dpnp.array(a)
if axis is None and ord is not None:
# Improper number of dimensions to norm
with pytest.raises(ValueError):
dpnp.linalg.norm(ia, ord=ord, axis=axis)
assert_raises(ValueError, dpnp.linalg.norm, ia, ord=ord, axis=axis)
assert_raises(ValueError, numpy.linalg.norm, a, ord=ord, axis=axis)
elif axis is not None:
with pytest.raises(AxisError):
dpnp.linalg.norm(ia, ord=ord, axis=axis)
assert_raises(IndexError, dpnp.linalg.norm, ia, ord=ord, axis=axis)
assert_raises(AxisError, numpy.linalg.norm, a, ord=ord, axis=axis)
else:
result = dpnp.linalg.norm(ia, ord=ord, axis=axis)
expected = numpy.linalg.norm(a, ord=ord, axis=axis)
Expand Down Expand Up @@ -2158,24 +2164,21 @@ def test_1D(self, dtype, ord, axis, keepdims):
def test_2D(self, dtype, ord, axis, keepdims):
a = generate_random_numpy_array((3, 5), dtype)
ia = dpnp.array(a)
kwarg = {"ord": ord, "axis": axis, "keepdims": keepdims}

if (axis in [-1, 0, 1] and ord in ["nuc", "fro"]) or (
(isinstance(axis, tuple) or axis is None) and ord == 3
):
# Invalid norm order for vectors
with pytest.raises(ValueError):
dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
assert_raises(ValueError, dpnp.linalg.norm, ia, **kwarg)
assert_raises(ValueError, numpy.linalg.norm, a, **kwarg)
else:
result = dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
expected = numpy.linalg.norm(
a, ord=ord, axis=axis, keepdims=keepdims
)
result = dpnp.linalg.norm(ia, **kwarg)
expected = numpy.linalg.norm(a, **kwarg)
assert_dtype_allclose(result, expected)

@pytest.mark.usefixtures("suppress_divide_numpy_warnings")
@pytest.mark.parametrize(
"dtype",
get_all_dtypes(no_none=True),
)
@pytest.mark.parametrize("dtype", get_all_dtypes(no_none=True))
@pytest.mark.parametrize(
"ord", [None, -dpnp.inf, -2, -1, 1, 2, 3, dpnp.inf, "fro", "nuc"]
)
Expand All @@ -2188,21 +2191,21 @@ def test_2D(self, dtype, ord, axis, keepdims):
def test_ND(self, dtype, ord, axis, keepdims):
a = generate_random_numpy_array((2, 3, 4, 5), dtype)
ia = dpnp.array(a)
kwarg = {"ord": ord, "axis": axis, "keepdims": keepdims}

if (axis in [-1, 0, 1] and ord in ["nuc", "fro"]) or (
isinstance(axis, tuple) and ord == 3
):
# Invalid norm order for vectors
with pytest.raises(ValueError):
dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
assert_raises(ValueError, dpnp.linalg.norm, ia, **kwarg)
assert_raises(ValueError, numpy.linalg.norm, a, **kwarg)
elif axis is None and ord is not None:
# Improper number of dimensions to norm
with pytest.raises(ValueError):
dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
assert_raises(ValueError, dpnp.linalg.norm, ia, **kwarg)
assert_raises(ValueError, numpy.linalg.norm, a, **kwarg)
else:
result = dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
expected = numpy.linalg.norm(
a, ord=ord, axis=axis, keepdims=keepdims
)
result = dpnp.linalg.norm(ia, **kwarg)
expected = numpy.linalg.norm(a, **kwarg)
assert_dtype_allclose(result, expected)

@pytest.mark.usefixtures("suppress_divide_numpy_warnings")
Expand All @@ -2219,21 +2222,21 @@ def test_ND(self, dtype, ord, axis, keepdims):
def test_usm_ndarray(self, dtype, ord, axis, keepdims):
a = generate_random_numpy_array((2, 3, 4, 5), dtype)
ia = dpt.asarray(a)
kwarg = {"ord": ord, "axis": axis, "keepdims": keepdims}

if (axis in [-1, 0, 1] and ord in ["nuc", "fro"]) or (
isinstance(axis, tuple) and ord == 3
):
# Invalid norm order for vectors
with pytest.raises(ValueError):
dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
assert_raises(ValueError, dpnp.linalg.norm, ia, **kwarg)
assert_raises(ValueError, numpy.linalg.norm, a, **kwarg)
elif axis is None and ord is not None:
# Improper number of dimensions to norm
with pytest.raises(ValueError):
dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
assert_raises(ValueError, dpnp.linalg.norm, ia, **kwarg)
assert_raises(ValueError, numpy.linalg.norm, a, **kwarg)
else:
result = dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
expected = numpy.linalg.norm(
a, ord=ord, axis=axis, keepdims=keepdims
)
result = dpnp.linalg.norm(ia, **kwarg)
expected = numpy.linalg.norm(a, **kwarg)
assert_dtype_allclose(result, expected)

@pytest.mark.parametrize("stride", [3, -1, -5])
Expand All @@ -2257,8 +2260,7 @@ def test_strided_2D(self, axis, stride):
A = numpy.random.rand(20, 30)
B = dpnp.asarray(A)
slices = tuple(slice(None, None, stride[i]) for i in range(A.ndim))
a = A[slices]
b = B[slices]
a, b = A[slices], B[slices]

result = dpnp.linalg.norm(b, axis=axis)
expected = numpy.linalg.norm(a, axis=axis)
Expand All @@ -2278,8 +2280,7 @@ def test_strided_ND(self, axis, stride):
A = numpy.random.rand(12, 16, 20, 24)
B = dpnp.asarray(A)
slices = tuple(slice(None, None, stride[i]) for i in range(A.ndim))
a = A[slices]
b = B[slices]
a, b = A[slices], B[slices]

result = dpnp.linalg.norm(b, axis=axis)
expected = numpy.linalg.norm(a, axis=axis)
Expand All @@ -2299,6 +2300,49 @@ def test_matrix_norm(self, ord, keepdims):
expected = numpy.linalg.matrix_norm(a, ord=ord, keepdims=keepdims)
assert_dtype_allclose(result, expected)

@pytest.mark.parametrize(
"xp",
[
dpnp,
pytest.param(
numpy,
marks=pytest.mark.skipif(
numpy_version() < "2.3.0",
reason="numpy raises an error",
),
),
],
)
@pytest.mark.parametrize("dtype", [dpnp.float32, dpnp.int32])
@pytest.mark.parametrize(
"shape_axis", [[(2, 0), None], [(2, 0), (0, 1)], [(0, 2), (0, 1)]]
)
@pytest.mark.parametrize("ord", [None, "fro", "nuc", 1, 2, dpnp.inf])
def test_matrix_norm_empty(self, xp, dtype, shape_axis, ord):
shape, axis = shape_axis[0], shape_axis[1]
x = xp.zeros(shape, dtype=dtype)
assert_equal(xp.linalg.norm(x, axis=axis, ord=ord), 0)

@pytest.mark.parametrize(
"xp",
[
dpnp,
pytest.param(
numpy,
marks=pytest.mark.skipif(
numpy_version() < "2.3.0",
reason="numpy raises an error",
),
),
],
)
@pytest.mark.parametrize("dtype", [dpnp.float32, dpnp.int32])
@pytest.mark.parametrize("axis", [None, 0])
@pytest.mark.parametrize("ord", [None, 1, 2, dpnp.inf])
def test_vector_norm_empty(self, xp, dtype, axis, ord):
x = xp.zeros(0, dtype=dtype)
assert_equal(xp.linalg.vector_norm(x, axis=axis, ord=ord), 0)

@testing.with_requires("numpy>=2.0")
@pytest.mark.parametrize(
"ord", [None, -dpnp.inf, -2, -1, 0, 1, 2, 3.5, dpnp.inf]
Expand All @@ -2320,13 +2364,10 @@ def test_vector_norm_0D(self, ord):
def test_vector_norm_1D(self, ord, axis, keepdims):
a = generate_random_numpy_array(10)
ia = dpnp.array(a)
kwarg = {"ord": ord, "axis": axis, "keepdims": keepdims}

result = dpnp.linalg.vector_norm(
ia, ord=ord, axis=axis, keepdims=keepdims
)
expected = numpy.linalg.vector_norm(
a, ord=ord, axis=axis, keepdims=keepdims
)
result = dpnp.linalg.vector_norm(ia, **kwarg)
expected = numpy.linalg.vector_norm(a, **kwarg)
assert_dtype_allclose(result, expected)

@testing.with_requires("numpy>=2.0")
Expand All @@ -2343,29 +2384,26 @@ def test_vector_norm_1D(self, ord, axis, keepdims):
def test_vector_norm_ND(self, ord, axis, keepdims):
a = numpy.arange(120).reshape(2, 3, 4, 5)
ia = dpnp.array(a)
kwarg = {"ord": ord, "axis": axis, "keepdims": keepdims}

result = dpnp.linalg.vector_norm(
ia, ord=ord, axis=axis, keepdims=keepdims
)
expected = numpy.linalg.vector_norm(
a, ord=ord, axis=axis, keepdims=keepdims
)
result = dpnp.linalg.vector_norm(ia, **kwarg)
expected = numpy.linalg.vector_norm(a, **kwarg)
assert_dtype_allclose(result, expected)

def test_error(self):
ia = dpnp.arange(120).reshape(2, 3, 4, 5)
a = numpy.arange(120).reshape(2, 3, 4, 5)
ia = dpnp.array(a)

# Duplicate axes given
with pytest.raises(ValueError):
dpnp.linalg.norm(ia, axis=(2, 2))
assert_raises(ValueError, dpnp.linalg.norm, ia, axis=(2, 2))
assert_raises(ValueError, numpy.linalg.norm, a, axis=(2, 2))

#'axis' must be None, an integer or a tuple of integers
with pytest.raises(TypeError):
dpnp.linalg.norm(ia, axis=[2])
assert_raises(TypeError, dpnp.linalg.norm, ia, axis=[2])
assert_raises(TypeError, numpy.linalg.norm, a, axis=[2])

# Invalid norm order for vectors
with pytest.raises(ValueError):
dpnp.linalg.norm(ia, axis=1, ord=[3])
assert_raises(ValueError, dpnp.linalg.norm, ia, axis=1, ord=[3])


class TestQr:
Expand Down
Loading