def _get_unit_data_from_expr(unit_expr, unit_symbol_lut): """ Grabs the total base_value and dimensions from a valid unit expression. Parameters ---------- unit_expr: Unit object, or sympy Expr object The expression containing unit symbols. unit_symbol_lut: dict Provides the unit data for each valid unit symbol. """ # The simplest case first if isinstance(unit_expr, Unit): return (unit_expr.base_value, unit_expr.dimensions) # Now for the sympy possibilities if isinstance(unit_expr, Symbol): return _lookup_unit_symbol(str(unit_expr), unit_symbol_lut) if isinstance(unit_expr, Number): return (float(unit_expr), sympy_one) if isinstance(unit_expr, Pow): unit_data = _get_unit_data_from_expr(unit_expr.args[0], unit_symbol_lut) power = unit_expr.args[1] if isinstance(power, Symbol): raise UnitParseError("Invalid unit expression '%s'." % unit_expr) conv = float(unit_data[0]**power) unit = unit_data[1]**power return (conv, unit) if isinstance(unit_expr, Mul): base_value = 1.0 dimensions = 1 for expr in unit_expr.args: unit_data = _get_unit_data_from_expr(expr, unit_symbol_lut) base_value *= unit_data[0] dimensions *= unit_data[1] return (float(base_value), dimensions) raise UnitParseError("Cannot parse for unit data from '%s'. Please supply" \ " an expression of only Unit, Symbol, Pow, and Mul" \ "objects." % str(unit_expr))
def validate_dimensions(dimensions): if isinstance(dimensions, Mul): for dim in dimensions.args: validate_dimensions(dim) elif isinstance(dimensions, Symbol): if dimensions not in base_dimensions: raise UnitParseError("Dimensionality expression contains an " "unknown symbol '%s'." % dimensions) elif isinstance(dimensions, Pow): if not isinstance(dimensions.args[1], Number): raise UnitParseError("Dimensionality expression '%s' contains a " "unit symbol as a power." % dimensions) elif isinstance(dimensions, (Add, Number)): if not isinstance(dimensions, One): raise UnitParseError("Only dimensions that are instances of Pow, " "Mul, or symbols in the base dimensions are " "allowed. Got dimensions '%s'" % dimensions) elif not isinstance(dimensions, Basic): raise UnitParseError("Bad dimensionality expression '%s'." % dimensions)
def _lookup_unit_symbol(symbol_str, unit_symbol_lut): """ Searches for the unit data tuple corresponding to the given symbol. Parameters ---------- symbol_str : str The unit symbol to look up. unit_symbol_lut : dict Dictionary with symbols as keys and unit data tuples as values. """ if symbol_str in unit_symbol_lut: # lookup successful, return the tuple directly return unit_symbol_lut[symbol_str] # could still be a known symbol with a prefix possible_prefix = symbol_str[0] if possible_prefix in unit_prefixes: # the first character could be a prefix, check the rest of the symbol symbol_wo_prefix = symbol_str[1:] unit_is_si_prefixable = (symbol_wo_prefix in unit_symbol_lut and symbol_wo_prefix in prefixable_units) if unit_is_si_prefixable is True: # lookup successful, it's a symbol with a prefix unit_data = unit_symbol_lut[symbol_wo_prefix] prefix_value = unit_prefixes[possible_prefix] if possible_prefix in latex_prefixes: latex_repr = symbol_str.replace( possible_prefix, '{'+latex_prefixes[possible_prefix]+'}') else: # Need to add some special handling for comoving units # this is fine for now, but it wouldn't work for a general # unit that has an arbitrary LaTeX representation if symbol_wo_prefix != 'cm' and symbol_wo_prefix.endswith('cm'): sub_symbol_wo_prefix = symbol_wo_prefix[:-2] sub_symbol_str = symbol_str[:-2] else: sub_symbol_wo_prefix = symbol_wo_prefix sub_symbol_str = symbol_str latex_repr = unit_data[3].replace( '{' + sub_symbol_wo_prefix + '}', '{' + sub_symbol_str + '}') # Leave offset and dimensions the same, but adjust scale factor and # LaTeX representation ret = (unit_data[0] * prefix_value, unit_data[1], unit_data[2], latex_repr) unit_symbol_lut[symbol_str] = ret return ret # no dice if symbol_str.startswith('code_'): raise UnitParseError( "Code units have not been defined. \n" "Try creating the array or quantity using ds.arr or ds.quan instead.") else: raise UnitParseError("Could not find unit symbol '%s' in the provided " \ "symbols." % symbol_str)
def __new__(cls, unit_expr=sympy_one, base_value=None, base_offset=0.0, dimensions=None, registry=None, latex_repr=None, **assumptions): """ Create a new unit. May be an atomic unit (like a gram) or combinations of atomic units (like g / cm**3). Parameters ---------- unit_expr : Unit object, sympy.core.expr.Expr object, or str The symbolic unit expression. base_value : float The unit's value in yt's base units. base_offset : float The offset necessary to normalize temperature units to a common zero point. dimensions : sympy.core.expr.Expr A sympy expression representing the dimensionality of this unit. It must contain only mass, length, time, temperature and angle symbols. registry : UnitRegistry object The unit registry we use to interpret unit symbols. latex_repr : string A string to render the unit as LaTeX Additional keyword arguments are passed as assumptions to the Sympy Expr initializer """ # Simplest case. If user passes a Unit object, just use the expr. unit_key = None if isinstance(unit_expr, (str, bytes, text_type)): if isinstance(unit_expr, bytes): unit_expr = unit_expr.decode("utf-8") if registry and unit_expr in registry.unit_objs: return registry.unit_objs[unit_expr] else: unit_key = unit_expr if not unit_expr: # Bug catch... # if unit_expr is an empty string, parse_expr fails hard... unit_expr = "1" try: unit_expr = parse_expr(unit_expr, global_dict=global_dict, transformations=unit_text_transform) except SyntaxError as e: msg = ("Unit expression %s raised an error " "during parsing:\n%s" % (unit_expr, repr(e))) raise UnitParseError(msg) elif isinstance(unit_expr, Unit): # grab the unit object's sympy expression. unit_expr = unit_expr.expr # Make sure we have an Expr at this point. if not isinstance(unit_expr, Expr): raise UnitParseError("Unit representation must be a string or " \ "sympy Expr. %s has type %s." \ % (unit_expr, type(unit_expr))) if unit_expr == sympy_one and dimensions is None: dimensions = dimensionless if registry is None: # Caller did not set the registry, so use the default. registry = default_unit_registry # done with argument checking... # see if the unit is atomic. is_atomic = False if isinstance(unit_expr, Symbol): is_atomic = True # # check base_value and dimensions # if base_value is not None: # check that base_value is a float or can be converted to one try: base_value = float(base_value) except ValueError: raise UnitParseError("Could not use base_value as a float. " \ "base_value is '%s' (type %s)." \ % (base_value, type(base_value)) ) # check that dimensions is valid if dimensions is not None: validate_dimensions(dimensions) else: # lookup the unit symbols unit_data = _get_unit_data_from_expr(unit_expr, registry.lut) base_value = unit_data[0] dimensions = unit_data[1] if len(unit_data) > 2: base_offset = unit_data[2] latex_repr = unit_data[3] else: base_offset = 0.0 # Create obj with superclass construct. obj = Expr.__new__(cls, **assumptions) # Attach attributes to obj. obj.expr = unit_expr obj.is_atomic = is_atomic obj.base_value = base_value obj.base_offset = base_offset obj.dimensions = dimensions obj._latex_repr = latex_repr obj.registry = registry if unit_key is not None: registry.unit_objs[unit_key] = obj # Return `obj` so __init__ can handle it. return obj