import itertools import warnings import numpy import pytest import cupy from cupy import testing float_types = [numpy.float16, numpy.float32, numpy.float64] complex_types = [numpy.complex64, numpy.complex128] signed_int_types = [numpy.int8, numpy.int16, numpy.int32, numpy.int64] unsigned_int_types = [numpy.uint8, numpy.uint16, numpy.uint32, numpy.uint64] int_types = signed_int_types + unsigned_int_types all_types = [numpy.bool_] + float_types + int_types + complex_types negative_types = ( [numpy.bool_] + float_types + signed_int_types + complex_types) negative_types_wo_fp16 = ( [numpy.bool_] + [numpy.float32, numpy.float64] + [numpy.int16, numpy.int32, numpy.int64] + complex_types) negative_no_complex_types = [numpy.bool_] + float_types + signed_int_types no_complex_types = [numpy.bool_] + float_types + int_types @testing.parameterize(*( testing.product({ 'nargs': [1], 'name': ['reciprocal', 'conj', 'conjugate', 'angle'], }) + testing.product({ 'nargs': [2], 'name': [ 'add', 'multiply', 'divide', 'power', 'subtract', 'true_divide', 'floor_divide', 'float_power', 'fmod', 'remainder'], }) )) class TestArithmeticRaisesWithNumpyInput: def test_raises_with_numpy_input(self): nargs = self.nargs name = self.name # Check TypeError is raised if numpy.ndarray is given as input func = getattr(cupy, name) for input_xp_list in itertools.product(*[[numpy, cupy]] * nargs): if all(xp is cupy for xp in input_xp_list): # We don't test all-cupy-array inputs here continue arys = [xp.array([2, -3]) for xp in input_xp_list] with pytest.raises(TypeError): func(*arys) @testing.parameterize(*( testing.product({ 'arg1': ([testing.shaped_arange((2, 3), numpy, dtype=d) for d in all_types ] + [0, 0.0j, 0j, 2, 2.0, 2j, True, False]), 'name': ['conj', 'conjugate', 'real', 'imag'], }) + testing.product({ 'arg1': ([testing.shaped_arange((2, 3), numpy, dtype=d) for d in all_types ] + [0, 0.0j, 0j, 2, 2.0, 2j, True, False]), 'deg': [True, False], 'name': ['angle'], }) + testing.product({ 'arg1': ([numpy.array([-3, -2, -1, 1, 2, 3], dtype=d) for d in negative_types_wo_fp16 ] + [0, 0.0j, 0j, 2, 2.0, 2j, -2, -2.0, -2j, True, False]), 'deg': [True, False], 'name': ['angle'], }) + testing.product({ 'arg1': ([testing.shaped_arange((2, 3), numpy, dtype=d) + 1 for d in all_types ] + [2, 2.0, 2j, True]), 'name': ['reciprocal'], }) )) class TestArithmeticUnary: @testing.numpy_cupy_allclose(atol=1e-5) def test_unary(self, xp): arg1 = self.arg1 if isinstance(arg1, numpy.ndarray): arg1 = xp.asarray(arg1) if self.name in {'angle'}: y = getattr(xp, self.name)(arg1, self.deg) else: y = getattr(xp, self.name)(arg1) if self.name in ('real', 'imag'): # Some NumPy functions return Python scalars for Python scalar # inputs. # We need to convert them to arrays to compare with CuPy outputs. if xp is numpy and isinstance(arg1, (bool, int, float, complex)): y = xp.asarray(y) # TODO(niboshi): Fix this # numpy.real and numpy.imag return Python int if the input is # Python bool. CuPy should return an array of dtype=int32 or # dtype=int64 (depending on the platform) in such cases, instead # of an array of dtype=bool. if xp is cupy and isinstance(arg1, bool): y = y.astype(int) return y @testing.parameterize(*testing.product({ 'shape': [(3, 2), (), (3, 0, 2)], })) class TestComplex: @testing.for_all_dtypes(no_complex=True) @testing.numpy_cupy_array_equal() def test_real_ndarray_nocomplex(self, xp, dtype): x = testing.shaped_arange(self.shape, xp, dtype=dtype) real = x.real assert real is x # real returns self return real @testing.for_all_dtypes(no_complex=True) @testing.numpy_cupy_array_equal() def test_real_nocomplex(self, xp, dtype): x = testing.shaped_arange(self.shape, xp, dtype=dtype) real = xp.real(x) assert real is x # real returns self return real @testing.for_all_dtypes(no_complex=True) @testing.numpy_cupy_array_equal() def test_imag_ndarray_nocomplex(self, xp, dtype): x = testing.shaped_arange(self.shape, xp, dtype=dtype) imag = x.imag return imag @testing.for_all_dtypes(no_complex=True) @testing.numpy_cupy_array_equal() def test_imag_nocomplex(self, xp, dtype): x = testing.shaped_arange(self.shape, xp, dtype=dtype) imag = xp.imag(x) return imag @testing.for_complex_dtypes() @testing.numpy_cupy_array_equal() def test_real_ndarray_complex(self, xp, dtype): x = testing.shaped_arange(self.shape, xp, dtype=dtype) x_ = x.copy() real = x_.real # real returns a view assert real.base is x_ x_ += 1 + 1j testing.assert_array_equal(real, x.real + 1) return real @testing.for_complex_dtypes() @testing.numpy_cupy_array_equal() def test_real_complex(self, xp, dtype): x = testing.shaped_arange(self.shape, xp, dtype=dtype) x_ = x.copy() real = xp.real(x_) # real returns a view assert real.base is x_ x_ += 1 + 1j testing.assert_array_equal(real, x.real + 1) return real @testing.for_complex_dtypes() @testing.numpy_cupy_array_equal() def test_imag_ndarray_complex(self, xp, dtype): x = testing.shaped_arange(self.shape, xp, dtype=dtype) x_ = x.copy() imag = x_.imag # imag returns a view assert imag.base is x_ x_ += 1 + 1j testing.assert_array_equal(imag, x.imag + 1) return imag @testing.for_complex_dtypes() @testing.numpy_cupy_array_equal() def test_imag_complex(self, xp, dtype): x = testing.shaped_arange(self.shape, xp, dtype=dtype) x_ = x.copy() imag = xp.imag(x_) # imag returns a view assert imag.base is x_ x_ += 1 + 1j testing.assert_array_equal(imag, x.imag + 1) return imag class ArithmeticBinaryBase: @testing.numpy_cupy_allclose(atol=1e-4) def check_binary(self, xp): arg1 = self.arg1 arg2 = self.arg2 np1 = numpy.asarray(arg1) np2 = numpy.asarray(arg2) dtype1 = np1.dtype dtype2 = np2.dtype if self.name == 'power' or self.name == 'float_power': # TODO(niboshi): Fix this: power(0, 1j) # numpy => 1+0j # cupy => 0j if dtype2 in complex_types and (np1 == 0).any(): return xp.array(True) # TODO(niboshi): Fix this: xp.add(0j, xp.array([2.], 'f')).dtype # numpy => complex64 # cupy => complex128 if isinstance(arg1, complex): if dtype2 in (numpy.float16, numpy.float32): return xp.array(True) if isinstance(arg1, numpy.ndarray): arg1 = xp.asarray(arg1) if isinstance(arg2, numpy.ndarray): arg2 = xp.asarray(arg2) # Subtraction between booleans is not allowed. if (self.name == 'subtract' and dtype1 == numpy.bool_ and dtype2 == numpy.bool_): return xp.array(True) func = getattr(xp, self.name) with numpy.errstate(divide='ignore'): with warnings.catch_warnings(): warnings.filterwarnings('ignore') if self.use_dtype: y = func(arg1, arg2, dtype=self.dtype) else: y = func(arg1, arg2) # TODO(niboshi): Fix this. If rhs is a Python complex, # numpy returns complex64 # cupy returns complex128 if (xp is cupy and isinstance(arg2, complex) and self.name != 'float_power'): if dtype1 in (numpy.float16, numpy.float32): y = y.astype(numpy.complex64) # NumPy returns different values (nan/inf) on division by zero # depending on the architecture. # As it is not possible for CuPy to replicate this behavior, we ignore # the difference here. if self.name in ('floor_divide', 'remainder'): if y.dtype in (float_types + complex_types) and (np2 == 0).any(): y = xp.asarray(y) y[y == numpy.inf] = numpy.nan y[y == -numpy.inf] = numpy.nan return y @testing.parameterize(*( testing.product({ # TODO(unno): boolean subtract causes DeprecationWarning in numpy>=1.13 'arg1': [testing.shaped_arange((2, 3), numpy, dtype=d) for d in all_types ] + [0, 0.0, 0j, 2, 2.0, 2j, True, False], 'arg2': [testing.shaped_reverse_arange((2, 3), numpy, dtype=d) for d in all_types ] + [0, 0.0, 0j, 2, 2.0, 2j, True, False], 'name': ['add', 'multiply', 'power', 'subtract', 'float_power'], }) + testing.product({ 'arg1': [numpy.array([-3, -2, -1, 1, 2, 3], dtype=d) for d in negative_types ] + [0, 0.0, 0j, 2, 2.0, 2j, -2, -2.0, -2j, True, False], 'arg2': [numpy.array([-3, -2, -1, 1, 2, 3], dtype=d) for d in negative_types ] + [0, 0.0, 0j, 2, 2.0, 2j, -2, -2.0, -2j, True, False], 'name': ['divide', 'true_divide', 'subtract'], }) )) class TestArithmeticBinary(ArithmeticBinaryBase): def test_binary(self): self.use_dtype = False self.check_binary() @testing.parameterize(*( testing.product({ 'arg1': [numpy.array([3, 2, 1, 1, 2, 3], dtype=d) for d in unsigned_int_types ] + [0, 0.0, 2, 2.0, -2, -2.0, True, False], 'arg2': [numpy.array([3, 2, 1, 1, 2, 3], dtype=d) for d in unsigned_int_types ] + [0, 0.0, 2, 2.0, -2, -2.0, True, False], 'name': ['true_divide'], 'dtype': [numpy.float64], 'use_dtype': [True, False], }) + testing.product({ 'arg1': [numpy.array([-3, -2, -1, 1, 2, 3], dtype=d) for d in signed_int_types ] + [0, 0.0, 2, 2.0, -2, -2.0, True, False], 'arg2': [numpy.array([-3, -2, -1, 1, 2, 3], dtype=d) for d in signed_int_types ] + [0, 0.0, 2, 2.0, -2, -2.0, True, False], 'name': ['true_divide'], 'dtype': [numpy.float64], 'use_dtype': [True, False], }) + testing.product({ 'arg1': [numpy.array([-3, -2, -1, 1, 2, 3], dtype=d) for d in float_types] + [0.0, 2.0, -2.0], 'arg2': [numpy.array([-3, -2, -1, 1, 2, 3], dtype=d) for d in float_types] + [0.0, 2.0, -2.0], 'name': ['power', 'true_divide', 'subtract', 'float_power'], 'dtype': [numpy.float64], 'use_dtype': [True, False], }) + testing.product({ 'arg1': [testing.shaped_arange((2, 3), numpy, dtype=d) for d in no_complex_types ] + [0, 0.0, 2, 2.0, -2, -2.0, True, False], 'arg2': [testing.shaped_reverse_arange((2, 3), numpy, dtype=d) for d in no_complex_types ] + [0, 0.0, 2, 2.0, -2, -2.0, True, False], 'name': ['floor_divide', 'fmod', 'remainder'], 'dtype': [numpy.float64], 'use_dtype': [True, False], }) + testing.product({ 'arg1': [numpy.array([-3, -2, -1, 1, 2, 3], dtype=d) for d in negative_no_complex_types ] + [0, 0.0, 2, 2.0, -2, -2.0, True, False], 'arg2': [numpy.array([-3, -2, -1, 1, 2, 3], dtype=d) for d in negative_no_complex_types ] + [0, 0.0, 2, 2.0, -2, -2.0, True, False], 'name': ['floor_divide', 'fmod', 'remainder'], 'dtype': [numpy.float64], 'use_dtype': [True, False], }) )) class TestArithmeticBinary2(ArithmeticBinaryBase): def test_binary(self): self.check_binary() class UfuncTestBase: @testing.numpy_cupy_allclose(accept_error=TypeError) def check_casting_out(self, in0_type, in1_type, out_type, casting, xp): a = testing.shaped_arange((2, 3), xp, in0_type) b = testing.shaped_arange((2, 3), xp, in1_type) c = xp.zeros((2, 3), out_type) if casting != 'unsafe': # may raise TypeError return xp.add(a, b, out=c, casting=casting) with warnings.catch_warnings(record=True) as ws: warnings.simplefilter('always') ret = xp.add(a, b, out=c, casting=casting) ws = [w.category for w in ws] assert all([w == numpy.ComplexWarning for w in ws]), str(ws) return ret, xp.array(len(ws)) @testing.numpy_cupy_allclose(accept_error=TypeError) def check_casting_dtype(self, in0_type, in1_type, dtype, casting, xp): a = testing.shaped_arange((2, 3), xp, in0_type) b = testing.shaped_arange((2, 3), xp, in1_type) if casting != 'unsafe': # may raise TypeError return xp.add(a, b, dtype=dtype, casting=casting) with warnings.catch_warnings(record=True) as ws: warnings.simplefilter('always') ret = xp.add(a, b, dtype=dtype, casting='unsafe') ws = [w.category for w in ws] assert all([w == numpy.ComplexWarning for w in ws]), str(ws) return ret, xp.array(len(ws)) # delete this, once check_casting_dtype passes @testing.numpy_cupy_allclose() def check_casting_dtype_unsafe_ignore_warnings( self, in0_type, in1_type, dtype, xp): a = testing.shaped_arange((2, 3), xp, in0_type) b = testing.shaped_arange((2, 3), xp, in1_type) with warnings.catch_warnings(): warnings.simplefilter('ignore') return xp.add(a, b, dtype=dtype, casting='unsafe') class TestUfunc(UfuncTestBase): @pytest.mark.parametrize('casting', [ 'no', 'equiv', 'safe', 'same_kind', 'unsafe', ]) @testing.for_all_dtypes_combination( names=['in_type', 'out_type']) def test_casting_out_only(self, in_type, out_type, casting): self.check_casting_out(in_type, in_type, out_type, casting) @pytest.mark.parametrize('casting', [ pytest.param('no', marks=pytest.mark.skip('flaky xfail')), pytest.param('equiv', marks=pytest.mark.skip('flaky xfail')), 'safe', 'same_kind', 'unsafe', ]) @testing.for_all_dtypes_combination( names=['in0_type', 'in1_type', 'out_type'], full=False) def test_casting_in_out(self, in0_type, in1_type, out_type, casting): self.check_casting_out(in0_type, in1_type, out_type, casting) @pytest.mark.xfail() @pytest.mark.parametrize('casting', [ 'no', 'equiv', ]) @pytest.mark.parametrize(('in0_type', 'in1_type', 'out_type'), [ (numpy.int16, numpy.int32, numpy.int32), ]) def test_casting_in_xfail1(self, in0_type, in1_type, out_type, casting): self.check_casting_out(in0_type, in1_type, out_type, casting) @pytest.mark.skip('flaky xfail') @pytest.mark.parametrize('casting', [ 'no', 'equiv', 'safe', 'same_kind', 'unsafe', ]) @testing.for_all_dtypes_combination( names=['in0_type', 'in1_type', 'dtype'], full=False) def test_casting_dtype(self, in0_type, in1_type, dtype, casting): self.check_casting_dtype(in0_type, in1_type, dtype, casting) @pytest.mark.xfail() @pytest.mark.parametrize('casting', [ 'no', 'equiv', ]) @pytest.mark.parametrize(('in0_type', 'in1_type', 'dtype'), [ (numpy.int16, numpy.int32, numpy.int32), ]) def test_casting_dtype_xfail1(self, in0_type, in1_type, dtype, casting): self.check_casting_dtype(in0_type, in1_type, dtype, casting) @pytest.mark.xfail() @pytest.mark.parametrize('casting', [ 'no', 'equiv', 'safe', 'same_kind', ]) @pytest.mark.parametrize(('in0_type', 'in1_type', 'dtype'), [ (numpy.int32, numpy.int32, numpy.bool_), (numpy.float64, numpy.float64, numpy.int32), ]) def test_casting_dtype_xfail2(self, in0_type, in1_type, dtype, casting): self.check_casting_dtype(in0_type, in1_type, dtype, casting) @testing.for_all_dtypes_combination( names=['in0_type', 'in1_type', 'dtype'], full=False) def test_casting_dtype_unsafe_ignore_warnings( self, in0_type, in1_type, dtype): self.check_casting_dtype_unsafe_ignore_warnings( in0_type, in1_type, dtype ) @testing.slow class TestUfuncSlow(UfuncTestBase): @pytest.mark.parametrize('casting', [ pytest.param('no', marks=pytest.mark.xfail()), pytest.param('equiv', marks=pytest.mark.xfail()), 'safe', 'same_kind', 'unsafe', ]) @testing.for_all_dtypes_combination( names=['in0_type', 'in1_type', 'out_type'], full=True) def test_casting_out(self, in0_type, in1_type, out_type, casting): self.check_casting_out(in0_type, in1_type, out_type, casting) @pytest.mark.xfail() @pytest.mark.parametrize('casting', [ 'no', 'equiv', 'safe', 'same_kind', 'unsafe', ]) @testing.for_all_dtypes_combination( names=['in0_type', 'in1_type', 'dtype'], full=True) def test_casting_dtype(self, in0_type, in1_type, dtype, casting): self.check_casting_dtype(in0_type, in1_type, dtype, casting) @testing.for_all_dtypes_combination( names=['in0_type', 'in1_type', 'dtype'], full=True) def test_casting_dtype_unsafe_ignore_warnings( self, in0_type, in1_type, dtype): self.check_casting_dtype_unsafe_ignore_warnings( in0_type, in1_type, dtype ) class TestArithmeticModf: @testing.for_float_dtypes() @testing.numpy_cupy_allclose() def test_modf(self, xp, dtype): a = xp.array([-2.5, -1.5, -0.5, 0, 0.5, 1.5, 2.5], dtype=dtype) b, c = xp.modf(a) d = xp.empty((2, 7), dtype=dtype) d[0] = b d[1] = c return d @testing.parameterize(*testing.product({ 'xp': [numpy, cupy], 'shape': [(3, 2), (), (3, 0, 2)] })) class TestBoolSubtract: def test_bool_subtract(self): xp = self.xp shape = self.shape x = testing.shaped_random(shape, xp, dtype=numpy.bool_) y = testing.shaped_random(shape, xp, dtype=numpy.bool_) with pytest.raises(TypeError): xp.subtract(x, y)