Skip to content

Commit

Permalink
Fix #1190 Make is_compatible_with function with dimensionless types &…
Browse files Browse the repository at this point in the history
… string parsing errors.

is_compatible_with was not consistent with dimensionless types when switching arguments.
String processing was not allowing strings with any magnitude.

- Updated registry.py managing dimensionless types & discarding types not managed by pint.
- Updated unit.py using parse_expression instead of parse_units.
  • Loading branch information
jules-ch committed Oct 28, 2020
1 parent 5e59f37 commit e90f50c
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 7 deletions.
2 changes: 1 addition & 1 deletion CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Pint Changelog
0.17 (unreleased)
-----------------

- Nothing changed yet.
- Fix bugs on `is_compatible_with` with dimensionless types & string parsing errors. (Issue #1190)


0.16.1 (2020-09-22)
Expand Down
7 changes: 5 additions & 2 deletions pint/quantity.py
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,9 @@ def compatible_units(self, *contexts):
return self._REGISTRY.get_compatible_units(self._units)

def is_compatible_with(self, other, *contexts, **ctx_kwargs):
""" check if the other object is compatible
"""Check if other object is compatible.
Compatibility is decided based on the dimensionality.
Parameters
----------
Expand Down Expand Up @@ -597,7 +599,8 @@ def is_compatible_with(self, other, *contexts, **ctx_kwargs):

if isinstance(other, str):
return (
self.dimensionality == self._REGISTRY.parse_units(other).dimensionality
self.dimensionality
== self._REGISTRY.parse_expression(other).dimensionality
)

return self.dimensionless
Expand Down
21 changes: 19 additions & 2 deletions pint/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -898,7 +898,9 @@ def _get_compatible_units(self, input_units, group_or_system):
return self._cache.dimensional_equivalents[src_dim]

def is_compatible_with(self, obj1, obj2, *contexts, **ctx_kwargs):
""" check if the other object is compatible
"""Check if both objects are compatible with each other.
Compatibility is decided based on the dimensionality.
Parameters
----------
Expand All @@ -913,7 +915,19 @@ def is_compatible_with(self, obj1, obj2, *contexts, **ctx_kwargs):
Returns
-------
bool
Raises
------
TypeError
If any objects can't be casted to Quantity to check dimensionality.
UnitUndefinedError
If string parsing does not return any defined unit.
"""
if isinstance(obj1, (dict, bool, type(None))) or isinstance(
obj2, (dict, bool, type(None))
):
raise TypeError("Type can't be casted to Quantity")

if isinstance(obj1, (self.Quantity, self.Unit)):
return obj1.is_compatible_with(obj2, *contexts, **ctx_kwargs)

Expand All @@ -922,7 +936,10 @@ def is_compatible_with(self, obj1, obj2, *contexts, **ctx_kwargs):
obj2, *contexts, **ctx_kwargs
)

return not isinstance(obj2, (self.Quantity, self.Unit))
if isinstance(obj2, (self.Quantity, self.Unit, str)):
return self.is_compatible_with(obj2, obj1, *contexts, **ctx_kwargs)
else:
return self.Quantity(obj1).is_compatible_with(obj2, *contexts, **ctx_kwargs)

def convert(self, value, src, dst, inplace=False):
"""Convert value from some source to destination units.
Expand Down
52 changes: 52 additions & 0 deletions pint/testsuite/test_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import math
import re

import pytest

from pint import (
DefinitionSyntaxError,
DimensionalityError,
Expand All @@ -12,6 +14,7 @@
from pint.compat import np
from pint.registry import LazyRegistry, UnitRegistry
from pint.testsuite import CaseInsensitveQuantityTestCase, QuantityTestCase, helpers
from pint.testsuite.helpers import requires_numpy
from pint.testsuite.parameterized import ParameterizedTestCase
from pint.util import ParserHelper, UnitsContainer

Expand Down Expand Up @@ -724,6 +727,55 @@ def test_case_sensitivity(self):
ureg.parse_units("j", case_sensitive=False), UnitsContainer(joule=1)
)

def test_is_compatible_with(self):
ureg = self.ureg

assert ureg.is_compatible_with(ureg.deg, 10.0)
assert ureg.is_compatible_with(10.0, ureg.deg)
assert ureg.is_compatible_with(ureg.km, ureg.m)

assert ureg.is_compatible_with(999.0, 10.0)
assert ureg.is_compatible_with("80 in", "35000 ft")
assert ureg.is_compatible_with("in", "35000 ft")
assert ureg.is_compatible_with("in", "ft")
assert ureg.is_compatible_with("3500 in", "ft")
assert ureg.is_compatible_with(ureg.m, "35000 ft")
assert ureg.is_compatible_with(ureg.m / ureg.inch, 10.0)

assert ureg.is_compatible_with(ureg.degC, ureg.K)
assert ureg.is_compatible_with(ureg.degC, ureg.degF)
assert ureg.is_compatible_with(ureg.degC, ureg.delta_degC)

assert not ureg.is_compatible_with(10.0, ureg.m)
assert not ureg.is_compatible_with(ureg.m, 10.0)
assert not ureg.is_compatible_with(ureg.m, "1 N")
assert not ureg.is_compatible_with("1 N", ureg.m)

assert not ureg.is_compatible_with(ureg.degC, ureg.m)
assert not ureg.is_compatible_with(ureg.m, ureg.degC)

assert ureg.is_compatible_with(ureg.hertz, ureg.nm, "spectroscopy")
assert not ureg.is_compatible_with(ureg.hertz, ureg.nm)

with pytest.raises(TypeError):
ureg.is_compatible_with({}, {})
ureg.is_compatible_with(None, ureg.deg)
ureg.is_compatible_with(True, ureg.deg)

with pytest.raises(UndefinedUnitError):
ureg.is_compatible_with("undefined_unit", "m")
ureg.is_compatible_with("m", "undefined_unit")
ureg.is_compatible_with(ureg.undefined_unit, ureg.m)

@requires_numpy()
def test_is_compatible_with_numpy(self):
ureg = self.ureg

assert ureg.is_compatible_with([1, 2, 3, 4], 10.0)
assert ureg.is_compatible_with(np.array([1, 2, 3, 4]), ureg.deg)
assert ureg.is_compatible_with(10.0, np.array([1, 2, 3, 4]))
assert not ureg.is_compatible_with(10.0 * ureg.m, np.array([1, 2, 3, 4]))


class TestCaseInsensitiveRegistry(CaseInsensitveQuantityTestCase):
def test_case_sensitivity(self):
Expand Down
7 changes: 5 additions & 2 deletions pint/unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@ def compatible_units(self, *contexts):
return self._REGISTRY.get_compatible_units(self)

def is_compatible_with(self, other, *contexts, **ctx_kwargs):
""" check if the other object is compatible
"""Check if the other object is compatible.
Compatibility is decided based on the dimensionality.
Parameters
----------
Expand Down Expand Up @@ -169,7 +171,8 @@ def is_compatible_with(self, other, *contexts, **ctx_kwargs):

if isinstance(other, str):
return (
self.dimensionality == self._REGISTRY.parse_units(other).dimensionality
self.dimensionality
== self._REGISTRY.parse_expression(other).dimensionality
)

return self.dimensionless
Expand Down

0 comments on commit e90f50c

Please sign in to comment.