diff options
author | David Linke <dalito@users.noreply.github.com> | 2014-06-15 03:54:41 +0200 |
---|---|---|
committer | David Linke <dalito@users.noreply.github.com> | 2014-06-15 03:54:41 +0200 |
commit | 621b01e89f4e7a71ec1b1a24107561832925f940 (patch) | |
tree | 59d2781e02ac1102ea025ce75539ca98a6659f72 | |
parent | c06153cbad57e8c33154d1a8e975247f5ec0f12d (diff) | |
download | pint-621b01e89f4e7a71ec1b1a24107561832925f940.tar.gz |
Improved exponentiation-methods of _Quantity and added __rpow__
- better handling of numpy arrays
- correct handling of offset units
- Q_(0, 'degC') raises no longer a ZeroDivisionError
- Q_(5, 'degC')**1 returns Q_(5, 'degC'). It does no longer convert to kelvin.
- Removed corresponding test that relied on the conversion for Q**1.
- Several new tests added.
- This version was used to create a table comparing the new behavior to
pint-0.5.
-rw-r--r-- | pint/quantity.py | 75 | ||||
-rw-r--r-- | pint/testsuite/test_issues.py | 3 | ||||
-rw-r--r-- | pint/testsuite/test_numpy.py | 38 | ||||
-rw-r--r-- | pint/testsuite/test_quantity.py | 82 | ||||
-rw-r--r-- | pint/unit.py | 2 |
5 files changed, 191 insertions, 9 deletions
diff --git a/pint/quantity.py b/pint/quantity.py index 74e4463..8592cd8 100644 --- a/pint/quantity.py +++ b/pint/quantity.py @@ -663,10 +663,36 @@ class _Quantity(object): except TypeError: return NotImplemented else: - if not self._is_multiplicative: - self.ito_base_units() + if not self._ok_for_muldiv: + raise OffsetUnitCalculusError(self.units) + + if isinstance(getattr(other, '_magnitude', other), ndarray): + # arrays are refused as exponent, because they would create + # len(array) quanitites of len(set(array)) different units + if np.size(other) > 1: + raise DimensionalityError(self.units, 'dimensionless') + + new_self = self + if other == 1: + return self + elif other == 0: + self._units = UnitsContainer() + else: + if not self._is_multiplicative: + if self._REGISTRY.autoconvert_offset_to_baseunit: + self.ito_base_units() + else: + raise OffsetUnitCalculusError(self.units) + + if getattr(other, 'dimensionless', False): + other = other.to_base_units() + self._units **= other.magnitude + elif not getattr(other, 'dimensionless', True): + raise DimensionalityError(self.units, 'dimensionless') + else: + self._units **= other + self._magnitude **= _to_magnitude(other, self.force_ndarray) - self._units **= other return self def __pow__(self, other): @@ -675,14 +701,51 @@ class _Quantity(object): except TypeError: return NotImplemented else: + if not self._ok_for_muldiv: + raise OffsetUnitCalculusError(self.units) + + if isinstance(getattr(other, '_magnitude', other), ndarray): + # arrays are refused as exponent, because they would create + # len(array) quantities of len(set(array)) different units + if np.size(other) > 1: + raise DimensionalityError(self.units, 'dimensionless') + new_self = self - if not self._is_multiplicative: - new_self = self.to_base_units() + if other == 1: + return self + elif other == 0: + units = UnitsContainer() + else: + if not self._is_multiplicative: + if self._REGISTRY.autoconvert_offset_to_baseunit: + new_self = self.to_base_units() + else: + raise OffsetUnitCalculusError(self.units) + + if getattr(other, 'dimensionless', False): + units = new_self._units ** other.to_base_units().magnitude + elif not getattr(other, 'dimensionless', True): + raise DimensionalityError(self.units, 'dimensionless') + else: + units = new_self._units ** other magnitude = new_self._magnitude ** _to_magnitude(other, self.force_ndarray) - units = new_self._units ** other return self.__class__(magnitude, units) + def __rpow__(self, other): + try: + other_magnitude = _to_magnitude(other, self.force_ndarray) + except TypeError: + return NotImplemented + else: + if not self.dimensionless: + raise DimensionalityError(self.units, 'dimensionless') + if isinstance(self._magnitude, ndarray): + if np.size(self._magnitude) > 1: + raise DimensionalityError(self.units, 'dimensionless') + new_self = self.to_base_units() + return other**new_self._magnitude + def __abs__(self): return self.__class__(abs(self._magnitude), self._units) diff --git a/pint/testsuite/test_issues.py b/pint/testsuite/test_issues.py index b9b508a..bfcba24 100644 --- a/pint/testsuite/test_issues.py +++ b/pint/testsuite/test_issues.py @@ -15,7 +15,7 @@ from pint.testsuite import QuantityTestCase, helpers class TestIssues(QuantityTestCase): FORCE_NDARRAY = False - + def setup(self): self.ureg.autoconvert_offset_to_baseunit = False @@ -162,7 +162,6 @@ class TestIssues(QuantityTestCase): self.assertEqual(parts(q1 / q3), (k1m / q3m, k1u / q3u)) self.assertEqual(parts(q3 * q1), (q3m * k1m, q3u * k1u)) self.assertEqual(parts(q3 / q1), (q3m / k1m, q3u / k1u)) - self.assertEqual(parts(q1 ** 1), (k1m ** 1, k1u ** 1)) self.assertEqual(parts(q1 ** -1), (k1m ** -1, k1u ** -1)) self.assertEqual(parts(q1 ** 2), (k1m ** 2, k1u ** 2)) self.assertEqual(parts(q1 ** -2), (k1m ** -2, k1u ** -2)) diff --git a/pint/testsuite/test_numpy.py b/pint/testsuite/test_numpy.py index 3b753d4..14b7786 100644 --- a/pint/testsuite/test_numpy.py +++ b/pint/testsuite/test_numpy.py @@ -2,6 +2,10 @@ from __future__ import division, unicode_literals, print_function, absolute_import +import copy +import operator as op + +from pint import DimensionalityError from pint.compat import np, unittest from pint.testsuite import QuantityTestCase, helpers @@ -385,3 +389,37 @@ class TestBitTwiddlingUfuncs(TestUFuncs): (self.qless, 2), (self.q1, self.q2, self.qs, ), 'same') + + +class TestNDArrayQunatityMath(QuantityTestCase): + + @helpers.requires_numpy() + def test_exponentiation_array_exp(self): + arr = np.array(range(3), dtype=np.float) + q = self.Q_(arr, None) + + for op_ in [op.pow, op.ipow]: + q_cp = copy.copy(q) + self.assertRaises(DimensionalityError, op_, 2., q_cp) + arr_cp = copy.copy(arr) + q_cp = copy.copy(q) + # this fails for op.ipow ! (--> next test)) + self.assertRaises(DimensionalityError, op.pow, arr_cp, q_cp) + arr_cp = copy.copy(arr) + q_cp = copy.copy(q) + self.assertRaises(DimensionalityError, op_, q_cp, arr_cp) + q_cp = copy.copy(q) + q2_cp = copy.copy(q) + self.assertRaises(DimensionalityError, op_, q_cp, q2_cp) + + @unittest.expectedFailure + @helpers.requires_numpy() + def test_exponentiation_array_exp_2(self): + arr = np.array(range(3), dtype=np.float) + #q = self.Q_(copy.copy(arr), None) + q = self.Q_(copy.copy(arr), 'meter') + arr_cp = copy.copy(arr) + q_cp = copy.copy(q) + # q_cp is treated as if it is an array. The units are completely ignored + # _Quantity.__ipow__ is never called + self.assertRaises(DimensionalityError, op.ipow, arr_cp, q_cp) diff --git a/pint/testsuite/test_quantity.py b/pint/testsuite/test_quantity.py index 8778d73..72122e1 100644 --- a/pint/testsuite/test_quantity.py +++ b/pint/testsuite/test_quantity.py @@ -911,3 +911,85 @@ class TestOffsetUnitMath(QuantityTestCase, ParameterizedTestCase): expected = self.Q_(*expected_copy[i]) self.assertEqual(op.truediv(in1, in2).units, expected.units) self.assertQuantityAlmostEqual(op.truediv(in1, in2), expected) + + exponentiation = [ # resuls without / with autoconvert + (((10, 'degC'), 1), [(10, 'degC'), (10, 'degC')]), + (((10, 'degC'), 0.5), ['error', (283.15**0.5, 'kelvin**0.5')]), + (((10, 'degC'), 0), [(1., ''), (1., '')]), + (((10, 'degC'), -1), ['error', (1/(10+273.15), 'kelvin**-1')]), + (((10, 'degC'), -2), ['error', (1/(10+273.15)**2., 'kelvin**-2')]), + ((( 0, 'degC'), -2), ['error', (1/(273.15)**2, 'kelvin**-2')]), + (((10, 'degC'), (2, '')), ['error', ((283.15)**2, 'kelvin**2')]), + (((10, 'degC'), (10, 'degK')), ['error', 'error']), + + (((10, 'kelvin'), (2, '')), [(100., 'kelvin**2'), (100., 'kelvin**2')]), + + (( 2, (2, 'kelvin')), ['error', 'error']), + (( 2, (500., 'millikelvin/kelvin')), [2**0.5, 2**0.5]), + (( 2, (0.5, 'kelvin/kelvin')), [2**0.5, 2**0.5]), + (((10, 'degC'), (500., 'millikelvin/kelvin')), + ['error', (283.15**0.5, 'kelvin**0.5')]), + ] + + @ParameterizedTestCase.parameterize( + ("input", "expected_output"), exponentiation) + def test_exponentiation(self, input_tuple, expected): + self.ureg.default_as_delta = False + in1, in2 = input_tuple + if type(in1) is tuple and type(in2) is tuple: + in1, in2 = self.Q_(*in1), self.Q_(*in2) + elif not type(in1) is tuple and type(in2) is tuple: + in2 = self.Q_(*in2) + else: + in1 = self.Q_(*in1) + input_tuple = in1, in2 + expected_copy = expected[:] + for i, mode in enumerate([False, True]): + self.ureg.autoconvert_offset_to_baseunit = mode + if expected_copy[i] == 'error': + self.assertRaises((OffsetUnitCalculusError, + DimensionalityError), op.pow, in1, in2) + else: + if type(expected_copy[i]) is tuple: + expected = self.Q_(*expected_copy[i]) + self.assertEqual(op.pow(in1, in2).units, expected.units) + else: + expected = expected_copy[i] + self.assertQuantityAlmostEqual(op.pow(in1, in2), expected) + + @helpers.requires_numpy() + @ParameterizedTestCase.parameterize( + ("input", "expected_output"), exponentiation) + def test_inplace_exponentiation(self, input_tuple, expected): + self.ureg.default_as_delta = False + in1, in2 = input_tuple + if type(in1) is tuple and type(in2) is tuple: + (q1v, q1u), (q2v, q2u) = in1, in2 + in1 = self.Q_(*(np.array([q1v]*2, dtype=np.float), q1u)) + in2 = self.Q_(q2v, q2u) + elif not type(in1) is tuple and type(in2) is tuple: + in2 = self.Q_(*in2) + else: + in1 = self.Q_(*in1) + + input_tuple = in1, in2 + + expected_copy = expected[:] + for i, mode in enumerate([False, True]): + self.ureg.autoconvert_offset_to_baseunit = mode + in1_cp = copy.copy(in1) + if expected_copy[i] == 'error': + self.assertRaises((OffsetUnitCalculusError, + DimensionalityError), op.ipow, in1_cp, in2) + else: + if type(expected_copy[i]) is tuple: + expected = self.Q_(np.array([expected_copy[i][0]]*2, + dtype=np.float), + expected_copy[i][1]) + self.assertEqual(op.ipow(in1_cp, in2).units, expected.units) + else: + expected = np.array([expected_copy[i]]*2, dtype=np.float) + + + in1_cp = copy.copy(in1) + self.assertQuantityAlmostEqual(op.ipow(in1_cp, in2), expected) diff --git a/pint/unit.py b/pint/unit.py index 0463a00..27f6b5e 100644 --- a/pint/unit.py +++ b/pint/unit.py @@ -111,7 +111,7 @@ class DimensionalityError(ValueError): class OffsetUnitCalculusError(ValueError): """Raised on ambiguous operations with offset units. """ - def __init__(self, units1, units2, extra_msg=''): + def __init__(self, units1, units2='', extra_msg=''): super(ValueError, self).__init__() self.units1 = units1 self.units2 = units2 |