Source code for aim2dat.utils.units

"""Module containing units and unit conversions."""

# Third party library imports
import numpy as np
import ase.units as ase_un


class _BaseQuantity:
    _units = {}

    def __init__(self):
        pass

    def __getattr__(self, name):
        return self._units.get(name.lower(), None)

    @property
    def available_units(self):
        """
        List of all available units.
        """
        return list(self._units.keys())

    def get_unit(self, unit):
        """
        Return the value of the unit.

        Parameters
        ----------
        unit : str
            Physical unit.

        Returns
        -------
        value : float
            Value of the unit.
        """
        return self._units[unit.lower()]


[docs] class Length(_BaseQuantity): """ Length units based on the ase library. Angstrom is set to ``1.0``. """ _units = { "bohr": ase_un.Bohr, "nm": ase_un.nm, "ang": ase_un.Angstrom, "angstrom": ase_un.Angstrom, "m": ase_un.m, "mm": ase_un.m * 1e-3, "micro_m": ase_un.m * 1e-6, "micron": ase_un.m * 1e-6, } _plot_labels = { "bohr": "Bohr", "nm": "nm", "ang": r"$\mathrm{\AA}$", "angstrom": r"$\mathrm{\AA}$", "m": "m", "mm": "mm", "micro_m": r"$\mathrm{\mu}$m", "micron": r"$\mathrm{\mu}$m", }
[docs] class Energy(_BaseQuantity): """ Energy units based on the ase library. eV is set to ``1.0``. """ _units = { "rydberg": ase_un.Rydberg, "hartree": ase_un.Hartree, "joule": ase_un.J, "j": ase_un.J, "ev": ase_un.eV, } _plot_labels = { "rydberg": "Rydberg", "hartree": "Hartree", "joule": "Joule", "j": "Joule", "ev": "eV", }
[docs] class Frequency(_BaseQuantity): """ Frequency units based on the ase library. """ _units = { "hz": 1.0 / ase_un.second, "khz": 1e3 / ase_un.second, "mhz": 1e6 / ase_un.second, "ghz": 1e9 / ase_un.second, "thz": 1e12 / ase_un.second, "phz": 1e15 / ase_un.second, } _plot_labels = { "hz": "Hz", "khz": "kHz", "mhz": "MHz", "ghz": "GHz", "thz": "THz", "phz": "PHz", }
[docs] class Wavevector(_BaseQuantity): """ Wavevector units based on the ase library. Angstrom-1 is set to ``1.0``. """ _units = { "nm-1": 1.0 / ase_un.nm, "angstrom-1": 1 / ase_un.Angstrom, "m-1": 1.0 / ase_un.m, "cm-1": 1.0 / (ase_un.m * 1e-2), "mm-1": 1.0 / (ase_un.m * 1e-3), "micro_m-1": 1.0 / (ase_un.m * 1e-6), } _plot_labels = { "nm-1": r"nm$^{-1}$", "angstrom-1": r"$\mathrm{\AA}^{-1}$", "m-1": r"m$^{-1}$", "cm-1": r"cm$^{-1}$", "mm-1": r"mm$^{-1}$", "micro_m-1": r"$\mathrm{\mu}$m$^{-1}$", }
length = Length() energy = Energy() frequency = Frequency() wavevector = Wavevector() class _BaseUnitConverter: """ Convert units related to spectroscopy. """ _quantities = { "length": Length(), "energy": Energy(), "frequency": Frequency(), "wavevector": Wavevector(), } _available_units = {} for quantity, qu_class in _quantities.items(): for unit in qu_class.available_units: _available_units[unit] = quantity available_units = list(_available_units.keys()) plot_labels = {} for qu_class in _quantities.values(): plot_labels.update(qu_class._plot_labels) @classmethod def _return_quantity(cls, unit): """ Return the quantity of the unit. Parameters ---------- unit : str Physical unit. Returns ------- quantity : str Physical quantity. """ for quantity_label, quantity in cls._quantities.items(): if unit in quantity.available_units: return quantity_label return None @classmethod def _convert_units(cls, value, unit_1, unit_2): """ Convert one unit into another. Parameters ---------- value : float Input value. unit_1 : str Physical unit of the input value. unit_2 : str Physical unit to be converted into. Returns ------- processed_data : float Output value. """ unit_1 = unit_1.lower() unit_2 = unit_2.lower() for unit in [unit_1, unit_2]: if unit not in cls._available_units.keys(): raise ValueError(f"'{unit}' is not supported for unit conversion.") # If both units are from the same quantity: if cls._available_units[unit_1] == cls._available_units[unit_2]: quantity_class = cls._quantities[cls._available_units[unit_1]] processed_data = cls._convert_unit(value, quantity_class, unit_1, unit_2) else: quantities = [cls._available_units[unit_1], cls._available_units[unit_2]] method_name = "_".join(sorted(quantities)) conv_method = getattr(cls, "_convert_" + method_name) processed_data = conv_method(value=value, unit_1=unit_1, unit_2=unit_2) return processed_data @classmethod def _convert_energy_length(cls, value, unit_1, unit_2): length = cls._quantities["length"] energy = cls._quantities["energy"] # E = h_planck * c / lambda if unit_1 not in length.available_units: unit_1, unit_2 = unit_2, unit_1 conv_factor = (energy.Joule / energy.get_unit(unit_2)) * ( length.m / length.get_unit(unit_1) ) # We should find here a different way without using private variables from ase... processed_data = (ase_un._c * ase_un._hplanck * conv_factor) / value return processed_data @classmethod def _convert_frequency_length(cls, value, unit_1, unit_2): length = cls._quantities["length"] frequency = cls._quantities["frequency"] # f = c / lambda if unit_1 not in frequency.available_units: unit_1, unit_2 = unit_2, unit_1 conv_factor = (length.m / length.get_unit(unit_2)) * ( frequency.Hz / frequency.get_unit(unit_1) ) processed_data = (ase_un._c * conv_factor) / value return processed_data @classmethod def _convert_length_wavevector(cls, value, unit_1, unit_2): length = cls._quantities["length"] wavevector = cls._quantities["wavevector"] # k = 2 * pi / lambda if unit_1 not in length.available_units: unit_1, unit_2 = unit_2, unit_1 conv_factor = 2 * np.pi / (length.get_unit(unit_1) * wavevector.get_unit(unit_2)) return conv_factor / value @classmethod def _convert_energy_frequency(cls, value, unit_1, unit_2): energy = cls._quantities["energy"] function1 = cls._convert_energy_length function2 = cls._convert_frequency_length if unit_1 not in energy.available_units: function1, function2 = function2, function1 return function2(function1(value, unit_1, "angstrom"), "angstrom", unit_2) @classmethod def _convert_energy_wavevector(cls, value, unit_1, unit_2): # energy = cls._quantities["energy"] function1 = cls._convert_energy_length function2 = cls._convert_length_wavevector if unit_1 not in energy.available_units: function1, function2 = function2, function1 return function2(function1(value, unit_1, "angstrom"), "angstrom", unit_2) @classmethod def _convert_frequency_wavevector(cls, value, unit_1, unit_2): frequency = cls._quantities["frequency"] function1 = cls._convert_frequency_length function2 = cls._convert_length_wavevector if unit_1 not in frequency.available_units: function1, function2 = function2, function1 return function2(function1(value, unit_1, "angstrom"), "angstrom", unit_2) @staticmethod def _convert_unit(value, quantity, unit_1, unit_2): conv_factor = quantity.get_unit(unit_1) / quantity.get_unit(unit_2) return conv_factor * value
[docs] class UnitConverter(_BaseUnitConverter): """ Convert units used in spectroscopy. """
[docs] @classmethod def convert_units(cls, value, unit_1, unit_2): """ Convert one unit into another. Parameters ---------- value : float Input value. unit_1 : str Physical unit of the input value. unit_2 : str Physical unit to be converted into. Returns ------- processed_data : float Output value. """ return cls._convert_units(value, unit_1, unit_2)