diff --git a/.travis.yml b/.travis.yml index 2fd8ea841..193e89e0f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,8 +9,11 @@ branches: env: # Should pandas tests be removed or replaced wih import checks? #- UNCERTAINTIES="N" PYTHON="3.6" NUMPY_VERSION=1.14 PANDAS=1 + - UNCERTAINTIES="N" PYTHON="2.7" NUMPY_VERSION=1.17 PANDAS=0 - UNCERTAINTIES="N" PYTHON="3.6" NUMPY_VERSION=1.17 PANDAS=0 + - UNCERTAINTIES="N" PYTHON="3.7" NUMPY_VERSION=1.17 PANDAS=0 - UNCERTAINTIES="N" PYTHON="3.6" NUMPY_VERSION=1.16 PANDAS=0 + - UNCERTAINTIES="N" PYTHON="3.6" NUMPY_VERSION=1.11.2 PANDAS=0 # - UNCERTAINTIES="N" PYTHON="3.3" NUMPY_VERSION=1.9.2 PANDAS=0 # - UNCERTAINTIES="N" PYTHON="3.4" NUMPY_VERSION=1.11.2 PANDAS=0 # - UNCERTAINTIES="N" PYTHON="3.5" NUMPY_VERSION=1.11.2 PANDAS=0 @@ -42,11 +45,6 @@ before_install: fi # Useful for debugging any issues with conda - conda info -a - - # The next couple lines fix a crash with multiprocessing on Travis and are not specific to using Miniconda - - sudo rm -rf /dev/shm - - sudo ln -s /run/shm /dev/shm - - export ENV_NAME=travis install: diff --git a/pint/compat/__init__.py b/pint/compat/__init__.py index 7867e4244..30241e8d2 100644 --- a/pint/compat/__init__.py +++ b/pint/compat/__init__.py @@ -70,12 +70,12 @@ def u(x): # TODO: remove this warning after v0.10 class BehaviorChangeWarning(UserWarning): pass -_msg = ('The way pint handles numpy operations has changed. ' -'Unimplemented numpy operations will now fail instead ' -'of making assumptions about units. Some functions, ' -'eg concat, will now return Quanties with units, where ' -'they returned ndarrays previously. See ' -'https://github.com/hgrecco/pint/pull/764 . ' +_msg = ('The way pint handles numpy operations has changed with ' +'the implementation of NEP 18. Unimplemented numpy operations ' +'will now fail instead of making assumptions about units. Some ' +'functions, eg concat, will now return Quanties with units, ' +'where they returned ndarrays previously. See ' +'https://github.com/hgrecco/pint/pull/764. ' 'To hide this warning use the following code to import pint:' """ @@ -83,6 +83,9 @@ class BehaviorChangeWarning(UserWarning): with warnings.catch_warnings(): warnings.simplefilter("ignore") import pint + +To disable the new behavior, see +https://www.numpy.org/neps/nep-0018-array-function-protocol.html#implementation --- """) @@ -107,10 +110,23 @@ def _to_magnitude(value, force_ndarray=False): if force_ndarray: return np.asarray(value) return value - - warnings.warn(_msg, BehaviorChangeWarning) + def _test_array_function_protocol(): + # Test if the __array_function__ protocol is enabled + try: + class FakeArray: + def __array_function__(self, *args, **kwargs): + return + + np.concatenate([FakeArray()]) + return True + except ValueError: + return False + + HAS_NUMPY_ARRAY_FUNCTION = _test_array_function_protocol() + if HAS_NUMPY_ARRAY_FUNCTION: + warnings.warn(_msg, BehaviorChangeWarning) except ImportError: @@ -122,6 +138,7 @@ class ndarray(object): HAS_NUMPY = False NUMPY_VER = '0' NUMERIC_TYPES = (Number, Decimal) + HAS_NUMPY_ARRAY_FUNCTION = False def _to_magnitude(value, force_ndarray=False): if isinstance(value, (dict, bool)) or value is None: diff --git a/pint/quantity.py b/pint/quantity.py index 5f39da949..f0e6298c6 100644 --- a/pint/quantity.py +++ b/pint/quantity.py @@ -67,7 +67,7 @@ def wrapped(self, *args, **kwargs): def check_implemented(f): def wrapped(self, *args, **kwargs): other=args[0] - if other.__class__.__name__ in ["PintArray", "Series"]: + if other.__class__.__name__ in ["PintArray", "Series", "DataArray"]: return NotImplemented # pandas often gets to arrays of quantities [ Q_(1,"m"), Q_(2,"m")] # and expects Quantity * array[Quantity] should return NotImplemented @@ -110,7 +110,6 @@ def convert_to_consistent_units(pre_calc_units=None, *args, **kwargs): """Takes the args for a numpy function and converts any Quantity or Sequence of Quantities into the units of the first Quantiy/Sequence of quantities. Other args are left untouched. """ - print(args,kwargs) def convert_arg(arg): if pre_calc_units is not None: if isinstance(arg,BaseQuantity): @@ -126,7 +125,6 @@ def convert_arg(arg): new_args=tuple(convert_arg(arg) for arg in args) new_kwargs = {key:convert_arg(arg) for key,arg in kwargs.items()} - print( new_args, new_kwargs) return new_args, new_kwargs def implement_func(func_str, pre_calc_units_, post_calc_units_, out_units_): @@ -153,12 +151,10 @@ def implement_func(func_str, pre_calc_units_, post_calc_units_, out_units_): """ func = getattr(np,func_str) - print(func_str) @implements(func) def _(*args, **kwargs): # TODO make work for kwargs - print("_",func_str) args_and_kwargs = list(args)+list(kwargs.values()) (pre_calc_units, post_calc_units, out_units)=(pre_calc_units_, post_calc_units_, out_units_) @@ -176,6 +172,8 @@ def _(*args, **kwargs): return res elif post_calc_units == "as_pre_calc": post_calc_units = pre_calc_units + elif post_calc_units == "sum": + post_calc_units = (1*first_input_units + 1*first_input_units).units elif post_calc_units == "prod": product = 1 for x in args_and_kwargs: @@ -193,7 +191,8 @@ def _(*args, **kwargs): for x in args_and_kwargs[1:]: product /= x post_calc_units = product.units - print(post_calc_units) + elif post_calc_units == "variance": + post_calc_units = ((1*first_input_units + 1*first_input_units)**2).units Q_ = first_input_units._REGISTRY.Quantity post_calc_Q_= Q_(res, post_calc_units) @@ -202,30 +201,72 @@ def _(*args, **kwargs): elif out_units == "infer_from_input": out_units = first_input_units return post_calc_Q_.to(out_units) + @implements(np.power) def _power(*args, **kwargs): - print(args) pass -for func_str in ['linspace', 'concatenate', 'block', 'stack', 'hstack', 'vstack', 'dstack', 'atleast_1d', 'column_stack', 'atleast_2d', 'atleast_3d', 'expand_dims','squeeze', 'swapaxes', 'compress', 'searchsorted' ,'rollaxis', 'broadcast_to', 'moveaxis', 'fix']: + +@implements(np.meshgrid) +def _meshgrid(*xi, **kwargs): + # Simply need to map input units to onto list of outputs + input_units = (x.units for x in xi) + res = np.meshgrid(*(x.m for x in xi), **kwargs) + return [out * unit for out, unit in zip(res, input_units)] + +@implements(np.full_like) +def _full_like(a, fill_value, dtype=None, order='K', subok=True, shape=None): + # Make full_like by multiplying with array from ones_like in a + # non-multiplicative-unit-safe way + if isinstance(fill_value, BaseQuantity): + return fill_value._REGISTRY.Quantity( + np.ones_like(a, dtype=dtype, order=order, subok=subok, shape=shape) * fill_value.m, + fill_value.units) + else: + return (np.ones_like(a, dtype=dtype, order=order, subok=subok, shape=shape) + * fill_value) + +@implements(np.interp) +def _interp(x, xp, fp, left=None, right=None, period=None): + # Need to handle x and y units separately + x_unit = _get_first_input_units([x, xp, period]) + y_unit = _get_first_input_units([fp, left, right]) + x_args, _ = convert_to_consistent_units(x_unit, x, xp, period) + y_args, _ = convert_to_consistent_units(y_unit, fp, left, right) + x, xp, period = x_args + fp, right, left = y_args + Q_ = y_unit._REGISTRY.Quantity + return Q_(np.interp(x, xp, fp, left=left, right=right, period=period), y_unit) + +for func_str in ['linspace', 'concatenate', 'block', 'stack', 'hstack', 'vstack', 'dstack', 'atleast_1d', 'column_stack', 'atleast_2d', 'atleast_3d', 'expand_dims','squeeze', 'swapaxes', 'compress', 'rollaxis', 'broadcast_to', 'moveaxis', 'fix', 'amax', 'amin', 'nanmax', 'nanmin', 'around', 'diagonal', 'mean', 'ptp', 'ravel', 'round_', 'sort', 'median', 'nanmedian', 'transpose', 'flip', 'copy', 'trim_zeros', 'append', 'clip', 'nan_to_num']: implement_func(func_str, 'consistent_infer', 'as_pre_calc', 'as_post_calc') - + +for func_str in ['isclose', 'searchsorted']: + implement_func(func_str, 'consistent_infer', None, None) for func_str in ['unwrap']: implement_func(func_str, 'rad', 'rad', 'infer_from_input') - -for func_str in ['size', 'isreal', 'iscomplex']: +for func_str in ['cumprod', 'cumproduct', 'nancumprod']: + implement_func(func_str, 'dimensionless', 'dimensionless', 'infer_from_input') + +for func_str in ['size', 'isreal', 'iscomplex', 'shape', 'ones_like', 'zeros_like', 'empty_like', 'argsort', 'argmin', 'argmax', 'alen', 'ndim', 'nanargmax', 'nanargmin', 'count_nonzero', 'nonzero', 'result_type']: implement_func(func_str, None, None, None) + +for func_str in ['average', 'mean', 'std', 'nanmean', 'nanstd', 'sum', 'nansum', 'cumsum', 'nancumsum']: + implement_func(func_str, None, 'sum', None) -for func_str in ['cross', 'trapz']: +for func_str in ['cross', 'trapz', 'dot']: implement_func(func_str, None, 'prod', None) - + for func_str in ['diff', 'ediff1d',]: implement_func(func_str, None, 'delta', None) - + for func_str in ['gradient', ]: implement_func(func_str, None, 'delta,div', None) - + +for func_str in ['var', 'nanvar']: + implement_func(func_str, None, 'variance', None) + @contextlib.contextmanager def printoptions(*args, **kwargs): @@ -252,7 +293,6 @@ class BaseQuantity(PrettyIPython, SharedRegistryObject): :type units: UnitsContainer, str or Quantity. """ def __array_function__(self, func, types, args, kwargs): - print("__array_function__", func) if func not in HANDLED_FUNCTIONS: return NotImplemented if not all(issubclass(t, BaseQuantity) for t in types): @@ -1380,7 +1420,7 @@ def __ne__(self, other): @check_implemented def compare(self, other, op): - if not isinstance(other, self.__class__): + if not isinstance(other, BaseQuantity): if self.dimensionless: return op(self._convert_magnitude_not_inplace(UnitsContainer()), other) elif _eq(other, 0, True): @@ -1500,10 +1540,10 @@ def shape(self, value): self._magnitude.shape = value def searchsorted(self, v, side='left', sorter=None): - if isinstance(v, self.__class__): + if isinstance(v, BaseQuantity): v = v.to(self).magnitude elif self.dimensionless: - v = self.__class__(v, '').to(self) + v = Quantity(v, '').to(self) else: raise DimensionalityError('dimensionless', self._units) return self.magnitude.searchsorted(v, side) diff --git a/pint/testsuite/helpers.py b/pint/testsuite/helpers.py index a21bdbd07..a3479b04f 100644 --- a/pint/testsuite/helpers.py +++ b/pint/testsuite/helpers.py @@ -8,7 +8,20 @@ import re import unittest -from pint.compat import HAS_NUMPY, HAS_PROPER_BABEL, HAS_UNCERTAINTIES, NUMPY_VER, PYTHON3 +from pint.compat import HAS_NUMPY, HAS_PROPER_BABEL, HAS_UNCERTAINTIES, NUMPY_VER, PYTHON3, HAS_NUMPY_ARRAY_FUNCTION + + +def requires_array_function_protocol(): + if not HAS_NUMPY: + return unittest.skip('Requires NumPy') + return unittest.skipUnless(HAS_NUMPY_ARRAY_FUNCTION, 'Requires __array_function__ protocol to be enabled') + + +def requires_not_array_function_protocol(): + if not HAS_NUMPY: + return unittest.skip('Requires NumPy') + return unittest.skipIf(HAS_NUMPY_ARRAY_FUNCTION, 'Requires __array_function__ protocol to be unavailable or disabled') + def requires_numpy18(): if not HAS_NUMPY: diff --git a/pint/testsuite/test_numpy.py b/pint/testsuite/test_numpy.py index 6764c6391..393480f2c 100644 --- a/pint/testsuite/test_numpy.py +++ b/pint/testsuite/test_numpy.py @@ -6,7 +6,7 @@ import operator as op import unittest -from pint import DimensionalityError, set_application_registry +from pint import DimensionalityError, OffsetUnitCalculusError, set_application_registry from pint.compat import np from pint.testsuite import QuantityTestCase, helpers from pint.testsuite.test_umath import TestUFuncs @@ -27,22 +27,35 @@ def setUpClass(cls): def q(self): return [[1,2],[3,4]] * self.ureg.m @property + def q_nan(self): + return [[1,2],[3,np.nan]] * self.ureg.m + @property def q_temperature(self): - return self.Q_([[1,2],[3,4]] , self.ureg.degC) + return self.Q_([[1,2],[3,4]], self.ureg.degC) class TestNumpyArrayCreation(TestNumpyMethods): # https://docs.scipy.org/doc/numpy/reference/routines.array-creation.html - # Ones and zeros - @unittest.expectedFailure + @helpers.requires_array_function_protocol() def test_ones_like(self): - """Needs implementing - """ - self._test1(np.ones_like, - (self.q2, self.qs, self.qless, self.qi), - (), - 2) + np.testing.assert_equal(np.ones_like(self.q), np.array([[1, 1], [1, 1]])) + + @helpers.requires_array_function_protocol() + def test_zeros_like(self): + np.testing.assert_equal(np.zeros_like(self.q), np.array([[0, 0], [0, 0]])) + + @helpers.requires_array_function_protocol() + def test_empty_like(self): + ret = np.empty_like(self.q) + self.assertEqual(ret.shape, (2, 2)) + self.assertTrue(isinstance(ret, np.ndarray)) + + @helpers.requires_array_function_protocol() + def test_full_like(self): + self.assertQuantityEqual(np.full_like(self.q, self.Q_(0, self.ureg.degC)), + self.Q_([[0, 0], [0, 0]], self.ureg.degC)) + np.testing.assert_equal(np.full_like(self.q, 2), np.array([[2, 2], [2, 2]])) class TestNumpyArrayManipulation(TestNumpyMethods): #TODO @@ -65,41 +78,59 @@ def test_reshape(self): def test_ravel(self): self.assertQuantityEqual(self.q.ravel(), [1, 2, 3, 4] * self.ureg.m) - + + @helpers.requires_array_function_protocol() + def test_ravel_numpy_func(self): + self.assertQuantityEqual(np.ravel(self.q), [1, 2, 3, 4] * self.ureg.m) + # Transpose-like operations + @helpers.requires_array_function_protocol() def test_moveaxis(self): self.assertQuantityEqual(np.moveaxis(self.q, 1,0), np.array([[1,2],[3,4]]).T * self.ureg.m) - + @helpers.requires_array_function_protocol() def test_rollaxis(self): self.assertQuantityEqual(np.rollaxis(self.q, 1), np.array([[1,2],[3,4]]).T * self.ureg.m) - - + + @helpers.requires_array_function_protocol() def test_swapaxes(self): self.assertQuantityEqual(np.swapaxes(self.q, 1,0), np.array([[1,2],[3,4]]).T * self.ureg.m) - def test_transpose(self): self.assertQuantityEqual(self.q.transpose(), [[1, 3], [2, 4]] * self.ureg.m) + + @helpers.requires_array_function_protocol() + def test_transpose_numpy_func(self): + self.assertQuantityEqual(np.transpose(self.q), [[1, 3], [2, 4]] * self.ureg.m) + + @helpers.requires_array_function_protocol() + def test_flip_numpy_func(self): + self.assertQuantityEqual(np.flip(self.q, axis=0), [[3, 4], [1, 2]] * self.ureg.m) # Changing number of dimensions + @helpers.requires_array_function_protocol() def test_atleast_1d(self): self.assertQuantityEqual(np.atleast_1d(self.q), self.q) - + + @helpers.requires_array_function_protocol() def test_atleast_2d(self): self.assertQuantityEqual(np.atleast_2d(self.q), self.q) - + + @helpers.requires_array_function_protocol() def test_atleast_3d(self): self.assertQuantityEqual(np.atleast_3d(self.q), np.array([[[1],[2]],[[3],[4]]])* self.ureg.m) - + + @helpers.requires_array_function_protocol() def test_broadcast_to(self): self.assertQuantityEqual(np.broadcast_to(self.q[:,1], (2,2)), np.array([[2,4],[2,4]]) * self.ureg.m) - + + @helpers.requires_array_function_protocol() def test_expand_dims(self): self.assertQuantityEqual(np.expand_dims(self.q, 0), np.array([[[1, 2],[3, 4]]])* self.ureg.m) - + + @helpers.requires_array_function_protocol() def test_squeeze(self): self.assertQuantityEqual(np.squeeze(self.q), self.q) self.assertQuantityEqual( @@ -109,55 +140,71 @@ def test_squeeze(self): # Changing number of dimensions # Joining arrays + @helpers.requires_array_function_protocol() def test_concatentate(self): self.assertQuantityEqual( np.concatenate([self.q]*2), self.Q_(np.concatenate([self.q.m]*2), self.ureg.m) ) - + + @helpers.requires_array_function_protocol() def test_stack(self): self.assertQuantityEqual( np.stack([self.q]*2), self.Q_(np.stack([self.q.m]*2), self.ureg.m) ) - + + @helpers.requires_array_function_protocol() def test_column_stack(self): self.assertQuantityEqual( np.column_stack([self.q[:,0],self.q[:,1]]), self.q ) - + + @helpers.requires_array_function_protocol() def test_dstack(self): self.assertQuantityEqual( np.dstack([self.q]*2), self.Q_(np.dstack([self.q.m]*2), self.ureg.m) ) - + + @helpers.requires_array_function_protocol() def test_hstack(self): self.assertQuantityEqual( np.hstack([self.q]*2), self.Q_(np.hstack([self.q.m]*2), self.ureg.m) ) + + @helpers.requires_array_function_protocol() def test_vstack(self): self.assertQuantityEqual( np.vstack([self.q]*2), self.Q_(np.vstack([self.q.m]*2), self.ureg.m) ) + + @helpers.requires_array_function_protocol() def test_block(self): self.assertQuantityEqual( np.block([self.q[0,:],self.q[1,:]]), self.Q_([1,2,3,4], self.ureg.m) ) + + @helpers.requires_array_function_protocol() + def test_append(self): + self.assertQuantityEqual(np.append(self.q, [[0, 0]] * self.ureg.m, axis=0), + [[1, 2], [3, 4], [0, 0]] * self.ureg.m) class TestNumpyMathematicalFunctions(TestNumpyMethods): # https://www.numpy.org/devdocs/reference/routines.math.html # Trigonometric functions + @helpers.requires_array_function_protocol() def test_unwrap(self): self.assertQuantityEqual(np.unwrap([0,3*np.pi]*self.ureg.radians), [0,np.pi]) self.assertQuantityEqual(np.unwrap([0,540]*self.ureg.deg), [0,180]*self.ureg.deg) # Rounding - + + @helpers.requires_array_function_protocol() def test_fix(self): self.assertQuantityEqual(np.fix(3.14 * self.ureg.m), 3.0 * self.ureg.m) self.assertQuantityEqual(np.fix(3.0 * self.ureg.m), 3.0 * self.ureg.m) @@ -175,20 +222,42 @@ def test_sum(self): self.assertQuantityEqual(self.q.sum(0), [4, 6]*self.ureg.m) self.assertQuantityEqual(self.q.sum(1), [3, 7]*self.ureg.m) + @helpers.requires_array_function_protocol() + def test_sum_numpy_func(self): + self.assertQuantityEqual(np.sum(self.q, axis=0), [4, 6] * self.ureg.m) + self.assertRaises(OffsetUnitCalculusError, np.sum, self.q_temperature) + + @helpers.requires_array_function_protocol() + def test_nansum_numpy_func(self): + self.assertQuantityEqual(np.nansum(self.q_nan, axis=0), [4, 2] * self.ureg.m) def test_cumprod(self): self.assertRaises(ValueError, self.q.cumprod) self.assertQuantityEqual((self.q / self.ureg.m).cumprod(), [1, 2, 6, 24]) + @helpers.requires_array_function_protocol() + def test_cumprod_numpy_func(self): + self.assertRaises(DimensionalityError, np.cumprod, self.q) + self.assertRaises(DimensionalityError, np.cumproduct, self.q) + self.assertQuantityEqual(np.cumprod(self.q / self.ureg.m), [1, 2, 6, 24]) + self.assertQuantityEqual(np.cumproduct(self.q / self.ureg.m), [1, 2, 6, 24]) + @helpers.requires_array_function_protocol() + def test_nancumprod_numpy_func(self): + self.assertRaises(DimensionalityError, np.nancumprod, self.q_nan) + self.assertQuantityEqual(np.nancumprod(self.q_nan / self.ureg.m), [1, 2, 6, 6]) + + @helpers.requires_array_function_protocol() def test_diff(self): self.assertQuantityEqual(np.diff(self.q, 1), [[1], [1]] * self.ureg.m) self.assertQuantityEqual(np.diff(self.q_temperature, 1), [[1], [1]] * self.ureg.delta_degC) + @helpers.requires_array_function_protocol() def test_ediff1d(self): self.assertQuantityEqual(np.ediff1d(self.q), [1, 1, 1] * self.ureg.m) self.assertQuantityEqual(np.ediff1d(self.q_temperature), [1, 1, 1] * self.ureg.delta_degC) + @helpers.requires_array_function_protocol() def test_gradient(self): l = np.gradient([[1,1],[3,4]] * self.ureg.m, 1 * self.ureg.J) self.assertQuantityEqual(l[0], [[2., 3.], [2., 3.]] * self.ureg.m / self.ureg.J) @@ -199,13 +268,20 @@ def test_gradient(self): self.assertQuantityEqual(l[1], [[0., 0.], [1., 1.]] * self.ureg.delta_degC / self.ureg.J) + @helpers.requires_array_function_protocol() def test_cross(self): a = [[3,-3, 1]] * self.ureg.kPa b = [[4, 9, 2]] * self.ureg.m**2 self.assertQuantityEqual(np.cross(a, b), [[-15, -2, 39]] * self.ureg.kPa * self.ureg.m**2) + @helpers.requires_array_function_protocol() def test_trapz(self): self.assertQuantityEqual(np.trapz([1. ,2., 3., 4.] * self.ureg.J, dx=1*self.ureg.m), 7.5 * self.ureg.J*self.ureg.m) + + @helpers.requires_array_function_protocol() + def test_dot_numpy_func(self): + self.assertQuantityEqual(np.dot(self.q.ravel(), [0, 0, 1, 0] * self.ureg.dimensionless), 3 * self.ureg.m) + # Arithmetic operations def test_power(self): @@ -279,14 +355,28 @@ def test_sort(self): q.sort() self.assertQuantityEqual(q, [1, 2, 3, 4, 5, 6] * self.ureg.m) + @helpers.requires_array_function_protocol() + def test_sort_numpy_func(self): + q = [4, 5, 2, 3, 1, 6] * self.ureg.m + self.assertQuantityEqual(np.sort(q), [1, 2, 3, 4, 5, 6] * self.ureg.m) + def test_argsort(self): q = [1, 4, 5, 6, 2, 9] * self.ureg.MeV np.testing.assert_array_equal(q.argsort(), [0, 4, 1, 2, 3, 5]) + @helpers.requires_array_function_protocol() + def test_argsort_numpy_func(self): + np.testing.assert_array_equal(np.argsort(self.q, axis=0), np.array([[0, 0], [1, 1]])) + def test_diagonal(self): q = [[1, 2, 3], [1, 2, 3], [1, 2, 3]] * self.ureg.m self.assertQuantityEqual(q.diagonal(offset=1), [2, 3] * self.ureg.m) + @helpers.requires_array_function_protocol() + def test_diagonal_numpy_func(self): + q = [[1, 2, 3], [1, 2, 3], [1, 2, 3]] * self.ureg.m + self.assertQuantityEqual(np.diagonal(q, offset=-1), [1, 2] * self.ureg.m) + def test_compress(self): self.assertQuantityEqual(self.q.compress([False, True], axis=0), [[3, 4]] * self.ureg.m) @@ -300,6 +390,7 @@ def test_searchsorted(self): q = self.q.flatten() self.assertRaises(ValueError, q.searchsorted, [1.5, 2.5]) + @helpers.requires_array_function_protocol() def test_searchsorted_numpy_func(self): """Test searchsorted as numpy function.""" q = self.q.flatten() @@ -310,21 +401,87 @@ def test_nonzero(self): q = [1, 0, 5, 6, 0, 9] * self.ureg.m np.testing.assert_array_equal(q.nonzero()[0], [0, 2, 3, 5]) + @helpers.requires_array_function_protocol() + def test_nonzero_numpy_func(self): + q = [1, 0, 5, 6, 0, 9] * self.ureg.m + np.testing.assert_array_equal(np.nonzero(q)[0], [0, 2, 3, 5]) + + @helpers.requires_array_function_protocol() + def test_count_nonzero_numpy_func(self): + q = [1, 0, 5, 6, 0, 9] * self.ureg.m + self.assertEqual(np.count_nonzero(q), 4) + def test_max(self): self.assertEqual(self.q.max(), 4*self.ureg.m) + @helpers.requires_array_function_protocol() + def test_max_numpy_func(self): + self.assertEqual(np.max(self.q), 4 * self.ureg.m) + + @helpers.requires_not_array_function_protocol() + def test_max_numpy_func_old_behavior(self): + self.assertEqual(np.max(self.q), 4) + + @helpers.requires_array_function_protocol() + def test_max_with_axis_arg(self): + self.assertQuantityEqual(np.max(self.q, axis=1), [2, 4] * self.ureg.m) + + @helpers.requires_array_function_protocol() + def test_max_with_initial_arg(self): + self.assertQuantityEqual(np.max(self.q[..., None], axis=2, initial=3 * self.ureg.m), [[3, 3], [3, 4]] * self.ureg.m) + + @helpers.requires_array_function_protocol() + def test_nanmax(self): + self.assertEqual(np.nanmax(self.q_nan), 3 * self.ureg.m) + def test_argmax(self): self.assertEqual(self.q.argmax(), 3) + @helpers.requires_array_function_protocol() + def test_argmax_numpy_func(self): + np.testing.assert_equal(np.argmax(self.q, axis=0), np.array([1, 1])) + + @helpers.requires_array_function_protocol() + def test_nanargmax_numpy_func(self): + np.testing.assert_equal(np.nanargmax(self.q_nan, axis=0), np.array([1, 0])) + def test_min(self): self.assertEqual(self.q.min(), 1 * self.ureg.m) + @helpers.requires_array_function_protocol() + def test_min_numpy_func(self): + self.assertEqual(np.min(self.q), 1 * self.ureg.m) + + @helpers.requires_array_function_protocol() + def test_min_with_axis_arg(self): + self.assertQuantityEqual(np.min(self.q, axis=1), [1, 3] * self.ureg.m) + + @helpers.requires_array_function_protocol() + def test_min_with_initial_arg(self): + self.assertQuantityEqual(np.min(self.q[..., None], axis=2, initial=3 * self.ureg.m), [[1, 2], [3, 3]] * self.ureg.m) + + @helpers.requires_array_function_protocol() + def test_nanmin(self): + self.assertEqual(np.nanmin(self.q_nan), 1 * self.ureg.m) + def test_argmin(self): self.assertEqual(self.q.argmin(), 0) + @helpers.requires_array_function_protocol() + def test_argmin_numpy_func(self): + np.testing.assert_equal(np.argmin(self.q, axis=0), np.array([0, 0])) + + @helpers.requires_array_function_protocol() + def test_nanargmin_numpy_func(self): + np.testing.assert_equal(np.nanargmin(self.q_nan, axis=0), np.array([0, 0])) + def test_ptp(self): self.assertEqual(self.q.ptp(), 3 * self.ureg.m) + @helpers.requires_array_function_protocol() + def test_ptp_numpy_func(self): + self.assertQuantityEqual(np.ptp(self.q, axis=0), [2, 2] * self.ureg.m) + def test_clip(self): self.assertQuantityEqual( self.q.clip(max=2*self.ureg.m), @@ -341,26 +498,81 @@ def test_clip(self): self.assertRaises(ValueError, self.q.clip, self.ureg.J) self.assertRaises(ValueError, self.q.clip, 1) + @helpers.requires_array_function_protocol() + def test_clip_numpy_func(self): + self.assertQuantityEqual(np.clip(self.q, 150 * self.ureg.cm, None), [[1.5, 2], [3, 4]] * self.ureg.m) + def test_round(self): q = [1, 1.33, 5.67, 22] * self.ureg.m self.assertQuantityEqual(q.round(0), [1, 1, 6, 22] * self.ureg.m) self.assertQuantityEqual(q.round(-1), [0, 0, 10, 20] * self.ureg.m) self.assertQuantityEqual(q.round(1), [1, 1.3, 5.7, 22] * self.ureg.m) + @helpers.requires_array_function_protocol() + def test_round_numpy_func(self): + self.assertQuantityEqual(np.around(1.0275 * self.ureg.m, decimals=2), 1.03 * self.ureg.m) + self.assertQuantityEqual(np.round_(1.0275 * self.ureg.m, decimals=2), 1.03 * self.ureg.m) + def test_trace(self): self.assertEqual(self.q.trace(), (1+4) * self.ureg.m) def test_cumsum(self): self.assertQuantityEqual(self.q.cumsum(), [1, 3, 6, 10] * self.ureg.m) + @helpers.requires_array_function_protocol() + def test_cumsum_numpy_func(self): + self.assertQuantityEqual(np.cumsum(self.q, axis=0), [[1, 2], [4, 6]] * self.ureg.m) + + @helpers.requires_array_function_protocol() + def test_nancumsum_numpy_func(self): + self.assertQuantityEqual(np.nancumsum(self.q_nan, axis=0), [[1, 2], [4, 2]] * self.ureg.m) + def test_mean(self): self.assertEqual(self.q.mean(), 2.5 * self.ureg.m) + @helpers.requires_array_function_protocol() + def test_mean_numpy_func(self): + self.assertEqual(np.mean(self.q), 2.5 * self.ureg.m) + self.assertRaises(OffsetUnitCalculusError, np.mean, self.q_temperature) + + @helpers.requires_array_function_protocol() + def test_nanmean_numpy_func(self): + self.assertEqual(np.nanmean(self.q_nan), 2 * self.ureg.m) + + @helpers.requires_array_function_protocol() + def test_average_numpy_func(self): + self.assertQuantityAlmostEqual(np.average(self.q, axis=0, weights=[1, 2]), [2.33333, 3.33333] * self.ureg.m, rtol=1e-5) + + @helpers.requires_array_function_protocol() + def test_median_numpy_func(self): + self.assertEqual(np.median(self.q), 2.5 * self.ureg.m) + + @helpers.requires_array_function_protocol() + def test_nanmedian_numpy_func(self): + self.assertEqual(np.nanmedian(self.q_nan), 2 * self.ureg.m) + def test_var(self): self.assertEqual(self.q.var(), 1.25*self.ureg.m**2) + @helpers.requires_array_function_protocol() + def test_var_numpy_func(self): + self.assertEqual(np.var(self.q), 1.25*self.ureg.m**2) + + @helpers.requires_array_function_protocol() + def test_nanvar_numpy_func(self): + self.assertQuantityAlmostEqual(np.nanvar(self.q_nan), 0.66667*self.ureg.m**2, rtol=1e-5) + def test_std(self): self.assertQuantityAlmostEqual(self.q.std(), 1.11803*self.ureg.m, rtol=1e-5) + + @helpers.requires_array_function_protocol() + def test_std_numpy_func(self): + self.assertQuantityAlmostEqual(np.std(self.q), 1.11803*self.ureg.m, rtol=1e-5) + self.assertRaises(OffsetUnitCalculusError, np.std, self.q_temperature) + + @helpers.requires_array_function_protocol() + def test_nanstd_numpy_func(self): + self.assertQuantityAlmostEqual(np.nanstd(self.q_nan), 0.81650 * self.ureg.m, rtol=1e-5) @helpers.requires_numpy_previous_than('1.10') def test_integer_div(self): @@ -448,6 +660,62 @@ def test_shape(self): u.shape = 4, 3 self.assertEqual(u.magnitude.shape, (4, 3)) + @helpers.requires_array_function_protocol() + def test_shape_numpy_func(self): + self.assertEqual(np.shape(self.q), (2, 2)) + + @helpers.requires_array_function_protocol() + def test_alen_numpy_func(self): + self.assertEqual(np.alen(self.q), 2) + + @helpers.requires_array_function_protocol() + def test_ndim_numpy_func(self): + self.assertEqual(np.ndim(self.q), 2) + + @helpers.requires_array_function_protocol() + def test_copy_numpy_func(self): + q_copy = np.copy(self.q) + self.assertQuantityEqual(self.q, q_copy) + self.assertIsNot(self.q, q_copy) + + @helpers.requires_array_function_protocol() + def test_trim_zeros_numpy_func(self): + q = [0, 4, 3, 0, 2, 2, 0, 0, 0] * self.ureg.m + self.assertQuantityEqual(np.trim_zeros(q), [4, 3, 0, 2, 2] * self.ureg.m) + + @helpers.requires_array_function_protocol() + def test_result_type_numpy_func(self): + self.assertEqual(np.result_type(self.q), np.dtype('int64')) + + @helpers.requires_array_function_protocol() + def test_nan_to_num_numpy_func(self): + self.assertQuantityEqual(np.nan_to_num(self.q_nan, nan=-999 * self.ureg.mm), + [[1, 2], [3, -0.999]] * self.ureg.m) + + @helpers.requires_array_function_protocol() + def test_meshgrid_numpy_func(self): + x = [1, 2] * self.ureg.m + y = [0, 50, 100] * self.ureg.mm + xx, yy = np.meshgrid(x, y) + self.assertQuantityEqual(xx, [[1, 2], [1, 2], [1, 2]] * self.ureg.m) + self.assertQuantityEqual(yy, [[0, 0], [50, 50], [100, 100]] * self.ureg.mm) + + @helpers.requires_array_function_protocol() + def test_isclose_numpy_func(self): + q2 = [[1000.05, 2000], [3000.00007, 4001]] * self.ureg.mm + np.testing.assert_equal(np.isclose(self.q, q2), np.array([[False, True], [True, False]])) + + @helpers.requires_array_function_protocol() + def test_interp_numpy_func(self): + x = [1, 4] * self.ureg.m + xp = np.linspace(0, 3, 5) * self.ureg.m + fp = self.Q_([0, 5, 10, 15, 20], self.ureg.degC) + self.assertQuantityAlmostEqual(np.interp(x, xp, fp), self.Q_([6.66667, 20.], self.ureg.degC), rtol=1e-5) + + def test_comparisons(self): + np.testing.assert_equal(self.q > 2 * self.ureg.m, np.array([[False, False], [True, True]])) + np.testing.assert_equal(self.q < 2 * self.ureg.m, np.array([[True, False], [False, False]])) + @unittest.skip class TestBitTwiddlingUfuncs(TestUFuncs):