From d31c9017ffb97eb67b5ce6c026d28118de1a5d66 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Fri, 2 Oct 2026 18:50:30 +0200 Subject: [PATCH] Support 'nuc' norm in dpnp.linalg.cond Add the nuclear norm ('nuc') to the set of norms accepted by dpnp.linalg.cond. The condition number for 'nuc' is already computed correctly through the inverse-based branch (norm(x, 'nuc') * norm(inv(x), 'nuc')); this change documents it as a supported value of p and adds test coverage. Unlike NumPy, dpnp.linalg.inv raises LinAlgError on singular input, so cond with 'nuc' raises on singular matrices consistently with the other inverse-based norms (1, -1, inf, -inf, 'fro'); no special NaN handling is required. Update the docstring (parameter set, norm table, Notes, example), add 'nuc' to the TestCond norm list and to the cond sycl-queue/usm-type propagation tests, and add a CHANGELOG entry. --- CHANGELOG.md | 1 + dpnp/linalg/dpnp_iface_linalg.py | 14 +++++++++----- dpnp/tests/test_linalg.py | 4 ++-- dpnp/tests/test_sycl_queue.py | 2 +- dpnp/tests/test_usm_type.py | 2 +- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7222877dbff6..c343d84c4fa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ This release is compatible with NumPy 2.5. * Added the `ndmax` keyword to `dpnp.array` for compatibility with NumPy [#3044](https://github.com/IntelPython/dpnp/pull/3044) * Added `UsmNDArray_RemoveQueueRef` C API function to release a queue reference obtained from `UsmNDArray_GetQueueRef` [#3042](https://github.com/IntelPython/dpnp/pull/3042) * Added a `rattler-build`-compatible conda recipe (`conda-recipe/rattler_recipe.yaml`) alongside the existing `conda-build` recipe [#3031](https://github.com/IntelPython/dpnp/pull/3031) +* Added support for the `"nuc"` (nuclear) norm in `dpnp.linalg.cond` [#3084](https://github.com/IntelPython/dpnp/pull/3084) ### Changed diff --git a/dpnp/linalg/dpnp_iface_linalg.py b/dpnp/linalg/dpnp_iface_linalg.py index 74fe3b65febc..8ed355278cbe 100644 --- a/dpnp/linalg/dpnp_iface_linalg.py +++ b/dpnp/linalg/dpnp_iface_linalg.py @@ -171,7 +171,7 @@ def cond(x, p=None): ---------- x : {dpnp.ndarray, usm_ndarray} The matrix whose condition number is sought. - p : {None, 1, -1, 2, -2, inf, -inf, "fro"}, optional + p : {None, 1, -1, 2, -2, inf, -inf, "fro", "nuc"}, optional Order of the norm used in the condition number computation: ===== ============================ @@ -179,6 +179,7 @@ def cond(x, p=None): ===== ============================ None 2-norm 'fro' Frobenius norm + 'nuc' nuclear norm inf max(sum(abs(x), axis=1)) -inf min(sum(abs(x), axis=1)) 1 max(sum(abs(x), axis=0)) @@ -188,7 +189,8 @@ def cond(x, p=None): ===== ============================ ``inf`` means the :obj:`dpnp.inf` object, and the Frobenius norm is - the root-of-sum-of-squares norm. + the root-of-sum-of-squares norm. The nuclear norm is the sum of the + singular values. Default: ``None``. @@ -204,9 +206,9 @@ def cond(x, p=None): Notes ----- This function will raise :class:`dpnp.linalg.LinAlgError` on singular input - when using any of the norm: ``1``, ``-1``, ``inf``, ``-inf``, or ``'fro'``. - In contrast, :obj:`numpy.linalg.cond` will fill the result array with - ``inf`` values for each 2D batch in the input array that is singular + when using any of the norm: ``1``, ``-1``, ``inf``, ``-inf``, ``'fro'``, or + ``'nuc'``. In contrast, :obj:`numpy.linalg.cond` will fill the result array + with ``inf`` values for each 2D batch in the input array that is singular when using these norms. Examples @@ -221,6 +223,8 @@ def cond(x, p=None): array(1.41421356) >>> np.linalg.cond(a, 'fro') array(3.16227766) + >>> np.linalg.cond(a, 'nuc') + array(9.24264069) >>> np.linalg.cond(a, np.inf) array(2.) >>> np.linalg.cond(a, -np.inf) diff --git a/dpnp/tests/test_linalg.py b/dpnp/tests/test_linalg.py index fe00f4211421..c639cb806d34 100644 --- a/dpnp/tests/test_linalg.py +++ b/dpnp/tests/test_linalg.py @@ -278,7 +278,7 @@ def test_cholesky_errors(self): class TestCond: - _norms = [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro"] + _norms = [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro", "nuc"] @pytest.mark.parametrize( "shape", [(0, 4, 4), (4, 0, 3, 3)], ids=["(0, 4, 4)", "(4, 0, 3, 3)"] @@ -325,7 +325,7 @@ def test_nan_to_inf(self, p): # NumPy does not raise LinAlgError on singular matrices. # It returns `inf`, `0`, or large/small finite values # depending on the norm and the matrix content. - # DPNP raises LinAlgError for 1, -1, inf, -inf, and 'fro' + # DPNP raises LinAlgError for 1, -1, inf, -inf, 'fro', and 'nuc' # due to use of gesv in the 2D case. # For [None, 2, -2], DPNP does not raise. if p in [None, 2, -2]: diff --git a/dpnp/tests/test_sycl_queue.py b/dpnp/tests/test_sycl_queue.py index fdfa8d558b16..e0f882f2175d 100644 --- a/dpnp/tests/test_sycl_queue.py +++ b/dpnp/tests/test_sycl_queue.py @@ -1619,7 +1619,7 @@ def test_cholesky(self, data, is_empty, device): assert_sycl_queue_equal(result.sycl_queue, x.sycl_queue) @pytest.mark.parametrize( - "p", [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro"] + "p", [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro", "nuc"] ) def test_cond(self, device, p): a = generate_random_numpy_array((2, 4, 4)) diff --git a/dpnp/tests/test_usm_type.py b/dpnp/tests/test_usm_type.py index aca0c3551506..567078c3eff2 100644 --- a/dpnp/tests/test_usm_type.py +++ b/dpnp/tests/test_usm_type.py @@ -1447,7 +1447,7 @@ def test_cholesky(self, data, is_empty, usm_type): assert x.usm_type == result.usm_type @pytest.mark.parametrize( - "p", [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro"] + "p", [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro", "nuc"] ) def test_cond(self, usm_type, p): a = generate_random_numpy_array((2, 4, 4), seed_value=42)