Skip to content

Commit 74b5e54

Browse files
authored
fix_issue-2319 (#2371)
resolves #2319 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
1 parent 4986f39 commit 74b5e54

File tree

3 files changed

+113
-67
lines changed

3 files changed

+113
-67
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
### Changed
1717

1818
* Allowed input array of `uint64` dtype in `dpnp.bincount` [#2361](https://github.com/IntelPython/dpnp/pull/2361)
19+
* 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)
1920

2021
### Fixed
2122

dpnp/linalg/dpnp_utils_linalg.py

+7
Original file line numberDiff line numberDiff line change
@@ -1185,6 +1185,9 @@ def _norm_int_axis(x, ord, axis, keepdims):
11851185
"""
11861186

11871187
if ord == dpnp.inf:
1188+
if x.shape[axis] == 0:
1189+
x = dpnp.moveaxis(x, axis, -1)
1190+
return dpnp.zeros_like(x, shape=x.shape[:-1])
11881191
return dpnp.abs(x).max(axis=axis, keepdims=keepdims)
11891192
if ord == -dpnp.inf:
11901193
return dpnp.abs(x).min(axis=axis, keepdims=keepdims)
@@ -1220,6 +1223,10 @@ def _norm_tuple_axis(x, ord, row_axis, col_axis, keepdims):
12201223
"""
12211224

12221225
axis = (row_axis, col_axis)
1226+
flag = x.shape[row_axis] == 0 or x.shape[col_axis] == 0
1227+
if flag and ord in [1, 2, dpnp.inf]:
1228+
x = dpnp.moveaxis(x, axis, (-2, -1))
1229+
return dpnp.zeros_like(x, shape=x.shape[:-2])
12231230
if row_axis == col_axis:
12241231
raise ValueError("Duplicate axes given.")
12251232
if ord == 2:

dpnp/tests/test_linalg.py

+105-67
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
assert_allclose,
99
assert_almost_equal,
1010
assert_array_equal,
11+
assert_equal,
1112
assert_raises,
1213
assert_raises_regex,
1314
suppress_warnings,
@@ -24,6 +25,7 @@
2425
has_support_aspect64,
2526
is_cpu_device,
2627
is_cuda_device,
28+
numpy_version,
2729
)
2830
from .third_party.cupy import testing
2931

@@ -2074,9 +2076,6 @@ def test_matrix_transpose():
20742076

20752077

