Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Moisture/temperature dependent gas constants & latent heat #3725

Merged
merged 10 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Dismissed Show dismissed Hide dismissed
Dismissed Show dismissed Hide dismissed
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')
dopplershift marked this conversation as resolved.
Show resolved Hide resolved

# 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
Loading