def add(self, symbol, base_value, dimensions, tex_repr=None, offset=None): """ Add a symbol to this registry. """ from unyt.unit_object import _validate_dimensions self._unit_system_id = None # Validate if not isinstance(base_value, float): raise UnitParseError("base_value (%s) must be a float, got a %s." % (base_value, type(base_value))) if offset is not None: if not isinstance(offset, float): raise UnitParseError( "offset value (%s) must be a float, got a %s." % (offset, type(offset))) else: offset = 0.0 _validate_dimensions(dimensions) if tex_repr is None: # make educated guess that will look nice in most cases tex_repr = r"\rm{" + symbol.replace('_', '\ ') + "}" # Add to lut self.lut.update({symbol: (base_value, dimensions, offset, tex_repr)})
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 add(self, symbol, base_value, dimensions, tex_repr=None, offset=None, prefixable=False): """ Add a symbol to this registry. Parameters ---------- symbol : str The name of the unit base_value : float The scaling from the units value to the equivalent SI unit with the same dimensions dimensions : expr The dimensions of the unit tex_repr : str, optional The LaTeX representation of the unit. If not provided a LaTeX representation is automatically generated from the name of the unit. offset : float, optional If set, the zero-point offset to apply to the unit to convert to SI. This is mostly used for units like Farhenheit and Celcius that are not defined on an absolute scale. prefixable : bool If True, then SI-prefix versions of the unit will be created along with the unit itself. """ from unyt.unit_object import _validate_dimensions self._unit_system_id = None # Validate if not isinstance(base_value, float): raise UnitParseError("base_value (%s) must be a float, got a %s." % (base_value, type(base_value))) if offset is not None: if not isinstance(offset, float): raise UnitParseError( "offset value (%s) must be a float, got a %s." % (offset, type(offset))) else: offset = 0.0 _validate_dimensions(dimensions) if tex_repr is None: # make educated guess that will look nice in most cases tex_repr = r"\rm{" + symbol.replace('_', '\ ') + "}" # Add to lut self.lut[symbol] = (base_value, dimensions, offset, tex_repr, prefixable)
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. """ # Now for the sympy possibilities if isinstance(unit_expr, Number): if unit_expr is sympy_one: return (1.0, sympy_one) return (float(unit_expr), sympy_one) if isinstance(unit_expr, Symbol): return _lookup_unit_symbol(unit_expr.name, unit_symbol_lut) 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 _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 prefix, symbol_wo_prefix = _split_prefix(symbol_str, unit_symbol_lut) if prefix: # lookup successful, it's a symbol with a prefix unit_data = unit_symbol_lut[symbol_wo_prefix] prefix_value = unit_prefixes[prefix][0] # 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, False, ) unit_symbol_lut[symbol_str] = ret return ret # no dice raise UnitParseError( "Could not find unit symbol '%s' in the provided " "symbols." % symbol_str )
def parse_unyt_expr(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 Exception as e: msg = "Unit expression '%s' raised an error during parsing:\n%s" % ( unit_expr, repr(e), ) raise UnitParseError(msg) return unit_expr
def parse_unyt_expr(unit_expr): if not unit_expr: # Bug catch... # if unit_expr is an empty string, parse_expr fails hard... unit_expr = "1" # Avoid a parse error if someone uses the percent unit and the # parser tries to interpret it as the modulo operator unit_expr = unit_expr.replace("%", "percent") try: unit_expr = parse_expr(unit_expr, global_dict=global_dict, transformations=unit_text_transform) except Exception as e: msg = "Unit expression '%s' raised an error during parsing:\n%s" % ( unit_expr, repr(e), ) raise UnitParseError(msg) return unit_expr
def __new__( cls, unit_expr=sympy_one, base_value=None, base_offset=0.0, dimensions=None, registry=None, latex_repr=None, ): """ 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 """ unit_cache_key = None # Simplest case. If user passes a Unit object, just use the expr. if hasattr(unit_expr, "is_Unit"): # grab the unit object's sympy expression. unit_expr = unit_expr.expr elif hasattr(unit_expr, "units") and hasattr(unit_expr, "value"): # something that looks like a unyt_array, grab the unit and value if unit_expr.shape != (): raise UnitParseError( "Cannot create a unit from a non-scalar unyt_array, " "received: %s" % (unit_expr,) ) value = unit_expr.value if value == 1: unit_expr = unit_expr.units.expr else: unit_expr = unit_expr.value * unit_expr.units.expr # Parse a text unit representation using sympy's parser elif isinstance(unit_expr, (str, bytes)): if isinstance(unit_expr, bytes): unit_expr = unit_expr.decode("utf-8") # this cache substantially speeds up unit conversions if registry and unit_expr in registry._unit_object_cache: return registry._unit_object_cache[unit_expr] unit_cache_key = unit_expr unit_expr = parse_unyt_expr(unit_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 dimensions is None and unit_expr is sympy_one: 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 = super(Unit, cls).__new__(cls) # 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 # lets us avoid isinstance calls obj.is_Unit = True # if we parsed a string unit expression, cache the result # for faster lookup later if unit_cache_key is not None: registry._unit_object_cache[unit_cache_key] = obj # Return `obj` so __init__ can handle it. return obj
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 symbol_str[:2] == 'da': possible_prefix = 'da' if possible_prefix in unit_prefixes: # the first character could be a prefix, check the rest of the symbol symbol_wo_pref = symbol_str[1:] # deca is the only prefix with length 2 if symbol_str[:2] == 'da': symbol_wo_pref = symbol_str[2:] possible_prefix = 'da' prefixable_units = [ u for u in unit_symbol_lut if unit_symbol_lut[u][4] ] unit_is_si_prefixable = (symbol_wo_pref in unit_symbol_lut and symbol_wo_pref 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_pref] 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_pref != 'cm' and symbol_wo_pref.endswith('cm'): sub_symbol_wo_prefix = symbol_wo_pref[:-2] sub_symbol_str = symbol_str[:-2] else: sub_symbol_wo_prefix = symbol_wo_pref 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, False) unit_symbol_lut[symbol_str] = ret return ret # no dice raise UnitParseError("Could not find unit symbol '%s' in the provided " "symbols." % symbol_str)