From c3b8403f5541d954ff6742c8b9f8d140f2cd020f Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Tue, 20 May 2025 06:49:35 -0700 Subject: [PATCH 1/8] Add Notes for dpnp.linalg.cond() --- dpnp/linalg/dpnp_iface_linalg.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dpnp/linalg/dpnp_iface_linalg.py b/dpnp/linalg/dpnp_iface_linalg.py index 904d935ebb72..f20ea1dd6780 100644 --- a/dpnp/linalg/dpnp_iface_linalg.py +++ b/dpnp/linalg/dpnp_iface_linalg.py @@ -186,6 +186,14 @@ def cond(x, p=None): Default: ``None``. + Notes + ----- + This function raises :class:`dpnp.linalg.LinAlgError` for singular input + matrices when using norm orders: + ``1``, ``-1``, ``inf``, ``-inf``, or ``'fro'``. + In contrast, :obj:`numpy.linalg.cond` returns ``inf``for each singular + matrix in the input regardless of the norm order. + Returns ------- out : dpnp.ndarray From 5f73575db5835049d205961af98d9e4c3ac35e07 Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Tue, 20 May 2025 06:51:17 -0700 Subject: [PATCH 2/8] Update dpnp.linalg.cond() test for singular matrix --- dpnp/tests/test_linalg.py | 67 +++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 17 deletions(-) diff --git a/dpnp/tests/test_linalg.py b/dpnp/tests/test_linalg.py index 65cd3bbc70d1..16d7461900a2 100644 --- a/dpnp/tests/test_linalg.py +++ b/dpnp/tests/test_linalg.py @@ -326,29 +326,62 @@ def test_bool(self, p): @pytest.mark.parametrize( "p", [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro"] ) - def test_nan(self, p): + def test_singular_2D(self, p): + a = numpy.ones((2, 2)) + ia = dpnp.array(a) + + # Unlike NumPy which returns `inf` for all norm orders, + # DPNP raises LinAlgError for 1, -1, inf, -inf, and 'fro' + # due to use of gesv in 2D case. + # For None, 2, and -2 DPNP matches NumPy behavior. + if p in [None, 2, -2]: + result = dpnp.linalg.cond(ia, p=p) + expected = numpy.linalg.cond(a, p=p) + assert_dtype_allclose(result, expected) + else: + assert_raises(dpnp.linalg.LinAlgError, dpnp.linalg.cond, ia, p=p) + + @pytest.mark.parametrize("shape", [(2, 2, 2), (2, 2, 2, 2)]) + @pytest.mark.parametrize( + "p", [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro"] + ) + def test_singular_ND(self, shape, p): # dpnp.linalg.cond uses dpnp.linalg.inv() # for the case when p is not None or p != -2 or p != 2 # For singular matrices cuSolver raises an error - # while OneMKL returns nans - if is_cuda_device() and p in [-dpnp.inf, -1, 1, dpnp.inf, "fro"]: + # while OneMKL < 2025.2 returns nans + # TODO: remove it when mkl=2025.2 is released + if ( + is_cuda_device() + and not requires_intel_mkl_version("2025.2") + and p in [-dpnp.inf, -1, 1, dpnp.inf, "fro"] + ): pytest.skip("Different behavior on CUDA") - elif requires_intel_mkl_version("2025.2") and p in [ - -dpnp.inf, - -1, - 1, - dpnp.inf, - "fro", - ]: - pytest.skip("SAT-7966") - a = generate_random_numpy_array((2, 2, 2, 2)) - a[0, 0] = 0 - a[1, 1] = 0 + a = numpy.ones((shape)) ia = dpnp.array(a) - result = dpnp.linalg.cond(ia, p=p) - expected = numpy.linalg.cond(a, p=p) - assert_dtype_allclose(result, expected) + # Unlike NumPy which returns `inf` for all norm orders, + # DPNP raises LinAlgError for 1, -1, inf, -inf, and 'fro' + # due to use of dpnp.linalg.inv() with OneMKL >= 2025.2. + # For None, 2, and -2 DPNP matches NumPy behavior. + if requires_intel_mkl_version("2025.2"): + if p in [None, 2, -2]: + result = dpnp.linalg.cond(ia, p=p) + expected = numpy.linalg.cond(a, p=p) + assert_dtype_allclose(result, expected) + else: + assert_raises( + dpnp.linalg.LinAlgError, dpnp.linalg.cond, ia, p=p + ) + else: + # For OneMKL < 2025.2: + # dpnp.linalg.inv() uses getrf_batch + getri_batch + # which do not raise LinAlgError. + # Instead, the result may contain `inf` or `nan` + # depending on singularity. + result = dpnp.linalg.cond(ia, p=p) + expected = numpy.linalg.cond(a, p=p) + assert_dtype_allclose(result, expected) @pytest.mark.parametrize( "p", [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro"] From 197910302302d47a83c8994e02dde7a2e56cba37 Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Tue, 20 May 2025 07:10:51 -0700 Subject: [PATCH 3/8] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7ac517e1221..f391cc92062f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ This release achieves 100% compliance with Python Array API specification (revis * Updates the list of required python versions documented in `Quick Start Guide` [#2449](https://github.com/IntelPython/dpnp/pull/2449) * Updated FFT module to ensure an input array is Hermitian before calling complex-to-real FFT [#2444](https://github.com/IntelPython/dpnp/pull/2444) * Aligned `black` configuration with the list of supported python versions [#2457](https://github.com/IntelPython/dpnp/pull/2457) +* Added a clarification to `dpnp.linalg.cond` docstring about its behavior with singular matrices [#2500] (https://github.com/IntelPython/dpnp/pull/2500) ### Fixed From b7896b787fd0527c21c00dd567a239843cbdf91c Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Tue, 20 May 2025 12:03:31 -0700 Subject: [PATCH 4/8] Update test_singular_2D and test_singular_ND --- dpnp/tests/test_linalg.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/dpnp/tests/test_linalg.py b/dpnp/tests/test_linalg.py index 16d7461900a2..9e42e48f7fe1 100644 --- a/dpnp/tests/test_linalg.py +++ b/dpnp/tests/test_linalg.py @@ -330,14 +330,17 @@ def test_singular_2D(self, p): a = numpy.ones((2, 2)) ia = dpnp.array(a) - # Unlike NumPy which returns `inf` for all norm orders, + # NumPy returns `inf` for most norms on singular matrices, + # and large (often meaningless) values for [None, 2, -2]. # DPNP raises LinAlgError for 1, -1, inf, -inf, and 'fro' # due to use of gesv in 2D case. - # For None, 2, and -2 DPNP matches NumPy behavior. + # DPNP matches NumPy behavior for [None, 2, -2]. if p in [None, 2, -2]: + # Only ensure the function runs and returns non-infinite values. result = dpnp.linalg.cond(ia, p=p) expected = numpy.linalg.cond(a, p=p) - assert_dtype_allclose(result, expected) + assert not dpnp.any(dpnp.isinf(result)) + assert not numpy.any(numpy.isinf(expected)) else: assert_raises(dpnp.linalg.LinAlgError, dpnp.linalg.cond, ia, p=p) @@ -360,15 +363,19 @@ def test_singular_ND(self, shape, p): a = numpy.ones((shape)) ia = dpnp.array(a) - # Unlike NumPy which returns `inf` for all norm orders, + # NumPy returns `inf` for most norms on singular matrices, + # and large (often meaningless) values for [None, 2, -2]. # DPNP raises LinAlgError for 1, -1, inf, -inf, and 'fro' - # due to use of dpnp.linalg.inv() with OneMKL >= 2025.2. - # For None, 2, and -2 DPNP matches NumPy behavior. + # due to use of dpnp.linalg.inv() with oneMKL >= 2025.2. + # DPNP matches NumPy behavior for [None, 2, -2]. if requires_intel_mkl_version("2025.2"): if p in [None, 2, -2]: + # Only ensure the function runs and + # returns non-infinite values. result = dpnp.linalg.cond(ia, p=p) expected = numpy.linalg.cond(a, p=p) - assert_dtype_allclose(result, expected) + assert not dpnp.any(dpnp.isinf(result)) + assert not numpy.any(numpy.isinf(expected)) else: assert_raises( dpnp.linalg.LinAlgError, dpnp.linalg.cond, ia, p=p From f561ec47c3f1d8aebe0f666bf9abbfb9dc6fac02 Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Tue, 20 May 2025 12:19:05 -0700 Subject: [PATCH 5/8] Apply remarks --- dpnp/linalg/dpnp_iface_linalg.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dpnp/linalg/dpnp_iface_linalg.py b/dpnp/linalg/dpnp_iface_linalg.py index f20ea1dd6780..2ecaf7d85c80 100644 --- a/dpnp/linalg/dpnp_iface_linalg.py +++ b/dpnp/linalg/dpnp_iface_linalg.py @@ -186,14 +186,6 @@ def cond(x, p=None): Default: ``None``. - Notes - ----- - This function raises :class:`dpnp.linalg.LinAlgError` for singular input - matrices when using norm orders: - ``1``, ``-1``, ``inf``, ``-inf``, or ``'fro'``. - In contrast, :obj:`numpy.linalg.cond` returns ``inf``for each singular - matrix in the input regardless of the norm order. - Returns ------- out : dpnp.ndarray @@ -203,6 +195,14 @@ def cond(x, p=None): -------- :obj:`dpnp.linalg.norm` : Matrix or vector norm. + 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 every 2D batch in the input array that is singular + when using these norms. + Examples -------- >>> import dpnp as np From ca1a118d2acc2774841f982e32ef5a2311d262fe Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Wed, 21 May 2025 03:58:04 -0700 Subject: [PATCH 6/8] Improve code coverage --- dpnp/linalg/dpnp_iface_linalg.py | 2 +- dpnp/tests/test_linalg.py | 45 ++++++++++++++------------------ 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/dpnp/linalg/dpnp_iface_linalg.py b/dpnp/linalg/dpnp_iface_linalg.py index 2ecaf7d85c80..137eed98d067 100644 --- a/dpnp/linalg/dpnp_iface_linalg.py +++ b/dpnp/linalg/dpnp_iface_linalg.py @@ -200,7 +200,7 @@ def cond(x, p=None): 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 every 2D batch in the input array that is singular + ``inf`` values for each 2D batch in the input array that is singular when using these norms. Examples diff --git a/dpnp/tests/test_linalg.py b/dpnp/tests/test_linalg.py index 9e42e48f7fe1..5d3f9feeb611 100644 --- a/dpnp/tests/test_linalg.py +++ b/dpnp/tests/test_linalg.py @@ -327,28 +327,25 @@ def test_bool(self, p): "p", [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro"] ) def test_singular_2D(self, p): - a = numpy.ones((2, 2)) + a = numpy.array([[1, 2], [0, 0]]) ia = dpnp.array(a) # NumPy returns `inf` for most norms on singular matrices, - # and large (often meaningless) values for [None, 2, -2]. + # and zero for norm -2. # DPNP raises LinAlgError for 1, -1, inf, -inf, and 'fro' # due to use of gesv in 2D case. # DPNP matches NumPy behavior for [None, 2, -2]. if p in [None, 2, -2]: - # Only ensure the function runs and returns non-infinite values. result = dpnp.linalg.cond(ia, p=p) expected = numpy.linalg.cond(a, p=p) - assert not dpnp.any(dpnp.isinf(result)) - assert not numpy.any(numpy.isinf(expected)) + assert_dtype_allclose(result, expected) else: assert_raises(dpnp.linalg.LinAlgError, dpnp.linalg.cond, ia, p=p) - @pytest.mark.parametrize("shape", [(2, 2, 2), (2, 2, 2, 2)]) @pytest.mark.parametrize( "p", [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro"] ) - def test_singular_ND(self, shape, p): + def test_singular_ND(self, p): # dpnp.linalg.cond uses dpnp.linalg.inv() # for the case when p is not None or p != -2 or p != 2 # For singular matrices cuSolver raises an error @@ -360,32 +357,28 @@ def test_singular_ND(self, shape, p): and p in [-dpnp.inf, -1, 1, dpnp.inf, "fro"] ): pytest.skip("Different behavior on CUDA") - a = numpy.ones((shape)) + a = generate_random_numpy_array((2, 2, 2, 2)) + a[0, 0] = 0 + a[1, 1] = 1 ia = dpnp.array(a) # NumPy returns `inf` for most norms on singular matrices, - # and large (often meaningless) values for [None, 2, -2]. + # and zeros for norm -2. # DPNP raises LinAlgError for 1, -1, inf, -inf, and 'fro' # due to use of dpnp.linalg.inv() with oneMKL >= 2025.2. # DPNP matches NumPy behavior for [None, 2, -2]. - if requires_intel_mkl_version("2025.2"): - if p in [None, 2, -2]: - # Only ensure the function runs and - # returns non-infinite values. - result = dpnp.linalg.cond(ia, p=p) - expected = numpy.linalg.cond(a, p=p) - assert not dpnp.any(dpnp.isinf(result)) - assert not numpy.any(numpy.isinf(expected)) - else: - assert_raises( - dpnp.linalg.LinAlgError, dpnp.linalg.cond, ia, p=p - ) + if p in [None, 2, -2]: + result = dpnp.linalg.cond(ia, p=p) + expected = numpy.linalg.cond(a, p=p) + assert_dtype_allclose(result, expected) + elif requires_intel_mkl_version("2025.2"): + assert_raises(dpnp.linalg.LinAlgError, dpnp.linalg.cond, ia, p=p) + # With oneMKL < 2025.2 and norms: 1, -1, inf, -inf, 'fro', + # dpnp.linalg.inv() uses getrf_batch + getri_batch + # which do not raise LinAlgError. + # Instead, the result contains `inf` for each 2D batch + # in the input array that is singular else: - # For OneMKL < 2025.2: - # dpnp.linalg.inv() uses getrf_batch + getri_batch - # which do not raise LinAlgError. - # Instead, the result may contain `inf` or `nan` - # depending on singularity. result = dpnp.linalg.cond(ia, p=p) expected = numpy.linalg.cond(a, p=p) assert_dtype_allclose(result, expected) From bee99fa5fbf452118f4cde423e191e667fdf97dc Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Wed, 21 May 2025 05:35:46 -0700 Subject: [PATCH 7/8] Ad test_nan_to_inf to improve coverage --- dpnp/tests/test_linalg.py | 50 +++++++-------------------------------- 1 file changed, 8 insertions(+), 42 deletions(-) diff --git a/dpnp/tests/test_linalg.py b/dpnp/tests/test_linalg.py index 5d3f9feeb611..ce6c19650957 100644 --- a/dpnp/tests/test_linalg.py +++ b/dpnp/tests/test_linalg.py @@ -326,8 +326,8 @@ def test_bool(self, p): @pytest.mark.parametrize( "p", [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro"] ) - def test_singular_2D(self, p): - a = numpy.array([[1, 2], [0, 0]]) + def test_nan_to_inf(self, p): + a = numpy.zeros((2, 2)) ia = dpnp.array(a) # NumPy returns `inf` for most norms on singular matrices, @@ -335,53 +335,19 @@ def test_singular_2D(self, p): # DPNP raises LinAlgError for 1, -1, inf, -inf, and 'fro' # due to use of gesv in 2D case. # DPNP matches NumPy behavior for [None, 2, -2]. - if p in [None, 2, -2]: - result = dpnp.linalg.cond(ia, p=p) - expected = numpy.linalg.cond(a, p=p) - assert_dtype_allclose(result, expected) - else: - assert_raises(dpnp.linalg.LinAlgError, dpnp.linalg.cond, ia, p=p) - @pytest.mark.parametrize( - "p", [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro"] - ) - def test_singular_ND(self, p): - # dpnp.linalg.cond uses dpnp.linalg.inv() - # for the case when p is not None or p != -2 or p != 2 - # For singular matrices cuSolver raises an error - # while OneMKL < 2025.2 returns nans - # TODO: remove it when mkl=2025.2 is released - if ( - is_cuda_device() - and not requires_intel_mkl_version("2025.2") - and p in [-dpnp.inf, -1, 1, dpnp.inf, "fro"] - ): - pytest.skip("Different behavior on CUDA") - a = generate_random_numpy_array((2, 2, 2, 2)) - a[0, 0] = 0 - a[1, 1] = 1 - ia = dpnp.array(a) - - # NumPy returns `inf` for most norms on singular matrices, - # and zeros for norm -2. + # 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' - # due to use of dpnp.linalg.inv() with oneMKL >= 2025.2. - # DPNP matches NumPy behavior for [None, 2, -2]. + # due to use of gesv in the 2D case. + # For [None, 2, -2], DPNP does not raise. if p in [None, 2, -2]: result = dpnp.linalg.cond(ia, p=p) expected = numpy.linalg.cond(a, p=p) assert_dtype_allclose(result, expected) - elif requires_intel_mkl_version("2025.2"): - assert_raises(dpnp.linalg.LinAlgError, dpnp.linalg.cond, ia, p=p) - # With oneMKL < 2025.2 and norms: 1, -1, inf, -inf, 'fro', - # dpnp.linalg.inv() uses getrf_batch + getri_batch - # which do not raise LinAlgError. - # Instead, the result contains `inf` for each 2D batch - # in the input array that is singular else: - result = dpnp.linalg.cond(ia, p=p) - expected = numpy.linalg.cond(a, p=p) - assert_dtype_allclose(result, expected) + assert_raises(dpnp.linalg.LinAlgError, dpnp.linalg.cond, ia, p=p) @pytest.mark.parametrize( "p", [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro"] From 80bc6e799af90394ec93f2f09552388377e3c7dd Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Wed, 21 May 2025 05:46:34 -0700 Subject: [PATCH 8/8] Remove old comment --- dpnp/tests/test_linalg.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/dpnp/tests/test_linalg.py b/dpnp/tests/test_linalg.py index ce6c19650957..2f567f7a9e5c 100644 --- a/dpnp/tests/test_linalg.py +++ b/dpnp/tests/test_linalg.py @@ -330,12 +330,6 @@ def test_nan_to_inf(self, p): a = numpy.zeros((2, 2)) ia = dpnp.array(a) - # NumPy returns `inf` for most norms on singular matrices, - # and zero for norm -2. - # DPNP raises LinAlgError for 1, -1, inf, -inf, and 'fro' - # due to use of gesv in 2D case. - # DPNP matches NumPy behavior for [None, 2, -2]. - # 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.