summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Linke <dalito@users.noreply.github.com>2014-06-15 03:54:41 +0200
committerDavid Linke <dalito@users.noreply.github.com>2014-06-15 03:54:41 +0200
commit621b01e89f4e7a71ec1b1a24107561832925f940 (patch)
tree59d2781e02ac1102ea025ce75539ca98a6659f72
parentc06153cbad57e8c33154d1a8e975247f5ec0f12d (diff)
downloadpint-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.py75
-rw-r--r--pint/testsuite/test_issues.py3
-rw-r--r--pint/testsuite/test_numpy.py38
-rw-r--r--pint/testsuite/test_quantity.py82
-rw-r--r--pint/unit.py2
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