Exemple #1
0
    def getRoot( self, operand ):
        if ( floor( operand ) != operand ):
            raise ValueError( 'cannot take a fractional root of a measurement' )

        newUnits = RPNUnits( self.units )

        for unit, exponent in newUnits.items( ):
            if fmod( exponent, operand ) != 0:
                if operand == 2:
                    name = 'square'
                elif operand == 3:
                    name = 'cube'
                else:
                    name = getOrdinalName( operand )

                baseUnits = self.convertToBaseUnits( )

                if ( baseUnits != self ):
                    return baseUnits.getRoot( operand )
                else:
                    raise ValueError( 'cannot take the ' + name + ' root of this measurement: ', self.units )

            newUnits[ unit ] /= operand

        value = root( self.value, operand )

        return RPNMeasurement( value, newUnits ).normalizeUnits( )
    def getRoot(self, operand):
        if floor(operand) != operand:
            raise ValueError('cannot take a fractional root of a measurement')

        newUnits = RPNUnits(self.units)

        for unit, exponent in newUnits.items():
            if fmod(exponent, operand) != 0:
                if operand == 2:
                    name = 'square'
                elif operand == 3:
                    name = 'cube'
                else:
                    # getOrdinalName( operand )
                    name = str(int(operand)) + 'th'

                baseUnits = self.convertToPrimitiveUnits()

                if baseUnits != self:
                    return baseUnits.getRoot(operand)

                raise ValueError(
                    'cannot take the ' + name + ' root of this measurement: ',
                    self.units)

            newUnits[unit] /= operand

        value = root(self.value, operand)

        return RPNMeasurement(value, newUnits).normalizeUnits()
    def convertToPrimitiveUnits(self):
        debugPrint()
        debugPrint('convertToPrimitiveUnits:', self.value, self.units)

        if not g.unitConversionMatrix:
            loadUnitConversionMatrix()

        value = self.value
        units = RPNUnits()

        debugPrint('value', value)

        for unit in self.units:

            if self.units[unit] == 0:
                continue

            newUnits = g.basicUnitTypes[getUnitType(unit)].primitiveUnit

            debugPrint('unit', unit, 'newUnits', newUnits)

            if unit != newUnits:
                debugPrint('unit vs newUnits:', unit, newUnits)

                if (unit, newUnits) in g.unitConversionMatrix:
                    value = fmul(
                        value,
                        power(mpf(g.unitConversionMatrix[(unit, newUnits)]),
                              self.units[unit]))
                elif (unit, newUnits) in specialUnitConversionMatrix:
                    value = power(
                        specialUnitConversionMatrix[(unit, newUnits)](value),
                        self.units[unit])
                else:
                    if unit == '1' and newUnits == '_null_unit':
                        reduced = RPNMeasurement(value, units)
                        debugPrint('convertToPrimitiveUnits 2:', reduced.value,
                                   reduced.units)
                        return reduced

                    raise ValueError('cannot find a conversion for ' + unit +
                                     ' and ' + newUnits)

            newUnits = RPNUnits(newUnits)

            for newUnit in newUnits:
                newUnits[newUnit] *= self.units[unit]

            units.update(newUnits)

            debugPrint('value', value)

        baseUnits = RPNMeasurement(value, units)
        debugPrint('convertToPrimitiveUnits 3:', baseUnits.value,
                   baseUnits.units)
        debugPrint()
        return baseUnits
Exemple #4
0
 def __init__( self, value, units = RPNUnits( ) ):
     if isinstance( value, str ):
         self.value = mpmathify( value )
         self.units = RPNUnits( units )
     elif isinstance( value, RPNMeasurement ):
         self.value = value.value
         self.units = RPNUnits( value.units )
     else:
         self.value = value
         self.units = RPNUnits( units )
