diff --git a/doc/release/upcoming_changes/28343.change.rst b/doc/release/upcoming_changes/28343.change.rst new file mode 100644 index 000000000000..378ef775b62e --- /dev/null +++ b/doc/release/upcoming_changes/28343.change.rst @@ -0,0 +1 @@ +* The vector norm ``ord=inf`` and the matrix norms ``ord={1, 2, inf, 'nuc'}`` now always returns zero for empty arrays. Empty arrays have at least one axis of size zero. This affects `np.linalg.norm`, `np.linalg.vector_norm`, and `np.linalg.matrix_norm`. Previously, NumPy would raises errors or return zero depending on the shape of the array. diff --git a/numpy/linalg/_linalg.py b/numpy/linalg/_linalg.py index 32a52b7b8cdb..f5bc524a27ae 100644 --- a/numpy/linalg/_linalg.py +++ b/numpy/linalg/_linalg.py @@ -2541,7 +2541,7 @@ def lstsq(a, b, rcond=None): return wrap(x), wrap(resids), rank, s -def _multi_svd_norm(x, row_axis, col_axis, op): +def _multi_svd_norm(x, row_axis, col_axis, op, initial=None): """Compute a function of the singular values of the 2-D matrices in `x`. This is a private utility function used by `numpy.linalg.norm()`. @@ -2565,7 +2565,7 @@ def _multi_svd_norm(x, row_axis, col_axis, op): """ y = moveaxis(x, (row_axis, col_axis), (-2, -1)) - result = op(svd(y, compute_uv=False), axis=-1) + result = op(svd(y, compute_uv=False), axis=-1, initial=initial) return result @@ -2763,7 +2763,7 @@ def norm(x, ord=None, axis=None, keepdims=False): if len(axis) == 1: if ord == inf: - return abs(x).max(axis=axis, keepdims=keepdims) + return abs(x).max(axis=axis, keepdims=keepdims, initial=0) elif ord == -inf: return abs(x).min(axis=axis, keepdims=keepdims) elif ord == 0: @@ -2797,17 +2797,17 @@ def norm(x, ord=None, axis=None, keepdims=False): if row_axis == col_axis: raise ValueError('Duplicate axes given.') if ord == 2: - ret = _multi_svd_norm(x, row_axis, col_axis, amax) + ret = _multi_svd_norm(x, row_axis, col_axis, amax, 0) elif ord == -2: ret = _multi_svd_norm(x, row_axis, col_axis, amin) elif ord == 1: if col_axis > row_axis: col_axis -= 1 - ret = add.reduce(abs(x), axis=row_axis).max(axis=col_axis) + ret = add.reduce(abs(x), axis=row_axis).max(axis=col_axis, initial=0) elif ord == inf: if row_axis > col_axis: row_axis -= 1 - ret = add.reduce(abs(x), axis=col_axis).max(axis=row_axis) + ret = add.reduce(abs(x), axis=col_axis).max(axis=row_axis, initial=0) elif ord == -1: if col_axis > row_axis: col_axis -= 1 @@ -2819,7 +2819,7 @@ def norm(x, ord=None, axis=None, keepdims=False): elif ord in [None, 'fro', 'f']: ret = sqrt(add.reduce((x.conj() * x).real, axis=axis)) elif ord == 'nuc': - ret = _multi_svd_norm(x, row_axis, col_axis, sum) + ret = _multi_svd_norm(x, row_axis, col_axis, sum, 0) else: raise ValueError("Invalid norm order for matrices.") if keepdims: diff --git a/numpy/linalg/tests/test_linalg.py b/numpy/linalg/tests/test_linalg.py index 356676e7ce6d..1a79629814e9 100644 --- a/numpy/linalg/tests/test_linalg.py +++ b/numpy/linalg/tests/test_linalg.py @@ -2372,6 +2372,16 @@ def test_matrix_norm(): assert_almost_equal(actual, np.array([[14.2828]]), double_decimal=3) +def test_matrix_norm_empty(): + for shape in [(0, 2), (2, 0), (0, 0)]: + for dtype in [np.float64, np.float32, np.int32]: + x = np.zeros(shape, dtype) + assert_equal(np.linalg.matrix_norm(x, ord="fro"), 0) + assert_equal(np.linalg.matrix_norm(x, ord="nuc"), 0) + assert_equal(np.linalg.matrix_norm(x, ord=1), 0) + assert_equal(np.linalg.matrix_norm(x, ord=2), 0) + assert_equal(np.linalg.matrix_norm(x, ord=np.inf), 0) + def test_vector_norm(): x = np.arange(9).reshape((3, 3)) actual = np.linalg.vector_norm(x) @@ -2388,3 +2398,11 @@ def test_vector_norm(): expected = np.full((1, 1), 14.2828, dtype='float64') assert_equal(actual.shape, expected.shape) assert_almost_equal(actual, expected, double_decimal=3) + + +def test_vector_norm_empty(): + for dtype in [np.float64, np.float32, np.int32]: + x = np.zeros(0, dtype) + assert_equal(np.linalg.vector_norm(x, ord=1), 0) + assert_equal(np.linalg.vector_norm(x, ord=2), 0) + assert_equal(np.linalg.vector_norm(x, ord=np.inf), 0)