"""
Equivalencies between different kinds of units
"""
# -----------------------------------------------------------------------------
# Copyright (c) 2018, yt Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the LICENSE file, distributed with this software.
# -----------------------------------------------------------------------------
from __future__ import division
from collections import OrderedDict
import numpy as np
from unyt.dimensions import (
density,
dimensionless,
energy,
flux,
length,
mass,
number_density,
rate,
spatial_frequency,
temperature,
velocity,
)
from unyt.exceptions import InvalidUnitEquivalence
equivalence_registry = OrderedDict()
class _RegisteredEquivalence(type):
def __init__(cls, name, b, d):
type.__init__(cls, name, b, d)
if hasattr(cls, "type_name"):
equivalence_registry[cls.type_name] = cls
[docs]class Equivalence(object, metaclass=_RegisteredEquivalence):
def __init__(self, in_place=False):
self.in_place = in_place
[docs] def convert(self, x, new_dims, **kwargs):
if x.units.dimensions in self._dims and new_dims in self._dims:
return self._convert(x, new_dims, **kwargs)
else:
raise InvalidUnitEquivalence(self, x.units, new_dims)
def _get_out(self, x):
if self.in_place:
return x
return None
[docs]class NumberDensityEquivalence(Equivalence):
"""Equivalence between mass and number density, given a mean molecular
weight.
Given a number density :math:`n`, the mass density :math:`\\rho` is:
.. math::
\\rho = \\mu m_{\\rm H} n
And similarly
.. math::
n = \\rho (\\mu m_{\\rm H})^{-1}
Parameters
----------
mu : float
The mean molecular weight. Defaults to 0.6 whcih is valid for fully
ionized gas with primordial composition.
Example
-------
>>> print(NumberDensityEquivalence())
number density: density <-> number density
>>> from unyt import Msun, pc
>>> rho = 3*Msun/pc**3
>>> rho.to_equivalent('cm**-3', 'number_density', mu=1.4)
unyt_quantity(86.64869896, 'cm**(-3)')
"""
type_name = "number_density"
_dims = (density, number_density)
def _convert(self, x, new_dims, mu=0.6):
from unyt import physical_constants as pc
if new_dims == number_density:
return np.true_divide(x, mu * pc.mh, out=self._get_out(x))
elif new_dims == density:
return np.multiply(x, mu * pc.mh, out=self._get_out(x))
def __str__(self):
return "number density: density <-> number density"
[docs]class ThermalEquivalence(Equivalence):
"""Equivalence between temperature and energy via the Boltzmann constant
Given a temperature :math:`T` in an absolute scale (e.g. Kelvin or
Rankine), the equivalent thermal energy :math:`E` for that temperature is
given by:
.. math::
E = k_B T
And
.. math::
T = E/k_B
Where :math:`k_B` is Boltzmann's constant.
Example
-------
>>> print(ThermalEquivalence())
thermal: temperature <-> energy
>>> from unyt import Kelvin
>>> temp = 1e6*Kelvin
>>> temp.to_equivalent('keV', 'thermal')
unyt_quantity(0.08617332, 'keV')
"""
type_name = "thermal"
_dims = (temperature, energy)
def _convert(self, x, new_dims):
from unyt import physical_constants as pc
if new_dims == energy:
return np.multiply(x, pc.kboltz, out=self._get_out(x))
elif new_dims == temperature:
return np.true_divide(x, pc.kboltz, out=self._get_out(x))
def __str__(self):
return "thermal: temperature <-> energy"
[docs]class MassEnergyEquivalence(Equivalence):
"""Equivalence between mass and energy in special relativity
Given a body with mass :math:`m`, the self-energy :math:`E` of that mass is
given by
.. math::
E = m c^2
where :math:`c` is the speed of light.
Example
-------
>>> print(MassEnergyEquivalence())
mass_energy: mass <-> energy
>>> from unyt import g
>>> (3.5*g).to_equivalent('J', 'mass_energy')
unyt_quantity(3.14564313e+14, 'J')
"""
type_name = "mass_energy"
_dims = (mass, energy)
def _convert(self, x, new_dims):
from unyt import physical_constants as pc
if new_dims == energy:
return np.multiply(x, pc.clight * pc.clight, out=self._get_out(x))
elif new_dims == mass:
return np.true_divide(x, pc.clight * pc.clight, out=self._get_out(x))
def __str__(self):
return "mass_energy: mass <-> energy"
[docs]class SpectralEquivalence(Equivalence):
"""Equivalence between wavelength, frequency, and energy of a photon.
Given a photon with wavelength :math:`\\lambda`, spatial frequency
:math:`\\bar\\nu`, frequency :math:`\\nu` and Energy :math:`E`,
these quantities are related by the following forumlae:
.. math::
E = h \\nu = h c / \\lambda = h c \\bar\\nu
where :math:`h` is Planck's constant and :math:`c` is the speed of light.
Example
------
>>> print(SpectralEquivalence())
spectral: length <-> spatial_frequency <-> frequency <-> energy
>>> from unyt import angstrom, km
>>> (3*angstrom).to_equivalent('keV', 'spectral')
unyt_quantity(4.13280644, 'keV')
>>> (1*km).to_equivalent('MHz', 'spectral')
unyt_quantity(0.29979246, 'MHz')
"""
type_name = "spectral"
_dims = (length, rate, energy, spatial_frequency)
def _convert(self, x, new_dims):
from unyt import physical_constants as pc
if new_dims == energy:
if x.units.dimensions == length:
return np.true_divide(pc.clight * pc.h_mks, x, out=self._get_out(x))
elif x.units.dimensions == rate:
return np.multiply(x, pc.h_mks, out=self._get_out(x))
elif x.units.dimensions == spatial_frequency:
return np.multiply(x, pc.h_mks * pc.clight, out=self._get_out(x))
elif new_dims == length:
if x.units.dimensions == rate:
return np.true_divide(pc.clight, x, out=self._get_out(x))
elif x.units.dimensions == energy:
return np.true_divide(pc.h_mks * pc.clight, x, out=self._get_out(x))
elif x.units.dimensions == spatial_frequency:
return np.true_divide(1, x, out=self._get_out(x))
elif new_dims == rate:
if x.units.dimensions == length:
return np.true_divide(pc.clight, x, out=self._get_out(x))
elif x.units.dimensions == energy:
return np.true_divide(x, pc.h_mks, out=self._get_out(x))
elif x.units.dimensions == spatial_frequency:
return np.multiply(x, pc.clight, out=self._get_out(x))
elif new_dims == spatial_frequency:
if x.units.dimensions == length:
return np.true_divide(1, x, out=self._get_out(x))
elif x.units.dimensions == energy:
return np.true_divide(x, pc.clight * pc.h_mks, out=self._get_out(x))
elif x.units.dimensions == rate:
return np.true_divide(x, pc.clight, out=self._get_out(x))
def __str__(self):
return "spectral: length <-> spatial_frequency <-> frequency " + "<-> energy"
[docs]class SoundSpeedEquivalence(Equivalence):
"""Equivalence between the sound speed, temperature, and thermal energy of
an ideal gas
For an ideal gas with sound speed :math:`c_s`, temperature :math:`T`, and
thermal energy :math:`E`, the following equalities will hold:
.. math::
c_s = \\sqrt{\\frac{\\gamma k_B T}{\\mu m_{\\rm H}}}
and
.. math::
E = c_s^2 \\mu m_{\\rm H} / \\gamma = k_B T
where :math:`k_B` is Boltzmann's constant, :math:`\\mu` is the mean
molecular weight of the gas, and :math:`\\gamma` is the ratio of specific
heats.
Parameters
----------
gamma : float
The ratio of specific heats. Defaults to 5/3, which is correct for
monatomic species.
mu : float
The mean molecular weight. Defaults to 0.6, which is valid for fully
ionized gas with primordial composition.
Example
-------
>>> print(SoundSpeedEquivalence())
sound_speed (ideal gas): velocity <-> temperature <-> energy
>>> from unyt import Kelvin, km, s
>>> hot = 1e6*Kelvin
>>> hot.to_equivalent('km/s', 'sound_speed')
unyt_quantity(151.37249927, 'km/s')
>>> hot.to_equivalent('keV', 'sound_speed')
unyt_quantity(0.08617332, 'keV')
>>> cs = 100*km/s
>>> cs.to_equivalent('K', 'sound_speed')
unyt_quantity(436421.39881617, 'K')
>>> cs.to_equivalent('keV', 'sound_speed')
unyt_quantity(0.03760788, 'keV')
"""
type_name = "sound_speed"
_dims = (velocity, temperature, energy)
def _convert(self, x, new_dims, mu=0.6, gamma=5.0 / 3.0):
from unyt import physical_constants as pc
if new_dims == velocity:
if x.units.dimensions == temperature:
v2 = np.multiply(
pc.kboltz * gamma / (mu * pc.mh), x, out=self._get_out(x)
)
elif x.units.dimensions == energy:
v2 = np.multiply(gamma / (mu * pc.mh), x, out=self._get_out(x))
return np.sqrt(v2, out=self._get_out(x))
elif new_dims == temperature:
if x.units.dimensions == velocity:
v2 = np.multiply(x, x, out=self._get_out(x))
kT = np.multiply(v2, mu * pc.mh / gamma, out=self._get_out(x))
return np.true_divide(kT, pc.kboltz, out=self._get_out(x))
else:
return np.true_divide(x, pc.kboltz, out=self._get_out(x))
else:
if x.units.dimensions == velocity:
v2 = np.multiply(x, x, out=self._get_out(x))
return np.multiply(mu * pc.mh / gamma, v2, out=self._get_out(x))
else:
return np.multiply(x, pc.kboltz, out=self._get_out(x))
def __str__(self):
return "sound_speed (ideal gas): velocity <-> temperature <-> energy"
[docs]class LorentzEquivalence(Equivalence):
"""Equivalence between velocity and the Lorentz gamma factor.
For a body with velocity :math:`v`, the Lorentz gamma factor,
:math:`\\gamma` is
.. math::
\\gamma = \\frac{1}{\\sqrt{1 - v^2/c^2}}
ans similarly
.. math::
v = \\frac{c}{\\sqrt{1 - \\gamma^2}}
where :math:`c` is the speed of light.
Example
-------
>>> print(LorentzEquivalence())
lorentz: velocity <-> dimensionless
>>> from unyt import c, dimensionless
>>> v = 0.99*c
>>> print(v.to_equivalent('', 'lorentz'))
7.088812050083393 dimensionless
>>> fast = 99.9*dimensionless
>>> fast.to_equivalent('c', 'lorentz')
unyt_quantity(0.9999499, 'c')
>>> fast.to_equivalent('km/s', 'lorentz')
unyt_quantity(299777.43797656, 'km/s')
"""
type_name = "lorentz"
_dims = (dimensionless, velocity)
def _convert(self, x, new_dims):
from unyt import physical_constants as pc
if new_dims == dimensionless:
beta = np.true_divide(x, pc.clight, out=self._get_out(x))
beta2 = np.multiply(beta, beta, out=self._get_out(x))
inv_gamma_2 = np.subtract(1, beta2, out=self._get_out(x))
inv_gamma = np.sqrt(inv_gamma_2, out=self._get_out(x))
gamma = np.true_divide(1.0, inv_gamma, out=self._get_out(x))
return gamma
elif new_dims == velocity:
gamma2 = np.multiply(x, x, out=self._get_out(x))
inv_gamma_2 = np.true_divide(1, gamma2, out=self._get_out(x))
beta2 = np.subtract(1, inv_gamma_2, out=self._get_out(x))
beta = np.sqrt(beta2, out=self._get_out(x))
return np.multiply(pc.clight, beta, out=self._get_out(x))
def __str__(self):
return "lorentz: velocity <-> dimensionless"
[docs]class SchwarzschildEquivalence(Equivalence):
"""Equivalence between the mass and radius of a Schwarzschild black hole
A Schwarzschild black hole of mass :math:`M` has radius :math:`R`
.. math::
R = \\frac{2 G M}{c^2}
and similarly
.. math::
M = \\frac{R c^2}{2 G}
where :math:`G` is Newton's gravitational constant and :math:`c` is the
speed of light.
Example
-------
>>> print(SchwarzschildEquivalence())
schwarzschild: mass <-> length
>>> from unyt import Msun, AU
>>> (10*Msun).to_equivalent('km', 'schwarzschild')
unyt_quantity(29.53161626, 'km')
>>> (1*AU).to_equivalent('Msun', 'schwarzschild')
unyt_quantity(50656851.7815179, 'Msun')
"""
type_name = "schwarzschild"
_dims = (mass, length)
def _convert(self, x, new_dims):
from unyt import physical_constants as pc
if new_dims == length:
return np.multiply(
2.0 * pc.G / (pc.clight * pc.clight), x, out=self._get_out(x)
)
elif new_dims == mass:
return np.multiply(
0.5 * pc.clight * pc.clight / pc.G, x, out=self._get_out(x)
)
def __str__(self):
return "schwarzschild: mass <-> length"
[docs]class ComptonEquivalence(Equivalence):
"""Equivalence between the Compton wavelength
of a particle and its mass.
.. math::
\\lambda_c = h/mc
Example
-------
>>> print(ComptonEquivalence())
compton: mass <-> length
>>> from unyt import me, fm
>>> me.to_equivalent('angstrom', 'compton')
unyt_quantity(0.0242631, 'Å')
>>> (10*fm).to_equivalent('me', 'compton')
unyt_quantity(242.63102371, 'me')
"""
type_name = "compton"
_dims = (mass, length)
def _convert(self, x, new_dims):
from unyt import physical_constants as pc
return np.true_divide(pc.h_mks / pc.clight, x, out=self._get_out(x))
def __str__(self):
return "compton: mass <-> length"
[docs]class EffectiveTemperatureEquivalence(Equivalence):
"""Equivalence between the emmitted flux accross all wavelengths and
temperature of a blackbody
For a blackbody emitter with Temperature :math:`T` emitting radiation with
a flux :math:`F`, the following equality holds:
.. math::
F = \\sigma T^4
where :math:`\\sigma` is the Stefan-Boltzmann constant.
Example
-------
>>> print(EffectiveTemperatureEquivalence())
effective_temperature: flux <-> temperature
>>> from unyt import K, W, m
>>> (5000.*K).to_equivalent('W/m**2', 'effective_temperature')
unyt_quantity(35439831.25, 'W/m**2')
>>> (100.*W/m**2).to_equivalent('K', 'effective_temperature')
unyt_quantity(204.92601414, 'K')
"""
type_name = "effective_temperature"
_dims = (flux, temperature)
def _convert(self, x, new_dims):
from unyt import physical_constants as pc
if new_dims == flux:
x4 = np.power(x, 4, out=self._get_out(x))
return np.multiply(
pc.stefan_boltzmann_constant_mks, x4, out=self._get_out(x)
)
elif new_dims == temperature:
T4 = np.true_divide(
x, pc.stefan_boltzmann_constant_mks, out=self._get_out(x)
)
ret = np.power(T4, 0.25, out=self._get_out(x))
return ret
def __str__(self):
return "effective_temperature: flux <-> temperature"