Commit 1db8640d authored by Peter Eastman's avatar Peter Eastman
Browse files

Further optimizations to units code

parent ee78f1e7
...@@ -261,6 +261,8 @@ class Quantity(object): ...@@ -261,6 +261,8 @@ class Quantity(object):
def __lt__(self, other): def __lt__(self, other):
return self._value < (other.value_in_unit(self.unit)) return self._value < (other.value_in_unit(self.unit))
_reduce_cache = {}
def reduce_unit(self, guide_unit=None): def reduce_unit(self, guide_unit=None):
""" """
Combine similar component units and scale, to form an Combine similar component units and scale, to form an
...@@ -268,41 +270,46 @@ class Quantity(object): ...@@ -268,41 +270,46 @@ class Quantity(object):
Returns underlying value type if unit is dimensionless. Returns underlying value type if unit is dimensionless.
""" """
value_factor = 1.0 key = (self.unit, guide_unit)
canonical_units = {} # dict of dimensionTuple: (Base/ScaledUnit, exponent) if key in Quantity._reduce_cache:
# Bias result toward guide units (unit, value_factor) = Quantity._reduce_cache[key]
if guide_unit != None: else:
for u, exponent in guide_unit.iter_base_or_scaled_units(): value_factor = 1.0
canonical_units = {} # dict of dimensionTuple: (Base/ScaledUnit, exponent)
# Bias result toward guide units
if guide_unit != None:
for u, exponent in guide_unit.iter_base_or_scaled_units():
d = u.get_dimension_tuple()
if d not in canonical_units:
canonical_units[d] = [u, 0]
for u, exponent in self.unit.iter_base_or_scaled_units():
d = u.get_dimension_tuple() d = u.get_dimension_tuple()
# Take first unit found in a dimension as canonical
if d not in canonical_units: if d not in canonical_units:
canonical_units[d] = [u, 0] canonical_units[d] = [u, exponent]
for u, exponent in self.unit.iter_base_or_scaled_units(): else:
d = u.get_dimension_tuple() value_factor *= (u.conversion_factor_to(canonical_units[d][0])**exponent)
# Take first unit found in a dimension as canonical canonical_units[d][1] += exponent
if d not in canonical_units: new_base_units = {}
canonical_units[d] = [u, exponent] for d in canonical_units:
u, exponent = canonical_units[d]
if exponent != 0:
assert u not in new_base_units
new_base_units[u] = exponent
# Create new unit
if len(new_base_units) == 0:
unit = dimensionless
else: else:
value_factor *= (u.conversion_factor_to(canonical_units[d][0])**exponent) unit = Unit(new_base_units)
canonical_units[d][1] += exponent # There might be a factor due to unit conversion, even though unit is dimensionless
new_base_units = {} # e.g. suppose unit is meter/centimeter
for d in canonical_units: if unit.is_dimensionless():
u, exponent = canonical_units[d] unit_factor = unit.conversion_factor_to(dimensionless)
if exponent != 0: if unit_factor != 1.0:
assert u not in new_base_units value_factor *= unit_factor
new_base_units[u] = exponent # print "value_factor = %s" % value_factor
# Create new unit unit = dimensionless
if len(new_base_units) == 0: Quantity._reduce_cache[key] = (unit, value_factor)
unit = dimensionless
else:
unit = Unit(new_base_units)
# There might be a factor due to unit conversion, even though unit is dimensionless
# e.g. suppose unit is meter/centimeter
if unit.is_dimensionless():
unit_factor = unit.conversion_factor_to(dimensionless)
if unit_factor != 1.0:
value_factor *= unit_factor
# print "value_factor = %s" % value_factor
unit = dimensionless
# Create Quantity, then scale (in case value is a container) # Create Quantity, then scale (in case value is a container)
# That's why we don't just scale the value. # That's why we don't just scale the value.
result = Quantity(self._value, unit) result = Quantity(self._value, unit)
......
...@@ -144,19 +144,7 @@ class Unit(object): ...@@ -144,19 +144,7 @@ class Unit(object):
def __eq__(self, other): def __eq__(self, other):
if not is_unit(other): if not is_unit(other):
return False return False
if self._all_base_units == other._all_base_units and self._scaled_units == other._scaled_units: return self.get_name() == other.get_name()
return True
if hash(self) != hash(other):
return False
factor = 1.0
factor *= self.get_conversion_factor_to_base_units()
factor /= other.get_conversion_factor_to_base_units()
for (base1, exp1), (base2, exp2) in zip(self.iter_all_base_units(), other.iter_all_base_units()):
if base1.dimension != base2.dimension or exp1 != exp2:
return False
if base1 != base2:
factor *= base1.conversion_factor_to(base2)**exp1
return factor == 1.0
def __ne__(self, other): def __ne__(self, other):
return not self.__eq__(other) return not self.__eq__(other)
...@@ -181,10 +169,7 @@ class Unit(object): ...@@ -181,10 +169,7 @@ class Unit(object):
return self._hash return self._hash
except AttributeError: except AttributeError:
pass pass
description = "" self._hash = hash(self.get_name())
for unit, power in self.iter_all_base_units():
description += unit.name + str(power)
self._hash = hash(description)
return self._hash return self._hash
# def __mul__(self, other): # def __mul__(self, other):
...@@ -204,15 +189,24 @@ class Unit(object): ...@@ -204,15 +189,24 @@ class Unit(object):
# def __rdiv__(self, other): # def __rdiv__(self, other):
# Because rdiv returns a Quantity, look in quantity.py for definition of Unit.__rdiv__ # Because rdiv returns a Quantity, look in quantity.py for definition of Unit.__rdiv__
_pow_cache = {}
def __pow__(self, exponent): def __pow__(self, exponent):
"""Raise a Unit to a power. """Raise a Unit to a power.
Returns a new Unit with different exponents on the BaseUnits. Returns a new Unit with different exponents on the BaseUnits.
""" """
if self in Unit._pow_cache:
if exponent in Unit._pow_cache[self]:
return Unit._pow_cache[self][exponent]
else:
Unit._pow_cache[self] = {}
result = {} # dictionary of unit: exponent result = {} # dictionary of unit: exponent
for unit, exponent2 in self.iter_base_or_scaled_units(): for unit, exponent2 in self.iter_base_or_scaled_units():
result[unit] = exponent2 * exponent result[unit] = exponent2 * exponent
return Unit(result) new_unit = Unit(result)
Unit._pow_cache[self][exponent] = new_unit
return new_unit
def sqrt(self): def sqrt(self):
""" """
...@@ -419,6 +413,10 @@ class Unit(object): ...@@ -419,6 +413,10 @@ class Unit(object):
Returns a unit name (string) for this Unit, composed of its various Returns a unit name (string) for this Unit, composed of its various
BaseUnit symbols. e.g. 'kilogram meter**2 secon**-1'. BaseUnit symbols. e.g. 'kilogram meter**2 secon**-1'.
""" """
try:
return self._name
except AttributeError:
pass
# emit positive exponents first # emit positive exponents first
pos = "" pos = ""
pos_count = 0 pos_count = 0
...@@ -456,6 +454,7 @@ class Unit(object): ...@@ -456,6 +454,7 @@ class Unit(object):
name = "dimensionless" name = "dimensionless"
else: else:
name = "%s%s" % (pos_string, neg_string) name = "%s%s" % (pos_string, neg_string)
self._name = name
return name return name
......
...@@ -61,6 +61,11 @@ def _unit_class_mul(self, other): ...@@ -61,6 +61,11 @@ def _unit_class_mul(self, other):
of the Quantity is returned. of the Quantity is returned.
""" """
if is_unit(other): if is_unit(other):
if self in Unit._multiplication_cache:
if other in Unit._multiplication_cache[self]:
return Unit._multiplication_cache[self][other]
else:
Unit._multiplication_cache[self] = {}
# print "unit * unit" # print "unit * unit"
result1 = {} # dictionary of dimensionTuple: (BaseOrScaledUnit, exponent) result1 = {} # dictionary of dimensionTuple: (BaseOrScaledUnit, exponent)
for unit, exponent in self.iter_base_or_scaled_units(): for unit, exponent in self.iter_base_or_scaled_units():
...@@ -83,7 +88,9 @@ def _unit_class_mul(self, other): ...@@ -83,7 +88,9 @@ def _unit_class_mul(self, other):
if exponent != 0: if exponent != 0:
assert unit not in result2 assert unit not in result2
result2[unit] = exponent result2[unit] = exponent
return Unit(result2) new_unit = Unit(result2)
Unit._multiplication_cache[self][other] = new_unit
return new_unit
elif is_quantity(other): elif is_quantity(other):
# print "unit * quantity" # print "unit * quantity"
value = other._value value = other._value
...@@ -99,6 +106,7 @@ def _unit_class_mul(self, other): ...@@ -99,6 +106,7 @@ def _unit_class_mul(self, other):
Unit.__mul__ = _unit_class_mul Unit.__mul__ = _unit_class_mul
Unit.__rmul__ = Unit.__mul__ Unit.__rmul__ = Unit.__mul__
Unit._multiplication_cache = {}
# run module directly for testing # run module directly for testing
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment