Example #1
0
    def delUnit(self, symToDelete):
        if symToDelete in self.units:
            unitsToDelete = {symToDelete}

            # Find all units to delete
            foundDependentUnit = True
            while foundDependentUnit:
                foundDependentUnit = False
                for sym, unit in self.units.items():
                    if sym in unitsToDelete: continue
                    for dependencySym in unit.baseUnits.keys():
                        prefix, baseSym = UC_Utils.stripPrefix(
                            self.units, dependencySym)
                        if baseSym in unitsToDelete:
                            unitsToDelete.add(sym)
                            foundDependentUnit = True
                            break

            # Delete all units which need to be deleted
            for sym in unitsToDelete:
                del self.units[sym]
                if sym in self.conversions: del self.conversions[sym]

            return unitsToDelete
        else:
            try:
                unitDefStr = self.getUnitDefinitionStr(symToDelete)
            except:
                raise UC_Common.UnitError(
                    f"Cannot delete '{symToDelete}' - unit does not exist")
            raise UC_Common.UnitError(
                f"Cannot delete '{symToDelete}' - unit contains a prefix: {unitDefStr}"
            )
Example #2
0
def parseUnit(units, conversions, tokens, overwrite=False):
    """
	Convert the next series of tokens into a unit
	@param units: a map of unit symbols to unit objects to be modified
	@param conversions: a map of unit symbols to scale factors to be modified
	@param tokens: a list of tokens
	"""
    baseUnitMap = {}

    # Handle base unit
    sym = UC_Utils.parseSymbol(tokens)
    if not overwrite and (sym in units):
        raise UC_Common.FileFormatError(
            f"Duplicate definition of unit '{sym}'")

    # Handle derived unit
    nextToken = UC_Utils.getNextToken(tokens)
    if nextToken == UC_Common.MAP_DELIMITER:
        scaleFactor = UC_Utils.parseFloat(tokens)
        UC_Utils.getNextToken(tokens, UC_Common.SEP_DELIMITER)
        baseUnitMap = parseBaseUnitMap(tokens)
        conversions[sym] = scaleFactor

    # Handle other tokens
    elif nextToken != UC_Common.END_DELIMITER:
        raise UC_Common.FileFormatError(
            f"Expected delimiter; received '{nextToken}'")

    # Create unit
    units[sym] = UC_Unit.Unit(sym, baseUnitMap)
Example #3
0
def getNextToken(tokens: list = [], expectedToken = None):
	"""
	Get the next token and remove it from the queue
	@param tokens: a list of tokens
	@param expectedToken: the expected token - an error is thrown if the next token
	does not equal the expected token
	@return the next token
	"""

	if not tokens: raise UC_Common.UnitError(f"Expected token; none received")
	token = tokens.pop(0)
	if expectedToken and token != expectedToken: raise UC_Common.UnitError(f"Expected '{expectedToken}'; received '{token}'")
	return token
Example #4
0
def validate(units, conversions, prefixes):
	# Ensure that all units and dependencies are defined
	for unit in units.values():
		for baseUnit in unit.baseUnits.keys():
			prefix, sym = stripPrefix(units, baseUnit)
			if sym not in units: raise UC_Common.UnitError(f"Invalid unit symbol: '{sym}'")
			if prefix and (prefix not in prefixes): raise UC_Common.UnitError(f"Invalid prefix: '{prefix}'")
	
	# Ensure that all conversion units are defined
	for unit in conversions.keys():
		if unit not in units: raise UC_Common.UnitError(f"No unit defined for conversion from: '{unit}'")
	
	# Ensure that there are no cycles in the dependency graph
	topologicalSort(units)
Example #5
0
    def delPrefix(self, symToDelete):
        if symToDelete in self.prefixes:
            unitsToDelete = set()

            # Find all units to delete
            foundDependentUnit = True
            while foundDependentUnit:
                foundDependentUnit = False
                for sym, unit in self.units.items():
                    if sym in unitsToDelete: continue
                    for dependencySym in unit.baseUnits.keys():
                        prefix, baseSym = UC_Utils.stripPrefix(
                            self.units, dependencySym)
                        if prefix == symToDelete or baseSym in unitsToDelete:
                            unitsToDelete.add(sym)
                            foundDependentUnit = True
                            break

            # Delete all units which need to be deleted
            for sym in unitsToDelete:
                del self.units[sym]
                if sym in self.conversions: del self.conversions[sym]

            # Delete from prefix map
            del self.prefixes[symToDelete]

            return unitsToDelete
        else:
            raise UC_Common.UnitError(
                f"Cannot delete '{symToDelete}' - prefix does not exist")
