def __init__(self, names, factor, powers, offset=0): """ :param names: a dictionary mapping each name component to its associated integer power (e.g. C{{'m': 1, 's': -1}}) for M{m/s}). As a shorthand, a string may be passed which is assigned an implicit power 1. :type names: C{dict} or C{str} :param factor: a scaling factor :type factor: C{float} :param powers: the integer powers for each of the nine base units :type powers: C{list} of C{int} :param offset: an additive offset to the base unit (used only for temperatures) :type offset: C{float} """ if type(names) == type(''): self.names = NumberDict() self.names[names] = 1 else: self.names = names self.factor = factor self.offset = offset self.powers = powers
def setName(self, name): self.names = NumberDict() self.names[name] = 1
class PhysicalUnit: """ Physical unit A physical unit is defined by a name (possibly composite), a scaling factor, and the exponentials of each of the SI base units that enter into it. Units can be multiplied, divided, and raised to integer powers. """ def __init__(self, names, factor, powers, offset=0): """ :param names: a dictionary mapping each name component to its associated integer power (e.g. C{{'m': 1, 's': -1}}) for M{m/s}). As a shorthand, a string may be passed which is assigned an implicit power 1. :type names: C{dict} or C{str} :param factor: a scaling factor :type factor: C{float} :param powers: the integer powers for each of the nine base units :type powers: C{list} of C{int} :param offset: an additive offset to the base unit (used only for temperatures) :type offset: C{float} """ if type(names) == type(''): self.names = NumberDict() self.names[names] = 1 else: self.names = names self.factor = factor self.offset = offset self.powers = powers def __repr__(self): return '<PhysicalUnit ' + self.name() + '>' __str__ = __repr__ def __cmp__(self, other): if self.powers != other.powers: raise TypeError('Incompatible units') return cmp(self.factor, other.factor) def __mul__(self, other): if self.offset != 0 or (isPhysicalUnit (other) and other.offset != 0): raise TypeError("cannot multiply units with non-zero offset") if isPhysicalUnit(other): return PhysicalUnit(self.names+other.names, self.factor*other.factor, map(lambda a,b: a+b, self.powers, other.powers)) else: return PhysicalUnit(self.names+{str(other): 1}, self.factor*other, self.powers, self.offset * other) __rmul__ = __mul__ def __div__(self, other): if self.offset != 0 or (isPhysicalUnit (other) and other.offset != 0): raise TypeError("cannot divide units with non-zero offset") if isPhysicalUnit(other): return PhysicalUnit(self.names-other.names, self.factor/other.factor, map(lambda a,b: a-b, self.powers, other.powers)) else: return PhysicalUnit(self.names+{str(other): -1}, self.factor/other, self.powers) __truediv__ = __div__ def __rdiv__(self, other): if self.offset != 0 or (isPhysicalUnit (other) and other.offset != 0): raise TypeError("cannot divide units with non-zero offset") if isPhysicalUnit(other): return PhysicalUnit(other.names-self.names, other.factor/self.factor, map(lambda a,b: a-b, other.powers, self.powers)) else: return PhysicalUnit({str(other): 1}-self.names, other/self.factor, map(lambda x: -x, self.powers)) def __pow__(self, other): if self.offset != 0: raise TypeError("cannot exponentiate units with non-zero offset") if isinstance(other, int): return PhysicalUnit(other*self.names, pow(self.factor, other), map(lambda x,p=other: x*p, self.powers)) if isinstance(other, float): inv_exp = 1./other rounded = int(numpy.floor(inv_exp+0.5)) if abs(inv_exp-rounded) < 1.e-10: if reduce(lambda a, b: a and b, map(lambda x, e=rounded: x%e == 0, self.powers)): f = pow(self.factor, other) p = map(lambda x,p=rounded: x/p, self.powers) if reduce(lambda a, b: a and b, map(lambda x, e=rounded: x%e == 0, self.names.values())): names = self.names/rounded else: names = NumberDict() if f != 1.: names[str(f)] = 1 for i in range(len(p)): names[_base_names[i]] = p[i] return PhysicalUnit(names, f, p) else: raise TypeError('Illegal exponent') raise TypeError('Only integer and inverse integer exponents allowed') def conversionFactorTo(self, other): """ :param other: another unit :type other: L{PhysicalUnit} :returns: the conversion factor from this unit to another unit :rtype: C{float} :raises TypeError: if the units are not compatible """ if self.powers != other.powers: raise TypeError('Incompatible units') if self.offset != other.offset and self.factor != other.factor: raise TypeError(('Unit conversion (%s to %s) cannot be expressed ' + 'as a simple multiplicative factor') % \ (self.name(), other.name())) return self.factor/other.factor def conversionTupleTo(self, other): # added 1998/09/29 GPW """ :param other: another unit :type other: L{PhysicalUnit} :returns: the conversion factor and offset from this unit to another unit :rtype: (C{float}, C{float}) :raises TypeError: if the units are not compatible """ if self.powers != other.powers: raise TypeError('Incompatible units') # let (s1,d1) be the conversion tuple from 'self' to base units # (ie. (x+d1)*s1 converts a value x from 'self' to base units, # and (x/s1)-d1 converts x from base to 'self' units) # and (s2,d2) be the conversion tuple from 'other' to base units # then we want to compute the conversion tuple (S,D) from # 'self' to 'other' such that (x+D)*S converts x from 'self' # units to 'other' units # the formula to convert x from 'self' to 'other' units via the # base units is (by definition of the conversion tuples): # ( ((x+d1)*s1) / s2 ) - d2 # = ( (x+d1) * s1/s2) - d2 # = ( (x+d1) * s1/s2 ) - (d2*s2/s1) * s1/s2 # = ( (x+d1) - (d1*s2/s1) ) * s1/s2 # = (x + d1 - d2*s2/s1) * s1/s2 # thus, D = d1 - d2*s2/s1 and S = s1/s2 factor = self.factor / other.factor offset = self.offset - (other.offset * other.factor / self.factor) return (factor, offset) def isCompatible (self, other): # added 1998/10/01 GPW """ :param other: another unit :type other: L{PhysicalUnit} :returns: C{True} if the units are compatible, i.e. if the powers of the base units are the same :rtype: C{bool} """ return self.powers == other.powers def isDimensionless(self): return not reduce(lambda a,b: a or b, self.powers) def isAngle(self): return self.powers[7] == 1 and \ reduce(lambda a,b: a + b, self.powers) == 1 def setName(self, name): self.names = NumberDict() self.names[name] = 1 def name(self): num = '' denom = '' for unit in self.names.keys(): power = self.names[unit] if power < 0: denom = denom + '/' + unit if power < -1: denom = denom + '**' + str(-power) elif power > 0: num = num + '*' + unit if power > 1: num = num + '**' + str(power) if len(num) == 0: num = '1' else: num = num[1:] return num + denom