Skip to content

Commit

Permalink
Merge pull request #3725 from dcamron/not-constants
Browse files Browse the repository at this point in the history
Moisture/temperature dependent gas constants & latent heat
  • Loading branch information
dopplershift authored Jan 6, 2025
2 parents 82e91cb + 26ea138 commit ea2fcfc
Show file tree
Hide file tree
Showing 9 changed files with 351 additions and 23 deletions.
6 changes: 6 additions & 0 deletions docs/_templates/overrides/metpy.calc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ Moist Thermodynamics
mixing_ratio
mixing_ratio_from_relative_humidity
mixing_ratio_from_specific_humidity
moist_air_gas_constant
moist_air_poisson_exponent
moist_air_specific_heat_pressure
moist_lapse
moist_static_energy
precipitable_water
Expand All @@ -60,6 +63,9 @@ Moist Thermodynamics
virtual_potential_temperature
virtual_temperature
virtual_temperature_from_dewpoint
water_latent_heat_melting
water_latent_heat_sublimation
water_latent_heat_vaporization
wet_bulb_temperature
wet_bulb_potential_temperature

Expand Down
8 changes: 8 additions & 0 deletions docs/api/references.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
References
==========

.. [Ambaum2020] Ambaum MHP, 2020: Accurate, simple equation for saturated vapour pressure over water and ice.
*QJR Meteorol Soc.*; **146**: 4252–4258,
doi: `10.1002/qj.3899 <https://doi.org/10.1002/qj.3899>`_.
.. [Anderson2013] Anderson, G. B., M. L. Bell, and R. D. Peng, 2013: Methods to
Calculate the Heat Index as an Exposure Metric in Environmental Health
Research. *Environmental Health Perspectives*, **121**, 1111-1119,
Expand Down Expand Up @@ -176,6 +180,10 @@ References
.. [Rochette2006] Rochette, Scott M., and Patrick S. Market. "A primer on the
ageostrophic wind." Natl. Weather Dig. 30 (2006): 17-28.
.. [Romps2017] Romps, D. M., 2017: Exact Expression for the Lifting Condensation Level.
*J. Atmos. Sci.*, **74**, 3891–3900,
doi: `10.1175/JAS-D-17-0102.1. <https://doi.org/10.1175/JAS-D-17-0102.1.>`_.
.. [Rothfusz1990] Rothfusz, L.P.: *The Heat Index "Equation"*. Fort Worth, TX: Scientific
Services Division, NWS Southern Region Headquarters, 1990.
`SR90-23 <../_static/rothfusz-1990-heat-index-equation.pdf>`_, 2 pp.
Expand Down
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@
# Couldn't fix these 403's with user agents
r'https://doi\.org/10\.1029/2010GL045777',
r'https://doi\.org/10\.1098/rspa\.2004\.1430',
r'https://doi\.org/10\.1002/qj\.3899',
# Currently giving certificate errors on GitHub
r'https://library.wmo.int/.*',
# For some reason GHA gets a 403 from Stack Overflow
Expand Down
245 changes: 245 additions & 0 deletions src/metpy/calc/thermo.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,251 @@
exporter = Exporter(globals())


@exporter.export
@preprocess_and_wrap(wrap_like='specific_humidity')
@process_units(input_dimensionalities={'specific_humidity': 'dimensionless'},
output_dimensionalities='[specific_heat_capacity]',
output_to='J K**-1 kg**-1 ')
def moist_air_gas_constant(specific_humidity):
r"""Calculate R_m, the specific gas constant for a parcel of moist air.
Parameters
----------
specific_humidity : `pint.Quantity`
Returns
-------
`pint.Quantity`
Specific gas constant
Examples
--------
>>> from metpy.calc import moist_air_gas_constant
>>> from metpy.units import units
>>> moist_air_gas_constant(11 * units('g/kg'))
<Quantity(288.966723, 'joule / kelvin / kilogram')>
See Also
--------
moist_air_specific_heat_pressure, moist_air_poisson_exponent
Notes
-----
Adapted from
.. math:: R_m = (1 - q_v) R_a + q_v R_v
Eq 16, [Romps2017]_ using MetPy-defined constants in place of cited values.
"""
return mpconsts.nounit.Rd + specific_humidity * (mpconsts.nounit.Rv - mpconsts.nounit.Rd)


