import unittest import numpy import pytest import cupy from cupy import testing from cupy.testing import _condition import cupyx from cupy.cublas import get_batched_gesv_limit, set_batched_gesv_limit @testing.parameterize(*testing.product({ 'batched_gesv_limit': [None, 0], 'order': ['C', 'F'], })) @testing.fix_random() class TestSolve(unittest.TestCase): def setUp(self): if self.batched_gesv_limit is not None: self.old_limit = get_batched_gesv_limit() set_batched_gesv_limit(self.batched_gesv_limit) def tearDown(self): if self.batched_gesv_limit is not None: set_batched_gesv_limit(self.old_limit) @testing.for_dtypes('ifdFD') # TODO(kataoka): Fix contiguity @testing.numpy_cupy_allclose(atol=1e-3, contiguous_check=False) def check_x(self, a_shape, b_shape, xp, dtype): a = testing.shaped_random(a_shape, xp, dtype=dtype, seed=0, scale=20) b = testing.shaped_random(b_shape, xp, dtype=dtype, seed=1) a = a.copy(order=self.order) b = b.copy(order=self.order) a_copy = a.copy() b_copy = b.copy() result = xp.linalg.solve(a, b) cupy.testing.assert_array_equal(a_copy, a) cupy.testing.assert_array_equal(b_copy, b) return result def test_solve(self): self.check_x((4, 4), (4,)) self.check_x((5, 5), (5, 2)) self.check_x((2, 4, 4), (2, 4,)) self.check_x((2, 5, 5), (2, 5, 2)) self.check_x((2, 3, 2, 2), (2, 3, 2,)) self.check_x((2, 3, 3, 3), (2, 3, 3, 2)) self.check_x((0, 0), (0,)) self.check_x((0, 0), (0, 2)) self.check_x((0, 2, 2), (0, 2,)) self.check_x((0, 2, 2), (0, 2, 3)) def check_shape(self, a_shape, b_shape, error_type): for xp in (numpy, cupy): a = xp.random.rand(*a_shape) b = xp.random.rand(*b_shape) with pytest.raises(error_type): xp.linalg.solve(a, b) @testing.numpy_cupy_allclose() def test_solve_singular_empty(self, xp): a = xp.zeros((3, 3)) # singular b = xp.empty((3, 0)) # nrhs = 0 # LinAlgError("Singular matrix") is not raised return xp.linalg.solve(a, b) def test_invalid_shape(self): self.check_shape((2, 3), (4,), numpy.linalg.LinAlgError) self.check_shape((3, 3), (2,), ValueError) self.check_shape((3, 3), (2, 2), ValueError) self.check_shape((3, 3, 4), (3,), numpy.linalg.LinAlgError) self.check_shape((2, 3, 3), (3,), ValueError) self.check_shape((3, 3), (0,), ValueError) self.check_shape((0, 3, 4), (3,), numpy.linalg.LinAlgError) @testing.parameterize(*testing.product({ 'a_shape': [(2, 3, 6), (3, 4, 4, 3)], 'axes': [None, (0, 2)], })) @testing.fix_random() class TestTensorSolve(unittest.TestCase): @testing.for_dtypes('ifdFD') @testing.numpy_cupy_allclose(atol=0.02) def test_tensorsolve(self, xp, dtype): a_shape = self.a_shape b_shape = self.a_shape[:2] a = testing.shaped_random(a_shape, xp, dtype=dtype, seed=0) b = testing.shaped_random(b_shape, xp, dtype=dtype, seed=1) return xp.linalg.tensorsolve(a, b, axes=self.axes) @testing.parameterize(*testing.product({ 'order': ['C', 'F'], })) class TestInv(unittest.TestCase): @testing.for_dtypes('ifdFD') @_condition.retry(10) def check_x(self, a_shape, dtype): a_cpu = numpy.random.randint(0, 10, size=a_shape) a_cpu = a_cpu.astype(dtype, order=self.order) a_gpu = cupy.asarray(a_cpu, order=self.order) a_gpu_copy = a_gpu.copy() result_cpu = numpy.linalg.inv(a_cpu) result_gpu = cupy.linalg.inv(a_gpu) assert result_cpu.dtype == result_gpu.dtype cupy.testing.assert_allclose(result_cpu, result_gpu, atol=1e-3) cupy.testing.assert_array_equal(a_gpu_copy, a_gpu) def check_shape(self, a_shape): a = cupy.random.rand(*a_shape) with self.assertRaises(numpy.linalg.LinAlgError): cupy.linalg.inv(a) def test_inv(self): self.check_x((3, 3)) self.check_x((4, 4)) self.check_x((5, 5)) self.check_x((2, 5, 5)) self.check_x((3, 4, 4)) self.check_x((4, 2, 3, 3)) self.check_x((0, 0)) self.check_x((3, 0, 0)) self.check_x((2, 0, 3, 4, 4)) def test_invalid_shape(self): self.check_shape((2, 3)) self.check_shape((4, 1)) self.check_shape((4, 3, 2)) self.check_shape((2, 4, 3)) self.check_shape((2, 0)) self.check_shape((0, 2, 3)) class TestInvInvalid(unittest.TestCase): @testing.for_dtypes('ifdFD') def test_inv(self, dtype): for xp in (numpy, cupy): a = xp.array([[1, 2], [2, 4]]).astype(dtype) with cupyx.errstate(linalg='raise'): with pytest.raises(numpy.linalg.LinAlgError): xp.linalg.inv(a) @testing.for_dtypes('ifdFD') def test_batched_inv(self, dtype): for xp in (numpy, cupy): a = xp.array([[[1, 2], [2, 4]]]).astype(dtype) assert a.ndim >= 3 # CuPy internally uses a batched function. with cupyx.errstate(linalg='raise'): with pytest.raises(numpy.linalg.LinAlgError): xp.linalg.inv(a) class TestPinv(unittest.TestCase): @testing.for_dtypes('ifdFD') @_condition.retry(10) def check_x(self, a_shape, rcond, dtype): a_gpu = testing.shaped_random(a_shape, dtype=dtype) a_cpu = cupy.asnumpy(a_gpu) a_gpu_copy = a_gpu.copy() if not isinstance(rcond, float): rcond = numpy.asarray(rcond) result_cpu = numpy.linalg.pinv(a_cpu, rcond=rcond) if not isinstance(rcond, float): rcond = cupy.asarray(rcond) result_gpu = cupy.linalg.pinv(a_gpu, rcond=rcond) assert result_cpu.dtype == result_gpu.dtype cupy.testing.assert_allclose(result_cpu, result_gpu, atol=1e-3) cupy.testing.assert_array_equal(a_gpu_copy, a_gpu) def test_pinv(self): self.check_x((3, 3), rcond=1e-15) self.check_x((2, 4), rcond=1e-15) self.check_x((3, 2), rcond=1e-15) self.check_x((4, 4), rcond=0.3) self.check_x((2, 5), rcond=0.5) self.check_x((5, 3), rcond=0.6) def test_pinv_batched(self): self.check_x((2, 3, 4), rcond=1e-15) self.check_x((2, 3, 4, 5), rcond=1e-15) def test_pinv_batched_vector_rcond(self): self.check_x((2, 3, 4), rcond=[0.2, 0.8]) self.check_x((2, 3, 4, 5), rcond=[[0.2, 0.9, 0.1], [0.7, 0.2, 0.5]]) def test_pinv_size_0(self): self.check_x((3, 0), rcond=1e-15) self.check_x((0, 3), rcond=1e-15) self.check_x((0, 0), rcond=1e-15) self.check_x((0, 2, 3), rcond=1e-15) self.check_x((2, 0, 3), rcond=1e-15) class TestLstsq: @testing.for_dtypes('ifdFD') @testing.numpy_cupy_allclose(atol=1e-3) def check_lstsq_solution(self, a_shape, b_shape, seed, rcond, xp, dtype, singular=False): if singular: m, n = a_shape rank = min(m, n) - 1 a = xp.matmul( testing.shaped_random( (m, rank), xp, dtype=dtype, scale=3, seed=seed), testing.shaped_random( (rank, n), xp, dtype=dtype, scale=3, seed=seed+42), ) else: a = testing.shaped_random(a_shape, xp, dtype=dtype, seed=seed) b = testing.shaped_random(b_shape, xp, dtype=dtype, seed=seed+37) a_copy = a.copy() b_copy = b.copy() results = xp.linalg.lstsq(a, b, rcond) if xp is cupy: testing.assert_array_equal(a_copy, a) testing.assert_array_equal(b_copy, b) return results def check_invalid_shapes(self, a_shape, b_shape): a = cupy.random.rand(*a_shape) b = cupy.random.rand(*b_shape) with pytest.raises(numpy.linalg.LinAlgError): cupy.linalg.lstsq(a, b, rcond=None) def test_lstsq_solutions(self): # Comapres numpy.linalg.lstsq and cupy.linalg.lstsq solutions for: # a shapes range from (3, 3) to (5, 3) and (3, 5) # b shapes range from (i, 3) to (i, ) # sets a random seed for deterministic testing for i in range(3, 6): for j in range(3, 6): for k in range(2, 4): seed = i + j + k # check when b has shape (i, k) self.check_lstsq_solution((i, j), (i, k), seed, rcond=-1) self.check_lstsq_solution((i, j), (i, k), seed, rcond=None) self.check_lstsq_solution((i, j), (i, k), seed, rcond=0.5) self.check_lstsq_solution((i, j), (i, k), seed, rcond=1e-6, singular=True) # check when b has shape (i, ) self.check_lstsq_solution((i, j), (i, ), seed+1, rcond=-1) self.check_lstsq_solution((i, j), (i, ), seed+1, rcond=None) self.check_lstsq_solution((i, j), (i, ), seed+1, rcond=0.5) self.check_lstsq_solution((i, j), (i, ), seed+1, rcond=1e-6, singular=True) @pytest.mark.parametrize("rcond", [-1, None, 0.5]) @pytest.mark.parametrize("k", [None, 0, 1, 4]) @pytest.mark.parametrize(("i", "j"), [(0, 0), (3, 0), (0, 7)]) @testing.for_dtypes('ifdFD') @testing.numpy_cupy_allclose(rtol=1e-6) def test_lstsq_empty_matrix(self, xp, dtype, i, j, k, rcond): a = xp.empty((i, j), dtype) assert a.size == 0 b_shape = (i,) if k is None else (i, k) b = testing.shaped_random(b_shape, xp, dtype) return xp.linalg.lstsq(a, b, rcond) def test_invalid_shapes(self): self.check_invalid_shapes((4, 3), (3, )) self.check_invalid_shapes((3, 3, 3), (2, 2)) self.check_invalid_shapes((3, 3, 3), (3, 3)) self.check_invalid_shapes((3, 3), (3, 3, 3)) self.check_invalid_shapes((2, 2), (10, )) self.check_invalid_shapes((3, 3), (2, 2)) self.check_invalid_shapes((4, 3), (10, 3, 3)) @testing.for_float_dtypes(no_float16=True) @testing.numpy_cupy_allclose(atol=1e-3) def test_warn_rcond(self, xp, dtype): a = testing.shaped_random((3, 3), xp, dtype) b = testing.shaped_random((3,), xp, dtype) with testing.assert_warns(FutureWarning): return xp.linalg.lstsq(a, b) class TestTensorInv(unittest.TestCase): @testing.for_dtypes('ifdFD') @_condition.retry(10) def check_x(self, a_shape, ind, dtype): a_cpu = numpy.random.randint(0, 10, size=a_shape).astype(dtype) a_gpu = cupy.asarray(a_cpu) a_gpu_copy = a_gpu.copy() result_cpu = numpy.linalg.tensorinv(a_cpu, ind=ind) result_gpu = cupy.linalg.tensorinv(a_gpu, ind=ind) assert result_cpu.dtype == result_gpu.dtype cupy.testing.assert_allclose(result_cpu, result_gpu, atol=1e-3) cupy.testing.assert_array_equal(a_gpu_copy, a_gpu) def check_shape(self, a_shape, ind): a = cupy.random.rand(*a_shape) with self.assertRaises(numpy.linalg.LinAlgError): cupy.linalg.tensorinv(a, ind=ind) def check_ind(self, a_shape, ind): a = cupy.random.rand(*a_shape) with self.assertRaises(ValueError): cupy.linalg.tensorinv(a, ind=ind) def test_tensorinv(self): self.check_x((12, 3, 4), ind=1) self.check_x((3, 8, 24), ind=2) self.check_x((18, 3, 3, 2), ind=1) self.check_x((1, 4, 2, 2), ind=2) self.check_x((2, 3, 5, 30), ind=3) self.check_x((24, 2, 2, 3, 2), ind=1) self.check_x((3, 4, 2, 3, 2), ind=2) self.check_x((1, 2, 3, 2, 3), ind=3) self.check_x((3, 2, 1, 2, 12), ind=4) def test_invalid_shape(self): self.check_shape((2, 3, 4), ind=1) self.check_shape((1, 2, 3, 4), ind=3) def test_invalid_index(self): self.check_ind((12, 3, 4), ind=-1) self.check_ind((18, 3, 3, 2), ind=0)