Exemple #5
0
    def convertToBaseUnits( self ):
        debugPrint( )
        debugPrint( 'convertToBaseUnits:', self.value, self.units )

        if not g.unitConversionMatrix:
            loadUnitConversionMatrix( )

        value = self.value
        units = RPNUnits( )

        debugPrint( 'value', value )

        for unit in self.units:
            newUnits = g.basicUnitTypes[ getUnitType( unit ) ].primitiveUnit

            debugPrint( 'unit', unit, 'newUnits', newUnits )

            if unit != newUnits:
                debugPrint( 'unit vs newUnits:', unit, newUnits )

                if ( unit, newUnits ) in g.unitConversionMatrix:
                    value = fmul( value, power( mpf( g.unitConversionMatrix[ ( unit, newUnits ) ] ), self.units[ unit ] ) )
                elif ( unit, newUnits ) in specialUnitConversionMatrix:
                    value = power( specialUnitConversionMatrix[ ( unit, newUnits ) ]( value ), self.units[ unit ] )
                else:
                    if unit == '1' and newUnits == '_null_unit':
                        reduced = RPNMeasurement( value, units )
                        debugPrint( 'convertToBaseUnits 2:', reduced.value, reduced.units )
                        return reduced
                    else:
                        raise ValueError( 'cannot find a conversion for ' + unit + ' and ' + newUnits )

            newUnits = RPNUnits( newUnits )

            for newUnit in newUnits:
                newUnits[ newUnit ] *= self.units[ unit ]

            units.update( newUnits )

            debugPrint( 'value', value )

        baseUnits = RPNMeasurement( value, units )
        debugPrint( 'convertToBaseUnits 3:', baseUnits.value, baseUnits.units )
        debugPrint( )
        return baseUnits
    def getInverted(self, invertValue=True):
        units = self.units

        newUnits = RPNUnits()

        for unit in units:
            newUnits[unit] = -units[unit]

        if invertValue:
            return RPNMeasurement(fdiv(1, self.value), newUnits)

        return RPNMeasurement(self.value, newUnits)
 def __init__(self, value, units=RPNUnits()):
     if isinstance(value, (str, int)):
         self.value = mpmathify(value)
         self.units = RPNUnits(units)
     elif isinstance(value, RPNMeasurement):
         self.value = value.value
         self.units = RPNUnits(value.units)
     else:
         self.value = value
         self.units = RPNUnits(units)
    def getUnitTypes(self):
        types = RPNUnits()

        for unit in self.units:
            if unit == '1':
                continue

            if unit not in g.unitOperators:
                raise ValueError('1 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
Exemple #9
0
def applyNumberValueToUnit(number, term, constant):
    if isinstance(term, RPNUnits):
        value = RPNMeasurement(number, term)
        value = value.normalizeUnits()

        if value.units == RPNUnits('_null_unit'):
            value = mpmathify(1)
    elif constant:
        if g.constantOperators[term].unit:
            value = RPNMeasurement(
                fmul(number, mpmathify(g.constantOperators[term].value)),
                g.constantOperators[term].unit)
        else:
            value = fmul(number, g.constantOperators[term].value)
    else:
        if g.unitOperators[term].unitType == 'constant':
            value = RPNMeasurement(number, term).convertValue(
                RPNMeasurement(1, {'unity': 1}))
        else:
            value = RPNMeasurement(number, term)

    return value
    def convertValue(self, other):
        if not isinstance(other, (RPNUnits, RPNMeasurement, str, list)):
            raise ValueError(
                'convertValue must be called with an RPNUnits object, '
                'an RPNMeasurement object, a string or a list of RPNMeasurement'
            )

        if isinstance(other, (str, RPNUnits)):
            other = RPNMeasurement(1, other)

        if not self.isCompatible(other):
            # We can try to convert incompatible units.
            return self.convertIncompatibleUnit(other)

        if not g.unitConversionMatrix:
            loadUnitConversionMatrix()

        # handle list conversions like [ year, day, minute, hour ]
        if isinstance(other, list):
            # a list of length one is treated the same as a single measurement
            if len(other) == 1:
                return self.convertValue(other[0])
            else:
                listToConvert = []

                for i in other:
                    if isinstance(i, str):
                        listToConvert.append(RPNMeasurement(1, i))
                    elif isinstance(i, RPNMeasurement):
                        listToConvert.append(i)
                    else:
                        raise ValueError('we\'ve got something else')

                return self.convertUnitList(listToConvert)

        conversions = []

        value = self.value  # This is what we'll return down below

        # let's look for straightforward conversions
        units1 = self.units
        units2 = other.units

        unit1String = units1.getUnitString()
        unit2String = units2.getUnitString()

        if unit1String == unit2String:
            return value

        exponents = {}

        # look for a straight-up conversion
        if (unit1String, unit2String) in g.unitConversionMatrix:
            value = fmul(
                value,
                mpmathify(g.unitConversionMatrix[(unit1String, unit2String)]))
        elif (unit1String, unit2String) in specialUnitConversionMatrix:
            value = specialUnitConversionMatrix[(unit1String,
                                                 unit2String)](value)
        else:
            # TODO:  Should we just convert to base units regardless?  It would be safer...
            if other.doDimensionsCancel():
                other = other.convertToPrimitiveUnits()
                units2 = other.units
                value = fdiv(value, other.value)

            # otherwise, we need to figure out how to do the conversion
            conversionValue = value

            # if that isn't found, then we need to do the hard work and break the units down
            newUnits1 = RPNUnits(units1)
            newUnits2 = RPNUnits(units2)

            debugPrint('newUnits1:', newUnits1)
            debugPrint('newUnits2:', newUnits2)

            debugPrint()
            debugPrint('iterating through units to match for conversion:')

            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):
                        debugPrint('found a conversion:', unit1, unit2)
                        conversions.append((unit1, unit2))
                        exponents[(unit1, unit2)] = units1[unit1]
                        foundConversion = True
                        break

                if not foundConversion:
                    debugPrint()
                    debugPrint('didn\'t find a conversion, try reducing')
                    debugPrint()
                    reduced = self.convertToPrimitiveUnits()

                    debugPrint('reduced:', self.units, 'becomes',
                               reduced.units)

                    reducedOther = other.convertToPrimitiveUnits()

                    reduced.value = fdiv(reduced.value, reducedOther.value)

                    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):
                        debugPrint('reducing didn\'t help')
                        break

                    return reduced.convertValue(reducedOther)

            debugPrint()

            value = conversionValue

            debugPrint('Iterating through conversions...')

            for conversion in conversions:
                if conversion[0] == conversion[1]:
                    continue  # no conversion needed

                debugPrint('----> conversion', conversion)

                conversionIndex = tuple(conversion)

                if conversionIndex in g.unitConversionMatrix:
                    debugPrint('unit conversion:',
                               g.unitConversionMatrix[tuple(conversion)])
                    debugPrint('exponents', exponents)

                    conversionValue = mpmathify(
                        g.unitConversionMatrix[conversionIndex])
                    conversionValue = power(conversionValue,
                                            exponents[conversionIndex])
                    debugPrint('conversion: ', conversion, conversionValue)

                    debugPrint('value before', value)
                    value = fmul(value, conversionValue)
                    debugPrint('value after', value)
                else:
                    # we're ignoring the exponents, but this works for dBm<->milliwatt, etc.
                    baseUnit = g.basicUnitTypes[getUnitType(
                        conversion[0])].baseUnit

                    conversion1 = (conversion[0], baseUnit)
                    conversion2 = (baseUnit, conversion[1])

                    debugPrint('conversion1', conversion1)
                    debugPrint('conversion2', conversion2)

                    debugPrint('value0', value)

                    if conversion1 in g.unitConversionMatrix:
                        debugPrint('standard conversion 1')
                        value = fmul(
                            value,
                            mpmathify(g.unitConversionMatrix[conversion1]))
                    else:
                        debugPrint('special conversion 1')
                        value = specialUnitConversionMatrix[conversion1](value)

                    debugPrint('value1', value)

                    if conversion2 in g.unitConversionMatrix:
                        debugPrint('standard conversion 2')
                        value = fmul(
                            value,
                            mpmathify(g.unitConversionMatrix[conversion2]))
                    else:
                        debugPrint('special conversion 2')
                        value = specialUnitConversionMatrix[conversion2](value)

                    debugPrint('value2', value)

        debugPrint('convertValue final', value)
        return value
    def isOfUnitType(self, unitType):
        if self.isCompatible(RPNUnits(g.basicUnitTypes[unitType].baseUnit)):
            return True

        return self.getDimensions() == g.basicUnitTypes[unitType].dimensions
    def normalizeUnits(self):
        units = self.units.normalizeUnits()

        debugPrint()
        debugPrint('normalize', units)

        if units == RPNUnits():
            return self.value

        # look for units that cancel between the numerator and denominator
        numerator, denominator = units.splitUnits()

        # if we don't have a numerator or denominator, we're done
        if not numerator or not denominator:
            return RPNMeasurement(self.value, units)

        if not g.basicUnitTypes:
            loadUnitData()

        debugPrint('numerator', numerator)
        debugPrint('denominator', denominator)

        nOriginalElements = sorted(list(numerator.elements()))
        dOriginalElements = sorted(list(denominator.elements()))

        changed = False

        nElements = []

        for nUnit in numerator:
            for i in range(min(numerator[nUnit], 3)):
                nElements.append(nUnit)

        dElements = []

        for dUnit in denominator:
            for i in range(min(denominator[dUnit], 3)):
                dElements.append(dUnit)

        debugPrint('nOriginalElements', nOriginalElements)
        debugPrint('nElements', nElements)

        debugPrint('dOriginalElements', dOriginalElements)
        debugPrint('dElements', dElements)

        matchFound = True  # technically not true yet, but it gets us into the loop

        conversionsNeeded = []

        while matchFound:
            matchFound = False

            for nSubset in getPowerSet(nElements):
                for dSubset in getPowerSet(dElements):
                    #debugPrint( '))) nSubset', list( nSubset ) )
                    #debugPrint( '))) dSubset', list( dSubset ) )

                    nSubsetUnit = RPNUnits('*'.join(sorted(list(nSubset))))
                    dSubsetUnit = RPNUnits('*'.join(sorted(list(dSubset))))

                    #debugPrint( '1 nSubset', dSubsetUnit )
                    #debugPrint( '2 dSubset', dSubsetUnit )

                    if nSubsetUnit.getDimensions(
                    ) == dSubsetUnit.getDimensions():
                        debugPrint('dimensions matched',
                                   dSubsetUnit.getDimensions())
                        newNSubset = []
                        newDSubset = []

                        for nUnit in nSubset:
                            baseUnit = g.basicUnitTypes[getUnitType(
                                nUnit)].baseUnit

                            if nUnit != baseUnit:
                                debugPrint('conversion added:', nUnit,
                                           baseUnit)
                                conversionsNeeded.append((nUnit, baseUnit))

                            newNSubset.append(baseUnit)

                        for dUnit in dSubset:
                            baseUnit = g.basicUnitTypes[getUnitType(
                                dUnit)].baseUnit

                            if dUnit != baseUnit:
                                debugPrint('conversion added:', dUnit,
                                           baseUnit)
                                conversionsNeeded.append((dUnit, baseUnit))

                            newDSubset.append(baseUnit)

                        # This maybe isn't quite what we want.
                        debugPrint('conversions added',
                                   '*'.join(sorted(newNSubset)),
                                   '*'.join(sorted(newDSubset)))
                        conversionsNeeded.append(
                            ('*'.join(sorted(newNSubset)),
                             '*'.join(sorted(newDSubset))))
                        matchFound = True

                        for nUnit in nSubset:
                            #print( 'nOriginalElements', nOriginalElements, 'n', nUnit )
                            nOriginalElements.remove(nUnit)
                            changed = True

                        for dUnit in dSubset:
                            #print( 'dOriginalElements', dOriginalElements, 'd', dUnit )
                            dOriginalElements.remove(dUnit)
                            changed = True

                        break

                if matchFound:
                    break

            if matchFound:
                break

        debugPrint('final nElements', nOriginalElements)
        debugPrint('final dElements', dOriginalElements)

        # convert the value based on all the conversions we queued up
        convertedValue = self.value

        if not g.unitConversionMatrix:
            loadUnitConversionMatrix()

        for i in conversionsNeeded:
            if i in g.unitConversionMatrix:
                convertedValue = fmul(convertedValue,
                                      g.unitConversionMatrix[i])
            else:
                convertedValue = fmul(
                    convertedValue,
                    RPNMeasurement(1, i[0]).convertValue(i[1]))

        # build up the resulting units
        units = RPNUnits('*'.join(nOriginalElements))
        denominator = RPNUnits('*'.join(dOriginalElements))

        for dUnit in denominator:
            units[dUnit] += denominator[dUnit] * -1

        debugPrint('normalizeUnits final units', units)
        debugPrint()

        if units and units != RPNUnits():
            result = RPNMeasurement(convertedValue, units)

            if changed:
                return result.normalizeUnits()

            return result

        return convertedValue
