diff --git a/pint/quantity.py b/pint/quantity.py index 7d20199..76e8f4f 100644 --- a/pint/quantity.py +++ b/pint/quantity.py @@ -174,13 +174,13 @@ class _Quantity(SharedRegistryObject): def unitless(self): """Return true if the quantity does not have units. """ - return not bool(self.to_base_units()._units) + return not bool(self.to_root_units()._units) @property def dimensionless(self): """Return true if the quantity is dimensionless. """ - tmp = self.to_base_units() + tmp = self.to_root_units() return not bool(tmp.dimensionality) @@ -243,6 +243,26 @@ class _Quantity(SharedRegistryObject): return self.__class__(magnitude, other) + def ito_root_units(self): + """Return Quantity rescaled to base units + """ + + _, other = self._REGISTRY._get_root_units(self._units) + + self._magnitude = self._convert_magnitude(other) + self._units = other + + return None + + def to_root_units(self): + """Return Quantity rescaled to base units + """ + _, other = self._REGISTRY._get_root_units(self._units) + + magnitude = self._convert_magnitude_not_inplace(other) + + return self.__class__(magnitude, other) + def ito_base_units(self): """Return Quantity rescaled to base units """ @@ -263,6 +283,7 @@ class _Quantity(SharedRegistryObject): return self.__class__(magnitude, other) + def to_compact(self, unit=None): """Return Quantity rescaled to compact, human-readable units. @@ -603,14 +624,14 @@ class _Quantity(SharedRegistryObject): if not self._ok_for_muldiv(no_offset_units_self): raise OffsetUnitCalculusError(self._units, other._units) elif no_offset_units_self == 1 and len(self._units) == 1: - self.ito_base_units() + self.ito_root_units() no_offset_units_other = len(other._get_non_multiplicative_units()) if not other._ok_for_muldiv(no_offset_units_other): raise OffsetUnitCalculusError(self._units, other._units) elif no_offset_units_other == 1 and len(other._units) == 1: - other.ito_base_units() + other.ito_root_units() self._magnitude = magnitude_op(self._magnitude, other._magnitude) self._units = units_op(self._units, other._units) @@ -665,14 +686,14 @@ class _Quantity(SharedRegistryObject): if not self._ok_for_muldiv(no_offset_units_self): raise OffsetUnitCalculusError(self._units, other._units) elif no_offset_units_self == 1 and len(self._units) == 1: - new_self = self.to_base_units() + new_self = self.to_root_units() no_offset_units_other = len(other._get_non_multiplicative_units()) if not other._ok_for_muldiv(no_offset_units_other): raise OffsetUnitCalculusError(self._units, other._units) elif no_offset_units_other == 1 and len(other._units) == 1: - other = other.to_base_units() + other = other.to_root_units() magnitude = magnitude_op(new_self._magnitude, other._magnitude) units = units_op(new_self._units, other._units) @@ -718,7 +739,7 @@ class _Quantity(SharedRegistryObject): if not self._ok_for_muldiv(no_offset_units_self): raise OffsetUnitCalculusError(self._units, '') elif no_offset_units_self == 1 and len(self._units) == 1: - self = self.to_base_units() + self = self.to_root_units() return self.__class__(other_magnitude / self._magnitude, 1 / self._units) @@ -732,7 +753,7 @@ class _Quantity(SharedRegistryObject): if not self._ok_for_muldiv(no_offset_units_self): raise OffsetUnitCalculusError(self._units, '') elif no_offset_units_self == 1 and len(self._units) == 1: - self = self.to_base_units() + self = self.to_root_units() return self.__class__(other_magnitude // self._magnitude, 1 / self._units) @@ -803,12 +824,12 @@ class _Quantity(SharedRegistryObject): else: if not self._is_multiplicative: if self._REGISTRY.autoconvert_offset_to_baseunit: - new_self = self.to_base_units() + new_self = self.to_root_units() else: raise OffsetUnitCalculusError(self._units) if getattr(other, 'dimensionless', False): - units = new_self._units ** other.to_base_units().magnitude + units = new_self._units ** other.to_root_units().magnitude elif not getattr(other, 'dimensionless', True): raise DimensionalityError(self._units, 'dimensionless') else: @@ -828,7 +849,7 @@ class _Quantity(SharedRegistryObject): if isinstance(self._magnitude, ndarray): if np.size(self._magnitude) > 1: raise DimensionalityError(self._units, 'dimensionless') - new_self = self.to_base_units() + new_self = self.to_root_units() return other**new_self._magnitude def __abs__(self): @@ -880,8 +901,8 @@ class _Quantity(SharedRegistryObject): if self.dimensionality != other.dimensionality: raise DimensionalityError(self._units, other._units, self.dimensionality, other.dimensionality) - return op(self.to_base_units().magnitude, - other.to_base_units().magnitude) + return op(self.to_root_units().magnitude, + other.to_root_units().magnitude) __lt__ = lambda self, other: self.compare(other, op=operator.lt) __le__ = lambda self, other: self.compare(other, op=operator.le) @@ -1089,9 +1110,9 @@ class _Quantity(SharedRegistryObject): try: if isinstance(value, self.__class__): - factor = self.__class__(value.magnitude, value._units / self._units).to_base_units() + factor = self.__class__(value.magnitude, value._units / self._units).to_root_units() else: - factor = self.__class__(value, self._units ** (-1)).to_base_units() + factor = self.__class__(value, self._units ** (-1)).to_root_units() if isinstance(factor, self.__class__): if not factor.dimensionless: @@ -1170,7 +1191,7 @@ class _Quantity(SharedRegistryObject): if unt == 'radian': mobjs.append(getattr(other, 'magnitude', other)) else: - factor, units = self._REGISTRY._get_base_units(unt) + factor, units = self._REGISTRY._get_root_units(unt) if units and units != UnitsContainer({'radian': 1}): raise DimensionalityError(units, dst_units) mobjs.append(getattr(other, 'magnitude', other) * factor) diff --git a/pint/systems.py b/pint/systems.py index bea5f97..6b55cf8 100644 --- a/pint/systems.py +++ b/pint/systems.py @@ -370,108 +370,48 @@ class System(object): return system -#: These dictionaries will be part of the registry -#: :type: dict[str, Group | System] -_groups_systems = dict() -_root_group = Group('root', _groups_systems) +class GSManager(object): + def __init__(self): + #: :type: dict[str, Group | System] + self._groups_systems = dict() + self._root_group = Group('root', self._groups_systems) -# These methods will be included in the registry, upgrading the existing ones. + def get_group(self, name, create_if_needed=True): + """Return a Group. -def get_group(registry, name, create_if_needed=True): - """Return a Group. + :param name: Name of the group. + :param create_if_needed: Create a group if not Found. If False, raise an Exception. + :return: Group + """ + try: + return self._groups_systems[name] + except KeyError: + if create_if_needed: + if name == 'root': + raise ValueError('The name root is reserved.') + return Group(name, self._groups_systems) + else: + raise KeyError('No group %s found.' % name) - :param registry: - :param name: Name of the group to be - :param create_if_needed: Create a group if not Found. If False, raise an Exception. - :return: Group - """ - if name == 'root': - raise ValueError('The name root is reserved.') + def get_system(self, name, create_if_needed=True): + """Return a Group. - try: - return _groups_systems[name] - except KeyError: - if create_if_needed: - return Group(name, _groups_systems) - else: - raise KeyError('No group %s found.' % name) + :param name: Name of the system + :param create_if_needed: Create a group if not Found. If False, raise an Exception. + :return: System + """ + try: + return self._groups_systems[name] + except KeyError: + if create_if_needed: + return System(name, self._groups_systems) + else: + raise KeyError('No system found named %s.' % name) -def get_system(registry, name, create_if_needed=True): - """Return a Group. + def __getitem__(self, item): + if item in self._groups_systems: + return self._groups_systems[item] - :param registry: - :param name: Name of the group to be - :param create_if_needed: Create a group if not Found. If False, raise an Exception. - :return: System - """ - - try: - return _groups_systems[name] - except KeyError: - if create_if_needed: - return System(name, _groups_systems) - else: - raise KeyError('No system %s found.' % name) - - -def get_compatible_units(registry, input_units, group_or_system=None): - """ - :param registry: - :param input_units: - :param group_or_system: - :type group_or_system: Group | System - :return: - """ - ret = registry.get_compatible_units(input_units) - - if not group_or_system: - return ret - - members = _groups_systems[group_or_system].members - - # This will not be necessary after integration with the registry as it has a strings intermediate - members = frozenset((getattr(registry, member) for member in members)) - - return ret.intersection(members) - - -# Current get_base_units will be renamed to get_root_units -# -# Not sure yet how to deal with the cache. -# Should we cache get_root_units, get_base_units or both? -# - get_base_units will need to be invalidated when the system is changed (How often will this happen?) -# - get_root_units will not need to be invalidated. - -def get_base_units(registry, input_units, check_nonmult=True, system=None): - """ - :param registry: - :param input_units: - :param check_nonmult: - :param system: System - :return: - """ - factor, units = registry.get_base_units(input_units, check_nonmult) - - if not system: - return factor, units - - # This will not be necessary after integration with the registry as it has a UnitsContainer intermediate - units = to_units_container(units, registry) - - destination_units = UnitsContainer() - - bu = _groups_systems[system].base_units - - for unit, value in units.items(): - if unit in bu: - new_unit = bu[unit] - new_unit = to_units_container(new_unit, registry) - destination_units *= new_unit ** value - else: - destination_units *= UnitsContainer({unit: value}) - - base_factor = registry.convert(factor, units, destination_units) - - return base_factor, destination_units + raise KeyError('No group or system found named %s.' % item) diff --git a/pint/testsuite/test_systems.py b/pint/testsuite/test_systems.py index 069cbc7..ecc71ba 100644 --- a/pint/testsuite/test_systems.py +++ b/pint/testsuite/test_systems.py @@ -4,7 +4,7 @@ from __future__ import division, unicode_literals, print_function, absolute_impo from pint import UnitRegistry -from pint.systems import Group, get_compatible_units, get_group, System, get_base_units +from pint.systems import Group, System from pint.testsuite import QuantityTestCase @@ -187,9 +187,9 @@ class TestGroup(QuantityTestCase): def test_get_compatible_units(self): ureg = UnitRegistry() - g = get_group(ureg, 'imperial') + g = ureg.get_group('imperial') g.add_units('inch', 'yard', 'pint') - c = get_compatible_units(ureg, 'meter', 'imperial') + c = ureg.get_compatible_units('meter', 'imperial') self.assertEqual(c, frozenset([ureg.inch, ureg.yard])) @@ -238,10 +238,10 @@ class TestSystem(QuantityTestCase): sysname = 'mysys1' ureg = UnitRegistry() - g = get_group(ureg, 'imperial') + g = ureg.get_group('imperial') g.add_units('inch', 'yard', 'pint') - c = get_compatible_units(ureg, 'meter', 'imperial') + c = ureg.get_compatible_units('meter', 'imperial') self.assertEqual(c, frozenset([ureg.inch, ureg.yard])) lines = ['@system %s using imperial' % sysname, @@ -249,7 +249,7 @@ class TestSystem(QuantityTestCase): ] s = System.from_lines(lines, lambda x: x, g._groups_systems) - c = get_compatible_units(ureg, 'meter', sysname) + c = ureg.get_compatible_units('meter', sysname) self.assertEqual(c, frozenset([ureg.inch, ureg.yard])) def test_get_base_units(self): @@ -257,7 +257,7 @@ class TestSystem(QuantityTestCase): ureg = UnitRegistry() - g = get_group(ureg, 'imperial') + g = ureg.get_group('imperial') g.add_units('inch', 'yard', 'pint') lines = ['@system %s using imperial' % sysname, @@ -267,11 +267,11 @@ class TestSystem(QuantityTestCase): s = System.from_lines(lines, ureg.get_base_units, g._groups_systems) # base_factor, destination_units - c = get_base_units(ureg, 'inch', system=sysname) + c = ureg.get_base_units('inch', system=sysname) self.assertAlmostEqual(c[0], 1) self.assertEqual(c[1], {'inch': 1}) - c = get_base_units(ureg, 'cm', system=sysname) + c = ureg.get_base_units('cm', system=sysname) self.assertAlmostEqual(c[0], 1./2.54) self.assertEqual(c[1], {'inch': 1}) @@ -280,9 +280,9 @@ class TestSystem(QuantityTestCase): ureg = UnitRegistry() - g = get_group(ureg, 'imperial') + g = ureg.get_group('imperial') g.add_units('inch', 'yard', 'pint') - c = get_compatible_units(ureg, 'meter', 'imperial') + c = ureg.get_compatible_units('meter', 'imperial') lines = ['@system %s using imperial' % sysname, 'pint:meter', @@ -291,19 +291,19 @@ class TestSystem(QuantityTestCase): s = System.from_lines(lines, ureg.get_base_units, g._groups_systems) # base_factor, destination_units - c = get_base_units(ureg, 'inch', system=sysname) + c = ureg.get_base_units('inch', system=sysname) self.assertAlmostEqual(c[0], 0.326, places=3) self.assertEqual(c[1], {'pint': 1./3}) - c = get_base_units(ureg, 'cm', system=sysname) + c = ureg.get_base_units('cm', system=sysname) self.assertAlmostEqual(c[0], 0.1283, places=3) self.assertEqual(c[1], {'pint': 1./3}) - c = get_base_units(ureg, 'inch**2', system=sysname) + c = ureg.get_base_units('inch**2', system=sysname) self.assertAlmostEqual(c[0], 0.326**2, places=3) self.assertEqual(c[1], {'pint': 2./3}) - c = get_base_units(ureg, 'cm**2', system=sysname) + c = ureg.get_base_units('cm**2', system=sysname) self.assertAlmostEqual(c[0], 0.1283**2, places=3) self.assertEqual(c[1], {'pint': 2./3}) @@ -312,7 +312,7 @@ class TestSystem(QuantityTestCase): ureg = UnitRegistry() - g = get_group(ureg, 'imperial') + g = ureg.get_group('imperial') g.add_units('inch', 'yard', 'pint') lines = ['@system %s using imperial' % sysname, @@ -322,10 +322,10 @@ class TestSystem(QuantityTestCase): s = System.from_lines(lines, ureg.get_base_units, g._groups_systems) # base_factor, destination_units - c = get_base_units(ureg, 'inch', system=sysname) + c = ureg.get_base_units('inch', system=sysname) self.assertAlmostEqual(c[0], 0.0568, places=3) self.assertEqual(c[1], {'mph': 1, 'second': 1}) - c = get_base_units(ureg, 'kph', system=sysname) + c = ureg.get_base_units('kph', system=sysname) self.assertAlmostEqual(c[0], .6214, places=4) self.assertEqual(c[1], {'mph': 1}) diff --git a/pint/unit.py b/pint/unit.py index 7ca1591..356e693 100644 --- a/pint/unit.py +++ b/pint/unit.py @@ -37,6 +37,7 @@ from .errors import (DimensionalityError, UndefinedUnitError, DefinitionSyntaxError, RedefinitionError) from .pint_eval import build_eval_tree +from . import systems class _Unit(SharedRegistryObject): """Implements a class to describe a unit supporting math operations. @@ -265,6 +266,9 @@ class UnitRegistry(object): #: Map dimension name (string) to its definition (DimensionDefinition). self._dimensions = {} + #: :type: systems.GSManager + self._gsmanager = systems.GSManager() + #: Map unit name (string) to its definition (UnitDefinition). #: Might contain prefixed units. self._units = {} @@ -289,6 +293,9 @@ class UnitRegistry(object): #: Maps dimensionality (UnitsContainer) to Units (str) self._dimensional_equivalents = dict() + #: Maps dimensionality (UnitsContainer) to Dimensionality (UnitsContainer) + self._root_units_cache = dict() + #: Maps dimensionality (UnitsContainer) to Dimensionality (UnitsContainer) self._base_units_cache = dict() @@ -344,6 +351,25 @@ class UnitRegistry(object): self.Unit.default_format = value self.Quantity.default_format = value + def get_group(self, name, create_if_needed=True): + """Return a Group. + + :param name: Name of the group to be + :param create_if_needed: Create a group if not Found. If False, raise an Exception. + :return: Group + """ + return self._gsmanager.get_group(name, create_if_needed) + + def get_system(self, name, create_if_needed=True): + """Return a Group. + + :param registry: + :param name: Name of the group to be + :param create_if_needed: Create a group if not Found. If False, raise an Exception. + :return: System + """ + return self._gsmanager.get_system(name, create_if_needed) + def add_context(self, context): """Add a context object to the registry. @@ -611,10 +637,10 @@ class UnitRegistry(object): try: uc = ParserHelper.from_word(unit_name) - bu = self._get_base_units(uc) + bu = self._get_root_units(uc) di = self._get_dimensionality(uc) - self._base_units_cache[uc] = bu + self._root_units_cache[uc] = bu self._dimensionality_cache[uc] = di if not prefixed: @@ -726,8 +752,8 @@ class UnitRegistry(object): if reg.reference is not None: self._get_dimensionality_recurse(reg.reference, exp2, accumulator) - def get_base_units(self, input_units, check_nonmult=True): - """Convert unit or dict of units to the base units. + def get_root_units(self, input_units, check_nonmult=True): + """Convert unit or dict of units to the root units. If any unit is non multiplicative and check_converter is True, then None is returned as the multiplicative factor. @@ -741,12 +767,12 @@ class UnitRegistry(object): """ input_units = to_units_container(input_units) - f, units = self._get_base_units(input_units, check_nonmult=True) + f, units = self._get_root_units(input_units, check_nonmult) return f, self.Unit(units) - def _get_base_units(self, input_units, check_nonmult=True): - """Convert unit or dict of units to the base units. + def _get_root_units(self, input_units, check_nonmult=True): + """Convert unit or dict of units to the root units. If any unit is non multiplicative and check_converter is True, then None is returned as the multiplicative factor. @@ -762,11 +788,11 @@ class UnitRegistry(object): return 1., UnitsContainer() # The cache is only done for check_nonmult=True - if check_nonmult and input_units in self._base_units_cache: - return self._base_units_cache[input_units] + if check_nonmult and input_units in self._root_units_cache: + return self._root_units_cache[input_units] accumulators = [1., defaultdict(float)] - self._get_base_units_recurse(input_units, 1.0, accumulators) + self._get_root_units_recurse(input_units, 1.0, accumulators) factor = accumulators[0] units = UnitsContainer(dict((k, v) for k, v in accumulators[1].items() @@ -780,7 +806,63 @@ class UnitRegistry(object): return factor, units - def _get_base_units_recurse(self, ref, exp, accumulators): + def get_base_units(self, input_units, check_nonmult=True, system=None): + """Convert unit or dict of units to the base units. + + If any unit is non multiplicative and check_converter is True, + then None is returned as the multiplicative factor. + + :param input_units: units + :type input_units: UnitsContainer or str + :param check_nonmult: if True, None will be returned as the + multiplicative factor if a non-multiplicative + units is found in the final Units. + :return: multiplicative factor, base units + """ + input_units = to_units_container(input_units) + + f, units = self._get_base_units(input_units, check_nonmult, system) + + return f, self.Unit(units) + + def _get_base_units(self, input_units, check_nonmult=True, system=None): + """ + :param registry: + :param input_units: + :param check_nonmult: + :param system: System + :return: + """ + + # The cache is only done for check_nonmult=True + if check_nonmult and input_units in self._base_units_cache: + return self._base_units_cache[input_units] + + factor, units = self.get_root_units(input_units, check_nonmult) + + if not system: + return factor, units + + # This will not be necessary after integration with the registry as it has a UnitsContainer intermediate + units = to_units_container(units, self) + + destination_units = UnitsContainer() + + bu = self._gsmanager.get_system(system, False).base_units + + for unit, value in units.items(): + if unit in bu: + new_unit = bu[unit] + new_unit = to_units_container(new_unit, self) + destination_units *= new_unit ** value + else: + destination_units *= UnitsContainer({unit: value}) + + base_factor = self.convert(factor, units, destination_units) + + return base_factor, destination_units + + def _get_root_units_recurse(self, ref, exp, accumulators): for key in ref: exp2 = exp*ref[key] key = self.get_name(key) @@ -790,19 +872,19 @@ class UnitRegistry(object): else: accumulators[0] *= reg._converter.scale ** exp2 if reg.reference is not None: - self._get_base_units_recurse(reg.reference, exp2, + self._get_root_units_recurse(reg.reference, exp2, accumulators) - def get_compatible_units(self, input_units): + def get_compatible_units(self, input_units, group_or_system=None): """ """ input_units = to_units_container(input_units) - equiv = self._get_compatible_units(input_units) + equiv = self._get_compatible_units(input_units, group_or_system) return frozenset(self.Unit(eq) for eq in equiv) - def _get_compatible_units(self, input_units): + def _get_compatible_units(self, input_units, group_or_system=None): """ """ if not input_units: @@ -819,7 +901,11 @@ class UnitRegistry(object): for node in nodes: ret |= self._dimensional_equivalents[node] - return frozenset(ret) + if group_or_system: + members = self._gsmanager[group_or_system].members + return frozenset(ret.intersection(members)) + + return ret def convert(self, value, src, dst, inplace=False): """Convert value from some source to destination units. @@ -927,7 +1013,7 @@ class UnitRegistry(object): # Here src and dst have only multiplicative units left. Thus we can # convert with a factor. - factor, units = self._get_base_units(src / dst) + factor, units = self._get_root_units(src / dst) # factor is type float and if our magnitude is type Decimal then # must first convert to Decimal before we can '*' the values