Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
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={1, 2, inf}` and the matrix norms `ord={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 @@ -2401,10 +2401,17 @@ def dpnp_norm(x, ord=None, axis=None, keepdims=False):
axis = (axis,)

if len(axis) == 1:
if x.shape[axis[0]] == 0 and ord in [1, 2, dpnp.inf]:
x = dpnp.moveaxis(x, axis, -1)
return dpnp.zeros_like(x, shape=x.shape[:-1])
axis = normalize_axis_index(axis[0], ndim)
return _norm_int_axis(x, ord, axis, keepdims)

if len(axis) == 2:
flag = x.shape[axis[0]] == 0 or x.shape[axis[1]] == 0
if flag and ord in ["fro", "nuc", 1, 2, dpnp.inf]:
x = dpnp.moveaxis(x, axis, (-2, -1))
return dpnp.zeros_like(x, shape=x.shape[:-2])
row_axis, col_axis = axis
row_axis = normalize_axis_index(row_axis, ndim)
col_axis = normalize_axis_index(col_axis, ndim)
Expand Down
160 changes: 87 additions & 73 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 Down Expand Up @@ -2074,9 +2075,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 +2085,30 @@ 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)
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)
assert_raises(ValueError, dpnp.linalg.norm, ia, **kwarg)
assert_raises(ValueError, numpy.linalg.norm, a, **kwarg)
elif axis is None and a.ndim != 1 and a.shape[-1] == 0:
# TODO: when similar changes in numpy are available,
# instead of assert_equal with zero, we should compare with numpy
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:
# ord in [None, 1, 2]
assert_equal(dpnp.linalg.norm(ia, **kwarg), 0)
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 +2120,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 +2157,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 +2184,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 +2215,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 +2253,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 +2273,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 +2293,32 @@ 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("dtype", [dpnp.float32, dpnp.int32])
@pytest.mark.parametrize(
"shape_axis", [[(2, 0), None], [(2, 0, 3), (0, 1)]]
)
def test_matrix_norm_empty(self, dtype, shape_axis):
shape, axis = shape_axis[0], shape_axis[1]
x = dpnp.zeros(shape, dtype=dtype)

# TODO: when similar changes in numpy are available,
# instead of assert_equal with zero, we should compare with numpy
assert_equal(dpnp.linalg.norm(x, axis=axis, ord="fro"), 0)
assert_equal(dpnp.linalg.norm(x, axis=axis, ord="nuc"), 0)
assert_equal(dpnp.linalg.norm(x, axis=axis, ord=2), 0)
assert_equal(dpnp.linalg.norm(x, axis=axis, ord=1), 0)
assert_equal(dpnp.linalg.norm(x, axis=axis, ord=dpnp.inf), 0)

@pytest.mark.parametrize("dtype", [dpnp.float32, dpnp.int32])
@pytest.mark.parametrize("axis", [None, 0])
def test_vector_norm_empty(self, dtype, axis):
x = dpnp.zeros(0, dtype=dtype)
# TODO: when similar changes in numpy are available,
# instead of assert_equal with zero, we should compare with numpy
assert_equal(dpnp.linalg.vector_norm(x, axis=axis, ord=1), 0)
assert_equal(dpnp.linalg.vector_norm(x, axis=axis, ord=2), 0)
assert_equal(dpnp.linalg.vector_norm(x, axis=axis, ord=dpnp.inf), 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 +2340,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 +2360,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