Merged systems into UnitRegistry

This commit is contained in:
Hernan Grecco 2015-07-21 18:34:34 -03:00
parent 5218647d54
commit f9729fa802
4 changed files with 195 additions and 148 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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})

View File

@ -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