@exporter.export
@preprocess_and_wrap(wrap_like='specific_humidity')
@process_units(input_dimensionalities={'specific_humidity': 'dimensionless'},
output_dimensionalities='[specific_heat_capacity]',
output_to='J K**-1 kg**-1 ')
def moist_air_specific_heat_pressure(specific_humidity):
r"""Calculate C_pm, the specific heat at constant pressure for a moist air parcel.
Parameters
----------
specific_humidity : `pint.Quantity`
Returns
-------
`pint.Quantity`
Specific heat capacity of air at constant pressure
Examples
--------
>>> from metpy.calc import moist_air_specific_heat_pressure
>>> from metpy.units import units
>>> moist_air_specific_heat_pressure(11 * units('g/kg'))
<Quantity(1014.07575, 'joule / kelvin / kilogram')>
See Also
--------
moist_air_gas_constant, moist_air_poisson_exponent
Notes
-----
Adapted from
.. math:: c_{pm} = (1 - q_v) c_{pa} + q_v c_{pv}
Eq 17, [Romps2017]_ using MetPy-defined constants in place of cited values.
"""
return (mpconsts.nounit.Cp_d
+ specific_humidity * (mpconsts.nounit.Cp_v - mpconsts.nounit.Cp_d))


@exporter.export
@preprocess_and_wrap(wrap_like='specific_humidity')
@process_units(
input_dimensionalities={'specific_humidity': 'dimensionless'},
output_dimensionalities='[dimensionless]'
)
def moist_air_poisson_exponent(specific_humidity):
r"""Calculate kappa_m, the Poisson exponent for a moist air parcel.
Parameters
----------
specific_humidity : `pint.Quantity`
Returns
-------
`pint.Quantity`
Poisson exponent of moist air parcel
Examples
--------
>>> from metpy.calc import moist_air_poisson_exponent
>>> from metpy.units import units
>>> moist_air_poisson_exponent(11 * units('g/kg'))
<Quantity(0.284955757, 'dimensionless')>
See Also
--------
moist_air_gas_constant, moist_air_specific_heat_pressure
"""
return (moist_air_gas_constant._nounit(specific_humidity)
/ moist_air_specific_heat_pressure._nounit(specific_humidity))


@exporter.export
@preprocess_and_wrap(wrap_like='temperature')
@process_units(input_dimensionalities={'temperature': '[temperature]'},
output_dimensionalities='[specific_enthalpy]',
output_to='J kg**-1')
def water_latent_heat_vaporization(temperature):
r"""Calculate the latent heat of vaporization for water.
Accounts for variations in latent heat across valid temperature range.
Parameters
----------
temperature : `pint.Quantity`
Returns
-------
`pint.Quantity`
Latent heat of vaporization
Examples
--------
>>> from metpy.calc import water_latent_heat_vaporization
>>> from metpy.units import units
>>> water_latent_heat_vaporization(20 * units.degC)
<Quantity(2453677.15, 'joule / kilogram')>
See Also
--------
water_latent_heat_sublimation, water_latent_heat_melting
Notes
-----
Assumption of constant :math:`C_{pv}` limits validity to :math:`0` -- :math:`100^{\circ} C`
range.
.. math:: L = L_0 - (c_{pl} - c_{pv}) (T - T_0)
Eq 15, [Ambaum2020]_, using MetPy-defined constants in place of cited values.
"""
return (mpconsts.nounit.Lv
- (mpconsts.nounit.Cp_l - mpconsts.nounit.Cp_v)
* (temperature - mpconsts.nounit.T0))


@exporter.export
@preprocess_and_wrap(wrap_like='temperature')
@process_units(input_dimensionalities={'temperature': '[temperature]'},
output_dimensionalities='[specific_enthalpy]',
output_to='J kg**-1')
def water_latent_heat_sublimation(temperature):
r"""Calculate the latent heat of sublimation for water.
Accounts for variations in latent heat across valid temperature range.
Parameters
----------
temperature : `pint.Quantity`
Returns
-------
`pint.Quantity`
Latent heat of vaporization
Examples
--------
>>> from metpy.calc import water_latent_heat_sublimation
>>> from metpy.units import units
>>> water_latent_heat_sublimation(-15 * units.degC)
<Quantity(2837991.13, 'joule / kilogram')>
See Also
--------
water_latent_heat_vaporization, water_latent_heat_melting
Notes
-----
.. math:: L_s = L_{s0} - (c_{pl} - c_{pv}) (T - T_0)
Eq 18, [Ambaum2020]_, using MetPy-defined constants in place of cited values.
"""
return (mpconsts.nounit.Ls
- (mpconsts.nounit.Cp_i - mpconsts.nounit.Cp_v)
* (temperature - mpconsts.nounit.T0))


