Source code for WrightTools.units

"""Unit and label handling in WrightTools."""

# --- import --------------------------------------------------------------------------------------


import warnings

import pint


# --- define --------------------------------------------------------------------------------------

# These "blessed" units are here primarily for backwards compatibility, in particular
# to enable the behavior of `data.convert` which will convert freely between the energy units
# but does not go to time (where delay will)
# Since both of these context can convert to [length] units, they are interconvertible, but we
# do not want them to automatically do so.
# This list is (at creation time) purely reflective of historical units supported pre pint
# There is nothing preventing other units from being used and converted to, only to enable
# expected behavior
# 2021-01-29 KFS
blessed_units = (
    # angle
    "rad",
    "deg",
    # delay
    "fs",
    "ps",
    "ns",
    "mm_delay",
    # energy
    "nm",
    "wn",
    "eV",
    "meV",
    "Hz",
    "THz",
    "GHz",
    # optical density
    "mOD",
    # position
    "nm_p",
    "um",
    "mm",
    "cm",
    "in",
    # absolute temperature
    "K",
    "deg_C",
    "deg_F",
    "deg_R",
    # time
    "fs_t",
    "ps_t",
    "ns_t",
    "us_t",
    "ns_t",
    "s_t",
    "m_t",
    "h_t",
    "d_t",
)

ureg = pint.UnitRegistry()
ureg.define("[fluence] = [energy] / [area]")

ureg.define("OD = [] ")

ureg.define("wavenumber = 1 / cm = cm^{-1} = wn")

# Aliases for backwards compatability
ureg.define("@alias s = s_t")
ureg.define("@alias min = m_t")
ureg.define("@alias hour = h_t")
ureg.define("@alias d = d_t")

ureg.define("@alias degC = deg_C")
ureg.define("@alias degF = deg_F")
ureg.define("@alias degR = deg_R")

ureg.define("@alias m = m_delay")

delay = pint.Context("delay", defaults={"n": 1, "num_pass": 2})
delay.add_transformation(
    "[length]",
    "[time]",
    lambda ureg, x, n=1, num_pass=2: num_pass * x / ureg.speed_of_light * n,
)
delay.add_transformation(
    "[time]",
    "[length]",
    lambda ureg, x, n=1, num_pass=2: x / num_pass * ureg.speed_of_light / n,
)
ureg.enable_contexts("spectroscopy", delay)

# --- functions -----------------------------------------------------------------------------------


def converter(val, current_unit, destination_unit):
    """Convert from one unit to another.
    If current_unit and/or destination_unit are None, input is returned.

    Parameters
    ----------
    val : number
        Number to convert.
    current_unit : string
        Current unit.
    destination_unit : string
        Destination unit.

    Returns
    -------
    number
        Converted value.
    """
    if (current_unit is None) and (destination_unit is None):
        return val
    if (current_unit is None) or (destination_unit is None):
        warnings.warn(
            f"conversion {current_unit} to {destination_unit} not valid: returning input"
        )
        return val
    try:
        val = ureg.Quantity(val, current_unit).to(destination_unit).magnitude
    except ZeroDivisionError:
        warnings.warn(
            f"conversion {current_unit} to {destination_unit} resulted in ZeroDivisionError: returning inf"
        )
        return float("inf")
    return val


convert = converter


[docs] def get_symbol(units) -> str: """Get default symbol type. Parameters ---------- units_str : string Units. Returns ------- string LaTeX formatted symbol. """ quantity = ureg.Quantity(1, ureg(units)) if quantity.check("[length]"): return r"\lambda" elif quantity.check("1 / [length]"): return r"\bar\nu" elif quantity.check("[energy]"): return r"\hslash\omega" elif quantity.check("1 / [time]"): return "f" elif quantity.check("[time]"): return r"\tau" elif quantity.check("[fluence]"): return r"\mathcal{F}" elif quantity.check("[temperature]"): return "T" elif ureg(units) in (ureg.deg, ureg.radian): return r"\omega" else: return None
[docs] def get_valid_conversions(units, options=blessed_units) -> tuple: return tuple(i for i in options if is_valid_conversion(units, i) and units != i)
[docs] def is_valid_conversion(a, b, blessed=True) -> bool: if a is None: return b is None if blessed and a in blessed_units and b in blessed_units: blessed_energy_units = {"nm", "wn", "eV", "meV", "Hz", "THz", "GHz"} if a in blessed_energy_units: return b in blessed_energy_units blessed_delay_units = {"fs", "ps", "ns", "mm_delay"} if a in blessed_delay_units: return b in blessed_delay_units return ureg.Unit(a).dimensionality == ureg.Unit(b).dimensionality try: return ureg.Unit(a).is_compatible_with(b, "spectroscopy") except pint.UndefinedUnitError: return False
[docs] def kind(units): """Find the dimensionality of given units. Parameters ---------- units : string The units of interest Returns ------- string The kind of the given units. If no match is found, returns None. """ if units is None: return None return str(ureg.Unit(units).dimensionality)