Example #6
0
    def addPrefix(self, sym, base, exp):
        if not UC_Utils.isValidSymbol(sym):
            raise UC_Common.UnitError(
                f"Invalid prefix '{sym}': valid prefix symbols are composed of alphabetical characters and underscores"
            )
        if sym in self.prefixes:
            base, exp = self.prefixes[sym]
            raise UC_Common.UnitError(
                f"Prefix '{sym}' already exists: '{sym}' = {base}^{exp} = {base**exp}"
            )
        else:
            # Try adding prefix
            prefixes = self.prefixes.copy()
            prefixes[sym] = (base, exp)

            # Check that all dependencies exist and check for an acyclic dependency graph
            UC_Utils.validate(self.units, self.conversions, prefixes)
            self.prefixes = prefixes
Example #7
0
def peekNextToken(tokens: list = []):
	"""
	Get the next token without removing it from the queue
	@param tokens: a list of tokens
	@return the next token
	"""

	if not tokens: raise UC_Common.UnitError(f"Expected token; none received")
	return tokens[0]
Example #8
0
def parseInt(tokens):
	"""
	Get the next token as an int and remove it from the queue
	@param tokens: a list of tokens
	@return the next token as an int
	"""
	scaleFactorStr = getNextToken(tokens)
	try: scaleFactor = int(scaleFactorStr)
	except: raise UC_Common.UnitError(f"Expected integer; received '{scaleFactorStr}'")
	return scaleFactor
Example #9
0
def parseSymbol(tokens):
	"""
	Get the next token as a unit symbol and remove it from the queue
	@param tokens: a list of tokens
	@return the next token as a unit symbol
	"""
	sym = getNextToken(tokens)
	if not isValidSymbol(sym):
		raise UC_Common.UnitError(f"Expected alphabetical symbol; received '{sym}'")
	return sym
Example #10
0
def stripPrefix(units, string):
	# Find longest matching suffix
	longestSuffix = ""
	for sym in units.keys():
		if string.endswith(sym) and len(sym) > len(longestSuffix):
			longestSuffix = sym
			if len(longestSuffix) == len(string): break
	
	if len(longestSuffix) == 0: raise UC_Common.UnitError(f"Invalid unit: received '{string}'")
	return string[0:len(string)-len(longestSuffix)], longestSuffix
Example #11
0
def parseFloat(tokens):
	"""
	Get the next token as a float and remove it from the queue
	@param tokens: a list of tokens
	@return the next token as a float
	"""
	scaleFactorStr = getNextToken(tokens)
	try: scaleFactor = Decimal(scaleFactorStr)
	except: raise UC_Common.UnitError(f"Expected float; received '{scaleFactorStr}'")
	return scaleFactor
Example #12
0
def aggregateQuantities(tokens):
    """
	Combine tokens which constitute a quantity
	@param tokens: a list of tokens
	"""
    aggregatedTokens = []
    needsValue = True

    while tokens:
        if UC_Utils.isOperator(tokens[0]):
            if needsValue:
                raise UC_Common.UnitError(
                    f"Expected float; received '{tokens[0]}'")
            aggregatedTokens.append(tokens.pop(0))
            needsValue = True
        elif UC_Utils.isSpecialChar(tokens[0]):
            aggregatedTokens.append(tokens.pop(0))
        else:
            needsValue = False

            # Get value
            quantity = '1'
            try:
                float(tokens[0])
                quantity = tokens.pop(0)
            except:
                # Inject multiplication where needed
                if aggregatedTokens and aggregatedTokens[
                        -1] == UC_Common.BRACKET_SHUT:
                    aggregatedTokens.append(UC_Common.OPERATOR_MUL)

            # Get unit
            unit = []
            if tokens and isinstance(tokens[0], list):
                unit = tokens.pop(0)

            aggregatedTokens.append((quantity, unit))
    if needsValue and aggregatedTokens:
        raise UC_Common.UnitError(f"Expected float; no tokens received")

    return aggregatedTokens
Example #13
0
    def addUnit(self, sym, scaleFactor, unit):
        if not UC_Utils.isValidSymbol(sym):
            raise UC_Common.UnitError(
                f"Invalid symbol '{sym}': valid unit symbols are composed of alphabetical characters and underscores"
            )
        if sym in self.units:
            quantity = self.conversions[sym] if sym in self.conversions else 1
            raise UC_Common.UnitError(
                f"Unit '{sym}' already exists: {self.getUnitDefinitionStr(sym)}"
            )
        else:
            # Try adding unit
            units = self.units.copy()
            units[sym] = UC_Unit.Unit(sym, unit.reduce())
            conversions = self.conversions.copy()
            conversions[sym] = scaleFactor

            # Check that all dependencies exist and check for an acyclic dependency graph
            UC_Utils.validate(units, conversions, self.prefixes)
            self.units = units
            self.conversions = conversions
