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) 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)