class RPNMeasurement():
    '''This class represents a measurement, which includes a numerical value
    and an RPNUnits instance.'''
    value = mpf()
    units = RPNUnits()

    def __init__(self, value, units=RPNUnits()):
        if isinstance(value, (str, int)):
            self.value = mpmathify(value)
            self.units = RPNUnits(units)
        elif isinstance(value, RPNMeasurement):
            self.value = value.value
            self.units = RPNUnits(value.units)
        else:
            self.value = value
            self.units = RPNUnits(units)

    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 self.__eq__(other)

    def __lt__(self, other):
        return self.isSmaller(other)

    def __gt__(self, other):
        return self.isLarger(other)

    def __ge__(self, other):
        return not self.isSmaller(other)

    def __le__(self, other):
        return not self.isLarger(other)

    #def __repr__( self ):
    #    return 'RPNMeasurement(\'' + str( self.value ) + ' ' + \
    #           ( '{}' if self.units is None else '\'' + self.units.getUnitString( ) + '\'' )

    #def __str__( self ):
    #    return repr( self )

    def increment(self, unit, amount=1):
        self.units[unit] += amount

    def decrement(self, unit, amount=1):
        self.increment(unit, -amount)

    def add(self, other):
        if isinstance(other, RPNMeasurement):
            if self.units == other.units:
                return RPNMeasurement(fadd(self.value, other.value),
                                      self.units)

            return RPNMeasurement(fadd(self.value, other.convertValue(self)),
                                  self.units)

        return RPNMeasurement(fadd(self.value, other), self.units)

    def subtract(self, other):
        if isinstance(other, RPNMeasurement):
            if self.units == other.units:
                return RPNMeasurement(fsub(self.value, other.value),
                                      self.units)

            return RPNMeasurement(fsub(self.value, other.convertValue(self)),
                                  self.units)

        return RPNMeasurement(fsub(self.value, other), self.units)

    def multiply(self, other):
        if isinstance(other, RPNMeasurement):
            factor, newUnits = self.units.combineUnits(other.units)
            newUnits = RPNMeasurement(1, newUnits).simplifyUnits().units
            return RPNMeasurement(fmul(fmul(self.value, other.value), factor),
                                  newUnits).normalizeUnits()

        return RPNMeasurement(fmul(self.value, other),
                              self.units).normalizeUnits()

    def divide(self, other):
        if isinstance(other, RPNMeasurement):
            factor, newUnits = self.units.combineUnits(other.units.inverted())
            newUnits = RPNMeasurement(1, newUnits).simplifyUnits().units

            return RPNMeasurement(fmul(fdiv(self.value, other.value), factor),
                                  newUnits).normalizeUnits()

        return RPNMeasurement(fdiv(self.value, other),
                              self.units).normalizeUnits()

    def getModulo(self, other):
        if isinstance(other, RPNMeasurement):
            measurement = RPNMeasurement(self).convert(other.units)
            measurement.value = fmod(measurement.value, other.value)

            return measurement.normalizeUnits()

        return RPNMeasurement(fmod(self.value, other),
                              self.units).normalizeUnits()

    def exponentiate(self, exponent):
        if floor(exponent) != exponent:
            raise ValueError(
                'cannot raise a measurement to a non-integral power')

        exponent = int(exponent)
        newValue = power(self.value, exponent)

        for unit in self.units:
            self.units[unit] *= exponent

        return RPNMeasurement(newValue, self.units)

    def getRoot(self, operand):
        if floor(operand) != operand:
            raise ValueError('cannot take a fractional root of a measurement')

        newUnits = RPNUnits(self.units)

        for unit, exponent in newUnits.items():
            if fmod(exponent, operand) != 0:
                if operand == 2:
                    name = 'square'
                elif operand == 3:
                    name = 'cube'
                else:
                    # getOrdinalName( operand )
                    name = str(int(operand)) + 'th'

                baseUnits = self.convertToPrimitiveUnits()

                if baseUnits != self:
                    return baseUnits.getRoot(operand)

                raise ValueError(
                    'cannot take the ' + name + ' root of this measurement: ',
                    self.units)

            newUnits[unit] /= operand

        value = root(self.value, operand)

        return RPNMeasurement(value, newUnits).normalizeUnits()

    def getInverted(self, invertValue=True):
        units = self.units

        newUnits = RPNUnits()

        for unit in units:
            newUnits[unit] = -units[unit]

        if invertValue:
            return RPNMeasurement(fdiv(1, self.value), newUnits)

        return RPNMeasurement(self.value, newUnits)

    def normalizeUnits(self):
        units = self.units.normalizeUnits()

        debugPrint()
        debugPrint('normalize', units)

        if units == RPNUnits():
            return self.value

        # look for units that cancel between the numerator and denominator
        numerator, denominator = units.splitUnits()

        # if we don't have a numerator or denominator, we're done
        if not numerator or not denominator:
            return RPNMeasurement(self.value, units)

        if not g.basicUnitTypes:
            loadUnitData()

        debugPrint('numerator', numerator)
        debugPrint('denominator', denominator)

        nOriginalElements = sorted(list(numerator.elements()))
        dOriginalElements = sorted(list(denominator.elements()))

        changed = False

        nElements = []

        for nUnit in numerator:
            for i in range(min(numerator[nUnit], 3)):
                nElements.append(nUnit)

        dElements = []

        for dUnit in denominator:
            for i in range(min(denominator[dUnit], 3)):
                dElements.append(dUnit)

        debugPrint('nOriginalElements', nOriginalElements)
        debugPrint('nElements', nElements)

        debugPrint('dOriginalElements', dOriginalElements)
        debugPrint('dElements', dElements)

        matchFound = True  # technically not true yet, but it gets us into the loop

        conversionsNeeded = []

        while matchFound:
            matchFound = False

            for nSubset in getPowerSet(nElements):
                for dSubset in getPowerSet(dElements):
                    #debugPrint( '))) nSubset', list( nSubset ) )
                    #debugPrint( '))) dSubset', list( dSubset ) )

                    nSubsetUnit = RPNUnits('*'.join(sorted(list(nSubset))))
                    dSubsetUnit = RPNUnits('*'.join(sorted(list(dSubset))))

                    #debugPrint( '1 nSubset', dSubsetUnit )
                    #debugPrint( '2 dSubset', dSubsetUnit )

                    if nSubsetUnit.getDimensions(
                    ) == dSubsetUnit.getDimensions():
                        debugPrint('dimensions matched',
                                   dSubsetUnit.getDimensions())
                        newNSubset = []
                        newDSubset = []

                        for nUnit in nSubset:
                            baseUnit = g.basicUnitTypes[getUnitType(
                                nUnit)].baseUnit

                            if nUnit != baseUnit:
                                debugPrint('conversion added:', nUnit,
                                           baseUnit)
                                conversionsNeeded.append((nUnit, baseUnit))

                            newNSubset.append(baseUnit)

                        for dUnit in dSubset:
                            baseUnit = g.basicUnitTypes[getUnitType(
                                dUnit)].baseUnit

                            if dUnit != baseUnit:
                                debugPrint('conversion added:', dUnit,
                                           baseUnit)
                                conversionsNeeded.append((dUnit, baseUnit))

                            newDSubset.append(baseUnit)

                        # This maybe isn't quite what we want.
                        debugPrint('conversions added',
                                   '*'.join(sorted(newNSubset)),
                                   '*'.join(sorted(newDSubset)))
                        conversionsNeeded.append(
                            ('*'.join(sorted(newNSubset)),
                             '*'.join(sorted(newDSubset))))
                        matchFound = True

                        for nUnit in nSubset:
                            #print( 'nOriginalElements', nOriginalElements, 'n', nUnit )
                            nOriginalElements.remove(nUnit)
                            changed = True

                        for dUnit in dSubset:
                            #print( 'dOriginalElements', dOriginalElements, 'd', dUnit )
                            dOriginalElements.remove(dUnit)
                            changed = True

                        break

                if matchFound:
                    break

            if matchFound:
                break

        debugPrint('final nElements', nOriginalElements)
        debugPrint('final dElements', dOriginalElements)

        # convert the value based on all the conversions we queued up
        convertedValue = self.value

        if not g.unitConversionMatrix:
            loadUnitConversionMatrix()

        for i in conversionsNeeded:
            if i in g.unitConversionMatrix:
                convertedValue = fmul(convertedValue,
                                      g.unitConversionMatrix[i])
            else:
                convertedValue = fmul(
                    convertedValue,
                    RPNMeasurement(1, i[0]).convertValue(i[1]))

        # build up the resulting units
        units = RPNUnits('*'.join(nOriginalElements))
        denominator = RPNUnits('*'.join(dOriginalElements))

        for dUnit in denominator:
            units[dUnit] += denominator[dUnit] * -1

        debugPrint('normalizeUnits final units', units)
        debugPrint()

        if units and units != RPNUnits():
            result = RPNMeasurement(convertedValue, units)

            if changed:
                return result.normalizeUnits()

            return result

        return convertedValue

    def update(self, units):
        for i in self.units:
            del self.units[i]

        if not isinstance(units, dict):
            raise ValueError('dict expected')

        self.units.update(units)

    def isCompatible(self, other):
        if isinstance(other, str):
            return self.isCompatible(RPNMeasurement(1, other))

        if isinstance(other, RPNUnits):
            return self.getUnitTypes() == other.getUnitTypes()

        if isinstance(other, RPNMeasurement):
            debugPrint('isCompatible -----------------------')
            debugPrint('units: ', self.units, other.units)
            debugPrint('types: ', self.getUnitTypes(), other.getUnitTypes())
            debugPrint('dimensions: ', self.getDimensions(),
                       other.getDimensions())
            debugPrint()

            if self.getUnitTypes() == other.getUnitTypes():
                return True

            if self.getDimensions() == other.getDimensions():
                return True

            debugPrint('RPNMeasurement.isCompatible exiting with false...')
            return False

        if isinstance(other, list):
            result = True

            for item in other:
                result = self.isCompatible(item)

                if not result:
                    break

            return result

        raise ValueError('RPNMeasurement or dict expected')

    def getUnitName(self):
        return self.units.getUnitString()

    def getPluralUnitName(self):
        unitString = self.getUnitName()

        if unitString in g.unitOperators:
            return g.unitOperators[unitString].plural

        return unitString

    def getUnitTypes(self):
        types = RPNUnits()

        for unit in self.units:
            if unit == '1':
                continue

            if unit not in g.unitOperators:
                raise ValueError('1 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 doDimensionsCancel(self):
        return self.units.doDimensionsCancel()

    def convertToPrimitiveUnits(self):
        debugPrint()
        debugPrint('convertToPrimitiveUnits:', self.value, self.units)

        if not g.unitConversionMatrix:
            loadUnitConversionMatrix()

        value = self.value
        units = RPNUnits()

        debugPrint('value', value)

        for unit in self.units:

            if self.units[unit] == 0:
                continue

            newUnits = g.basicUnitTypes[getUnitType(unit)].primitiveUnit

            debugPrint('unit', unit, 'newUnits', newUnits)

            if unit != newUnits:
                debugPrint('unit vs newUnits:', unit, newUnits)

                if (unit, newUnits) in g.unitConversionMatrix:
                    value = fmul(
                        value,
                        power(mpf(g.unitConversionMatrix[(unit, newUnits)]),
                              self.units[unit]))
                elif (unit, newUnits) in specialUnitConversionMatrix:
                    value = power(
                        specialUnitConversionMatrix[(unit, newUnits)](value),
                        self.units[unit])
                else:
                    if unit == '1' and newUnits == '_null_unit':
                        reduced = RPNMeasurement(value, units)
                        debugPrint('convertToPrimitiveUnits 2:', reduced.value,
                                   reduced.units)
                        return reduced

                    raise ValueError('cannot find a conversion for ' + unit +
                                     ' and ' + newUnits)

            newUnits = RPNUnits(newUnits)

            for newUnit in newUnits:
                newUnits[newUnit] *= self.units[unit]

            units.update(newUnits)

            debugPrint('value', value)

        baseUnits = RPNMeasurement(value, units)
        debugPrint('convertToPrimitiveUnits 3:', baseUnits.value,
                   baseUnits.units)
        debugPrint()
        return baseUnits

    def simplifyUnits(self):
        '''
        This is a subset of convertToPrimitiveUnits' functionality.  It calls
        convertToPrimitiveUnits( ), but if the value changes (i.e., there's a conversion
        factor needed, then it will ignore the conversion.   The reason we are doing
        this is because we want joules/watt to convert to seconds, but we don't want
        miles/hour to convert to meters/second.
        '''
        originalValue = self.value

        # Try converting to base units, but only keep it if there's no conversion factor.
        baseUnits = self.convertToPrimitiveUnits()

        if baseUnits.value == originalValue:
            result = baseUnits
        else:
            result = self

        # Let's see if we have a base unit type, and use it if we do, since that's a great
        # simplification.  This way you can multiply watts by seconds and get joules.  However,
        # again we don't want a conversion factor.  e.g., meters^3 should not be converted to liters.
        for _, unitTypeInfo in basicUnitTypes.items():
            if result.getUnitName() == unitTypeInfo.primitiveUnit:
                test = RPNMeasurement(result)

                if test.convert(unitTypeInfo.baseUnit).value == result.value:
                    result = RPNMeasurement(originalValue,
                                            unitTypeInfo.baseUnit)

                break

        return result

    def convert(self, other):
        if isinstance(other, RPNMeasurement):
            return RPNMeasurement(self.convertValue(other), other.units)

        if isinstance(other, (RPNUnits, dict, str)):
            measurement = RPNMeasurement(1, other)
            return RPNMeasurement(self.convertValue(measurement),
                                  measurement.units)

        if isinstance(other, mpf):
            measurement = RPNMeasurement(other, 'unity')
            return RPNMeasurement(self.convertValue(measurement),
                                  measurement.units)

        raise ValueError('convert doesn\'t know what to do with this argument')

    def isLarger(self, other):
        newValue = self.convertValue(other.units)
        return newValue > other.value

    def isNotLarger(self, other):
        return not self.isLarger(other)

    def isSmaller(self, other):
        newValue = self.convertValue(other.units)
        return newValue < other.value

    def isNotSmaller(self, other):
        return not self.isSmaller(other)

    def isEqual(self, other):
        newValue = self.convertValue(other.units)
        return newValue == other.value

    def isNotEqual(self, other):
        return not self.isEqual(other)

    def isOfUnitType(self, unitType):
        if self.isCompatible(RPNUnits(g.basicUnitTypes[unitType].baseUnit)):
            return True

        return self.getDimensions() == g.basicUnitTypes[unitType].dimensions

    def validateUnits(self, unitType):
        if not self.isOfUnitType(unitType):
            raise ValueError(unitType + ' unit expected')

    def convertValue(self, other):
        if not isinstance(other, (RPNUnits, RPNMeasurement, str, list)):
            raise ValueError(
                'convertValue must be called with an RPNUnits object, '
                'an RPNMeasurement object, a string or a list of RPNMeasurement'
            )

        if isinstance(other, (str, RPNUnits)):
            other = RPNMeasurement(1, other)

        if not self.isCompatible(other):
            # We can try to convert incompatible units.
            return self.convertIncompatibleUnit(other)

        if not g.unitConversionMatrix:
            loadUnitConversionMatrix()

        # handle list conversions like [ year, day, minute, hour ]
        if isinstance(other, list):
            # a list of length one is treated the same as a single measurement
            if len(other) == 1:
                return self.convertValue(other[0])
            else:
                listToConvert = []

                for i in other:
                    if isinstance(i, str):
                        listToConvert.append(RPNMeasurement(1, i))
                    elif isinstance(i, RPNMeasurement):
                        listToConvert.append(i)
                    else:
                        raise ValueError('we\'ve got something else')

                return self.convertUnitList(listToConvert)

        conversions = []

        value = self.value  # This is what we'll return down below

        # let's look for straightforward conversions
        units1 = self.units
        units2 = other.units

        unit1String = units1.getUnitString()
        unit2String = units2.getUnitString()

        if unit1String == unit2String:
            return value

        exponents = {}

        # look for a straight-up conversion
        if (unit1String, unit2String) in g.unitConversionMatrix:
            value = fmul(
                value,
                mpmathify(g.unitConversionMatrix[(unit1String, unit2String)]))
        elif (unit1String, unit2String) in specialUnitConversionMatrix:
            value = specialUnitConversionMatrix[(unit1String,
                                                 unit2String)](value)
        else:
            # TODO:  Should we just convert to base units regardless?  It would be safer...
            if other.doDimensionsCancel():
                other = other.convertToPrimitiveUnits()
                units2 = other.units
                value = fdiv(value, other.value)

            # otherwise, we need to figure out how to do the conversion
            conversionValue = value

            # if that isn't found, then we need to do the hard work and break the units down
            newUnits1 = RPNUnits(units1)
            newUnits2 = RPNUnits(units2)

            debugPrint('newUnits1:', newUnits1)
            debugPrint('newUnits2:', newUnits2)

            debugPrint()
            debugPrint('iterating through units to match for conversion:')

            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):
                        debugPrint('found a conversion:', unit1, unit2)
                        conversions.append((unit1, unit2))
                        exponents[(unit1, unit2)] = units1[unit1]
                        foundConversion = True
                        break

                if not foundConversion:
                    debugPrint()
                    debugPrint('didn\'t find a conversion, try reducing')
                    debugPrint()
                    reduced = self.convertToPrimitiveUnits()

                    debugPrint('reduced:', self.units, 'becomes',
                               reduced.units)

                    reducedOther = other.convertToPrimitiveUnits()

                    reduced.value = fdiv(reduced.value, reducedOther.value)

                    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):
                        debugPrint('reducing didn\'t help')
                        break

                    return reduced.convertValue(reducedOther)

            debugPrint()

            value = conversionValue

            debugPrint('Iterating through conversions...')

            for conversion in conversions:
                if conversion[0] == conversion[1]:
                    continue  # no conversion needed

                debugPrint('----> conversion', conversion)

                conversionIndex = tuple(conversion)

                if conversionIndex in g.unitConversionMatrix:
                    debugPrint('unit conversion:',
                               g.unitConversionMatrix[tuple(conversion)])
                    debugPrint('exponents', exponents)

                    conversionValue = mpmathify(
                        g.unitConversionMatrix[conversionIndex])
                    conversionValue = power(conversionValue,
                                            exponents[conversionIndex])
                    debugPrint('conversion: ', conversion, conversionValue)

                    debugPrint('value before', value)
                    value = fmul(value, conversionValue)
                    debugPrint('value after', value)
                else:
                    # we're ignoring the exponents, but this works for dBm<->milliwatt, etc.
                    baseUnit = g.basicUnitTypes[getUnitType(
                        conversion[0])].baseUnit

                    conversion1 = (conversion[0], baseUnit)
                    conversion2 = (baseUnit, conversion[1])

                    debugPrint('conversion1', conversion1)
                    debugPrint('conversion2', conversion2)

                    debugPrint('value0', value)

                    if conversion1 in g.unitConversionMatrix:
                        debugPrint('standard conversion 1')
                        value = fmul(
                            value,
                            mpmathify(g.unitConversionMatrix[conversion1]))
                    else:
                        debugPrint('special conversion 1')
                        value = specialUnitConversionMatrix[conversion1](value)

                    debugPrint('value1', value)

                    if conversion2 in g.unitConversionMatrix:
                        debugPrint('standard conversion 2')
                        value = fmul(
                            value,
                            mpmathify(g.unitConversionMatrix[conversion2]))
                    else:
                        debugPrint('special conversion 2')
                        value = specialUnitConversionMatrix[conversion2](value)

                    debugPrint('value2', value)

        debugPrint('convertValue final', value)
        return value

    def convertUnitList(self, other):
        if not isinstance(other, list):
            raise ValueError('convertUnitList expects a list argument')

        result = []

        nonIntegral = False

        for i in range(1, len(other)):
            conversion = g.unitConversionMatrix[(other[i - 1].getUnitName(),
                                                 other[i].getUnitName())]

            if conversion != floor(conversion):
                nonIntegral = True

        if nonIntegral:
            source = self

            for count, measurement in enumerate(other):
                with extradps(2):
                    conversion = source.convertValue(measurement)

                    if count < len(other) - 1:
                        result.append(
                            RPNMeasurement(floor(conversion),
                                           measurement.units))
                        source = RPNMeasurement(chop(frac(conversion)),
                                                measurement.units)
                    else:
                        result.append(
                            RPNMeasurement(conversion, measurement.units))

            return result
        else:
            source = self.convert(other[-2])

            with extradps(2):
                result.append(source.getModulo(other[-2]).convert(other[-1]))

            source = source.subtract(result[-1])

            for i in range(len(other) - 2, 0, -1):
                source = source.convert(other[i - 1])

                with extradps(2):
                    result.append(
                        source.getModulo(other[i - 1]).convert(other[i]))

                source = source.subtract(result[-1])

            result.append(source)

            return result[::-1]

    def convertIncompatibleUnit(self, other):
        if isinstance(other, list):
            otherUnit = '[ ' + ', '.join(
                [unit.getUnitString() for unit in other]) + ' ]'
        else:
            otherUnit = other.getUnitName()

        # last chance check, some units are reciprocals of each other
        unit = self.getUnitName()

        try:
            baseUnit1 = g.basicUnitTypes[getUnitType(unit)].baseUnit
            baseUnit2 = g.basicUnitTypes[getUnitType(otherUnit)].baseUnit
        except ValueError:
            inverted = self.getInverted()

            if inverted.isCompatible(other):
                return inverted.convert(other)
            else:
                raise ValueError('incompatible units cannot be converted: ' +
                                 self.getUnitName() + ' and ' + otherUnit)

        if (baseUnit1, baseUnit2) in specialUnitConversionMatrix:
            # debugPrint( '----->', self.getUnitName( ), baseUnit1, baseUnit2, otherUnit )
            result = RPNMeasurement(self)

            if unit != baseUnit1:
                result = self.convert(baseUnit1)

            result = RPNMeasurement(
                specialUnitConversionMatrix[(baseUnit1,
                                             baseUnit2)](result.value),
                baseUnit2)

            if baseUnit2 != otherUnit:
                result = result.convert(otherUnit)

            return result

        raise ValueError('incompatible units cannot be converted: ' +
                         self.getUnitName() + ' and ' + otherUnit)