Example #14
0
def parsePrefixMapping(prefixes, tokens, base, overwrite=False):
    """
	Convert the next series of tokens into a prefix-exponent pair
	@param prefixes: the prefix-exponent map to modify
	@param tokens: a list of tokens
	@param base: the base for the exponent
	"""
    prefix = UC_Utils.getNextToken(tokens)
    if not overwrite and (prefix in prefixes):
        raise UC_Common.FileFormatError(
            f"Duplicate definition of prefix '{prefix}'")
    prefixes[prefix] = (base, Decimal(UC_Utils.parseInt(tokens)))
Example #15
0
def topologicalSortVisit(units: dict, unit: str, sortedValues: list, permVisited: set, tempVisited: set):
	if unit in permVisited: return
	if unit in tempVisited: raise UC_Common.UnitError(f"Dependency cycle detected for unit '{unit}'")
	tempVisited[unit] = True

	for baseUnit in units[unit].baseUnits.keys():
		prefix, sym = stripPrefix(units, baseUnit)
		topologicalSortVisit(units, sym, sortedValues, permVisited, tempVisited)

	del tempVisited[unit]
	permVisited[unit] = True

	sortedValues.insert(0, unit)
Example #16
0
    def processPrefixes(self, units):
        scaleFactor = Decimal(1)
        unitsToUpdate = {}
        for prefixedSym, exp in units.items():
            # Find prefix and base unit
            prefix, baseUnit = UC_Utils.stripPrefix(self.units, prefixedSym)
            if len(prefix) == 0: continue
            if prefix not in self.prefixes:
                raise UC_Common.UnitError(f"Unknown unit: '{prefixedSym}'")
            unitsToUpdate[prefixedSym] = baseUnit

            # Calculate scale factor
            scaleFactor *= self.getPrefixScaleFactor(prefix)**(exp)

        # Update unit map
        for fromSym, toSym in unitsToUpdate.items():
            if not (toSym in units): units[toSym] = 0
            units[toSym] += units[fromSym]
            units.pop(fromSym)

        return scaleFactor
Example #17
0
def aggregateSign(tokens, updatedTokens=[]):
    """
	Replace the negation operator '-' with a multiplication by -1
	@param tokens: a list of tokens
	"""
    def aggregateSignHelper(tokens, updatedTokens):
        while tokens:
            token = tokens.pop(0)
            if token == UC_Common.BRACKET_OPEN:
                updatedTokens.append(token)
                aggregateSignHelper(tokens, updatedTokens)
            elif token == UC_Common.BRACKET_SHUT:
                updatedTokens.append(token)
                return updatedTokens
            elif ((token == UC_Common.OPERATOR_ADD
                   or token == UC_Common.OPERATOR_SUB)
                  and (not updatedTokens
                       or UC_Utils.isSpecialChar(updatedTokens[-1]))):
                if tokens and UC_Utils.isFloat(tokens[0]):
                    updatedTokens.append(f"{token}{tokens.pop(0)}")
                elif not updatedTokens or updatedTokens[
                        -1] != UC_Common.BRACKET_SHUT:
                    updatedTokens.extend([
                        UC_Common.BRACKET_OPEN, f"{token}1",
                        UC_Common.OPERATOR_MUL
                    ])
                    aggregateSignHelper(tokens, updatedTokens)
                    updatedTokens.append(UC_Common.BRACKET_SHUT)
                else:
                    updatedTokens.append(token)
            else:
                updatedTokens.append(token)

    updatedTokens = []
    aggregateSignHelper(tokens, updatedTokens)
    if tokens:
        raise UC_Common.UnitError(
            f"Detected mismatched parentheses: '{UC_Common.BRACKET_SHUT}'")
    return updatedTokens
