def inout_map(arg, unit_info, name=None): if unit_info is None: return arg use_units_msg = (" Make sure you're passing in a unitful value, either as a string or by " "using `instrumental.u` or `instrumental.Q_()`") optional, units = unit_info if optional and arg is None: return None elif arg == 0: # Allow naked zeroes as long as we're using absolute units (e.g. not degF) # It's a bit dicey using this private method; works in 0.6 at least if units._ok_for_muldiv(): return Q_(arg, units) else: if name is not None: extra_msg = " for argument '{}'.".format(name) + use_units_msg raise pint.DimensionalityError(u.dimensionless.units, units.units, extra_msg=extra_msg) else: extra_msg = " for return value." + use_units_msg raise pint.DimensionalityError(u.dimensionless.units, units.units, extra_msg=extra_msg) else: q = to_quantity(arg) if q.dimensionality != units.dimensionality: extra_info = '' if isinstance(arg, Q_) else use_units_msg if name is not None: extra_msg = " for argument '{}'.".format(name) + extra_info raise pint.DimensionalityError(q.units, units.units, extra_msg=extra_msg) else: extra_msg = " for return value." + extra_info raise pint.DimensionalityError(q.units, units.units, extra_msg=extra_msg) return q
def inout_map(arg, unit_info, name=None): if unit_info is None: return arg optional, units = unit_info if optional and arg is None: return None elif arg == 0: # Allow naked zeroes as long as we're using absolute units (e.g. not degF) # It's a bit dicey using this private method; works in 0.6 at least if units._ok_for_muldiv(): return Q_(arg, units) else: if name is not None: raise pint.DimensionalityError(u.dimensionless.units, units.units, extra_msg=" for argument '{}'".format(name)) else: raise pint.DimensionalityError(u.dimensionless.units, units.units, extra_msg=" for return value") else: q = Q_(arg) if q.dimensionality != units.dimensionality: if name is not None: raise pint.DimensionalityError(q.units, units.units, extra_msg=" for argument '{}'".format(name)) else: raise pint.DimensionalityError(q.units, units.units, extra_msg=" for return value") return q
def in_map(arg, unit_info, name): if unit_info is None: return arg optional, units = unit_info if optional and arg is None: return None elif arg == 0: # Allow naked zeroes as long as we're using absolute units (e.g. not degF) # It's a bit dicey using this private method; works in 0.6 at least if units._ok_for_muldiv(): return arg else: if name is not None: raise pint.DimensionalityError( u.dimensionless.units, units.units, extra_msg=" for argument '{}'".format(name)) else: raise pint.DimensionalityError( u.dimensionless.units, units.units, extra_msg=" for return value") else: q = to_quantity(arg) try: if q.units == units: return q.magnitude # Speed up the common case else: return q.to(units).magnitude except pint.DimensionalityError: raise pint.DimensionalityError( q.units, units.units, extra_msg=" for argument '{}'".format(name))
def validate_geq(value_name, value, low_lim): """Raise error if value lower than specified lower limit or wrong type. Parameters --------- value_name : str Name of value being tested value : int, float, numpy.ndarray, pint.Quantity Value to be tested low_lim : type(value) Lowest acceptable limit of ``value`` Returns ------- value : type(value) The original value """ try: if validate_num(value_name, value) < low_lim: msg = (value_name + ' must be greater than or equal to ' + str(low_lim) + '.\n' 'Value provided was: ' + str(value)) # RuntimeError used to avoid being caught by Pint comparison error. # Pint should really raise TypeError (or something) rather than # ValueError. raise RuntimeError(msg) else: return value except ValueError: if isinstance(value, units.Quantity): msg = ('\n' + value_name + ' given with units, when variable ' 'should be dimensionless.') raise pint.DimensionalityError(value.units, None, extra_msg=msg) else: msg = ('\n' + value_name + ' not given in units. ' 'Correct units share dimensionality with: ' + str(low_lim.units)) raise pint.DimensionalityError(None, low_lim.units, extra_msg=msg) except pint.DimensionalityError: msg = ('\n' + value_name + ' given in incompatible units. ' 'Correct units share dimensionality with: ' + str(low_lim.units)) raise pint.DimensionalityError(value.units, low_lim.units, extra_msg=msg) except: raise
def convert_gwp(context, qty, to): """Helper for :meth:`convert_unit` to perform GWP conversions.""" # Remove a leading 'gwp_' to produce the metric name metric = context.split('gwp_')[1] if context else context # Extract the species from *qty* and *to*, allowing supported aliases species_from, units_from = extract_species(qty[1]) species_to, units_to = extract_species(to) try: # Convert using a (magnitude, unit) tuple with only units, and explicit # input and output units result = iam_units.convert_gwp(metric, (qty[0], units_from), species_from, species_to) except (AttributeError, ValueError): # Missing *metric*, or *species_to* contains invalid units. pyam # promises UndefinedUnitError in these cases. Use a subclass (above) to # add a usage hint. raise UndefinedUnitError(species_to) from None except pint.DimensionalityError: # Provide an exception with the user's inputs raise pint.DimensionalityError(qty[1], to) from None # Other exceptions are not caught and will pass up through convert_unit() if units_to: # Also convert the units result = result.to(units_to) else: # *to* was only a species name. Provide units based on input and the # output species name. to = iam_units.format_mass(result, species_to, spec=':~') return result, to
def inout_map(arg, unit_info, name=None): if unit_info is None: return arg optional, units = unit_info if optional and arg is None: return None else: q = Q_(arg) if q.dimensionality != units.dimensionality: if name is not None: raise pint.DimensionalityError(q.units, units.units, extra_msg=" for argument '{}'".format(name)) else: raise pint.DimensionalityError(q.units, units.units, extra_msg=" for return value") return q
def span(self, span): span = Q_(span) if span.check('[frequency]'): self._rsrc.write('SPANF %07.3f' % span.to('THz').m) elif span.check('[length]'): self._rsrc.write('SPAN %06.1f' % span.to('nm').m) else: if self.x_unit == XUnit['frequency']: target_unit = u.THz else: target_unit = u.nm raise pint.DimensionalityError(span.units, target_unit)
def out_map(res, unit_info): if unit_info is None: return res optional, units = unit_info if optional and res is None: return None else: q = Q_(res) try: return q except pint.DimensionalityError: raise pint.DimensionalityError(q.units, units.units, extra_msg=" for return value")
def in_map(arg, unit_info, name): if unit_info is None: return arg optional, units = unit_info if optional and arg is None: return None else: q = Q_(arg) try: return q.to(units).magnitude except pint.DimensionalityError: raise pint.DimensionalityError(q.units, units.units, extra_msg=" for argument '{}'".format(name))
def _enforce_dimensionality(arg, dimensionality): """ Ensure quantity conforms to specified dimensionality. """ # WARNING: Error has to be raised manually because is_compatible_with does not # accept dimensionality containers and no other public methods are available. if not isinstance(arg, (pint.Unit, pint.Quantity)): raise RuntimeError(f'Invalid input argument {arg!r}.') dimensionality = ureg.get_dimensionality(dimensionality) arg_dimensionality = ureg.get_dimensionality(arg) if arg_dimensionality != dimensionality: raise pint.DimensionalityError( arg, 'a quantity of', arg_dimensionality, dimensionality )
def to_ureg(input_, unit=None, convert_quantities=True): """ This method is an import function to import alien quantities (of different unit registries) or numeric formats into the ureg. :param input_: The input quantity or numeric format (e.g. float, int, numpy array) or string (e.g. "5 nm") :param unit: Given as a valid unit string. If a numeric format is used, this specifies the unit of it. If a quantity is used, the output quantity will be converted to it. :param convert_quantities: Boolean: Specifies what should be done if input_ is a quantity. True: convert the quantity to the unit if specified by unit parameter. (The default) False: only assure the dimension is correct and import to ureg, but leave unit of input_ as is is. :return: The imported quantity. """ # Python2/3 compatibility: If unit is a byte string, decode it for usage in pint.Quantity. if isinstance(unit, bytes): unit = unit.decode() # Check if input is quantity: if is_quantity(input_): # If output unit is specified, make sure it has a compatible dimension. Else a DimensionalityError will be # raised: if unit and not same_dimension(input_, to_ureg(1, unit)): raise pint.DimensionalityError( input_.units, to_ureg(1, unit).units, extra_msg="Incompatible units given to to_ureg!") # Check if input is already of our ureg. If so, nothing to do other then convert if unit is specified: if input_._REGISTRY is ureg: if unit and convert_quantities and input_.units != to_ureg( 1, unit).units: return input_.to(unit) else: return input_ else: # Use inputs magnitude, but our corresponding ureg unit. if unit and convert_quantities: return Quantity(input_.magnitude, str(input_.units)).to(unit) else: return Quantity(input_.magnitude, str(input_.units)) elif (isinstance(input_, str) or isinstance(input_, str)): if unit: return ureg(input_).to(unit) else: return ureg(input_) else: # we are dealing with numerial data return Quantity(input_, unit)
def _check_dimensionality(self): for quant in self._code_registry.unit_dict.keys(): if (not self._base_registry[quant].dimensionality == self._code_registry[quant].dimensionality): raise pint.DimensionalityError( self._base_registry[quant], self._code_registry[quant], extra_msg= "\n Dimensional inequality: Quantity {} has dimensionality {} " "in the base registry but {} in the code " "registry".format( quant, self._base_registry[quant].dimensionality, self._code_registry[quant].dimensionality, ), )
def _build_coordinates( cls, coordinates, time: Time = None ) -> Union[xr.DataArray, TimeSeries]: """Create xarray coordinates from different formats and time-inputs.""" if isinstance(coordinates, TimeSeries): if coordinates.is_expression: return coordinates coordinates = cls._coords_from_discrete_time_series(coordinates) if coordinates is None: coordinates = np.zeros(3) if not isinstance(coordinates, xr.DataArray): if not isinstance(coordinates, (np.ndarray, pint.Quantity)): coordinates = np.array(coordinates) coordinates = ut.xr_3d_vector(coordinates, time) if isinstance(coordinates.data, pint.Quantity): # The first branch is a workaround until units are mandatory if coordinates.data.u == pint.Unit(""): coordinates.data = coordinates.data.m elif not coordinates.data.is_compatible_with(_DEFAULT_LEN_UNIT): raise pint.DimensionalityError( coordinates.data.u, _DEFAULT_LEN_UNIT, extra_msg="\nThe coordinates require units representing a length.", ) if not isinstance(coordinates.data, pint.Quantity) and not ( coordinates.shape == (3,) and np.allclose(coordinates.data, np.zeros(3)) ): warnings.warn( "Coordinates without units are deprecated and won't be supported in " "the future", DeprecationWarning, stacklevel=2, ) # make sure we have correct "time" format coordinates = coordinates.weldx.time_ref_restore() return coordinates
def convert_gwp(metric, quantity, *species): """Convert *quantity* between GHG *species* with a GWP *metric*. Parameters ---------- metric : 'SARGWP100' or 'AR4GWP100' or 'AR5GWP100' or None Metric conversion factors to use. May be :obj:`None` if the input and output species are the same. quantity : str or pint.Quantity or tuple Quantity to convert. If a tuple of (magnitude, unit), these are passed as arguments to :class:`pint.Quantity`. species : sequence of str, length 1 or 2 Output, or (input, output) species symbols, e.g. ('CH4', 'CO2') to convert mass of CH₄ to GWP-equivalent mass of CO₂. If only the output species is provided, *quantity* must contain the symbol of the input species in some location, e.g. '1.0 tonne CH4 / year'. Returns ------- pint.Quantity `quantity` converted from the input to output species. """ # Handle *species*: either (in, out) or only out try: species_in, species_out = species except ValueError: if len(species) != 1: raise ValueError('Must provide (from, to) or (to,) species') species_in, species_out = None, species[0] # Split *quantity* if it is a tuple. After this step: # - *mag* is the magnitude, or None. # - *expr* is a string expression for either just the units, or the entire # quantity, including magnitude, as a str or pint.Quantity. mag, expr = quantity if isinstance(quantity, tuple) else (None, quantity) # If species_in wasn't provided, then *expr* must contain it if not species_in: # Extract it using the regex, then re-assemble the expression for the # units or whole quantity q0, species_in, q1 = emissions.pattern.split(expr, maxsplit=1) expr = q0 + q1 # *metric* can only be None if the input and output species symbols are # identical or equivalent if metric is None: if (species_in == species_out or any({species_in, species_out} <= g for g in emissions.EQUIV)): metric = 'AR5GWP100' elif species_in in species_out: # Eg. 'CO2' in 'CO2 / a'. This is both a DimensionalityError and a # ValueError (no metric); raise the former for pyam compat raise pint.DimensionalityError(species_in, species_out) else: msg = f'Must provide GWP metric for ({species_in}, {species_out})' raise ValueError(msg) # Ensure a pint.Quantity object: # - If *quantity* was a tuple, use the 2-arg constructor. # - If a str, use the 1-arg form to parse it. # - If already a pint.Quantity, this is a no-op. args = (expr,) if mag is None else (mag, expr) quantity = registry.Quantity(*args) # Construct intermediate units with the same dimensionality as *quantity*, # except '[mass]' replaced with the dummy unit '_gwp' dummy = quantity.units / registry.Unit('tonne / _gwp') # Convert to dummy units using 'a' for the input species; then back to the # input units using 'a' for the output species. return quantity.to(dummy, metric, _a=f'a_{species_in}') \ .to(quantity.units, metric, _a=f'a_{species_out}')