Skip to content

Commit

Permalink
Add Rm, Cm, Lv, Lf, Lm
Browse files Browse the repository at this point in the history
Calculate gas constant and specific heat capacity of moist air. Add temperature-dependent latent heat quantity calculations.
  • Loading branch information
dcamron committed Dec 23, 2024
1 parent ed3c0d7 commit 4a8c745
Show file tree
Hide file tree
Showing 2 changed files with 249 additions and 2 deletions.
197 changes: 197 additions & 0 deletions src/metpy/calc/thermo.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,203 @@
exporter = Exporter(globals())


@exporter.export
@preprocess_and_wrap(wrap_like='specific_humidity')
@check_units('[dimensionless]')
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
Notes
-----
.. 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 ((1 - specific_humidity) * mpconsts.dry_air_gas_constant
+ specific_humidity * mpconsts.water_gas_constant)


@exporter.export
@preprocess_and_wrap(wrap_like='specific_humidity')
@check_units('[dimensionless]')
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
Notes
-----
.. 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 ((1 - specific_humidity) * mpconsts.dry_air_spec_heat_press
+ specific_humidity * mpconsts.wv_specific_heat_press)


@exporter.export
@preprocess_and_wrap(wrap_like='temperature')
@check_units('[temperature]')
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 \deg \en 100 \deg 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.water_heat_vaporization
- (mpconsts.water_specific_heat - mpconsts.wv_specific_heat_press)
* (temperature - mpconsts.water_triple_point_temperature))


@exporter.export
@preprocess_and_wrap(wrap_like='temperature')
@check_units('[temperature]')
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.water_heat_sublimation
- (mpconsts.ice_specific_heat - mpconsts.wv_specific_heat_press)
* (temperature - mpconsts.water_triple_point_temperature))


@exporter.export
@preprocess_and_wrap(wrap_like='temperature')
@check_units('[temperature]')
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.water_heat_fusion
- (mpconsts.water_specific_heat - mpconsts.ice_specific_heat)
* (temperature - mpconsts.water_triple_point_temperature))


@exporter.export
@preprocess_and_wrap(wrap_like='temperature', broadcast=('temperature', 'dewpoint'))
@check_units('[temperature]', '[temperature]')
Expand Down
54 changes: 52 additions & 2 deletions tests/calc/test_thermo.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
isentropic_interpolation, isentropic_interpolation_as_dataset, k_index,
lcl, lfc, lifted_index, mixed_layer, mixed_layer_cape_cin,
mixed_parcel, mixing_ratio, mixing_ratio_from_relative_humidity,
mixing_ratio_from_specific_humidity, moist_lapse, moist_static_energy,
mixing_ratio_from_specific_humidity, moist_air_gas_constant,
moist_air_specific_heat_pressure, moist_lapse, moist_static_energy,
most_unstable_cape_cin, most_unstable_parcel, parcel_profile,
parcel_profile_with_lcl, parcel_profile_with_lcl_as_dataset,
potential_temperature, psychrometric_vapor_pressure_wet,
Expand All @@ -36,13 +37,62 @@
vapor_pressure, vertical_totals, vertical_velocity,
vertical_velocity_pressure, virtual_potential_temperature,
virtual_temperature, virtual_temperature_from_dewpoint,
wet_bulb_potential_temperature, wet_bulb_temperature)
water_latent_heat_melting, water_latent_heat_sublimation,
water_latent_heat_vaporization, wet_bulb_potential_temperature,
wet_bulb_temperature)
from metpy.calc.thermo import _find_append_zero_crossings, galvez_davison_index
from metpy.constants import (dry_air_gas_constant, dry_air_spec_heat_press, water_heat_fusion,
water_heat_sublimation, water_heat_vaporization,
water_triple_point_temperature)
from metpy.testing import (assert_almost_equal, assert_array_almost_equal, assert_nan,
version_check)
from metpy.units import is_quantity, masked_array, units


def test_moist_air_gas_constant():
"""Test calculation of gas constant for moist air."""
q = 9 * units('g/kg')
assert_almost_equal(moist_air_gas_constant(q), 288.62 * units('J / kg / K'), 2)
assert_almost_equal(moist_air_gas_constant(0), dry_air_gas_constant)


def test_moist_air_specific_heat_pressure():
"""Test calculation of specific heat for moist air."""
q = 9 * units('g/kg')
assert_almost_equal(moist_air_specific_heat_pressure(q), 1012.36 * units('J / kg /K'), 2)
assert_almost_equal(moist_air_specific_heat_pressure(0), dry_air_spec_heat_press)


def test_water_latent_heat_vaporization():
"""Test temperature-dependent calculation of latent heat of vaporization for water."""
temperature = 300 * units.K
# Divide out sig figs in results for decimal comparison
assert_almost_equal(water_latent_heat_vaporization(temperature) / 10**6,
2.4375 * units('J / kg'), 4)
assert_almost_equal(water_latent_heat_vaporization(water_triple_point_temperature),
water_heat_vaporization)


def test_water_latent_heat_sublimation():
"""Test temperature-dependent calculation of latent heat of sublimation for water."""
temperature = 233 * units.K
# Divide out sig figs in results for decimal comparison
assert_almost_equal(water_latent_heat_sublimation(temperature) / 10**6,
2.8438 * units('J / kg'), 4)
assert_almost_equal(water_latent_heat_sublimation(water_triple_point_temperature),
water_heat_sublimation)


def test_water_latent_heat_melting():
"""Test temperature-dependent calculation of latent heat of melting for water."""
temperature = 233 * units.K
# Divide out sig figs in results for decimal comparison
assert_almost_equal(water_latent_heat_melting(temperature) / 10**6,
0.4192 * units('J / kg'), 4)
assert_almost_equal(water_latent_heat_melting(water_triple_point_temperature),
water_heat_fusion)


def test_relative_humidity_from_dewpoint():
"""Test Relative Humidity calculation."""
assert_almost_equal(relative_humidity_from_dewpoint(25. * units.degC, 15. * units.degC),
Expand Down

0 comments on commit 4a8c745

Please sign in to comment.