Example #18
0
def parseExpr(tokens):
    stack = []
    for token in tokens:
        if token == UC_Common.OPERATOR_ADD:
            a = stack.pop()
            b = stack.pop()
            stack.append(UC_AST.AST_Add(b, a))
        elif token == UC_Common.OPERATOR_SUB:
            a = stack.pop()
            b = stack.pop()
            stack.append(UC_AST.AST_Sub(b, a))
        elif token == UC_Common.OPERATOR_MUL:
            a = stack.pop()
            b = stack.pop()
            stack.append(UC_AST.AST_Mul(b, a))
        elif token == UC_Common.OPERATOR_DIV:
            a = stack.pop()
            b = stack.pop()
            stack.append(UC_AST.AST_Div(b, a))
        elif token == UC_Common.OPERATOR_EXP:
            a = stack.pop()
            b = stack.pop()
            stack.append(UC_AST.AST_Exp(b, a))
        elif token == UC_Common.OPERATOR_EQL:
            a = stack.pop()
            b = stack.pop()
            stack.append(UC_AST.AST_Eql(b, a))
        else:
            valStr, unitTokens = token
            val = Decimal(valStr)
            baseUnits = UC_Unit.Unit(baseUnits=parseUnit(unitTokens)).reduce()
            unit = UC_Unit.Unit(baseUnits=baseUnits)
            stack.append(UC_Unit.Quantity(val, unit))
    if not stack: return UC_Unit.Quantity(1, UC_Unit.Unit())
    if len(stack) != 1: raise UC_Common.UnitError("Invalid expression")
    return stack[0]
Example #19
0
    def convert(self, srcUnit, dstUnit):
        srcUnits = srcUnit.reduce()
        dstUnits = dstUnit.reduce()

        # Reduce all units to irreducible
        performedReduction = True
        scaleFactor = 1
        while performedReduction:
            # Simplify units using SI prefixes
            scaleFactor *= self.processPrefixes(srcUnits)
            scaleFactor /= self.processPrefixes(dstUnits)

            # Factor out common units and perform a topological sort to find the next unit to reduce
            self.factorUnits(srcUnits, dstUnits)
            unitsToReduce = UC_Utils.topologicalSort(
                self.units, [*srcUnits.keys()] + [*dstUnits.keys()])

            # Reduce units
            if len(unitsToReduce) == 0: break
            performedReduction = self.units[unitsToReduce[0]].isDerivedUnit()
            if performedReduction:
                if unitsToReduce[0] in srcUnits:
                    scaleFactor *= self.reduceUnit(unitsToReduce[0], srcUnits)
                else:
                    scaleFactor /= self.reduceUnit(unitsToReduce[0], dstUnits)

        # Remove cancelled units
        removeCancelledUnits(srcUnits)
        removeCancelledUnits(dstUnits)

        # Check for conversion error
        if len(srcUnits) > 0 or len(dstUnits) > 0:
            raise UC_Common.UnitError(
                f"Invalid conversion: {str(srcUnit)} to {str(dstUnit)}")

        return scaleFactor
Example #20
0
def parseUnit(tokens):
    tokens = convertToRPN(tokens)

    stack = []
    for token in tokens:
        if token == UC_Common.OPERATOR_ADD:
            a = stack.pop()
            if not isinstance(a, int):
                raise UC_Common.UnitError(f"Expected int; received '{a}'")
            b = stack.pop()
            if not isinstance(b, int):
                raise UC_Common.UnitError(f"Expected int; received '{b}'")
            stack.append(b + a)
        elif token == UC_Common.OPERATOR_SUB:
            a = stack.pop()
            if not isinstance(a, int):
                raise UC_Common.UnitError(f"Expected int; received '{a}'")
            b = stack.pop()
            if not isinstance(b, int):
                raise UC_Common.UnitError(f"Expected int; received '{b}'")
            stack.append(b - a)
        elif token == UC_Common.OPERATOR_MUL:
            a = stack.pop()
            if not isinstance(a, dict): a = {a: 1}
            b = stack.pop()
            if not isinstance(b, dict): b = {b: 1}
            for sym, exp in b.items():
                if sym not in a: a[sym] = 0
                a[sym] += exp
            stack.append(a)
        elif token == UC_Common.OPERATOR_DIV:
            a = stack.pop()
            if not isinstance(a, dict): a = {a: 1}
            b = stack.pop()
            if not isinstance(b, dict): b = {b: 1}
            for sym, exp in a.items():
                if sym not in b: b[sym] = 0
                b[sym] -= exp
            stack.append(b)
        elif token == UC_Common.OPERATOR_EXP:
            a = stack.pop()
            b = stack.pop()
            if not isinstance(a, int):
                raise UC_Common.UnitError(f"Expected int; received '{a}'")
            stack.append({b: a})
        else:
            if UC_Utils.isInt(token): stack.append(int(token))
            else: stack.append(token)

    # Aggregate into a single map
    units = {}
    while stack:
        top = stack.pop()
        if isinstance(top, dict):
            for sym, exp in top.items():
                if sym not in units: units[sym] = 0
                units[sym] += exp
        elif UC_Utils.isValidSymbol(top):
            if top not in units: units[top] = 0
            units[top] += 1
        else:
            raise UC_Common.UnitError("Invalid expression")
    return units
