From 273da9e6edb5caca74fc7af00dbd876cdae8c8c6 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Wed, 7 Oct 2026 17:16:11 +0200 Subject: [PATCH 1/2] Use computation dtype in dpnp.linalg.pinv for default tolerance and empty input The default tolerance of `dpnp.linalg.pinv` used the machine epsilon of `result_type(a.dtype, default_float_type)`, which is float64 for single-precision input on devices with fp64 support and for integer input on devices without it, while the SVD runs in single precision. It now uses the epsilon of the dtype the SVD is computed in. The empty-input shortcut returned the input dtype, so an empty integer or boolean array gave an integer/boolean result while a non-empty one gave a floating-point result. It now returns the computation dtype too. Both changes align `dpnp.linalg.pinv` with NumPy 2.6 behavior. --- CHANGELOG.md | 1 + dpnp/linalg/dpnp_utils_linalg.py | 8 +++++--- dpnp/tests/test_linalg.py | 28 +++++++++++++++++++++++++++- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0240e4d5d9eb..5e82ec0d1428 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ This release is compatible with NumPy 2.5. * Updated the implementation of `dpnp.putmask` by adding dedicated contiguous and strided SYCL kernels [#3014](https://github.com/IntelPython/dpnp/pull/3014) * Changed `dpnp.sort`, `dpnp.argsort`, and their `dpnp.ndarray`/`dpnp.tensor` counterparts to place `NaN` values last instead of first when sorting in descending order [#3066](https://github.com/IntelPython/dpnp/pull/3066) * Documented and made explicit support for `copy=None` ("copy only if needed") in `dpnp.nan_to_num` [#3083](https://github.com/IntelPython/dpnp/pull/3083) +* Changed `dpnp.linalg.pinv` to compute the default tolerance from the machine epsilon of the data type used for the computation, and to return a floating-point result for an empty integer or boolean array [#3090](https://github.com/IntelPython/dpnp/pull/3090) ### Deprecated diff --git a/dpnp/linalg/dpnp_utils_linalg.py b/dpnp/linalg/dpnp_utils_linalg.py index 5639de12ae5a..954fcfac4b9b 100644 --- a/dpnp/linalg/dpnp_utils_linalg.py +++ b/dpnp/linalg/dpnp_utils_linalg.py @@ -2402,10 +2402,12 @@ def dpnp_pinv(a, rcond=None, hermitian=False, rtol=None): """ + # dtype the SVD is computed in + res_type = _common_type(a) + if rcond is None: if rtol is None: - dtype = dpnp.result_type(a.dtype, dpnp.default_float_type(a.device)) - rcond = max(a.shape[-2:]) * dpnp.finfo(dtype).eps + rcond = max(a.shape[-2:]) * dpnp.finfo(res_type).eps else: rcond = rtol elif rtol is not None: @@ -2414,7 +2416,7 @@ def dpnp_pinv(a, rcond=None, hermitian=False, rtol=None): if _is_empty_2d(a): m, n = a.shape[-2:] sh = a.shape[:-2] + (n, m) - return dpnp.empty_like(a, shape=sh) + return dpnp.empty_like(a, shape=sh, dtype=res_type) if dpnp.is_supported_array_type(rcond): # Check that `a` and `rcond` are allocated on the same device diff --git a/dpnp/tests/test_linalg.py b/dpnp/tests/test_linalg.py index c639cb806d34..5504cad7675c 100644 --- a/dpnp/tests/test_linalg.py +++ b/dpnp/tests/test_linalg.py @@ -20,6 +20,7 @@ generate_random_numpy_array, get_all_dtypes, get_float_complex_dtypes, + get_integer_dtypes, get_integer_float_dtypes, has_support_aspect64, numpy_version, @@ -4370,7 +4371,29 @@ def test_pinv_rtol(self): result = dpnp.linalg.pinv(a_dp, rtol=1e-15) assert_dtype_allclose(result, expected) - @pytest.mark.parametrize("dtype", get_all_dtypes(no_bool=True)) + @testing.with_requires("numpy>=2.0") + @pytest.mark.parametrize("dtype", get_float_complex_dtypes()) + def test_pinv_rtol_none(self, dtype): + # singular value 1e-9 is cut off for single precision only + a = numpy.diag([1.0, 1e-9]).astype(dtype) + a_dp = dpnp.array(a) + + expected = numpy.linalg.pinv(a, rtol=None) + result = dpnp.linalg.pinv(a_dp, rtol=None) + assert_dtype_allclose(result, expected) + + @testing.with_requires("numpy>=2.0") + @pytest.mark.parametrize("dtype", get_integer_dtypes() + [dpnp.bool]) + def test_pinv_rtol_none_non_inexact(self, dtype): + a = numpy.array([[1, 2, 3], [4, 1, 1], [2, 3, 1]]).astype(dtype) + a_dp = dpnp.array(a) + + # NumPy < 2.6.0 raises ValueError for non-inexact input + expected = numpy.linalg.pinv(a.astype(numpy.float64), rtol=None) + result = dpnp.linalg.pinv(a_dp, rtol=None) + assert_dtype_allclose(result, expected) + + @pytest.mark.parametrize("dtype", get_all_dtypes()) @pytest.mark.parametrize( "shape", [(0, 0), (0, 2), (2, 0), (2, 0, 3), (2, 3, 0), (0, 2, 3)], @@ -4388,6 +4411,9 @@ def test_pinv_empty(self, dtype, shape): a_dp = dpnp.array(a) B = numpy.linalg.pinv(a) + if numpy_version() < "2.6.0" and a.dtype.kind in "biu": + # NumPy < 2.6.0 returns the input dtype for empty int/bool input + B = B.astype(numpy.float64) B_dp = dpnp.linalg.pinv(a_dp) assert_dtype_allclose(B_dp, B) From 573e7f08eda322abe0091e09b39be3f4211a7584 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Wed, 7 Oct 2026 20:11:05 +0200 Subject: [PATCH 2/2] Cast NumPy reference to float64 in cupy pinv test for empty int input on NumPy < 2.6 --- dpnp/tests/third_party/cupy/linalg_tests/test_solve.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dpnp/tests/third_party/cupy/linalg_tests/test_solve.py b/dpnp/tests/third_party/cupy/linalg_tests/test_solve.py index eeb5712d84ef..29db4661805f 100644 --- a/dpnp/tests/third_party/cupy/linalg_tests/test_solve.py +++ b/dpnp/tests/third_party/cupy/linalg_tests/test_solve.py @@ -7,6 +7,7 @@ from dpnp.tests.helper import ( assert_dtype_allclose, has_support_aspect64, + numpy_version, ) from dpnp.tests.third_party.cupy import testing from dpnp.tests.third_party.cupy.testing import _condition @@ -247,6 +248,9 @@ def check_x(self, a_shape, rcond, dtype): if not isinstance(rcond, float): rcond = numpy.asarray(rcond) result_cpu = numpy.linalg.pinv(a_cpu, rcond=rcond) + if numpy_version() < "2.6.0" and a_cpu.dtype.kind in "biu": + # NumPy < 2.6.0 returns the input dtype for empty int/bool input + result_cpu = result_cpu.astype(numpy.float64) if not isinstance(rcond, float): rcond = cupy.asarray(rcond) result_gpu = cupy.linalg.pinv(a_gpu, rcond=rcond)