Skip to content

Commit

Permalink
Merge #630
Browse files Browse the repository at this point in the history
630: Fix unit tests errors with numpy >=1.13 (Fix 577) r=cpascual a=cpascual

This PR fixes the 4 test errors/failiures reported in #577 and adds new travis builds with numpy 1.14

3 of the 4 problems are relatively trivial to solve.

The remaining one (`test_isreal`) is not so trivial because it  involves allowing to compare a *non-dimensionless* quantity against zero. The reason is that `numpy.isreal(x)` essentially does `x.imag !=0` if the `imag` method is implemented in x... and since Quantity reimplements .imag to return units (see #171), the check always returns False if x is a non-dimensionless quantity.

As mentioned, this can be solved by allowing comparisons against zero. Actually, this has already been requested in #194, but it was not implemented.

In this PR, I implemented the comparison-to-zero feature and provide several new unit tests for it. AFAIK this implementation takes into account the concerns expressed by @hgrecco in #194. But if the feature is not wanted, we can just revert the last 3 commits of this PR and then `test_isreal` will left with an expected failure flag.

The rules that I implemented for the comparison-with-zero are:
```    
    a) comparing against (non-quantity) zero is allowed for any quantity
    b) comparing against zero-magnitude quantities of incompatible
    dimensionality raises a Dimensionality error, except for the case
    of equality, for which it always returns False
    
    Notes:
    1.- Numpy arrays of zeros are also supported and the comparison rules
    apply elementwise
    2.- In the case of non-multiplicative units, the rules above
    apply after converting to base units if the autoconvert offset flag
    is set. Otherwise they raise an OffsetUnitCalculusError.
```

Fixes #577


Co-authored-by: Carlos Pascual <[email protected]>
  • Loading branch information
bors[bot] and Carlos Pascual committed Apr 13, 2018
2 parents 90174a3 + 4f50583 commit fc2c503
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 4 deletions.
7 changes: 6 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
language: python

env:
- UNCERTAINTIES="N" PYTHON="2.7" NUMPY_VERSION=1.11.2
- UNCERTAINTIES="N" PYTHON="3.3" NUMPY_VERSION=1.9.2
- UNCERTAINTIES="N" PYTHON="3.4" NUMPY_VERSION=1.11.2
- UNCERTAINTIES="N" PYTHON="3.5" NUMPY_VERSION=1.11.2
- UNCERTAINTIES="Y" PYTHON="3.5" NUMPY_VERSION=1.11.2
- UNCERTAINTIES="N" PYTHON="3.6" NUMPY_VERSION=1.11.2
- UNCERTAINTIES="N" PYTHON="2.7" NUMPY_VERSION=0
- UNCERTAINTIES="N" PYTHON="3.5" NUMPY_VERSION=0
# Test with the latest numpy version
- UNCERTAINTIES="N" PYTHON="2.7" NUMPY_VERSION=1.14
- UNCERTAINTIES="N" PYTHON="3.4" NUMPY_VERSION=1.14
- UNCERTAINTIES="N" PYTHON="3.5" NUMPY_VERSION=1.14
- UNCERTAINTIES="Y" PYTHON="3.5" NUMPY_VERSION=1.14
- UNCERTAINTIES="N" PYTHON="3.6" NUMPY_VERSION=1.14

before_install:
- sudo apt-get update
Expand Down
31 changes: 29 additions & 2 deletions pint/quantity.py
Original file line number Diff line number Diff line change
Expand Up @@ -1003,8 +1003,8 @@ def __ipow__(self, other):
raise OffsetUnitCalculusError(self._units)

if getattr(other, 'dimensionless', False):
other = other.to_base_units()
self._units **= other.magnitude
other = other.to_base_units().magnitude
self._units **= other
elif not getattr(other, 'dimensionless', True):
raise DimensionalityError(self._units, 'dimensionless')
else:
Expand Down Expand Up @@ -1090,6 +1090,20 @@ def __eq__(self, other):
# We compare to the base class of Quantity because
# each Quantity class is unique.
if not isinstance(other, _Quantity):
if _eq(other, 0, True):
# Handle the special case in which we compare to zero
# (or an array of zeros)
if self._is_multiplicative:
# compare magnitude
return _eq(self._magnitude, other, False)
else:
# compare the magnitude after converting the
# non-multiplicative quantity to base units
if self._REGISTRY.autoconvert_offset_to_baseunit:
return _eq(self.to_base_units()._magnitude, other, False)
else:
raise OffsetUnitCalculusError(self._units)

return (self.dimensionless and
_eq(self._convert_magnitude(UnitsContainer()), other, False))

Expand All @@ -1115,6 +1129,19 @@ def compare(self, other, op):
if not isinstance(other, self.__class__):
if self.dimensionless:
return op(self._convert_magnitude_not_inplace(UnitsContainer()), other)
elif _eq(other, 0, True):
# Handle the special case in which we compare to zero
# (or an array of zeros)
if self._is_multiplicative:
# compare magnitude
return op(self._magnitude, other)
else:
# compare the magnitude after converting the
# non-multiplicative quantity to base units
if self._REGISTRY.autoconvert_offset_to_baseunit:
return op(self.to_base_units()._magnitude, other)
else:
raise OffsetUnitCalculusError(self._units)
else:
raise ValueError('Cannot compare Quantity and {}'.format(type(other)))

Expand Down
104 changes: 104 additions & 0 deletions pint/testsuite/test_quantity.py
Original file line number Diff line number Diff line change
Expand Up @@ -1296,3 +1296,107 @@ def test_iadd_isub(self):
after = 3 * self.ureg.second
with self.assertRaises(DimensionalityError):
after -= d


class TestCompareZero(QuantityTestCase):
"""This test case checks the special treatment that the zero value
receives in the comparisons: pint>=0.9 supports comparisons against zero
even for non-dimensionless quantities
"""

def test_equal_zero(self):
ureg = self.ureg
ureg.autoconvert_offset_to_baseunit = False
self.assertTrue(ureg.Quantity(0, ureg.J) == 0)
self.assertFalse(ureg.Quantity(0, ureg.J) == ureg.Quantity(0, ''))
self.assertFalse(ureg.Quantity(5, ureg.J) == 0)

@helpers.requires_numpy()
def test_equal_zero_NP(self):
ureg = self.ureg
ureg.autoconvert_offset_to_baseunit = False
aeq = np.testing.assert_array_equal
aeq(ureg.Quantity(0, ureg.J) == np.zeros(3),
np.asarray([True, True, True]))
aeq(ureg.Quantity(5, ureg.J) == np.zeros(3),
np.asarray([False, False, False]))
aeq(ureg.Quantity(np.arange(3), ureg.J) == np.zeros(3),
np.asarray([True, False, False]))
self.assertFalse(ureg.Quantity(np.arange(4), ureg.J) == np.zeros(3))

def test_offset_equal_zero(self):
ureg = self.ureg
ureg.autoconvert_offset_to_baseunit = False
q0 = ureg.Quantity(-273.15, 'degC')
q1 = ureg.Quantity(0, 'degC')
q2 = ureg.Quantity(5, 'degC')
self.assertRaises(OffsetUnitCalculusError, q0.__eq__, 0)
self.assertRaises(OffsetUnitCalculusError, q1.__eq__, 0)
self.assertRaises(OffsetUnitCalculusError, q2.__eq__, 0)
self.assertFalse(q0 == ureg.Quantity(0, ''))

def test_offset_autoconvert_equal_zero(self):
ureg = self.ureg
ureg.autoconvert_offset_to_baseunit = True
q0 = ureg.Quantity(-273.15, 'degC')
q1 = ureg.Quantity(0, 'degC')
q2 = ureg.Quantity(5, 'degC')
self.assertTrue(q0 == 0)
self.assertFalse(q1 == 0)
self.assertFalse(q2 == 0)
self.assertFalse(q0 == ureg.Quantity(0, ''))

def test_gt_zero(self):
ureg = self.ureg
ureg.autoconvert_offset_to_baseunit = False
q0 = ureg.Quantity(0, 'J')
q0m = ureg.Quantity(0, 'm')
q0less = ureg.Quantity(0, '')
qpos = ureg.Quantity(5, 'J')
qneg = ureg.Quantity(-5, 'J')
self.assertTrue(qpos > q0)
self.assertTrue(qpos > 0)
self.assertFalse(qneg > 0)
self.assertRaises(DimensionalityError, qpos.__gt__, q0less)
self.assertRaises(DimensionalityError, qpos.__gt__, q0m)

@helpers.requires_numpy()
def test_gt_zero_NP(self):
ureg = self.ureg
ureg.autoconvert_offset_to_baseunit = False
qpos = ureg.Quantity(5, 'J')
qneg = ureg.Quantity(-5, 'J')
aeq = np.testing.assert_array_equal
aeq(qpos > np.zeros(3), np.asarray([True, True, True]))
aeq(qneg > np.zeros(3), np.asarray([False, False, False]))
aeq(ureg.Quantity(np.arange(-1, 2), ureg.J) > np.zeros(3),
np.asarray([False, False, True]))
aeq(ureg.Quantity(np.arange(-1, 2), ureg.J) > np.zeros(3),
np.asarray([False, False, True]))
self.assertRaises(ValueError,
ureg.Quantity(np.arange(-1, 2), ureg.J).__gt__,
np.zeros(4))

def test_offset_gt_zero(self):
ureg = self.ureg
ureg.autoconvert_offset_to_baseunit = False
q0 = ureg.Quantity(-273.15, 'degC')
q1 = ureg.Quantity(0, 'degC')
q2 = ureg.Quantity(5, 'degC')
self.assertRaises(OffsetUnitCalculusError, q0.__gt__, 0)
self.assertRaises(OffsetUnitCalculusError, q1.__gt__, 0)
self.assertRaises(OffsetUnitCalculusError, q2.__gt__, 0)
self.assertRaises(DimensionalityError, q1.__gt__,
ureg.Quantity(0, ''))

def test_offset_autoconvert_gt_zero(self):
ureg = self.ureg
ureg.autoconvert_offset_to_baseunit = True
q0 = ureg.Quantity(-273.15, 'degC')
q1 = ureg.Quantity(0, 'degC')
q2 = ureg.Quantity(5, 'degC')
self.assertFalse(q0 > 0)
self.assertTrue(q1 > 0)
self.assertTrue(q2 > 0)
self.assertRaises(DimensionalityError, q1.__gt__,
ureg.Quantity(0, ''))
2 changes: 1 addition & 1 deletion pint/testsuite/test_umath.py
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@ def test_iscomplex(self):
(self.q1, self.qm, self.qless))

def test_isfinite(self):
self._testn(np.isreal,
self._testn(np.isfinite,
(self.q1, self.qm, self.qless))

def test_isinf(self):
Expand Down

0 comments on commit fc2c503

Please sign in to comment.