# -*- coding: utf-8 -*- from __future__ import division, unicode_literals, print_function, absolute_import from pint.compat import np from pint.testsuite import QuantityTestCase, helpers # Following http://docs.scipy.org/doc/numpy/reference/ufuncs.html if np: pi = np.pi @helpers.requires_numpy() class TestUFuncs(QuantityTestCase): FORCE_NDARRAY = True @property def qless(self): return np.asarray([1., 2., 3., 4.]) * self.ureg.dimensionless @property def qs(self): return 8 * self.ureg.J @property def q1(self): return np.asarray([1., 2., 3., 4.]) * self.ureg.J @property def q2(self): return 2 * self.q1 @property def qm(self): return np.asarray([1., 2., 3., 4.]) * self.ureg.m @property def qi(self): return np.asarray([1 + 1j, 2 + 2j, 3 + 3j, 4 + 4j]) * self.ureg.m def assertEqual(self, first, second, msg=None): np.testing.assert_equal(first, second, msg) def assertRaisesMsg(self, msg, ExcType, func, *args, **kwargs): try: func(*args, **kwargs) self.assertFalse(True, msg='Exception {0} not raised {1}'.format(ExcType, msg)) except ExcType as e: pass except Exception as e: self.assertFalse(True, msg='{0} not raised but {1}\n{2}'.format(ExcType, e, msg)) def _test1(self, func, ok_with, raise_with=(), output_units='same', results=None, rtol=1e-6): """Test function that takes a single argument and returns Quantity. :param func: function callable. :param ok_with: iterables of values that work fine. :param raise_with: iterables of values that raise exceptions. :param output_units: units to be used when building results. 'same': ok_with[n].units (default). is float: ok_with[n].units ** output_units. None: no output units, the result should be an ndarray. Other value will be parsed as unit. :param results: iterable of results. If None, the result will be obtained by applying func to each ok_with value :param rtol: relative tolerance. """ if results is None: results = [None, ] * len(ok_with) for x1, res in zip(ok_with, results): err_msg = 'At {0} with {1}'.format(func.__name__, x1) if output_units == 'same': ou = x1.units elif isinstance(output_units, (int, float)): ou = x1.units ** output_units else: ou = output_units qm = func(x1) if res is None: res = func(x1.magnitude) if ou is not None: res = self.Q_(res, ou) self.assertQuantityAlmostEqual(qm, res, rtol=rtol, msg=err_msg) for x1 in raise_with: self.assertRaisesMsg('At {0} with {1}'.format(func.__name__, x1), ValueError, func, x1) def _testn(self, func, ok_with, raise_with=(), results=None): """Test function that takes a single argument and returns and ndarray (not a Quantity) :param func: function callable. :param ok_with: iterables of values that work fine. :param raise_with: iterables of values that raise exceptions. :param results: iterable of results. If None, the result will be obtained by applying func to each ok_with value """ self._test1(func, ok_with, raise_with, output_units=None, results=results) def _test1_2o(self, func, ok_with, raise_with=(), output_units=('same', 'same'), results=None, rtol=1e-6): """Test functions that takes a single argument and return two Quantities. :param func: function callable. :param ok_with: iterables of values that work fine. :param raise_with: iterables of values that raise exceptions. :param output_units: tuple of units to be used when building the result tuple. 'same': ok_with[n].units (default). is float: ok_with[n].units ** output_units. None: no output units, the result should be an ndarray. Other value will be parsed as unit. :param results: iterable of results. If None, the result will be obtained by applying func to each ok_with value :param rtol: relative tolerance. """ if results is None: results = [None, ] * len(ok_with) for x1, res in zip(ok_with, results): err_msg = 'At {0} with {1}'.format(func.__name__, x1) qms = func(x1) if res is None: res = func(x1.magnitude) for ndx, (qm, re, ou) in enumerate(zip(qms, res, output_units)): if ou == 'same': ou = x1.units elif isinstance(ou, (int, float)): ou = x1.units ** ou if ou is not None: re = self.Q_(re, ou) self.assertQuantityAlmostEqual(qm, re, rtol=rtol, msg=err_msg) for x1 in raise_with: self.assertRaisesMsg('At {0} with {1}'.format(func.__name__, x1), ValueError, func, x1) def _test2(self, func, x1, ok_with, raise_with=(), output_units='same', rtol=1e-6, convert2=True): """Test function that takes two arguments and return a Quantity. :param func: function callable. :param x1: first argument of func. :param ok_with: iterables of values that work fine. :param raise_with: iterables of values that raise exceptions. :param output_units: units to be used when building results. 'same': x1.units (default). 'prod': x1.units * ok_with[n].units 'div': x1.units / ok_with[n].units 'second': x1.units * ok_with[n] None: no output units, the result should be an ndarray. Other value will be parsed as unit. :param rtol: relative tolerance. :param convert2: if the ok_with[n] should be converted to x1.units. """ for x2 in ok_with: err_msg = 'At {0} with {1} and {2}'.format(func.__name__, x1, x2) if output_units == 'same': ou = x1.units elif output_units == 'prod': ou = x1.units * x2.units elif output_units == 'div': ou = x1.units / x2.units elif output_units == 'second': ou = x1.units ** x2 else: ou = output_units qm = func(x1, x2) if convert2 and hasattr(x2, 'magnitude'): m2 = x2.to(getattr(x1, 'units', '')).magnitude else: m2 = getattr(x2, 'magnitude', x2) res = func(x1.magnitude, m2) if ou is not None: res = self.Q_(res, ou) self.assertQuantityAlmostEqual(qm, res, rtol=rtol, msg=err_msg) for x2 in raise_with: self.assertRaisesMsg('At {0} with {1} and {2}'.format(func.__name__, x1, x2), ValueError, func, x1, x2) def _testn2(self, func, x1, ok_with, raise_with=()): """Test function that takes two arguments and return a ndarray. :param func: function callable. :param x1: first argument of func. :param ok_with: iterables of values that work fine. :param raise_with: iterables of values that raise exceptions. """ self._test2(func, x1, ok_with, raise_with, output_units=None) @helpers.requires_numpy() class TestMathUfuncs(TestUFuncs): """Universal functions (ufunc) > Math operations http://docs.scipy.org/doc/numpy/reference/ufuncs.html#math-operations add(x1, x2[, out]) Add arguments element-wise. subtract(x1, x2[, out]) Subtract arguments, element-wise. multiply(x1, x2[, out]) Multiply arguments element-wise. divide(x1, x2[, out]) Divide arguments element-wise. logaddexp(x1, x2[, out]) Logarithm of the sum of exponentiations of the inputs. logaddexp2(x1, x2[, out]) Logarithm of the sum of exponentiations of the inputs in base-2. true_divide(x1, x2[, out]) Returns a true division of the inputs, element-wise. floor_divide(x1, x2[, out]) Return the largest integer smaller or equal to the division of the inputs. negative(x[, out]) Returns an array with the negative of each element of the original array. power(x1, x2[, out]) First array elements raised to powers from second array, element-wise. NOT IMPLEMENTED remainder(x1, x2[, out]) Return element-wise remainder of division. mod(x1, x2[, out]) Return element-wise remainder of division. fmod(x1, x2[, out]) Return the element-wise remainder of division. absolute(x[, out]) Calculate the absolute value element-wise. rint(x[, out]) Round elements of the array to the nearest integer. sign(x[, out]) Returns an element-wise indication of the sign of a number. conj(x[, out]) Return the complex conjugate, element-wise. exp(x[, out]) Calculate the exponential of all elements in the input array. exp2(x[, out]) Calculate 2**p for all p in the input array. log(x[, out]) Natural logarithm, element-wise. log2(x[, out]) Base-2 logarithm of x. log10(x[, out]) Return the base 10 logarithm of the input array, element-wise. expm1(x[, out]) Calculate exp(x) - 1 for all elements in the array. log1p(x[, out]) Return the natural logarithm of one plus the input array, element-wise. sqrt(x[, out]) Return the positive square-root of an array, element-wise. square(x[, out]) Return the element-wise square of the input. reciprocal(x[, out]) Return the reciprocal of the argument, element-wise. ones_like(x[, out]) Returns an array of ones with the same shape and type as a given array. """ def test_add(self): self._test2(np.add, self.q1, (self.q2, self.qs), (self.qm, )) def test_subtract(self): self._test2(np.subtract, self.q1, (self.q2, self.qs), (self.qm, )) def test_multiply(self): self._test2(np.multiply, self.q1, (self.q2, self.qs), (), 'prod') def test_divide(self): self._test2(np.divide, self.q1, (self.q2, self.qs, self.qless), (), 'div', convert2=False) def test_logaddexp(self): self._test2(np.logaddexp, self.qless, (self.qless, ), (self.q1, ), '') def test_logaddexp2(self): self._test2(np.logaddexp2, self.qless, (self.qless, ), (self.q1, ), 'div') def test_true_divide(self): self._test2(np.true_divide, self.q1, (self.q2, self.qs, self.qless), (), 'div', convert2=False) def test_floor_divide(self): self._test2(np.floor_divide, self.q1, (self.q2, self.qs, self.qless), (), 'div', convert2=False) def test_negative(self): self._test1(np.negative, (self.qless, self.q1), ()) def test_remainder(self): self._test2(np.remainder, self.q1, (self.q2, self.qs, self.qless), (), 'same', convert2=False) def test_mod(self): self._test2(np.mod, self.q1, (self.q2, self.qs, self.qless), (), 'same', convert2=False) def test_fmod(self): self._test2(np.fmod, self.q1, (self.q2, self.qs, self.qless), (), 'same', convert2=False) def test_absolute(self): self._test1(np.absolute, (self.q2, self.qs, self.qless, self.qi), (), 'same') def test_rint(self): self._test1(np.rint, (self.q2, self.qs, self.qless, self.qi), (), 'same') def test_conj(self): self._test1(np.conj, (self.q2, self.qs, self.qless, self.qi), (), 'same') def test_exp(self): self._test1(np.exp, (self.qless, ), (self.q1, ), '') def test_exp2(self): self._test1(np.exp2, (self.qless,), (self.q1, ), '') def test_log(self): self._test1(np.log, (self.qless,), (self.q1, ), '') def test_log2(self): self._test1(np.log2, (self.qless,), (self.q1, ), '') def test_log10(self): self._test1(np.log10, (self.qless,), (self.q1, ), '') def test_expm1(self): self._test1(np.expm1, (self.qless,), (self.q1, ), '') def test_sqrt(self): self._test1(np.sqrt, (self.q2, self.qs, self.qless, self.qi), (), 0.5) def test_square(self): self._test1(np.square, (self.q2, self.qs, self.qless, self.qi), (), 2) def test_reciprocal(self): self._test1(np.reciprocal, (self.q2, self.qs, self.qless, self.qi), (), -1) @helpers.requires_numpy() class TestTrigUfuncs(TestUFuncs): """Universal functions (ufunc) > Trigonometric functions http://docs.scipy.org/doc/numpy/reference/ufuncs.html#trigonometric-functions sin(x[, out]) Trigonometric sine, element-wise. cos(x[, out]) Cosine elementwise. tan(x[, out]) Compute tangent element-wise. arcsin(x[, out]) Inverse sine, element-wise. arccos(x[, out]) Trigonometric inverse cosine, element-wise. arctan(x[, out]) Trigonometric inverse tangent, element-wise. arctan2(x1, x2[, out]) Element-wise arc tangent of x1/x2 choosing the quadrant correctly. hypot(x1, x2[, out]) Given the “legs” of a right triangle, return its hypotenuse. sinh(x[, out]) Hyperbolic sine, element-wise. cosh(x[, out]) Hyperbolic cosine, element-wise. tanh(x[, out]) Compute hyperbolic tangent element-wise. arcsinh(x[, out]) Inverse hyperbolic sine elementwise. arccosh(x[, out]) Inverse hyperbolic cosine, elementwise. arctanh(x[, out]) Inverse hyperbolic tangent elementwise. deg2rad(x[, out]) Convert angles from degrees to radians. rad2deg(x[, out]) Convert angles from radians to degrees. """ def test_sin(self): self._test1(np.sin, (np.arange(0, pi/2, pi/4) * self.ureg.dimensionless, np.arange(0, pi/2, pi/4) * self.ureg.radian, np.arange(0, pi/2, pi/4) * self.ureg.mm / self.ureg.m ), (self.ureg.m, ), '', results=(None, None, np.sin(np.arange(0, pi/2, pi/4)*0.001))) self._test1(np.sin, (np.rad2deg(np.arange(0, pi/2, pi/4)) * self.ureg.degrees, ), results=(np.sin(np.arange(0, pi/2, pi/4)), )) def test_cos(self): self._test1(np.cos, (np.arange(0, pi/2, pi/4) * self.ureg.dimensionless, np.arange(0, pi/2, pi/4) * self.ureg.radian, np.arange(0, pi/2, pi/4) * self.ureg.mm / self.ureg.m, ), (self.ureg.m, ), '', results=(None, None, np.cos(np.arange(0, pi/2, pi/4)*0.001), ) ) self._test1(np.cos, (np.rad2deg(np.arange(0, pi/2, pi/4)) * self.ureg.degrees, ), results=(np.cos(np.arange(0, pi/2, pi/4)), ) ) def test_tan(self): self._test1(np.tan, (np.arange(0, pi/2, pi/4) * self.ureg.dimensionless, np.arange(0, pi/2, pi/4) * self.ureg.radian, np.arange(0, pi/2, pi/4) * self.ureg.mm / self.ureg.m ), (self.ureg.m, ), '', results=(None, None, np.tan(np.arange(0, pi/2, pi/4)*0.001))) self._test1(np.tan, (np.rad2deg(np.arange(0, pi/2, pi/4)) * self.ureg.degrees, ), results=(np.tan(np.arange(0, pi/2, pi/4)), )) def test_arcsin(self): self._test1(np.arcsin, (np.arange(0, .9, .1) * self.ureg.dimensionless, np.arange(0, .9, .1) * self.ureg.m / self.ureg.m ), (self.ureg.m, ), 'radian') def test_arccos(self): x = np.arange(0, .9, .1) * self.ureg.m self._test1(np.arccos, (np.arange(0, .9, .1) * self.ureg.dimensionless, np.arange(0, .9, .1) * self.ureg.m / self.ureg.m ), (self.ureg.m, ), 'radian') def test_arctan(self): self._test1(np.arctan, (np.arange(0, .9, .1) * self.ureg.dimensionless, np.arange(0, .9, .1) * self.ureg.m / self.ureg.m ), (self.ureg.m, ), 'radian') def test_arctan2(self): m = self.ureg.m j = self.ureg.J km = self.ureg.km self._test2(np.arctan2, np.arange(0, .9, .1) * m, (np.arange(0, .9, .1) * m, np.arange(.9, 0., -.1) * m, np.arange(0, .9, .1) * km, np.arange(.9, 0., -.1) * km, ), raise_with=np.arange(0, .9, .1) * j, output_units='radian') def test_hypot(self): self.assertTrue(np.hypot(3. * self.ureg.m, 4. * self.ureg.m) == 5. * self.ureg.m) self.assertTrue(np.hypot(3. * self.ureg.m, 400. * self.ureg.cm) == 5. * self.ureg.m) self.assertRaises(ValueError, np.hypot, 1. * self.ureg.m, 2. * self.ureg.J) def test_sinh(self): self._test1(np.sinh, (np.arange(0, pi/2, pi/4) * self.ureg.dimensionless, np.arange(0, pi/2, pi/4) * self.ureg.radian, np.arange(0, pi/2, pi/4) * self.ureg.mm / self.ureg.m ), (self.ureg.m, ), '', results=(None, None, np.sinh(np.arange(0, pi/2, pi/4)*0.001))) self._test1(np.sinh, (np.rad2deg(np.arange(0, pi/2, pi/4)) * self.ureg.degrees, ), results=(np.sinh(np.arange(0, pi/2, pi/4)), )) def test_cosh(self): self._test1(np.cosh, (np.arange(0, pi/2, pi/4) * self.ureg.dimensionless, np.arange(0, pi/2, pi/4) * self.ureg.radian, np.arange(0, pi/2, pi/4) * self.ureg.mm / self.ureg.m ), (self.ureg.m, ), '', results=(None, None, np.cosh(np.arange(0, pi/2, pi/4)*0.001))) self._test1(np.cosh, (np.rad2deg(np.arange(0, pi/2, pi/4)) * self.ureg.degrees, ), results=(np.cosh(np.arange(0, pi/2, pi/4)), )) def test_tanh(self): self._test1(np.tanh, (np.arange(0, pi/2, pi/4) * self.ureg.dimensionless, np.arange(0, pi/2, pi/4) * self.ureg.radian, np.arange(0, pi/2, pi/4) * self.ureg.mm / self.ureg.m ), (self.ureg.m, ), '', results=(None, None, np.tanh(np.arange(0, pi/2, pi/4)*0.001))) self._test1(np.tanh, (np.rad2deg(np.arange(0, pi/2, pi/4)) * self.ureg.degrees, ), results=(np.tanh(np.arange(0, pi/2, pi/4)), )) def test_arcsinh(self): self._test1(np.arcsinh, (np.arange(0, .9, .1) * self.ureg.dimensionless, np.arange(0, .9, .1) * self.ureg.m / self.ureg.m ), (self.ureg.m, ), 'radian') def test_arccosh(self): self._test1(np.arccosh, (np.arange(1., 1.9, .1) * self.ureg.dimensionless, np.arange(1., 1.9, .1) * self.ureg.m / self.ureg.m ), (self.ureg.m, ), 'radian') def test_arctanh(self): self._test1(np.arctanh, (np.arange(0, .9, .1) * self.ureg.dimensionless, np.arange(0, .9, .1) * self.ureg.m / self.ureg.m ), (.1 * self.ureg.m, ), 'radian') def test_deg2rad(self): self._test1(np.deg2rad, (np.arange(0, pi/2, pi/4) * self.ureg.degrees, ), (self.ureg.m, ), 'radians') def test_rad2deg(self): self._test1(np.rad2deg, (np.arange(0, pi/2, pi/4) * self.ureg.dimensionless, np.arange(0, pi/2, pi/4) * self.ureg.radian, np.arange(0, pi/2, pi/4) * self.ureg.mm / self.ureg.m, ), (self.ureg.m, ), 'degree', results=(None, None, np.rad2deg(np.arange(0, pi/2, pi/4)*0.001) * self.ureg.degree, )) class TestComparisonUfuncs(TestUFuncs): """Universal functions (ufunc) > Comparison functions http://docs.scipy.org/doc/numpy/reference/ufuncs.html#comparison-functions greater(x1, x2[, out]) Return the truth value of (x1 > x2) element-wise. greater_equal(x1, x2[, out]) Return the truth value of (x1 >= x2) element-wise. less(x1, x2[, out]) Return the truth value of (x1 < x2) element-wise. less_equal(x1, x2[, out]) Return the truth value of (x1 =< x2) element-wise. not_equal(x1, x2[, out]) Return (x1 != x2) element-wise. equal(x1, x2[, out]) Return (x1 == x2) element-wise. """ def test_greater(self): self._testn2(np.greater, self.q1, (self.q2, ), (self.qm, )) def test_greater_equal(self): self._testn2(np.greater_equal, self.q1, (self.q2, ), (self.qm, )) def test_less(self): self._testn2(np.less, self.q1, (self.q2, ), (self.qm, )) def test_less_equal(self): self._testn2(np.less_equal, self.q1, (self.q2, ), (self.qm, )) def test_not_equal(self): self._testn2(np.not_equal, self.q1, (self.q2, ), (self.qm, )) def test_equal(self): self._testn2(np.equal, self.q1, (self.q2, ), (self.qm, )) class TestFloatingUfuncs(TestUFuncs): """Universal functions (ufunc) > Floating functions http://docs.scipy.org/doc/numpy/reference/ufuncs.html#floating-functions isreal(x) Returns a bool array, where True if input element is real. iscomplex(x) Returns a bool array, where True if input element is complex. isfinite(x[, out]) Test element-wise for finite-ness (not infinity or not Not a Number). isinf(x[, out]) Test element-wise for positive or negative infinity. isnan(x[, out]) Test element-wise for Not a Number (NaN), return result as a bool array. signbit(x[, out]) Returns element-wise True where signbit is set (less than zero). copysign(x1, x2[, out]) Change the sign of x1 to that of x2, element-wise. nextafter(x1, x2[, out]) Return the next representable floating-point value after x1 in the direction of x2 element-wise. modf(x[, out1, out2]) Return the fractional and integral parts of an array, element-wise. ldexp(x1, x2[, out]) Compute y = x1 * 2**x2. frexp(x[, out1, out2]) Split the number, x, into a normalized fraction (y1) and exponent (y2) fmod(x1, x2[, out]) Return the element-wise remainder of division. floor(x[, out]) Return the floor of the input, element-wise. ceil(x[, out]) Return the ceiling of the input, element-wise. trunc(x[, out]) Return the truncated value of the input, element-wise. """ def test_isreal(self): self._testn(np.isreal, (self.q1, self.qm, self.qless)) def test_iscomplex(self): self._testn(np.iscomplex, (self.q1, self.qm, self.qless)) def test_isfinite(self): self._testn(np.isreal, (self.q1, self.qm, self.qless)) def test_isinf(self): self._testn(np.isinf, (self.q1, self.qm, self.qless)) def test_isnan(self): self._testn(np.isnan, (self.q1, self.qm, self.qless)) def test_signbit(self): self._testn(np.signbit, (self.q1, self.qm, self.qless)) def test_copysign(self): self._test2(np.copysign, self.q1, (self.q2, self.qs), (self.qm, )) def test_nextafter(self): self._test2(np.nextafter, self.q1, (self.q2, self.qs), (self.qm, )) def test_modf(self): self._test1_2o(np.modf, (self.q2, self.qs), ) def test_ldexp(self): x1, x2 = np.frexp(self.q2) self._test2(np.ldexp, x1, (x2, )) def test_frexp(self): self._test1_2o(np.frexp, (self.q2, self.qs), output_units=('same', None)) def test_fmod(self): # See TestMathUfuncs.test_fmod pass def test_floor(self): self._test1(np.floor, (self.q1, self.qm, self.qless)) def test_ceil(self): self._test1(np.ceil, (self.q1, self.qm, self.qless)) def test_trunc(self): self._test1(np.trunc, (self.q1, self.qm, self.qless))