Source code for astronomix.units.unit_helpers
"""
Conversion between simulation code units and physical units.
The simulation works in dimensionless code units defined by a chosen unit
length, unit mass and unit velocity. :class:`CodeUnits` builds the derived
code units (time, density, pressure, energy, magnetic field) from those three
base units and offers convenience conversions back to physical quantities.
When post-processing with Paicos these helpers are not needed.
"""
# units and constants
from astropy import constants as c
from astropy import units as u
[docs]
class CodeUnits:
"""Derived code-unit system built from a base length, mass and velocity.
From the three base units every other code unit (time, density, pressure,
energy, magnetic field) follows by dimensional analysis. The magnetic field
is stored as ``B / sqrt(mu_0)``, which has units of ``sqrt(pressure)``.
"""
def __init__(self, unit_length, unit_mass, unit_velocity):
"""Define the code-unit system from three astropy-valued base units.
Args:
unit_length: The code length, e.g. ``3 * u.parsec``.
unit_mass: The code mass, e.g. ``1e5 * u.M_sun``.
unit_velocity: The code velocity, e.g. ``1 * u.km / u.s``.
"""
self.code_length = u.def_unit('code_length', unit_length)
self.code_mass = u.def_unit('code_mass', unit_mass)
self.code_velocity = u.def_unit('code_velocity', unit_velocity)
# The remaining code units are fixed by the three base units above.
self.code_time = self.code_length / self.code_velocity
self.code_density = self.code_mass / self.code_length**3
self.code_pressure = self.code_mass / self.code_length / self.code_time**2
self.code_energy = self.code_mass * self.code_velocity**2
# The code stores the magnetic field as B / sqrt(mu_0), which carries
# units of sqrt(pressure).
self.code_magnetic_field = self.code_pressure ** 0.5
[docs]
def init_from_unit_params(UnitLength_in_cm, UnitMass_in_g, UnitVelocity_in_cm_per_s):
"""Build a :class:`CodeUnits` from raw CGS unit magnitudes.
Args:
UnitLength_in_cm: The code length in centimetres.
UnitMass_in_g: The code mass in grams.
UnitVelocity_in_cm_per_s: The code velocity in cm/s.
Returns:
The corresponding :class:`CodeUnits` instance.
"""
return CodeUnits(
UnitLength_in_cm * u.cm,
UnitMass_in_g * u.g,
UnitVelocity_in_cm_per_s * u.cm / u.s,
)
[docs]
def get_temperature_from_internal_energy(self, internal_energy, gamma = 5 / 3, hydrogen_abundance = 0.76):
"""Convert specific internal energy (code units) to a temperature.
Uses the ideal-gas relation ``T = (gamma - 1) u mu m_H / k_B`` with the
mean molecular weight derived from the hydrogen abundance.
Args:
internal_energy: The specific internal energy in code units.
gamma: The adiabatic index of the gas.
hydrogen_abundance: The hydrogen mass fraction, used to set the mean
molecular weight.
Returns:
The temperature as an astropy quantity in kelvin.
"""
mhydrogen = c.m_e + c.m_p
gm1 = gamma - 1
mean_molecular_weight = 4 / (5 * hydrogen_abundance + 3)
return (gm1 * internal_energy * mean_molecular_weight * mhydrogen * self.code_velocity**2 / c.k_B).to(u.K)
[docs]
def print_simulation_parameters(self, final_time_wanted):
"""Print the base and derived code units in physical units.
Args:
final_time_wanted: A target physical time, reported in code-time
units for convenience when choosing a run length.
"""
print(f"Code length in cm: {self.code_length.to(u.cm)}")
print(f"Code mass in g: {self.code_mass.to(u.g)}")
print(f"Code velocity in cm/s: {self.code_velocity.to(u.cm / u.s)}")
print(f"Code time in s: {self.code_time.to(u.s)}")
print(f"Final time in code units: {final_time_wanted.to(self.code_time)}")
print(f"Code density in g/cm^3: {self.code_density.to(u.g / u.cm**3)}")
print(f"Code pressure in g/cm/s^2: {self.code_pressure.to(u.g / u.cm / u.s**2)}")