diff --git a/pint/quantity.py b/pint/quantity.py index d9286ba..eb10207 100644 --- a/pint/quantity.py +++ b/pint/quantity.py @@ -44,6 +44,25 @@ class _Exception(Exception): # pragma: no cover self.internal = internal +def reduce_dimensions(f): + def wrapped(self, *args, **kwargs): + result = f(self, *args, **kwargs) + if result._REGISTRY.auto_reduce_dimensions: + return result.to_root_units() + else: + return result + return wrapped + + +def ireduce_dimensions(f): + def wrapped(self, *args, **kwargs): + result = f(self, *args, **kwargs) + if result._REGISTRY.auto_reduce_dimensions: + result.ito_root_units() + return result + return wrapped + + @fix_str_conversions class _Quantity(SharedRegistryObject): """Implements a class to describe a physical quantity: @@ -657,6 +676,7 @@ class _Quantity(SharedRegistryObject): def __rsub__(self, other): return -self._add_sub(other, operator.sub) + @ireduce_dimensions def _imul_div(self, other, magnitude_op, units_op=None): """Perform multiplication or division operation in-place and return the result. @@ -714,6 +734,7 @@ class _Quantity(SharedRegistryObject): return self + @reduce_dimensions def _mul_div(self, other, magnitude_op, units_op=None): """Perform multiplication or division operation and return the result. diff --git a/pint/registry.py b/pint/registry.py index a5b74c6..b778fd8 100644 --- a/pint/registry.py +++ b/pint/registry.py @@ -96,6 +96,7 @@ class BaseRegistry(meta.with_metaclass(_Meta)): :param on_redefinition: action to take in case a unit is redefined. 'warn', 'raise', 'ignore' :type on_redefinition: str + :param auto_reduce_dimensions: If True, reduce dimensionality on appropriate operations. """ #: Map context prefix to function @@ -111,7 +112,7 @@ class BaseRegistry(meta.with_metaclass(_Meta)): 'parse_unit_name', 'parse_units', 'parse_expression', 'convert'] - def __init__(self, filename='', force_ndarray=False, on_redefinition='warn'): + def __init__(self, filename='', force_ndarray=False, on_redefinition='warn', auto_reduce_dimensions=False): self._register_parsers() @@ -129,6 +130,9 @@ class BaseRegistry(meta.with_metaclass(_Meta)): #: Action to take in case a unit is redefined. 'warn', 'raise', 'ignore' self._on_redefinition = on_redefinition + #: Determines if dimensionality should be reduced on appropriate operations. + self.auto_reduce_dimensions = auto_reduce_dimensions + #: Map between name (string) and value (string) of defaults stored in the definitions file. self._defaults = {} @@ -1435,17 +1439,20 @@ class UnitRegistry(SystemRegistry, ContextRegistry, NonMultiplicativeRegistry): :param on_redefinition: action to take in case a unit is redefined. 'warn', 'raise', 'ignore' :type on_redefinition: str + :param auto_reduce_dimensions: If True, reduce dimensionality on appropriate operations. """ def __init__(self, filename='', force_ndarray=False, default_as_delta=True, autoconvert_offset_to_baseunit=False, - on_redefinition='warn', system=None): + on_redefinition='warn', system=None, + auto_reduce_dimensions=False): super(UnitRegistry, self).__init__(filename=filename, force_ndarray=force_ndarray, on_redefinition=on_redefinition, default_as_delta=default_as_delta, autoconvert_offset_to_baseunit=autoconvert_offset_to_baseunit, - system=system) + system=system, + auto_reduce_dimensions=auto_reduce_dimensions) def pi_theorem(self, quantities): """Builds dimensionless quantities using the Buckingham π theorem diff --git a/pint/testsuite/test_quantity.py b/pint/testsuite/test_quantity.py index 7a184a1..e83abec 100644 --- a/pint/testsuite/test_quantity.py +++ b/pint/testsuite/test_quantity.py @@ -1168,3 +1168,40 @@ class TestOffsetUnitMath(QuantityTestCase, ParameterizedTestCase): in1_cp = copy.copy(in1) self.assertQuantityAlmostEqual(op.ipow(in1_cp, in2), expected) + + +class TestDimensionReduction(QuantityTestCase): + def _calc_mass(self, ureg): + density = 3 * ureg.g / ureg.L + volume = 32 * ureg.milliliter + return density * volume + + def _icalc_mass(self, ureg): + res = ureg.Quantity(3.0, 'gram/liter') + res *= ureg.Quantity(32.0, 'milliliter') + return res + + def test_mul_and_div_reduction(self): + ureg = UnitRegistry(auto_reduce_dimensions=True) + mass = self._calc_mass(ureg) + self.assertEqual(mass.units, ureg.g) + ureg = UnitRegistry(auto_reduce_dimensions=False) + mass = self._calc_mass(ureg) + self.assertEqual(mass.units, ureg.g / ureg.L * ureg.milliliter) + + @helpers.requires_numpy() + def test_imul_and_div_reduction(self): + ureg = UnitRegistry(auto_reduce_dimensions=True, force_ndarray=True) + mass = self._icalc_mass(ureg) + self.assertEqual(mass.units, ureg.g) + ureg = UnitRegistry(auto_reduce_dimensions=False, force_ndarray=True) + mass = self._icalc_mass(ureg) + self.assertEqual(mass.units, ureg.g / ureg.L * ureg.milliliter) + + def test_reduction_to_dimensionless(self): + ureg = UnitRegistry(auto_reduce_dimensions=True) + x = (10 * ureg.feet) / (3 * ureg.inches) + self.assertEqual(x.units, UnitsContainer({})) + ureg = UnitRegistry(auto_reduce_dimensions=False) + x = (10 * ureg.feet) / (3 * ureg.inches) + self.assertEqual(x.units, ureg.feet / ureg.inches)