@exporter.export
@preprocess_and_wrap(wrap_like='temperature')
@process_units(input_dimensionalities={'temperature': '[temperature]'},
output_dimensionalities='[specific_enthalpy]',
output_to='J kg**-1')
def water_latent_heat_melting(temperature):
r"""Calculate the latent heat of melting for water.
Accounts for variations in latent heat across valid temperature range.
Parameters
----------
temperature : `pint.Quantity`
Returns
-------
`pint.Quantity`
Latent heat of vaporization
Examples
--------
>>> from metpy.calc import water_latent_heat_melting
>>> from metpy.units import units
>>> water_latent_heat_melting(-15 * units.degC)
<Quantity(365662.294, 'joule / kilogram')>
See Also
--------
water_latent_heat_vaporization, water_latent_heat_sublimation
Notes
-----
.. math:: L_m = L_{m0} + (c_{pl} - c_{pi}) (T - T_0)
Body text below Eq 20, [Ambaum2020]_, derived from Eq 15, Eq 18.
Uses MetPy-defined constants in place of cited values.
"""
return (mpconsts.nounit.Lf
- (mpconsts.nounit.Cp_l - mpconsts.nounit.Cp_i)
* (temperature - mpconsts.nounit.T0))


@exporter.export
@preprocess_and_wrap(wrap_like='temperature', broadcast=('temperature', 'dewpoint'))
@check_units('[temperature]', '[temperature]')
Expand Down
30 changes: 16 additions & 14 deletions src/metpy/constants/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,22 @@
Water
-----
======================= ================ ========== ============================ ====================================================
Name Symbol Short Name Units Description
----------------------- ---------------- ---------- ---------------------------- ----------------------------------------------------
water_molecular_weight :math:`M_w` Mw :math:`\text{g mol}^{-1}` Molecular weight of water [5]_
water_gas_constant :math:`R_v` Rv :math:`\text{J (K kg)}^{-1}` Gas constant for water vapor [2]_ [5]_
density_water :math:`\rho_l` rho_l :math:`\text{kg m}^{-3}` Maximum recommended density of liquid water, 0-40C [5]_
wv_specific_heat_press :math:`C_{pv}` Cp_v :math:`\text{J (K kg)}^{-1}` Specific heat at constant pressure for water vapor
wv_specific_heat_vol :math:`C_{vv}` Cv_v :math:`\text{J (K kg)}^{-1}` Specific heat at constant volume for water vapor
water_specific_heat :math:`Cp_l` Cp_l :math:`\text{J (K kg)}^{-1}` Specific heat of liquid water at 0C [6]_
water_heat_vaporization :math:`L_v` Lv :math:`\text{J kg}^{-1}` Latent heat of vaporization for liquid water at 0C [7]_
water_heat_fusion :math:`L_f` Lf :math:`\text{J kg}^{-1}` Latent heat of fusion for liquid water at 0C [7]_
ice_specific_heat :math:`C_{pi}` Cp_i :math:`\text{J (K kg)}^{-1}` Specific heat of ice at 0C [7]_
density_ice :math:`\rho_i` rho_i :math:`\text{kg m}^{-3}` Density of ice at 0C
======================= ================ ========== ============================ ====================================================
============================== ================ ========== ============================== ==========================================================
Name Symbol Short Name Units Description
------------------------------ ---------------- ---------- ------------------------------ ----------------------------------------------------------
water_molecular_weight :math:`M_w` Mw :math:`\text{g mol}^{-1}` Molecular weight of water [5]_
water_gas_constant :math:`R_v` Rv :math:`\text{J (K kg)}^{-1}` Gas constant for water vapor [2]_ [5]_
density_water :math:`\rho_l` rho_l :math:`\text{kg m}^{-3}` Maximum recommended density of liquid water, 0-40C [5]_
wv_specific_heat_press :math:`C_{pv}` Cp_v :math:`\text{J (K kg)}^{-1}` Specific heat at constant pressure for water vapor
wv_specific_heat_vol :math:`C_{vv}` Cv_v :math:`\text{J (K kg)}^{-1}` Specific heat at constant volume for water vapor
water_specific_heat :math:`C_{pl}` Cp_l :math:`\text{J (K kg)}^{-1}` Specific heat of liquid water at 0C [6]_
water_heat_vaporization :math:`L_v` Lv :math:`\text{J kg}^{-1}` Latent heat of vaporization for liquid water at 0C [7]_
water_heat_fusion :math:`L_f` Lf :math:`\text{J kg}^{-1}` Latent heat of fusion for liquid water at 0C [7]_
water_heat_sublimation :math:`L_s` Ls :math:`\text{J kg}^{-1}` Latent heat of sublimation for water, Lv + Lf
ice_specific_heat :math:`C_{pi}` Cp_i :math:`\text{J (K kg)}^{-1}` Specific heat of ice at 0C [7]_
density_ice :math:`\rho_i` rho_i :math:`\text{kg m}^{-3}` Density of ice at 0C
water_triple_point_temperature :math:`T_0` T0 :math:`\text{K}` Triple-point temperature of water [2]_
============================== ================ ========== ============================== ==========================================================
Dry Air
-------
Expand Down
2 changes: 2 additions & 0 deletions src/metpy/constants/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@
Cp_l = water_specific_heat = units.Quantity(4.2194, 'kJ / kg / K').to('J / kg / K')
Lv = water_heat_vaporization = units.Quantity(2.50084e6, 'J / kg')
Lf = water_heat_fusion = units.Quantity(3.337e5, 'J / kg')
Ls = water_heat_sublimation = Lv + Lf
Cp_i = ice_specific_heat = units.Quantity(2090, 'J / kg / K')
rho_i = density_ice = units.Quantity(917, 'kg / m^3')
sat_pressure_0c = units.Quantity(6.112, 'millibar')
T0 = water_triple_point_temperature = units.Quantity(273.16, 'K')