Example #21
0
def aggregateUnits(tokens):
    """
	Combine tokens which constitute compound units
	@param tokens: a list of tokens
	"""
    aggregatedTokens = []
    unitTokens = []
    parsingExp = 0

    def appendUnitTokens(aggregatedTokens, unitTokens, token=None):
        # Append unit tokens to list of aggregated tokens
        if unitTokens:
            if unitTokens[-1] == UC_Common.OPERATOR_MUL or unitTokens[
                    -1] == UC_Common.OPERATOR_DIV:
                operator = unitTokens.pop()
                aggregatedTokens.append(unitTokens)
                aggregatedTokens.append(operator)
            else:
                aggregatedTokens.append(unitTokens)
        if token is not None:
            # Inject multiplication if needed
            if ((aggregatedTokens)
                    and (not UC_Utils.isSpecialChar(aggregatedTokens[-1]))
                    and (token == UC_Common.BRACKET_OPEN)):
                aggregatedTokens.append(UC_Common.OPERATOR_MUL)
            aggregatedTokens.append(token)
        return []

    def handleParseExpDecrement(tokens, unitTokens, parsingExp):
        # Check if multiplication needs to be injected between adjacent units
        if parsingExp != 1: return parsingExp
        if tokens:
            if tokens[0] == UC_Common.OPERATOR_MUL or tokens[
                    0] == UC_Common.OPERATOR_DIV:
                unitTokens.append(tokens.pop(0))
            elif UC_Utils.isValidSymbol(tokens[0]):
                unitTokens.append(UC_Common.OPERATOR_MUL)
        return 0

    def handleAppendUnitSymbol(tokens, unitTokens, parsingExp):
        if tokens:
            token = tokens[0]
            if token == UC_Common.OPERATOR_EXP:
                unitTokens.append(tokens.pop(0))
                return 1
            elif token == UC_Common.OPERATOR_MUL or token == UC_Common.OPERATOR_DIV:
                unitTokens.append(tokens.pop(0))
            elif UC_Utils.isValidSymbol(token):
                unitTokens.append(UC_Common.OPERATOR_MUL)
        return parsingExp

    while tokens:
        token = UC_Utils.getNextToken(tokens)
        if token == UC_Common.BRACKET_OPEN:
            if parsingExp:
                unitTokens.append(token)
                parsingExp += 1
            else:
                unitTokens = appendUnitTokens(aggregatedTokens, unitTokens,
                                              token)
        elif token == UC_Common.BRACKET_SHUT:
            if parsingExp:
                unitTokens.append(token)
                parsingExp = handleParseExpDecrement(tokens, unitTokens,
                                                     parsingExp - 1)
            else:
                unitTokens = appendUnitTokens(aggregatedTokens, unitTokens,
                                              token)
        elif UC_Utils.isFloat(token):
            if parsingExp:
                if not UC_Utils.isInt(token):
                    raise UC_Common.UnitError(
                        f"Expected int; received '{token}'")
                unitTokens.append(token)
                parsingExp = handleParseExpDecrement(tokens, unitTokens,
                                                     parsingExp)
            else:
                unitTokens = appendUnitTokens(aggregatedTokens, unitTokens,
                                              token)
        elif UC_Utils.isValidSymbol(token):
            if parsingExp:
                raise UC_Common.UnitError(f"Expected int; received '{token}'")
            unitTokens.append(token)
            parsingExp = handleAppendUnitSymbol(tokens, unitTokens, parsingExp)
        elif UC_Utils.isOperator(token):
            if parsingExp:
                raise UC_Common.UnitError(f"Expected int; received '{token}'")
            else:
                unitTokens = appendUnitTokens(aggregatedTokens, unitTokens,
                                              token)
        else:
            raise UC_Common.UnitError(f"Unknown token; received '{token}'")

    appendUnitTokens(aggregatedTokens, unitTokens)
    return aggregatedTokens
Example #22
0
 def __sub__(self, other):
     if self.unit != other.unit:
         raise UC_Common.UnitError('Incompatible units')
     return Quantity(self.value - other.value, self.unit.clone())
Example #23
0
 def __pow__(self, other):
     if other.unit.reduce():
         raise UC_Common.UnitError(
             f"Cannot exponentiate with unit '{str(other.unit)}'")
     return Quantity(self.value**other.value, self.unit**other.value)