From 101365d9af11b6e863941d7a3ed08c4be5072cf3 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Thu, 31 Jul 2025 13:47:45 +0200 Subject: [PATCH 1/5] Always return a real dtype from dpnp.linalg.cond --- dpnp/linalg/dpnp_utils_linalg.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dpnp/linalg/dpnp_utils_linalg.py b/dpnp/linalg/dpnp_utils_linalg.py index 51cebb2815b..55d140c5c88 100644 --- a/dpnp/linalg/dpnp_utils_linalg.py +++ b/dpnp/linalg/dpnp_utils_linalg.py @@ -1976,13 +1976,15 @@ def dpnp_cond(x, p=None): else: r = s[..., 0] / s[..., -1] else: - result_t = _common_type(x) # The result array will contain nans in the entries # where inversion failed invx = dpnp.linalg.inv(x) r = dpnp.linalg.norm(x, p, axis=(-2, -1)) * dpnp.linalg.norm( invx, p, axis=(-2, -1) ) + + # condition number is always real + result_t = _real_type(_common_type(x), device=x.sycl_queue) r = r.astype(result_t, copy=False) # Convert nans to infs unless the original array had nan entries From e97a5d8ed53f6b0ab7b924219dd1257101102715 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Thu, 31 Jul 2025 13:48:59 +0200 Subject: [PATCH 2/5] Update tests --- dpnp/tests/test_linalg.py | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/dpnp/tests/test_linalg.py b/dpnp/tests/test_linalg.py index 931c631b563..f6c045f80e8 100644 --- a/dpnp/tests/test_linalg.py +++ b/dpnp/tests/test_linalg.py @@ -278,15 +278,15 @@ def test_cholesky_errors(self): class TestCond: + _norms = [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro"] + def setup_method(self): numpy.random.seed(70) @pytest.mark.parametrize( - "shape", [(0, 4, 4), (4, 0, 3, 3)], ids=["(0, 5, 3)", "(4, 0, 2, 3)"] - ) - @pytest.mark.parametrize( - "p", [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro"] + "shape", [(0, 4, 4), (4, 0, 3, 3)], ids=["(0, 4, 4)", "(4, 0, 3, 3)"] ) + @pytest.mark.parametrize("p", _norms) def test_empty(self, shape, p): a = numpy.empty(shape) ia = dpnp.array(a) @@ -295,26 +295,27 @@ def test_empty(self, shape, p): expected = numpy.linalg.cond(a, p=p) assert_dtype_allclose(result, expected) + # TODO: uncomment once numpy 2.3.3 release is published + # @testing.with_requires("numpy>=2.3.3") @pytest.mark.parametrize( "dtype", get_all_dtypes(no_none=True, no_bool=True) ) @pytest.mark.parametrize( "shape", [(4, 4), (2, 4, 3, 3)], ids=["(4, 4)", "(2, 4, 3, 3)"] ) - @pytest.mark.parametrize( - "p", [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro"] - ) + @pytest.mark.parametrize("p", _norms) def test_basic(self, dtype, shape, p): a = generate_random_numpy_array(shape, dtype) ia = dpnp.array(a) result = dpnp.linalg.cond(ia, p=p) expected = numpy.linalg.cond(a, p=p) + # TODO: remove when numpy#29333 is released + if numpy_version() < "2.3.3": + expected = expected.real assert_dtype_allclose(result, expected, factor=16) - @pytest.mark.parametrize( - "p", [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro"] - ) + @pytest.mark.parametrize("p", _norms) def test_bool(self, p): a = numpy.array([[True, True], [True, False]]) ia = dpnp.array(a) @@ -323,9 +324,7 @@ def test_bool(self, 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"] - ) + @pytest.mark.parametrize("p", _norms) def test_nan_to_inf(self, p): a = numpy.zeros((2, 2)) ia = dpnp.array(a) @@ -343,9 +342,7 @@ def test_nan_to_inf(self, p): 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"] - ) + @pytest.mark.parametrize("p", _norms) @pytest.mark.parametrize( "stride", [(-2, -3, 2, -2), (-2, 4, -4, -4), (2, 3, 4, 4), (-1, 3, 3, -3)], @@ -367,11 +364,12 @@ def test_strided(self, p, stride): expected = numpy.linalg.cond(a, p=p) assert_dtype_allclose(result, expected, factor=24) - def test_error(self): + @pytest.mark.parametrize("xp", [dpnp, numpy]) + def test_error(self, xp): # cond is not defined on empty arrays - ia = dpnp.empty((2, 0)) + a = xp.empty((2, 0)) with pytest.raises(ValueError): - dpnp.linalg.cond(ia, p=1) + xp.linalg.cond(a, p=1) class TestDet: From a6f695a6f2fd2363ae21117622cbbc24ac6bf236 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Thu, 31 Jul 2025 13:54:10 +0200 Subject: [PATCH 3/5] Add PR to the changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ed9491d0ec..eb31d3cd325 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Avoided negating unsigned integers in ceil division used in `dpnp.resize` implementation [#2508](https://github.com/IntelPython/dpnp/pull/2508) * Fixed `dpnp.unique` with 1d input array and `axis=0`, `equal_nan=True` keywords passed where the produced result doesn't collapse the NaNs [#2530](https://github.com/IntelPython/dpnp/pull/2530) * Resolved issue when `dpnp.ndarray` constructor is called with `dpnp.ndarray.data` as `buffer` keyword [#2533](https://github.com/IntelPython/dpnp/pull/2533) +* Fixed `dpnp.linalg.cond` to always return a real dtype [#2547](https://github.com/IntelPython/dpnp/pull/2547) ### Security From 58c7de5fef1577373a8e1e14fd03f4cb33c5c451 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Mon, 4 Aug 2025 17:27:36 +0200 Subject: [PATCH 4/5] Use generate_random_numpy_array in test_strided per review comment --- dpnp/tests/test_linalg.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/dpnp/tests/test_linalg.py b/dpnp/tests/test_linalg.py index f6c045f80e8..10693db21d3 100644 --- a/dpnp/tests/test_linalg.py +++ b/dpnp/tests/test_linalg.py @@ -280,9 +280,6 @@ def test_cholesky_errors(self): class TestCond: _norms = [None, -dpnp.inf, -2, -1, 1, 2, dpnp.inf, "fro"] - def setup_method(self): - numpy.random.seed(70) - @pytest.mark.parametrize( "shape", [(0, 4, 4), (4, 0, 3, 3)], ids=["(0, 4, 4)", "(4, 0, 3, 3)"] ) @@ -354,13 +351,12 @@ def test_nan_to_inf(self, p): ], ) def test_strided(self, p, stride): - A = numpy.random.rand(6, 8, 10, 10) - B = dpnp.asarray(A) + A = generate_random_numpy_array((6, 8, 10, 10)) + iA = dpnp.array(A) slices = tuple(slice(None, None, stride[i]) for i in range(A.ndim)) - a = A[slices] - b = B[slices] + a, ia = A[slices], iA[slices] - result = dpnp.linalg.cond(b, p=p) + result = dpnp.linalg.cond(ia, p=p) expected = numpy.linalg.cond(a, p=p) assert_dtype_allclose(result, expected, factor=24) From f69b9d5652493ecf0a5c97c0819f54506eeb5a22 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Tue, 5 Aug 2025 00:02:01 +0200 Subject: [PATCH 5/5] Pass seed value explicitly in test_strided --- dpnp/tests/test_linalg.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dpnp/tests/test_linalg.py b/dpnp/tests/test_linalg.py index 10693db21d3..34571ddf799 100644 --- a/dpnp/tests/test_linalg.py +++ b/dpnp/tests/test_linalg.py @@ -351,7 +351,9 @@ def test_nan_to_inf(self, p): ], ) def test_strided(self, p, stride): - A = generate_random_numpy_array((6, 8, 10, 10)) + A = generate_random_numpy_array( + (6, 8, 10, 10), seed_value=70, low=0, high=1 + ) iA = dpnp.array(A) slices = tuple(slice(None, None, stride[i]) for i in range(A.ndim)) a, ia = A[slices], iA[slices]