diff --git a/dpnp/linalg/dpnp_iface_linalg.py b/dpnp/linalg/dpnp_iface_linalg.py index 137eed98d067..07ed0078be28 100644 --- a/dpnp/linalg/dpnp_iface_linalg.py +++ b/dpnp/linalg/dpnp_iface_linalg.py @@ -180,8 +180,22 @@ 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 - Order of the norm used in the condition number computation. - ``inf`` means the `dpnp.inf` object, and the Frobenius norm is + Order of the norm used in the condition number computation: + + ===== ============================ + p norm for matrices + ===== ============================ + None 2-norm + 'fro' Frobenius norm + inf max(sum(abs(x), axis=1)) + -inf min(sum(abs(x), axis=1)) + 1 max(sum(abs(x), axis=0)) + -1 min(sum(abs(x), axis=0)) + 2 2-norm (largest singular value) + -2 smallest singular value + ===== ============================ + + ``inf`` means the :obj:`dpnp.inf` object, and the Frobenius norm is the root-of-sum-of-squares norm. Default: ``None``. diff --git a/dpnp/tests/third_party/cupy/linalg_tests/test_norms.py b/dpnp/tests/third_party/cupy/linalg_tests/test_norms.py index 297ce282928a..b26a6a4826a1 100644 --- a/dpnp/tests/third_party/cupy/linalg_tests/test_norms.py +++ b/dpnp/tests/third_party/cupy/linalg_tests/test_norms.py @@ -4,7 +4,6 @@ import pytest import dpnp as cupy -from dpnp.tests.helper import is_cpu_device from dpnp.tests.third_party.cupy import testing @@ -224,3 +223,133 @@ def test_slogdet_one_dim(self, dtype): a = testing.shaped_arange((2,), xp, dtype) with pytest.raises(xp.linalg.LinAlgError): xp.linalg.slogdet(a) + + +@testing.parameterize( + *testing.product({"ord": [-numpy.inf, -2, -1, 1, 2, numpy.inf, "fro"]}) +) +class TestCond(unittest.TestCase): + @testing.for_float_dtypes(no_float16=True) + @testing.numpy_cupy_allclose(rtol=1e-3, atol=1e-4) + def test_singular_zeros(self, xp, dtype): + if self.ord not in [None, 2, -2]: + pytest.skip("no LinAlgError is raising on singular matrices") + + A = xp.zeros(shape=(2, 2), dtype=dtype) + result = xp.linalg.cond(A, self.ord) + + # singular matrices don't always hit infinity. + result = xp.asarray(result) # numpy is scalar and can't be replaced + large_number = 1.0 / (xp.finfo(dtype).eps) + result[result >= large_number] = xp.inf + + return result + + @testing.for_float_dtypes(no_float16=True) + @testing.numpy_cupy_allclose(rtol=1e-3, atol=1e-4) + def test_singular_ones(self, xp, dtype): + if self.ord not in [None, 2, -2]: + pytest.skip("no LinAlgError is raising on singular matrices") + + A = xp.ones(shape=(2, 2), dtype=dtype) + result = xp.linalg.cond(A, self.ord) + + # singular matrices don't always hit infinity. + result = xp.asarray(result) # numpy is scalar and can't be replaced + large_number = 1.0 / (xp.finfo(dtype).eps) + result[result >= large_number] = xp.inf + + return result + + @testing.for_float_dtypes(no_float16=True) + @testing.numpy_cupy_allclose(rtol=1e-3, atol=1e-4) + def test_stacked_singular(self, xp, dtype): + if self.ord not in [None, 2, -2]: + pytest.skip("no LinAlgError is raising on singular matrices") + + # Check behavior when only some of the stacked matrices are + # singular + + A = xp.arange(16, dtype=dtype).reshape((2, 2, 2, 2)) + A[0, 0] = 0 + A[1, 1] = 0 + + res = xp.linalg.cond(A, self.ord) + return res + + @testing.for_float_dtypes(no_float16=True) + @testing.numpy_cupy_allclose(rtol=1e-3, atol=1e-4) + def test_default(self, xp, dtype): + A = testing.shaped_arange((2, 2), xp, dtype=dtype) + return xp.linalg.cond(A) + + @testing.for_float_dtypes(no_float16=True) + @testing.numpy_cupy_allclose(rtol=1e-3, atol=1e-4) + def test_basic(self, xp, dtype): + A = testing.shaped_arange((2, 2), xp, dtype=dtype) + return xp.linalg.cond(A, self.ord) + + @testing.for_float_dtypes(no_float16=True) + @testing.numpy_cupy_allclose(rtol=1e-3, atol=1e-4) + def test_generalized_1(self, xp, dtype): + A = testing.shaped_arange((2, 2), xp, dtype=dtype) + A = xp.array([A, 2 * A, 3 * A]) + return xp.linalg.cond(A, self.ord) + + @testing.for_float_dtypes(no_float16=True) + @testing.numpy_cupy_allclose(rtol=1e-3, atol=1e-4) + def test_generalized_2(self, xp, dtype): + A = testing.shaped_arange((2, 2), xp, dtype=dtype) + A = xp.array([A, 2 * A, 3 * A]) + A = xp.array([A] * 2 * 3).reshape((3, 2) + A.shape) + + return xp.linalg.cond(A, self.ord) + + @testing.for_float_dtypes(no_float16=True) + def test_0x0(self, dtype): + for xp in (numpy, cupy): + A = xp.empty((0, 0), dtype=dtype) + with pytest.raises( + xp.linalg.LinAlgError, + match="cond is not defined on empty arrays", + ): + xp.linalg.cond(A, self.ord) + + @testing.for_float_dtypes(no_float16=True) + @testing.numpy_cupy_allclose(rtol=1e-3, atol=1e-4) + def test_1x1(self, xp, dtype): + A = xp.ones((1, 1), dtype=dtype) + return xp.linalg.cond(A, self.ord) + + @testing.for_float_dtypes(no_float16=True) + @testing.numpy_cupy_allclose(rtol=1e-3, atol=1e-4) + def test_8x8(self, xp, dtype): + A = testing.shaped_arange((8, 8), xp, dtype=dtype) + xp.diag( + xp.ones(8, dtype=dtype) + ) + return xp.linalg.cond(A, self.ord) + + @pytest.mark.skip("only ndarray input is supported") + @testing.numpy_cupy_allclose(rtol=1e-3, atol=1e-4) + def test_nonarray(self, xp): + A = [[1.0, 2.0], [3.0, 4.0]] + return xp.linalg.cond(A, self.ord) + + @testing.for_float_dtypes(no_float16=True) + @testing.numpy_cupy_allclose(rtol=1e-3, atol=1e-4) + def test_hermitian(self, xp, dtype): + A = xp.array([[1.0, 2.0], [2.0, 1.0]], dtype=dtype) + return xp.linalg.cond(A, self.ord) + + +class TestCondBasicNonSVD(unittest.TestCase): + def test_basic_nonsvd(self): + # Smoketest the non-svd norms + A = cupy.array([[1.0, 0, 1], [0, -2.0, 0], [0, 0, 3.0]]) + testing.assert_array_almost_equal(cupy.linalg.cond(A, cupy.inf), 4) + testing.assert_array_almost_equal(cupy.linalg.cond(A, -cupy.inf), 2 / 3) + testing.assert_array_almost_equal(cupy.linalg.cond(A, 1), 4) + testing.assert_array_almost_equal(cupy.linalg.cond(A, -1), 0.5) + testing.assert_array_almost_equal( + cupy.linalg.cond(A, "fro"), numpy.sqrt(265 / 12) + )