def getReduced( self ): debugPrint( 'getReduced 1:', self, [ ( i, self.units[ i ] ) for i in self.units ] ) if not g.unitConversionMatrix: loadUnitConversionMatrix( ) value = self.value units = RPNUnits( ) debugPrint( 'value', value ) for unit in self.units: newUnit = g.basicUnitTypes[ getUnitType( unit ) ].baseUnit debugPrint( 'unit', unit, 'newUnit', newUnit ) if unit != newUnit: value = fmul( value, power( mpf( g.unitConversionMatrix[ ( unit, newUnit ) ] ), self.units[ unit ] ) ) units.update( RPNUnits( g.unitOperators[ newUnit ].representation + "^" + str( self.units[ unit ] ) ) ) debugPrint( 'value', value ) reduced = RPNMeasurement( value, units ) debugPrint( 'getReduced 2:', reduced ) return reduced
def convertValue( self, other, tryReverse=True ): if self.isEquivalent( other ): return self.getValue( ) if self.isCompatible( other ): conversions = [ ] if isinstance( other, list ): result = [ ] source = self for count, measurement in enumerate( other ): with extradps( 1 ): conversion = source.convertValue( measurement ) if count < len( other ) - 1: result.append( RPNMeasurement( floor( conversion ), measurement.getUnits( ) ) ) source = RPNMeasurement( chop( frac( conversion ) ), measurement.getUnits( ) ) else: result.append( RPNMeasurement( conversion, measurement.getUnits( ) ) ) return result units1 = self.getUnits( ) units2 = other.getUnits( ) unit1String = units1.getUnitString( ) unit2String = units2.getUnitString( ) debugPrint( 'unit1String: ', unit1String ) debugPrint( 'unit2String: ', unit2String ) if unit1String == unit2String: return fmul( self.getValue( ), other.getValue( ) ) if unit1String in g.operatorAliases: unit1String = g.operatorAliases[ unit1String ] if unit2String in g.operatorAliases: unit2String = g.operatorAliases[ unit2String ] exponents = { } if not g.unitConversionMatrix: loadUnitConversionMatrix( ) # look for a straight-up conversion unit1NoStar = unit1String.replace( '*', '-' ) unit2NoStar = unit2String.replace( '*', '-' ) debugPrint( 'unit1NoStar: ', unit1NoStar ) debugPrint( 'unit2NoStar: ', unit2NoStar ) if ( unit1NoStar, unit2NoStar ) in g.unitConversionMatrix: value = fmul( self.value, mpmathify( g.unitConversionMatrix[ ( unit1NoStar, unit2NoStar ) ] ) ) elif ( unit1NoStar, unit2NoStar ) in specialUnitConversionMatrix: value = specialUnitConversionMatrix[ ( unit1NoStar, unit2NoStar ) ]( self.value ) else: # otherwise, we need to figure out how to do the conversion conversionValue = mpmathify( 1 ) # if that isn't found, then we need to do the hard work and break the units down newUnits1 = RPNUnits( ) for unit in units1: newUnits1.update( RPNUnits( g.unitOperators[ unit ].representation + "^" + str( units1[ unit ] ) ) ) newUnits2 = RPNUnits( ) for unit in units2: newUnits2.update( RPNUnits( g.unitOperators[ unit ].representation + "^" + str( units2[ unit ] ) ) ) debugPrint( 'units1:', units1 ) debugPrint( 'units2:', units2 ) debugPrint( 'newUnits1:', newUnits1 ) debugPrint( 'newUnits2:', newUnits2 ) debugPrint( ) debugPrint( 'iterating through units:' ) for unit1 in newUnits1: foundConversion = False for unit2 in newUnits2: debugPrint( 'units 1:', unit1, newUnits1[ unit1 ], getUnitType( unit1 ) ) debugPrint( 'units 2:', unit2, newUnits2[ unit2 ], getUnitType( unit2 ) ) if getUnitType( unit1 ) == getUnitType( unit2 ): conversions.append( [ unit1, unit2 ] ) exponents[ ( unit1, unit2 ) ] = units1[ unit1 ] foundConversion = True break if not foundConversion: debugPrint( 'didn\'t find a conversion, try reducing' ) reduced = self.getReduced( ) debugPrint( 'reduced:', self.units, 'becomes', reduced.units ) reducedOther = other.getReduced( ) debugPrint( 'reduced other:', other.units, 'becomes', reducedOther.units ) # check to see if reducing did anything and bail if it didn't... bail out if ( reduced.units == self.units ) and ( reducedOther.units == other.units ): break reduced = reduced.convertValue( reducedOther ) return RPNMeasurement( fdiv( reduced, reducedOther.value ), reducedOther.getUnits( ) ).getValue( ) debugPrint( ) value = conversionValue if not foundConversion: # This is a cheat. The conversion logic has flaws, but if it's possible to do the # conversion in the opposite direction, then we can do that and return the reciprocal. # This allows more conversions without fixing the underlying problems, which will # require some redesign. if tryReverse: return fdiv( 1, other.convertValue( self, False ) ) else: raise ValueError( 'unable to convert ' + self.getUnitString( ) + ' to ' + other.getUnitString( ) ) for conversion in conversions: if conversion[ 0 ] == conversion[ 1 ]: continue # no conversion needed debugPrint( 'unit conversion:', g.unitConversionMatrix[ tuple( conversion ) ] ) debugPrint( 'exponents', exponents ) conversionValue = mpmathify( g.unitConversionMatrix[ tuple( conversion ) ] ) conversionValue = power( conversionValue, exponents[ tuple( conversion ) ] ) debugPrint( 'conversion: ', conversion, conversionValue ) value = fmul( value, conversionValue ) value = fmul( self.value, value ) return value else: if isinstance( other, list ): otherUnit = '[ ' + ', '.join( [ unit.getUnitString( ) for unit in other ] ) + ' ]' else: otherUnit = other.getUnitString( ) raise ValueError( 'incompatible units cannot be converted: ' + self.getUnitString( ) + ' and ' + otherUnit )
class RPNMeasurement( object ): '''This class represents a measurement, which includes a numerical value and an RPNUnits instance.''' value = mpf( ) units = RPNUnits( ) unitName = None pluralUnitName = None def __init__( self, value, units = RPNUnits( ), unitName = None, pluralUnitName = None ): if not g.unitOperators: loadUnitData( ) #print( '__init__', value, units, unitName, pluralUnitName ) if isinstance( value, str ): self.value = mpmathify( value ) self.units = RPNUnits( units ) elif isinstance( value, RPNMeasurement ): self.value = value.getValue( ) self.units = RPNUnits( value.getUnits( ) ) else: self.value = value self.units = RPNUnits( units ) self.unitName = unitName self.pluralUnitName = pluralUnitName def __eq__( self, other ): if isinstance( other, RPNMeasurement ): result = mpf.__eq__( self.value, other.value ) else: result = mpf.__eq__( self.value, other ) if not result: return result if isinstance( other, RPNMeasurement ): result = ( self.units == other.units ) return result def __ne__( self, other ): return not __eq__( self, other ) #def __repr__( self ): # return 'RPNMeasurement(\'' + str( mpf( self ) ) + '\',' + \ # ( '{}' if self.units is None else '\'' + self.units.getUnitString( ) + '\'' ) + \ # '\'' + self.unitName + '\',\'' + self.pluralUnitName + '\')' #def __str__( self ): # return repr( self ) def increment( self, unit, amount = 1 ): self.unitName = None self.pluralUnitName = None self.units[ unit ] += amount def decrement( self, unit, amount = 1 ): increment( self, unit, -amount ) def add( self, other ): if isinstance( other, RPNMeasurement ): if self.getUnits( ) == other.getUnits( ): return RPNMeasurement( fadd( self.value, other.value ), self.getUnits( ), self.getUnitName( ), self.getPluralUnitName( ) ) else: newOther = other.convertValue( self ) return RPNMeasurement( fadd( self.value, newOther ), self.getUnits( ), self.getUnitName( ), self.getPluralUnitName( ) ) else: return RPNMeasurement( fadd( self.value, other ), self.getUnits( ), self.getUnitName( ), self.getPluralUnitName( ) ) def subtract( self, other ): if isinstance( other, RPNMeasurement ): if self.getUnits( ) == other.getUnits( ): return RPNMeasurement( fsub( self.value, other.value ), self.getUnits( ), self.getUnitName( ), self.getPluralUnitName( ) ) else: newOther = other.convertValue( self ) return RPNMeasurement( fsub( self.value, newOther ), self.getUnits( ), self.getUnitName( ), self.getPluralUnitName( ) ) else: return RPNMeasurement( fsub( self.value, other ), self.getUnits( ), self.getUnitName( ), self.getPluralUnitName( ) ) def multiply( self, other ): if isinstance( other, RPNMeasurement ): newValue = fmul( self.value, other.value ) factor, newUnits = combineUnits( self.getUnits( ), other.getUnits( ) ) self = RPNMeasurement( fmul( newValue, factor ), newUnits ) else: newValue = fmul( self.value, other ) self = RPNMeasurement( newValue, self.getUnits( ), self.getUnitName( ), self.getPluralUnitName( ) ) return self.normalizeUnits( ) def divide( self, other ): if isinstance( other, RPNMeasurement ): newValue = fdiv( self.value, other.value ) factor, newUnits = combineUnits( self.getUnits( ), other.getUnits( ).inverted( ) ) self = RPNMeasurement( fmul( newValue, factor ), newUnits ) else: newValue = fdiv( self.value, other ) self = RPNMeasurement( newValue, self.getUnits( ), self.getUnitName( ), self.getPluralUnitName( ) ) return self.normalizeUnits( ) def exponentiate( self, exponent ): if ( floor( exponent ) != exponent ): raise ValueError( 'cannot raise a measurement to a non-integral power' ) newValue = power( self.value, exponent ) for unit in self.units: self.units[ unit ] *= exponent self = RPNMeasurement( newValue, self.units ) return self def invert( self ): units = self.getUnits( ) newUnits = RPNUnits( ) for unit in units: newUnits[ unit ] = -units[ unit ] return RPNMeasurement( self.value, newUnits ) def normalizeUnits( self ): units = self.getUnits( ) newUnits = RPNUnits( ) for unit in units: if units[ unit ] != 0 and unit != '_null_unit': newUnits[ unit ] = units[ unit ] return RPNMeasurement( self.value, newUnits, self.getUnitName( ), self.getPluralUnitName( ) ) def update( self, units ): for i in self.units: del self.units[ i ] if not isinstance( units, dict ): raise ValueError( 'dict expected' ) self.unitName = None self.pluralUnitName = None self.units.update( units ) def isCompatible( self, other ): if isinstance( other, RPNUnits ): return self.getUnitTypes( ) == other.getUnitTypes( ) elif isinstance( other, dict ): return self.getUnitTypes( ) == other elif isinstance( other, list ): result = True for item in other: result = self.isCompatible( item ) if not result: break return result elif isinstance( other, RPNMeasurement ): debugPrint( 'units: ', self.units, other.units ) debugPrint( 'types: ', self.getUnitTypes( ), other.getUnitTypes( ) ) debugPrint( 'dimensions: ', self.getDimensions( ), other.getDimensions( ) ) if self.getUnitTypes( ) == other.getUnitTypes( ): return True elif self.getDimensions( ) == other.getDimensions( ): return True else: debugPrint( 'RPNMeasurement.isCompatible exiting with false...' ) return False else: raise ValueError( 'RPNMeasurement or dict expected' ) def isEquivalent( self, other ): if isinstance( other, list ): result = True for item in other: result = self.isEquivalent( item ) if not result: break return result elif isinstance( other, RPNMeasurement ): return self.getUnits( ) == other.getUnits( ) else: raise ValueError( 'RPNMeasurement or dict expected' ) def getValue( self ): return self.value def getUnits( self ): return self.units def getUnitString( self ): return self.units.getUnitString( ) def getUnitName( self ): if self.unitName: return self.unitName unitString = self.getUnitString( ) if unitString in g.unitOperators: return g.unitOperators[ unitString ].representation else: return unitString def getPluralUnitName( self ): if self.pluralUnitName: return self.pluralUnitName unitString = self.getUnitString( ) if unitString in g.unitOperators: return g.unitOperators[ unitString ].plural else: return unitString def getUnitTypes( self ): types = RPNUnits( ) for unit in self.units: if unit not in g.unitOperators: raise ValueError( 'undefined unit type \'{}\''.format( unit ) ) unitType = getUnitType( unit ) if unitType in types: types[ unitType ] += self.units[ unit ] elif self.units[ unit ] != 0: types[ unitType ] = self.units[ unit ] return types def getDimensions( self ): return self.units.getDimensions( ) def getReduced( self ): debugPrint( 'getReduced 1:', self, [ ( i, self.units[ i ] ) for i in self.units ] ) if not g.unitConversionMatrix: loadUnitConversionMatrix( ) value = self.value units = RPNUnits( ) debugPrint( 'value', value ) for unit in self.units: newUnit = g.basicUnitTypes[ getUnitType( unit ) ].baseUnit debugPrint( 'unit', unit, 'newUnit', newUnit ) if unit != newUnit: value = fmul( value, power( mpf( g.unitConversionMatrix[ ( unit, newUnit ) ] ), self.units[ unit ] ) ) units.update( RPNUnits( g.unitOperators[ newUnit ].representation + "^" + str( self.units[ unit ] ) ) ) debugPrint( 'value', value ) reduced = RPNMeasurement( value, units ) debugPrint( 'getReduced 2:', reduced ) return reduced def convert( self, other ): if isinstance( other, RPNMeasurement ): return RPNMeasurement( self.convertValue( other ), other.getUnits( ) ) elif isinstance( other, ( RPNUnits, dict, str ) ): measurement = RPNMeasurement( 1, other ) return RPNMeasurement( self.convertValue( measurement ), measurement.getUnits( ) ) else: raise ValueError( 'convert doesn\'t know what to do with this argument' ) def isLarger( self, other ): newValue = self.convert( other ) return ( newValue.getValue( ) > other.getValue( ) ) def isNotLarger( self, other ): return not self.isLarger( other ) def isSmaller( self, other ): newValue = self.convert( other ) return ( newValue.getValue( ) < other.getValue( ) ) def isNotSmaller( self, other ): return not self.isSmaller( other ) def isEqual( self, other ): newValue = self.convert( other ) #print( newValue.getValue( ), other.getValue( ) ) return ( newValue.getValue( ) == other.getValue( ) ) def isNotEqual( self, other ): return not self.isEqual( other ) def convertValue( self, other, tryReverse=True ): if self.isEquivalent( other ): return self.getValue( ) if self.isCompatible( other ): conversions = [ ] if isinstance( other, list ): result = [ ] source = self for count, measurement in enumerate( other ): with extradps( 1 ): conversion = source.convertValue( measurement ) if count < len( other ) - 1: result.append( RPNMeasurement( floor( conversion ), measurement.getUnits( ) ) ) source = RPNMeasurement( chop( frac( conversion ) ), measurement.getUnits( ) ) else: result.append( RPNMeasurement( conversion, measurement.getUnits( ) ) ) return result units1 = self.getUnits( ) units2 = other.getUnits( ) unit1String = units1.getUnitString( ) unit2String = units2.getUnitString( ) debugPrint( 'unit1String: ', unit1String ) debugPrint( 'unit2String: ', unit2String ) if unit1String == unit2String: return fmul( self.getValue( ), other.getValue( ) ) if unit1String in g.operatorAliases: unit1String = g.operatorAliases[ unit1String ] if unit2String in g.operatorAliases: unit2String = g.operatorAliases[ unit2String ] exponents = { } if not g.unitConversionMatrix: loadUnitConversionMatrix( ) # look for a straight-up conversion unit1NoStar = unit1String.replace( '*', '-' ) unit2NoStar = unit2String.replace( '*', '-' ) debugPrint( 'unit1NoStar: ', unit1NoStar ) debugPrint( 'unit2NoStar: ', unit2NoStar ) if ( unit1NoStar, unit2NoStar ) in g.unitConversionMatrix: value = fmul( self.value, mpmathify( g.unitConversionMatrix[ ( unit1NoStar, unit2NoStar ) ] ) ) elif ( unit1NoStar, unit2NoStar ) in specialUnitConversionMatrix: value = specialUnitConversionMatrix[ ( unit1NoStar, unit2NoStar ) ]( self.value ) else: # otherwise, we need to figure out how to do the conversion conversionValue = mpmathify( 1 ) # if that isn't found, then we need to do the hard work and break the units down newUnits1 = RPNUnits( ) for unit in units1: newUnits1.update( RPNUnits( g.unitOperators[ unit ].representation + "^" + str( units1[ unit ] ) ) ) newUnits2 = RPNUnits( ) for unit in units2: newUnits2.update( RPNUnits( g.unitOperators[ unit ].representation + "^" + str( units2[ unit ] ) ) ) debugPrint( 'units1:', units1 ) debugPrint( 'units2:', units2 ) debugPrint( 'newUnits1:', newUnits1 ) debugPrint( 'newUnits2:', newUnits2 ) debugPrint( ) debugPrint( 'iterating through units:' ) for unit1 in newUnits1: foundConversion = False for unit2 in newUnits2: debugPrint( 'units 1:', unit1, newUnits1[ unit1 ], getUnitType( unit1 ) ) debugPrint( 'units 2:', unit2, newUnits2[ unit2 ], getUnitType( unit2 ) ) if getUnitType( unit1 ) == getUnitType( unit2 ): conversions.append( [ unit1, unit2 ] ) exponents[ ( unit1, unit2 ) ] = units1[ unit1 ] foundConversion = True break if not foundConversion: debugPrint( 'didn\'t find a conversion, try reducing' ) reduced = self.getReduced( ) debugPrint( 'reduced:', self.units, 'becomes', reduced.units ) reducedOther = other.getReduced( ) debugPrint( 'reduced other:', other.units, 'becomes', reducedOther.units ) # check to see if reducing did anything and bail if it didn't... bail out if ( reduced.units == self.units ) and ( reducedOther.units == other.units ): break reduced = reduced.convertValue( reducedOther ) return RPNMeasurement( fdiv( reduced, reducedOther.value ), reducedOther.getUnits( ) ).getValue( ) debugPrint( ) value = conversionValue if not foundConversion: # This is a cheat. The conversion logic has flaws, but if it's possible to do the # conversion in the opposite direction, then we can do that and return the reciprocal. # This allows more conversions without fixing the underlying problems, which will # require some redesign. if tryReverse: return fdiv( 1, other.convertValue( self, False ) ) else: raise ValueError( 'unable to convert ' + self.getUnitString( ) + ' to ' + other.getUnitString( ) ) for conversion in conversions: if conversion[ 0 ] == conversion[ 1 ]: continue # no conversion needed debugPrint( 'unit conversion:', g.unitConversionMatrix[ tuple( conversion ) ] ) debugPrint( 'exponents', exponents ) conversionValue = mpmathify( g.unitConversionMatrix[ tuple( conversion ) ] ) conversionValue = power( conversionValue, exponents[ tuple( conversion ) ] ) debugPrint( 'conversion: ', conversion, conversionValue ) value = fmul( value, conversionValue ) value = fmul( self.value, value ) return value else: if isinstance( other, list ): otherUnit = '[ ' + ', '.join( [ unit.getUnitString( ) for unit in other ] ) + ' ]' else: otherUnit = other.getUnitString( ) raise ValueError( 'incompatible units cannot be converted: ' + self.getUnitString( ) + ' and ' + otherUnit )