# Dry air
Md = dry_air_molecular_weight = units.Quantity(28.96546e-3, 'kg / mol')
Expand Down
7 changes: 7 additions & 0 deletions src/metpy/constants/nounit.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,17 @@
from ..units import units

Rd = default.Rd.m_as('m**2 / K / s**2')
Rv = default.Rv.m_as('m**2 / K / s**2')
Lv = default.Lv.m_as('m**2 / s**2')
Lf = default.Lf.m_as('m**2 / s**2')
Ls = default.Ls.m_as('m**2 / s**2')
Cp_d = default.Cp_d.m_as('m**2 / K / s**2')
Cp_l = default.Cp_l.m_as('m**2 / K / s**2')
Cp_v = default.Cp_v.m_as('m**2 / K / s**2')
Cp_i = default.Cp_i.m_as('m**2 / K / s**2')
zero_degc = units.Quantity(0., 'degC').m_as('K')
sat_pressure_0c = default.sat_pressure_0c.m_as('Pa')
epsilon = default.epsilon.m_as('')
kappa = default.kappa.m_as('')
g = default.g.m_as('m / s**2')
T0 = default.T0.m_as('K')
6 changes: 5 additions & 1 deletion src/metpy/units.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
'[temperature]': 'K',
'[dimensionless]': '',
'[length]': 'm',
'[speed]': 'm s**-1'
'[speed]': 'm s**-1',
'[specific_enthalpy]': 'm**2 s**-2',
'[specific_heat_capacity]': 'm**2 s**-2 K-1'
}


Expand Down Expand Up @@ -83,6 +85,8 @@ def setup_registry(reg):
reg.define('degrees_east = degree = degrees_E = degreesE = degree_east = degree_E '
'= degreeE')
reg.define('dBz = 1e-18 m^3; logbase: 10; logfactor: 10 = dBZ')
reg.define('[specific_enthalpy] = [energy] / [mass]')
reg.define('[specific_heat_capacity] = [specific_enthalpy] / [temperature]')

# Alias geopotential meters (gpm) to just meters
reg.define('@alias meter = gpm')
Expand Down
Loading

0 comments on commit ea2fcfc

Please sign in to comment.