Exemple #14
0
    def normalizeUnits( self ):
        units = self.units.normalizeUnits( )

        debugPrint( )
        debugPrint( 'normalize', units )

        # look for units that cancel between the numerator and denominator
        numerator, denominator = units.splitUnits( )

        # if we don't have a numerator or denominator, we're done
        if not numerator or not denominator:
            return RPNMeasurement( self.value, units )

        if not g.basicUnitTypes:
            loadUnitData( )

        debugPrint( 'numerator', numerator )
        debugPrint( 'denominator', denominator )

        nOriginalElements = sorted( list( numerator.elements( ) ) )
        dOriginalElements = sorted( list( denominator.elements( ) ) )

        changed = False

        nElements = [ ]

        for n in numerator:
            for i in range( min( numerator[ n ], 3 ) ):
                nElements.append( n )

        dElements = [ ]

        for d in denominator:
            for i in range( min( denominator[ d ], 3 ) ):
                dElements.append( d )

        debugPrint( 'nOriginalElements', nOriginalElements )
        debugPrint( 'nElements', nElements )

        debugPrint( 'dOriginalElements', dOriginalElements )
        debugPrint( 'dElements', dElements )

        matchFound = True   # technically not true yet, but it gets us into the loop

        conversionsNeeded = [ ]

        while matchFound:
            matchFound = False

            for nSubset in getPowerset( nElements ):
                for dSubset in getPowerset( dElements ):
                    #debugPrint( '))) nSubset', list( nSubset ) )
                    #debugPrint( '))) dSubset', list( dSubset ) )

                    nSubsetUnit = RPNUnits( '*'.join( sorted( list( nSubset ) ) ) )
                    dSubsetUnit = RPNUnits( '*'.join( sorted( list( dSubset ) ) ) )

                    #debugPrint( '1 nSubset', dSubsetUnit )
                    #debugPrint( '2 dSubset', dSubsetUnit )

                    if nSubsetUnit.getDimensions( ) == dSubsetUnit.getDimensions( ):
                        debugPrint( 'dimensions matched', dSubsetUnit.getDimensions( ) )
                        newNSubset = [ ]
                        newDSubset = [ ]

                        for n in nSubset:
                            baseUnit = g.basicUnitTypes[ getUnitType( n ) ].baseUnit

                            if n != baseUnit:
                                debugPrint( 'conversion added:', n, baseUnit )
                                conversionsNeeded.append( ( n, baseUnit ) )

                            newNSubset.append( baseUnit )

                        for d in dSubset:
                            baseUnit = g.basicUnitTypes[ getUnitType( d ) ].baseUnit

                            if d != baseUnit:
                                debugPrint( 'conversion added:', d, baseUnit )
                                conversionsNeeded.append( ( d, baseUnit ) )

                            newDSubset.append( baseUnit )

                        # TODO:  This maybe isn't quite what we want.
                        debugPrint( 'conversions added', '*'.join( sorted( newNSubset ) ),
                                                         '*'.join( sorted( newDSubset ) ) )
                        conversionsNeeded.append( ( '*'.join( sorted( newNSubset ) ),
                                                    '*'.join( sorted( newDSubset ) ) ) )
                        matchFound = True

                        for n in nSubset:
                            #print( 'nOriginalElements', nOriginalElements, 'n', n )
                            nOriginalElements.remove( n )
                            changed = True

                        for d in dSubset:
                            #print( 'dOriginalElements', dOriginalElements, 'd', d )
                            dOriginalElements.remove( d )
                            changed = True

                        break

                if matchFound:
                    break

            if matchFound:
                break

        debugPrint( 'final nElements', nOriginalElements )
        debugPrint( 'final dElements', dOriginalElements )

        # convert the value based on all the conversions we queued up
        convertedValue = self.value

        if not g.unitConversionMatrix:
            loadUnitConversionMatrix( )

        for i in conversionsNeeded:
            if i in g.unitConversionMatrix:
                convertedValue = fmul( convertedValue, g.unitConversionMatrix[ i ] )
            else:
                convertedValue = fmul( convertedValue, RPNMeasurement( 1, i[ 0 ] ).convertValue( i[ 1 ] ) )

        # build up the resulting units
        units = RPNUnits( '*'.join( nOriginalElements ) )
        denominator = RPNUnits( '*'.join( dOriginalElements ) )

        for d in denominator:
            units[ d ] += denominator[ d ] * -1

        debugPrint( 'normalizeUnits final units', units )
        debugPrint( )

        if units:
            result = RPNMeasurement( convertedValue, units )

            if changed:
                return result.normalizeUnits( )
            else:
                return result
        else:
            return convertedValue