20762078
class TestNorm:
2077-
def setup_method(self):
2078-
numpy.random.seed(42)
2079-
20802079
@pytest.mark.usefixtures("suppress_divide_numpy_warnings")
20812080
@pytest.mark.parametrize(
20822081
"shape", [(0,), (5, 0), (2, 0, 3)], ids=["(0,)", "(5, 0)", "(2, 0, 3)"]
@@ -2087,29 +2086,36 @@ def setup_method(self):
20872086
def test_empty(self, shape, ord, axis, keepdims):
20882087
a = numpy.empty(shape)
20892088
ia = dpnp.array(a)
2089+
kwarg = {"ord": ord, "axis": axis, "keepdims": keepdims}
2090+
20902091
if axis is None and a.ndim > 1 and ord in [0, 3]:
20912092
# Invalid norm order for matrices (a.ndim == 2) or
20922093
# Improper number of dimensions to norm (a.ndim>2)
2093-
with pytest.raises(ValueError):
2094-
dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
2094+
assert_raises(ValueError, dpnp.linalg.norm, ia, **kwarg)
2095+
assert_raises(ValueError, numpy.linalg.norm, a, **kwarg)
20952096
elif axis is None and a.ndim > 2 and ord is not None:
20962097
# Improper number of dimensions to norm
2097-
with pytest.raises(ValueError):
2098-
dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
2098+
assert_raises(ValueError, dpnp.linalg.norm, ia, **kwarg)
2099+
assert_raises(ValueError, numpy.linalg.norm, a, **kwarg)
20992100
elif (
21002101
axis is None
21012102
and ord is not None
21022103
and a.ndim != 1
21032104
and a.shape[-1] == 0
21042105
):
2105-
# reduction cannot be performed over zero-size axes
2106-
with pytest.raises(ValueError):
2107-
dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
2106+
if ord in [-2, -1, 0, 3]:
2107+
# reduction cannot be performed over zero-size axes
2108+
assert_raises(ValueError, dpnp.linalg.norm, ia, **kwarg)
2109+
assert_raises(ValueError, numpy.linalg.norm, a, **kwarg)
2110+
else:
2111+
# TODO: when similar changes in numpy are available, instead
2112+
# of assert_equal with zero, we should compare with numpy
2113+
# ord in [None, 1, 2]
2114+
assert_equal(dpnp.linalg.norm(ia, **kwarg), 0)
2115+
assert_raises(ValueError, numpy.linalg.norm, a, **kwarg)
21082116
else:
2109-
result = dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
2110-
expected = numpy.linalg.norm(
2111-
a, ord=ord, axis=axis, keepdims=keepdims
2112-
)
2117+
result = dpnp.linalg.norm(ia, **kwarg)
2118+
expected = numpy.linalg.norm(a, **kwarg)
21132119
assert_dtype_allclose(result, expected)
21142120

21152121
@pytest.mark.parametrize(
@@ -2121,11 +2127,11 @@ def test_0D(self, ord, axis):
21212127
ia = dpnp.array(a)
21222128
if axis is None and ord is not None:
21232129
# Improper number of dimensions to norm
2124-
with pytest.raises(ValueError):
2125-
dpnp.linalg.norm(ia, ord=ord, axis=axis)
2130+
assert_raises(ValueError, dpnp.linalg.norm, ia, ord=ord, axis=axis)
2131+
assert_raises(ValueError, numpy.linalg.norm, a, ord=ord, axis=axis)
21262132
elif axis is not None:
2127-
with pytest.raises(AxisError):
2128-
dpnp.linalg.norm(ia, ord=ord, axis=axis)
2133+
assert_raises(IndexError, dpnp.linalg.norm, ia, ord=ord, axis=axis)
2134+
assert_raises(AxisError, numpy.linalg.norm, a, ord=ord, axis=axis)
21292135
else:
21302136
result = dpnp.linalg.norm(ia, ord=ord, axis=axis)
21312137
expected = numpy.linalg.norm(a, ord=ord, axis=axis)
@@ -2158,24 +2164,21 @@ def test_1D(self, dtype, ord, axis, keepdims):
21582164
def test_2D(self, dtype, ord, axis, keepdims):
21592165
a = generate_random_numpy_array((3, 5), dtype)
21602166
ia = dpnp.array(a)
2167+
kwarg = {"ord": ord, "axis": axis, "keepdims": keepdims}
2168+
21612169
if (axis in [-1, 0, 1] and ord in ["nuc", "fro"]) or (
21622170
(isinstance(axis, tuple) or axis is None) and ord == 3
21632171
):
21642172
# Invalid norm order for vectors
2165-
with pytest.raises(ValueError):
2166-
dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
2173+
assert_raises(ValueError, dpnp.linalg.norm, ia, **kwarg)
2174+
assert_raises(ValueError, numpy.linalg.norm, a, **kwarg)
21672175
else:
2168-
result = dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
2169-
expected = numpy.linalg.norm(
2170-
a, ord=ord, axis=axis, keepdims=keepdims
2171-
)
2176+
result = dpnp.linalg.norm(ia, **kwarg)
2177+
expected = numpy.linalg.norm(a, **kwarg)
21722178
assert_dtype_allclose(result, expected)
21732179

21742180
@pytest.mark.usefixtures("suppress_divide_numpy_warnings")
2175-
@pytest.mark.parametrize(
2176-
"dtype",
2177-
get_all_dtypes(no_none=True),
2178-
)
2181+
@pytest.mark.parametrize("dtype", get_all_dtypes(no_none=True))
21792182
@pytest.mark.parametrize(
21802183
"ord", [None, -dpnp.inf, -2, -1, 1, 2, 3, dpnp.inf, "fro", "nuc"]
21812184
)
@@ -2188,21 +2191,21 @@ def test_2D(self, dtype, ord, axis, keepdims):
21882191
def test_ND(self, dtype, ord, axis, keepdims):
21892192
a = generate_random_numpy_array((2, 3, 4, 5), dtype)
21902193
ia = dpnp.array(a)
2194+
kwarg = {"ord": ord, "axis": axis, "keepdims": keepdims}
2195+
21912196
if (axis in [-1, 0, 1] and ord in ["nuc", "fro"]) or (
21922197
isinstance(axis, tuple) and ord == 3
21932198
):
21942199
# Invalid norm order for vectors
2195-
with pytest.raises(ValueError):
2196-
dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
2200+
assert_raises(ValueError, dpnp.linalg.norm, ia, **kwarg)
2201+
assert_raises(ValueError, numpy.linalg.norm, a, **kwarg)
21972202
elif axis is None and ord is not None:
21982203
# Improper number of dimensions to norm
2199-
with pytest.raises(ValueError):
2200-
dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
2204+
assert_raises(ValueError, dpnp.linalg.norm, ia, **kwarg)
2205+
assert_raises(ValueError, numpy.linalg.norm, a, **kwarg)
22012206
else:
2202-
result = dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
2203-
expected = numpy.linalg.norm(
2204-
a, ord=ord, axis=axis, keepdims=keepdims
2205-
)
2207+
result = dpnp.linalg.norm(ia, **kwarg)
2208+
expected = numpy.linalg.norm(a, **kwarg)
22062209
assert_dtype_allclose(result, expected)
22072210

22082211
@pytest.mark.usefixtures("suppress_divide_numpy_warnings")
@@ -2219,21 +2222,21 @@ def test_ND(self, dtype, ord, axis, keepdims):
22192222
def test_usm_ndarray(self, dtype, ord, axis, keepdims):
22202223
a = generate_random_numpy_array((2, 3, 4, 5), dtype)
22212224
ia = dpt.asarray(a)
2225+
kwarg = {"ord": ord, "axis": axis, "keepdims": keepdims}
2226+
22222227
if (axis in [-1, 0, 1] and ord in ["nuc", "fro"]) or (
22232228
isinstance(axis, tuple) and ord == 3
22242229
):
22252230
# Invalid norm order for vectors
2226-
with pytest.raises(ValueError):
2227-
dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
2231+
assert_raises(ValueError, dpnp.linalg.norm, ia, **kwarg)
2232+
assert_raises(ValueError, numpy.linalg.norm, a, **kwarg)
22282233
elif axis is None and ord is not None:
22292234
# Improper number of dimensions to norm
2230-
with pytest.raises(ValueError):
2231-
dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
2235+
assert_raises(ValueError, dpnp.linalg.norm, ia, **kwarg)
2236+
assert_raises(ValueError, numpy.linalg.norm, a, **kwarg)
22322237
else:
2233-
result = dpnp.linalg.norm(ia, ord=ord, axis=axis, keepdims=keepdims)
2234-
expected = numpy.linalg.norm(
2235-
a, ord=ord, axis=axis, keepdims=keepdims
2236-
)
2238+
result = dpnp.linalg.norm(ia, **kwarg)
2239+
expected = numpy.linalg.norm(a, **kwarg)
22372240
assert_dtype_allclose(result, expected)
22382241

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

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

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

2303+
@pytest.mark.parametrize(
2304+
"xp",
2305+
[
2306+
dpnp,
2307+
pytest.param(
2308+
numpy,
2309+
marks=pytest.mark.skipif(
2310+
numpy_version() < "2.3.0",
2311+
reason="numpy raises an error",
2312+
),
2313+
),
2314+
],
2315+
)
2316+
@pytest.mark.parametrize("dtype", [dpnp.float32, dpnp.int32])
2317+
@pytest.mark.parametrize(
2318+
"shape_axis", [[(2, 0), None], [(2, 0), (0, 1)], [(0, 2), (0, 1)]]
2319+
)
2320+
@pytest.mark.parametrize("ord", [None, "fro", "nuc", 1, 2, dpnp.inf])
2321+
def test_matrix_norm_empty(self, xp, dtype, shape_axis, ord):
2322+
shape, axis = shape_axis[0], shape_axis[1]
2323+
x = xp.zeros(shape, dtype=dtype)
2324+
assert_equal(xp.linalg.norm(x, axis=axis, ord=ord), 0)
2325+
2326+
@pytest.mark.parametrize(
2327+
"xp",
2328+
[
2329+
dpnp,
2330+
pytest.param(
2331+
numpy,
2332+
marks=pytest.mark.skipif(
2333+
numpy_version() < "2.3.0",
2334+
reason="numpy raises an error",
2335+
),
2336+
),
2337+
],
2338+
)
2339+
@pytest.mark.parametrize("dtype", [dpnp.float32, dpnp.int32])
2340+
@pytest.mark.parametrize("axis", [None, 0])
2341+
@pytest.mark.parametrize("ord", [None, 1, 2, dpnp.inf])
2342+
def test_vector_norm_empty(self, xp, dtype, axis, ord):
2343+
x = xp.zeros(0, dtype=dtype)
2344+
assert_equal(xp.linalg.vector_norm(x, axis=axis, ord=ord), 0)
2345+
23022346
@testing.with_requires("numpy>=2.0")
23032347
@pytest.mark.parametrize(
23042348
"ord", [None, -dpnp.inf, -2, -1, 0, 1, 2, 3.5, dpnp.inf]
@@ -2320,13 +2364,10 @@ def test_vector_norm_0D(self, ord):
23202364
def test_vector_norm_1D(self, ord, axis, keepdims):
23212365
a = generate_random_numpy_array(10)
23222366
ia = dpnp.array(a)
2367+
kwarg = {"ord": ord, "axis": axis, "keepdims": keepdims}
23232368

2324-
result = dpnp.linalg.vector_norm(
2325-
ia, ord=ord, axis=axis, keepdims=keepdims
2326-
)
2327-
expected = numpy.linalg.vector_norm(
2328-
a, ord=ord, axis=axis, keepdims=keepdims
2329-
)
2369+
result = dpnp.linalg.vector_norm(ia, **kwarg)
2370+
expected = numpy.linalg.vector_norm(a, **kwarg)
23302371
assert_dtype_allclose(result, expected)
23312372

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

2347-
result = dpnp.linalg.vector_norm(
2348-
ia, ord=ord, axis=axis, keepdims=keepdims
2349-
)
2350-
expected = numpy.linalg.vector_norm(
2351-
a, ord=ord, axis=axis, keepdims=keepdims
2352-
)
2389+
result = dpnp.linalg.vector_norm(ia, **kwarg)
2390+
expected = numpy.linalg.vector_norm(a, **kwarg)
23532391
assert_dtype_allclose(result, expected)
23542392

23552393
def test_error(self):
2356-
ia = dpnp.arange(120).reshape(2, 3, 4, 5)
2394+
a = numpy.arange(120).reshape(2, 3, 4, 5)
2395+
ia = dpnp.array(a)
23572396

23582397
# Duplicate axes given
2359-
with pytest.raises(ValueError):
2360-
dpnp.linalg.norm(ia, axis=(2, 2))
2398+
assert_raises(ValueError, dpnp.linalg.norm, ia, axis=(2, 2))
2399+
assert_raises(ValueError, numpy.linalg.norm, a, axis=(2, 2))
23612400

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

23662405
# Invalid norm order for vectors
2367-
with pytest.raises(ValueError):
2368-
dpnp.linalg.norm(ia, axis=1, ord=[3])
2406+
assert_raises(ValueError, dpnp.linalg.norm, ia, axis=1, ord=[3])
23692407

23702408

23712409
class TestQr:

0 commit comments

Comments
 (0)