Exemple #15
0
class RPNMeasurement( object ):
    '''This class represents a measurement, which includes a numerical value
    and an RPNUnits instance.'''
    value = mpf( )
    units = RPNUnits( )

    def __init__( self, value, units = RPNUnits( ) ):
        if isinstance( value, str ):
            self.value = mpmathify( value )
            self.units = RPNUnits( units )
        elif isinstance( value, RPNMeasurement ):
            self.value = value.value
            self.units = RPNUnits( value.units )
        else:
            self.value = value
            self.units = RPNUnits( units )

    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 self.__eq__( other )

    def __lt__( self, other ):
        return self.isSmaller( other )

    def __gt__( self, other ):
        return self.isLarger( other )

    def __ge__( self, other ):
        return not self.isSmaller( other )

    def __le__( self, other ):
        return not self.isLarger( other )

    #def __repr__( self ):
    #    return 'RPNMeasurement(\'' + str( self.value ) + ' ' + \
    #           ( '{}' if self.units is None else '\'' + self.units.getUnitString( ) + '\'' )

    #def __str__( self ):
    #    return repr( self )

    def increment( self, unit, amount = 1 ):
        self.units[ unit ] += amount

    def decrement( self, unit, amount = 1 ):
        increment( self, unit, -amount )

    def add( self, other ):
        if isinstance( other, RPNMeasurement ):
            if self.units == other.units:
                return RPNMeasurement( fadd( self.value, other.value ), self.units )
            else:
                return RPNMeasurement( fadd( self.value, other.convertValue( self ) ), self.units )
        else:
            return RPNMeasurement( fadd( self.value, other ), self.units )

    def subtract( self, other ):
        if isinstance( other, RPNMeasurement ):
            if self.units == other.units:
                return RPNMeasurement( fsub( self.value, other.value ), self.units )
            else:
                return RPNMeasurement( fsub( self.value, other.convertValue( self ) ), self.units )

        else:
            return RPNMeasurement( fsub( self.value, other ), self.units )

    def multiply( self, other ):
        if isinstance( other, RPNMeasurement ):
            factor, newUnits = self.units.combineUnits( other.units )
            return RPNMeasurement( fmul( fmul( self.value, other.value ), factor ), newUnits ).normalizeUnits( )
        else:
            return RPNMeasurement( fmul( self.value, other ), self.units ).normalizeUnits( )

    def divide( self, other ):
        if isinstance( other, RPNMeasurement ):
            factor, newUnits = self.units.combineUnits( other.units.inverted( ) )
            return RPNMeasurement( fmul( fdiv( self.value, other.value ), factor ), newUnits ).normalizeUnits( )
        else:
            return RPNMeasurement( fdiv( self.value, other ), self.units ).normalizeUnits( )

    def getModulo( self, other ):
        if isinstance( other, RPNMeasurement ):
            measurement = RPNMeasurement( self ).convert( other.units )
            measurement.value = fmod( measurement.value, other.value )

            return measurement.normalizeUnits( )
        else:
            return RPNMeasurement( fmod( self.value, other ), self.units ).normalizeUnits( )

    def exponentiate( self, exponent ):
        if ( floor( exponent ) != exponent ):
            raise ValueError( 'cannot raise a measurement to a non-integral power' )

        exponent = int( exponent )
        newValue = power( self.value, exponent )

        for unit in self.units:
            self.units[ unit ] *= exponent

        return RPNMeasurement( newValue, self.units )

    def getRoot( self, operand ):
        if ( floor( operand ) != operand ):
            raise ValueError( 'cannot take a fractional root of a measurement' )

        newUnits = RPNUnits( self.units )

        for unit, exponent in newUnits.items( ):
            if fmod( exponent, operand ) != 0:
                if operand == 2:
                    name = 'square'
                elif operand == 3:
                    name = 'cube'
                else:
                    name = getOrdinalName( operand )

                baseUnits = self.convertToBaseUnits( )

                if ( baseUnits != self ):
                    return baseUnits.getRoot( operand )
                else:
                    raise ValueError( 'cannot take the ' + name + ' root of this measurement: ', self.units )

            newUnits[ unit ] /= operand

        value = root( self.value, operand )

        return RPNMeasurement( value, newUnits ).normalizeUnits( )

    def getInverted( self, invertValue=True ):
        units = self.units

        newUnits = RPNUnits( )

        for unit in units:
            newUnits[ unit ] = -units[ unit ]

        if invertValue:
            return RPNMeasurement( fdiv( 1, self.value ), newUnits )
        else:
            return RPNMeasurement( self.value, newUnits )

    def normalizeUnits( self ):
        units = self.units.normalizeUnits( )

        debugPrint( )
        debugPrint( 'normalize', units )

        # look for units that cancel between the numerator and denominator
        numerator, denominator = units.splitUnits( )

        # if we don't have a numerator or denominator, we're done
        if not numerator or not denominator:
            return RPNMeasurement( self.value, units )

        if not g.basicUnitTypes:
            loadUnitData( )

        debugPrint( 'numerator', numerator )
        debugPrint( 'denominator', denominator )

        nOriginalElements = sorted( list( numerator.elements( ) ) )
        dOriginalElements = sorted( list( denominator.elements( ) ) )

        changed = False

        nElements = [ ]

        for n in numerator:
            for i in range( min( numerator[ n ], 3 ) ):
                nElements.append( n )

        dElements = [ ]

        for d in denominator:
            for i in range( min( denominator[ d ], 3 ) ):
                dElements.append( d )

        debugPrint( 'nOriginalElements', nOriginalElements )
        debugPrint( 'nElements', nElements )

        debugPrint( 'dOriginalElements', dOriginalElements )
        debugPrint( 'dElements', dElements )

        matchFound = True   # technically not true yet, but it gets us into the loop

        conversionsNeeded = [ ]

        while matchFound:
            matchFound = False

            for nSubset in getPowerset( nElements ):
                for dSubset in getPowerset( dElements ):
                    #debugPrint( '))) nSubset', list( nSubset ) )
                    #debugPrint( '))) dSubset', list( dSubset ) )

                    nSubsetUnit = RPNUnits( '*'.join( sorted( list( nSubset ) ) ) )
                    dSubsetUnit = RPNUnits( '*'.join( sorted( list( dSubset ) ) ) )

                    #debugPrint( '1 nSubset', dSubsetUnit )
                    #debugPrint( '2 dSubset', dSubsetUnit )

                    if nSubsetUnit.getDimensions( ) == dSubsetUnit.getDimensions( ):
                        debugPrint( 'dimensions matched', dSubsetUnit.getDimensions( ) )
                        newNSubset = [ ]
                        newDSubset = [ ]

                        for n in nSubset:
                            baseUnit = g.basicUnitTypes[ getUnitType( n ) ].baseUnit

                            if n != baseUnit:
                                debugPrint( 'conversion added:', n, baseUnit )
                                conversionsNeeded.append( ( n, baseUnit ) )

                            newNSubset.append( baseUnit )

                        for d in dSubset:
                            baseUnit = g.basicUnitTypes[ getUnitType( d ) ].baseUnit

                            if d != baseUnit:
                                debugPrint( 'conversion added:', d, baseUnit )
                                conversionsNeeded.append( ( d, baseUnit ) )

                            newDSubset.append( baseUnit )

                        # TODO:  This maybe isn't quite what we want.
                        debugPrint( 'conversions added', '*'.join( sorted( newNSubset ) ),
                                                         '*'.join( sorted( newDSubset ) ) )
                        conversionsNeeded.append( ( '*'.join( sorted( newNSubset ) ),
                                                    '*'.join( sorted( newDSubset ) ) ) )
                        matchFound = True

                        for n in nSubset:
                            #print( 'nOriginalElements', nOriginalElements, 'n', n )
                            nOriginalElements.remove( n )
                            changed = True

                        for d in dSubset:
                            #print( 'dOriginalElements', dOriginalElements, 'd', d )
                            dOriginalElements.remove( d )
                            changed = True

                        break

                if matchFound:
                    break

            if matchFound:
                break

        debugPrint( 'final nElements', nOriginalElements )
        debugPrint( 'final dElements', dOriginalElements )

        # convert the value based on all the conversions we queued up
        convertedValue = self.value

        if not g.unitConversionMatrix:
            loadUnitConversionMatrix( )

        for i in conversionsNeeded:
            if i in g.unitConversionMatrix:
                convertedValue = fmul( convertedValue, g.unitConversionMatrix[ i ] )
            else:
                convertedValue = fmul( convertedValue, RPNMeasurement( 1, i[ 0 ] ).convertValue( i[ 1 ] ) )

        # build up the resulting units
        units = RPNUnits( '*'.join( nOriginalElements ) )
        denominator = RPNUnits( '*'.join( dOriginalElements ) )

        for d in denominator:
            units[ d ] += denominator[ d ] * -1

        debugPrint( 'normalizeUnits final units', units )
        debugPrint( )

        if units:
            result = RPNMeasurement( convertedValue, units )

            if changed:
                return result.normalizeUnits( )
            else:
                return result
        else:
            return convertedValue

    def update( self, units ):
        for i in self.units:
            del self.units[ i ]

        if not isinstance( units, dict ):
            raise ValueError( 'dict expected' )

        self.units.update( units )

    def isCompatible( self, other ):
        if isinstance( other, str ):
            return self.isCompatible( RPNMeasurement( 1, other ) )
        if isinstance( other, RPNUnits ):
            return self.getUnitTypes( ) == other.getUnitTypes( )
        elif isinstance( other, RPNMeasurement ):
            debugPrint( 'isCompatible -----------------------' )
            debugPrint( 'units: ', self.units, other.units )
            debugPrint( 'types: ', self.getUnitTypes( ), other.getUnitTypes( ) )
            debugPrint( 'dimensions: ', self.getDimensions( ), other.getDimensions( ) )
            debugPrint( )

            if self.getUnitTypes( ) == other.getUnitTypes( ):
                return True
            elif self.getDimensions( ) == other.getDimensions( ):
                return True
            else:
                debugPrint( 'RPNMeasurement.isCompatible exiting with false...' )
                return False
        elif isinstance( other, list ):
            result = True

            for item in other:
                result = self.isCompatible( item )

                if not result:
                    break

            return result
        else:
            raise ValueError( 'RPNMeasurement or dict expected' )

    def getUnitName( self ):
        return self.units.getUnitString( )

    def getPluralUnitName( self ):
        unitString = self.getUnitName( )

        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 == '1':
                continue

            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 doDimensionsCancel( self ):
        return self.units.doDimensionsCancel( )

    def convertToBaseUnits( self ):
        debugPrint( )
        debugPrint( 'convertToBaseUnits:', self.value, self.units )

        if not g.unitConversionMatrix:
            loadUnitConversionMatrix( )

        value = self.value
        units = RPNUnits( )

        debugPrint( 'value', value )

        for unit in self.units:
            newUnits = g.basicUnitTypes[ getUnitType( unit ) ].primitiveUnit

            debugPrint( 'unit', unit, 'newUnits', newUnits )

            if unit != newUnits:
                debugPrint( 'unit vs newUnits:', unit, newUnits )

                if ( unit, newUnits ) in g.unitConversionMatrix:
                    value = fmul( value, power( mpf( g.unitConversionMatrix[ ( unit, newUnits ) ] ), self.units[ unit ] ) )
                elif ( unit, newUnits ) in specialUnitConversionMatrix:
                    value = power( specialUnitConversionMatrix[ ( unit, newUnits ) ]( value ), self.units[ unit ] )
                else:
                    if unit == '1' and newUnits == '_null_unit':
                        reduced = RPNMeasurement( value, units )
                        debugPrint( 'convertToBaseUnits 2:', reduced.value, reduced.units )
                        return reduced
                    else:
                        raise ValueError( 'cannot find a conversion for ' + unit + ' and ' + newUnits )

            newUnits = RPNUnits( newUnits )

            for newUnit in newUnits:
                newUnits[ newUnit ] *= self.units[ unit ]

            units.update( newUnits )

            debugPrint( 'value', value )

        baseUnits = RPNMeasurement( value, units )
        debugPrint( 'convertToBaseUnits 3:', baseUnits.value, baseUnits.units )
        debugPrint( )
        return baseUnits

    def convert( self, other ):
        if isinstance( other, RPNMeasurement ):
            return RPNMeasurement( self.convertValue( other ), other.units )
        elif isinstance( other, ( RPNUnits, dict, str ) ):
            measurement = RPNMeasurement( 1, other )
            return RPNMeasurement( self.convertValue( measurement ), measurement.units )
        elif isinstance( other, mpf ):
            measurement = RPNMeasurement( other, 'unity' )
            return RPNMeasurement( self.convertValue( measurement ), measurement.units )
        else:
            raise ValueError( 'convert doesn\'t know what to do with this argument' )

    def isLarger( self, other ):
        newValue = self.convertValue( other.units )
        return ( newValue > other.value )

    def isNotLarger( self, other ):
        return not self.isLarger( other )

    def isSmaller( self, other ):
        newValue = self.convertValue( other.units )
        return ( newValue < other.value )

    def isNotSmaller( self, other ):
        return not self.isSmaller( other )

    def isEqual( self, other ):
        newValue = self.convertValue( other.units )
        return ( newValue == other.value )

    def isNotEqual( self, other ):
        return not self.isEqual( other )

    def isOfUnitType( self, unitType ):
        if self.isCompatible( RPNUnits( g.basicUnitTypes[ unitType ].baseUnit ) ):
            return True

        return self.getDimensions( ) == g.basicUnitTypes[ unitType ].dimensions

    def validateUnits( self, unitType ):
        if not self.isOfUnitType( unitType ):
            raise ValueError( unitType + ' unit expected' )

    def convertValue( self, other ):
        if not isinstance( other, ( RPNUnits, RPNMeasurement, str, list ) ):
            raise ValueError( 'convertValue must be called with an RPNUnits object, an RPNMeasurement object, a string or a list of RPNMeasurement' )

        if isinstance( other, ( str, RPNUnits ) ):
            other = RPNMeasurement( 1, other )

        if not self.isCompatible( other ):
            # We can try to convert incompatible units.
            return self.convertIncompatibleUnit( other )

        if not g.unitConversionMatrix:
            loadUnitConversionMatrix( )

        # handle list conversions like [ year, day, minute, hour ]
        if isinstance( other, list ):
            # a list of length one is treated the same as a single measurement
            if len( other ) == 1:
                return convertValue( other[ 0 ] )
            else:
                listToConvert = [ ]

                for i in other:
                    if isinstance( i, str ):
                        listToConvert.append( RPNMeasurement( 1, i ) )
                    elif isinstance( i, RPNMeasurement ):
                        listToConvert.append( i )
                    else:
                        raise ValueError( 'we\'ve got something else' )

                return self.convertUnitList( listToConvert )

        conversions = [ ]

        value = self.value    # This is what we'll return down below

        # let's look for straightforward conversions
        units1 = self.units
        units2 = other.units

        unit1String = units1.getUnitString( )
        unit2String = units2.getUnitString( )

        if unit1String == unit2String:
            return value

        exponents = { }

        # look for a straight-up conversion
        if ( unit1String, unit2String ) in g.unitConversionMatrix:
            value = fmul( value, mpmathify( g.unitConversionMatrix[ ( unit1String, unit2String ) ] ) )
        elif ( unit1String, unit2String ) in specialUnitConversionMatrix:
            value = specialUnitConversionMatrix[ ( unit1String, unit2String ) ]( value )
        else:
            # TODO:  Should we just convert to base units regardless?  It would be safer...
            if other.doDimensionsCancel( ):
                other = other.convertToBaseUnits( )
                units2 = other.units
                value = fdiv( value, other.value )

            # otherwise, we need to figure out how to do the conversion
            conversionValue = value

            # if that isn't found, then we need to do the hard work and break the units down
            newUnits1 = RPNUnits( units1 )
            newUnits2 = RPNUnits( units2 )

            debugPrint( 'newUnits1:', newUnits1 )
            debugPrint( 'newUnits2:', newUnits2 )

            debugPrint( )
            debugPrint( 'iterating through units to match for conversion:' )

            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 ):
                        debugPrint( 'found a conversion:', unit1, unit2 )
                        conversions.append( ( unit1, unit2 ) )
                        exponents[ ( unit1, unit2 ) ] = units1[ unit1 ]
                        foundConversion = True
                        break

                skip = False

                if not foundConversion:
                    debugPrint( )
                    debugPrint( 'didn\'t find a conversion, try reducing' )
                    debugPrint( )
                    reduced = self.convertToBaseUnits( )

                    debugPrint( 'reduced:', self.units, 'becomes', reduced.units )

                    reducedOther = other.convertToBaseUnits( )

                    reduced.value = fdiv( reduced.value, reducedOther.value )

                    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 ):
                        debugPrint( 'reducing didn\'t help' )
                        break

                    return reduced.convertValue( reducedOther )

            debugPrint( )

            value = conversionValue

            debugPrint( 'Iterating through conversions...' )

            for conversion in conversions:
                if conversion[ 0 ] == conversion[ 1 ]:
                    continue  # no conversion needed

                debugPrint( '----> conversion', conversion )

                conversionIndex = tuple( conversion )

                if conversionIndex in g.unitConversionMatrix:
                    debugPrint( 'unit conversion:', g.unitConversionMatrix[ tuple( conversion ) ] )
                    debugPrint( 'exponents', exponents )

                    conversionValue = mpmathify( g.unitConversionMatrix[ conversionIndex ] )
                    conversionValue = power( conversionValue, exponents[ conversionIndex ] )
                    debugPrint( 'conversion: ', conversion, conversionValue )

                    debugPrint( 'value before', value )
                    value = fmul( value, conversionValue )
                    debugPrint( 'value after', value )
                else:
                    # we're ignoring the exponents, but this works for dBm<->milliwatt, etc.
                    baseUnit = g.basicUnitTypes[ getUnitType( conversion[ 0 ] ) ].baseUnit

                    conversion1 = ( conversion[ 0 ], baseUnit )
                    conversion2 = ( baseUnit, conversion[ 1 ] )

                    debugPrint( 'conversion1', conversion1 )
                    debugPrint( 'conversion2', conversion2 )

                    debugPrint( 'value0', value )

                    if conversion1 in g.unitConversionMatrix:
                        debugPrint( 'standard conversion 1' )
                        value = fmul( value, mpmathify( g.unitConversionMatrix[ conversion1 ] ) )
                    else:
                        debugPrint( 'special conversion 1' )
                        value = specialUnitConversionMatrix[ conversion1 ]( value )

                    debugPrint( 'value1', value )

                    if conversion2 in g.unitConversionMatrix:
                        debugPrint( 'standard conversion 2' )
                        value = fmul( value, mpmathify( g.unitConversionMatrix[ conversion2 ] ) )
                    else:
                        debugPrint( 'special conversion 2' )
                        value = specialUnitConversionMatrix[ conversion2 ]( value )

                    debugPrint( 'value2', value )

        debugPrint( 'convertValue final', value )
        return value

    def convertUnitList( self, other ):
        if not isinstance( other, list ):
            raise ValueError( 'convertUnitList expects a list argument' )

        result = [ ]

        nonIntegral = False

        for i in range( 1, len( other ) ):
            conversion = g.unitConversionMatrix[ ( other[ i - 1 ].getUnitName( ), other[ i ].getUnitName( ) ) ]

            if conversion != floor( conversion ):
                nonIntegral = True

        if nonIntegral:
            source = self

            for count, measurement in enumerate( other ):
                with extradps( 2 ):
                    conversion = source.convertValue( measurement )

                    if count < len( other ) - 1:
                        result.append( RPNMeasurement( floor( conversion ), measurement.units ) )
                        source = RPNMeasurement( chop( frac( conversion ) ), measurement.units )
                    else:
                        result.append( RPNMeasurement( conversion, measurement.units ) )

            return result
        else:
            source = self.convert( other[ -2 ] )

            with extradps( 2 ):
                result.append( source.getModulo( other[ -2 ] ).convert( other[ -1 ] ) )

            source = source.subtract( result[ -1 ] )

            for i in range( len( other ) - 2, 0, -1 ):
                source = source.convert( other[ i - 1 ] )

                with extradps( 2 ):
                    result.append( source.getModulo( other[ i - 1 ] ).convert( other[ i ] ) )

                source = source.subtract( result[ -1 ] )

            result.append( source )

            return result[ : : -1 ]

    def convertIncompatibleUnit( self, other ):
        if isinstance( other, list ):
            otherUnit = '[ ' + ', '.join( [ unit.getUnitString( ) for unit in other ] ) + ' ]'
        else:
            otherUnit = other.getUnitName( )

        # last chance check, some units are reciprocals of each other
        unit = self.getUnitName( )

        try:
            baseUnit1 = g.basicUnitTypes[ getUnitType( unit ) ].baseUnit
            baseUnit2 = g.basicUnitTypes[ getUnitType( otherUnit ) ].baseUnit
        except:
            inverted = self.getInverted( )

            try:
                return inverted.convert( other )
            except:
                raise ValueError( 'incompatible units cannot be converted: ' + self.getUnitName( ) + ' and ' + otherUnit )

        if ( baseUnit1, baseUnit2 ) in specialUnitConversionMatrix:
            debugPrint( '----->', self.getUnitName( ), baseUnit1, baseUnit2, otherUnit )
            result = RPNMeasurement( self )

            if ( unit != baseUnit1 ):
                result = self.convert( baseUnit1 )

            result = RPNMeasurement( specialUnitConversionMatrix[ ( baseUnit1, baseUnit2 ) ]( result.value ), baseUnit2 )

            if ( baseUnit2 != otherUnit ):
                result = result.convert( otherUnit )

            return result

        raise ValueError( 'incompatible units cannot be converted: ' + self.getUnitName( ) + ' and ' + otherUnit )