Skip to content

Commit ea2fcfc

Browse files
authored
Merge pull request #3725 from dcamron/not-constants
Moisture/temperature dependent gas constants & latent heat
2 parents 82e91cb + 26ea138 commit ea2fcfc

File tree

9 files changed

+351
-23
lines changed

9 files changed

+351
-23
lines changed

docs/_templates/overrides/metpy.calc.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ Moist Thermodynamics
3939
mixing_ratio
4040
mixing_ratio_from_relative_humidity
4141
mixing_ratio_from_specific_humidity
42+
moist_air_gas_constant
43+
moist_air_poisson_exponent
44+
moist_air_specific_heat_pressure
4245
moist_lapse
4346
moist_static_energy
4447
precipitable_water
@@ -60,6 +63,9 @@ Moist Thermodynamics
6063
virtual_potential_temperature
6164
virtual_temperature
6265
virtual_temperature_from_dewpoint
66+
water_latent_heat_melting
67+
water_latent_heat_sublimation
68+
water_latent_heat_vaporization
6369
wet_bulb_temperature
6470
wet_bulb_potential_temperature
6571

docs/api/references.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
References
33
==========
44

5+
.. [Ambaum2020] Ambaum MHP, 2020: Accurate, simple equation for saturated vapour pressure over water and ice.
6+
*QJR Meteorol Soc.*; **146**: 4252–4258,
7+
doi: `10.1002/qj.3899 <https://doi.org/10.1002/qj.3899>`_.
8+
59
.. [Anderson2013] Anderson, G. B., M. L. Bell, and R. D. Peng, 2013: Methods to
610
Calculate the Heat Index as an Exposure Metric in Environmental Health
711
Research. *Environmental Health Perspectives*, **121**, 1111-1119,
@@ -176,6 +180,10 @@ References
176180
.. [Rochette2006] Rochette, Scott M., and Patrick S. Market. "A primer on the
177181
ageostrophic wind." Natl. Weather Dig. 30 (2006): 17-28.
178182
183+
.. [Romps2017] Romps, D. M., 2017: Exact Expression for the Lifting Condensation Level.
184+
*J. Atmos. Sci.*, **74**, 3891–3900,
185+
doi: `10.1175/JAS-D-17-0102.1. <https://doi.org/10.1175/JAS-D-17-0102.1.>`_.
186+
179187
.. [Rothfusz1990] Rothfusz, L.P.: *The Heat Index "Equation"*. Fort Worth, TX: Scientific
180188
Services Division, NWS Southern Region Headquarters, 1990.
181189
`SR90-23 <../_static/rothfusz-1990-heat-index-equation.pdf>`_, 2 pp.

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,7 @@
455455
# Couldn't fix these 403's with user agents
456456
r'https://doi\.org/10\.1029/2010GL045777',
457457
r'https://doi\.org/10\.1098/rspa\.2004\.1430',
458+
r'https://doi\.org/10\.1002/qj\.3899',
458459
# Currently giving certificate errors on GitHub
459460
r'https://library.wmo.int/.*',
460461
# For some reason GHA gets a 403 from Stack Overflow

src/metpy/calc/thermo.py

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,251 @@
2929
exporter = Exporter(globals())
3030

3131

32+
@exporter.export
33+
@preprocess_and_wrap(wrap_like='specific_humidity')
34+
@process_units(input_dimensionalities={'specific_humidity': 'dimensionless'},
35+
output_dimensionalities='[specific_heat_capacity]',
36+
output_to='J K**-1 kg**-1 ')
37+
def moist_air_gas_constant(specific_humidity):
38+
r"""Calculate R_m, the specific gas constant for a parcel of moist air.
39+
40+
Parameters
41+
----------
42+
specific_humidity : `pint.Quantity`
43+
44+
Returns
45+
-------
46+
`pint.Quantity`
47+
Specific gas constant
48+
49+
Examples
50+
--------
51+
>>> from metpy.calc import moist_air_gas_constant
52+
>>> from metpy.units import units
53+
>>> moist_air_gas_constant(11 * units('g/kg'))
54+
<Quantity(288.966723, 'joule / kelvin / kilogram')>
55+
56+
See Also
57+
--------
58+
moist_air_specific_heat_pressure, moist_air_poisson_exponent
59+
60+
Notes
61+
-----
62+
Adapted from
63+
64+
.. math:: R_m = (1 - q_v) R_a + q_v R_v
65+
66+
Eq 16, [Romps2017]_ using MetPy-defined constants in place of cited values.
67+
68+
"""
69+
return mpconsts.nounit.Rd + specific_humidity * (mpconsts.nounit.Rv - mpconsts.nounit.Rd)
70+
71+
72+
@exporter.export
73+
@preprocess_and_wrap(wrap_like='specific_humidity')
74+
@process_units(input_dimensionalities={'specific_humidity': 'dimensionless'},
75+
output_dimensionalities='[specific_heat_capacity]',
76+
output_to='J K**-1 kg**-1 ')
77+
def moist_air_specific_heat_pressure(specific_humidity):
78+
r"""Calculate C_pm, the specific heat at constant pressure for a moist air parcel.
79+
80+
Parameters
81+
----------
82+
specific_humidity : `pint.Quantity`
83+
84+
Returns
85+
-------
86+
`pint.Quantity`
87+
Specific heat capacity of air at constant pressure
88+
89+
Examples
90+
--------
91+
>>> from metpy.calc import moist_air_specific_heat_pressure
92+
>>> from metpy.units import units
93+
>>> moist_air_specific_heat_pressure(11 * units('g/kg'))
94+
<Quantity(1014.07575, 'joule / kelvin / kilogram')>
95+
96+
See Also
97+
--------
98+
moist_air_gas_constant, moist_air_poisson_exponent
99+
100+
Notes
101+
-----
102+
Adapted from
103+
104+
.. math:: c_{pm} = (1 - q_v) c_{pa} + q_v c_{pv}
105+
106+
Eq 17, [Romps2017]_ using MetPy-defined constants in place of cited values.
107+
108+
"""
109+
return (mpconsts.nounit.Cp_d
110+
+ specific_humidity * (mpconsts.nounit.Cp_v - mpconsts.nounit.Cp_d))
111+
112+
113+
@exporter.export
114+
@preprocess_and_wrap(wrap_like='specific_humidity')
115+
@process_units(
116+
input_dimensionalities={'specific_humidity': 'dimensionless'},
117+
output_dimensionalities='[dimensionless]'
118+
)
119+
def moist_air_poisson_exponent(specific_humidity):
120+
r"""Calculate kappa_m, the Poisson exponent for a moist air parcel.
121+
122+
Parameters
123+
----------
124+
specific_humidity : `pint.Quantity`
125+
126+
Returns
127+
-------
128+
`pint.Quantity`
129+
Poisson exponent of moist air parcel
130+
131+
Examples
132+
--------
133+
>>> from metpy.calc import moist_air_poisson_exponent
134+
>>> from metpy.units import units
135+
>>> moist_air_poisson_exponent(11 * units('g/kg'))
136+
<Quantity(0.284955757, 'dimensionless')>
137+
138+
See Also
139+
--------
140+
moist_air_gas_constant, moist_air_specific_heat_pressure
141+
142+
"""
143+
return (moist_air_gas_constant._nounit(specific_humidity)
144+
/ moist_air_specific_heat_pressure._nounit(specific_humidity))
145+
146+
147+
@exporter.export
148+
@preprocess_and_wrap(wrap_like='temperature')
149+
@process_units(input_dimensionalities={'temperature': '[temperature]'},
150+
output_dimensionalities='[specific_enthalpy]',
151+
output_to='J kg**-1')
152+
def water_latent_heat_vaporization(temperature):
153+
r"""Calculate the latent heat of vaporization for water.
154+
155+
Accounts for variations in latent heat across valid temperature range.
156+
157+
Parameters
158+
----------
159+
temperature : `pint.Quantity`
160+
161+
Returns
162+
-------
163+
`pint.Quantity`
164+
Latent heat of vaporization
165+
166+
Examples
167+
--------
168+
>>> from metpy.calc import water_latent_heat_vaporization
169+
>>> from metpy.units import units
170+
>>> water_latent_heat_vaporization(20 * units.degC)
171+
<Quantity(2453677.15, 'joule / kilogram')>
172+
173+
See Also
174+
--------
175+
water_latent_heat_sublimation, water_latent_heat_melting
176+
177+
Notes
178+
-----
179+
Assumption of constant :math:`C_{pv}` limits validity to :math:`0` -- :math:`100^{\circ} C`
180+
range.
181+
182+
.. math:: L = L_0 - (c_{pl} - c_{pv}) (T - T_0)
183+
184+
Eq 15, [Ambaum2020]_, using MetPy-defined constants in place of cited values.
185+
186+
"""
187+
return (mpconsts.nounit.Lv
188+
- (mpconsts.nounit.Cp_l - mpconsts.nounit.Cp_v)
189+
* (temperature - mpconsts.nounit.T0))
190+
191+
192+
@exporter.export
193+
@preprocess_and_wrap(wrap_like='temperature')
194+
@process_units(input_dimensionalities={'temperature': '[temperature]'},
195+
output_dimensionalities='[specific_enthalpy]',
196+
output_to='J kg**-1')
197+
def water_latent_heat_sublimation(temperature):
198+
r"""Calculate the latent heat of sublimation for water.
199+
200+
Accounts for variations in latent heat across valid temperature range.
201+
202+
Parameters
203+
----------
204+
temperature : `pint.Quantity`
205+
206+
Returns
207+
-------
208+
`pint.Quantity`
209+
Latent heat of vaporization
210+
211+
Examples
212+
--------
213+
>>> from metpy.calc import water_latent_heat_sublimation
214+
>>> from metpy.units import units
215+
>>> water_latent_heat_sublimation(-15 * units.degC)
216+
<Quantity(2837991.13, 'joule / kilogram')>
217+
218+
See Also
219+
--------
220+
water_latent_heat_vaporization, water_latent_heat_melting
221+
222+
Notes
223+
-----
224+
.. math:: L_s = L_{s0} - (c_{pl} - c_{pv}) (T - T_0)
225+
226+
Eq 18, [Ambaum2020]_, using MetPy-defined constants in place of cited values.
227+
228+
"""
229+
return (mpconsts.nounit.Ls
230+
- (mpconsts.nounit.Cp_i - mpconsts.nounit.Cp_v)
231+
* (temperature - mpconsts.nounit.T0))
232+
233+
234+
@exporter.export
235+
@preprocess_and_wrap(wrap_like='temperature')
236+
@process_units(input_dimensionalities={'temperature': '[temperature]'},
237+
output_dimensionalities='[specific_enthalpy]',
238+
output_to='J kg**-1')
239+
def water_latent_heat_melting(temperature):
240+
r"""Calculate the latent heat of melting for water.
241+
242+
Accounts for variations in latent heat across valid temperature range.
243+
244+
Parameters
245+
----------
246+
temperature : `pint.Quantity`
247+
248+
Returns
249+
-------
250+
`pint.Quantity`
251+
Latent heat of vaporization
252+
253+
Examples
254+
--------
255+
>>> from metpy.calc import water_latent_heat_melting
256+
>>> from metpy.units import units
257+
>>> water_latent_heat_melting(-15 * units.degC)
258+
<Quantity(365662.294, 'joule / kilogram')>
259+
260+
See Also
261+
--------
262+
water_latent_heat_vaporization, water_latent_heat_sublimation
263+
264+
Notes
265+
-----
266+
.. math:: L_m = L_{m0} + (c_{pl} - c_{pi}) (T - T_0)
267+
268+
Body text below Eq 20, [Ambaum2020]_, derived from Eq 15, Eq 18.
269+
Uses MetPy-defined constants in place of cited values.
270+
271+
"""
272+
return (mpconsts.nounit.Lf
273+
- (mpconsts.nounit.Cp_l - mpconsts.nounit.Cp_i)
274+
* (temperature - mpconsts.nounit.T0))
275+
276+
32277
@exporter.export
33278
@preprocess_and_wrap(wrap_like='temperature', broadcast=('temperature', 'dewpoint'))
34279
@check_units('[temperature]', '[temperature]')

src/metpy/constants/__init__.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,22 @@
2222
2323
Water
2424
-----
25-
======================= ================ ========== ============================ ====================================================
26-
Name Symbol Short Name Units Description
27-
----------------------- ---------------- ---------- ---------------------------- ----------------------------------------------------
28-
water_molecular_weight :math:`M_w` Mw :math:`\text{g mol}^{-1}` Molecular weight of water [5]_
29-
water_gas_constant :math:`R_v` Rv :math:`\text{J (K kg)}^{-1}` Gas constant for water vapor [2]_ [5]_
30-
density_water :math:`\rho_l` rho_l :math:`\text{kg m}^{-3}` Maximum recommended density of liquid water, 0-40C [5]_
31-
wv_specific_heat_press :math:`C_{pv}` Cp_v :math:`\text{J (K kg)}^{-1}` Specific heat at constant pressure for water vapor
32-
wv_specific_heat_vol :math:`C_{vv}` Cv_v :math:`\text{J (K kg)}^{-1}` Specific heat at constant volume for water vapor
33-
water_specific_heat :math:`Cp_l` Cp_l :math:`\text{J (K kg)}^{-1}` Specific heat of liquid water at 0C [6]_
34-
water_heat_vaporization :math:`L_v` Lv :math:`\text{J kg}^{-1}` Latent heat of vaporization for liquid water at 0C [7]_
35-
water_heat_fusion :math:`L_f` Lf :math:`\text{J kg}^{-1}` Latent heat of fusion for liquid water at 0C [7]_
36-
ice_specific_heat :math:`C_{pi}` Cp_i :math:`\text{J (K kg)}^{-1}` Specific heat of ice at 0C [7]_
37-
density_ice :math:`\rho_i` rho_i :math:`\text{kg m}^{-3}` Density of ice at 0C
38-
======================= ================ ========== ============================ ====================================================
25+
============================== ================ ========== ============================== ==========================================================
26+
Name Symbol Short Name Units Description
27+
------------------------------ ---------------- ---------- ------------------------------ ----------------------------------------------------------
28+
water_molecular_weight :math:`M_w` Mw :math:`\text{g mol}^{-1}` Molecular weight of water [5]_
29+
water_gas_constant :math:`R_v` Rv :math:`\text{J (K kg)}^{-1}` Gas constant for water vapor [2]_ [5]_
30+
density_water :math:`\rho_l` rho_l :math:`\text{kg m}^{-3}` Maximum recommended density of liquid water, 0-40C [5]_
31+
wv_specific_heat_press :math:`C_{pv}` Cp_v :math:`\text{J (K kg)}^{-1}` Specific heat at constant pressure for water vapor
32+
wv_specific_heat_vol :math:`C_{vv}` Cv_v :math:`\text{J (K kg)}^{-1}` Specific heat at constant volume for water vapor
33+
water_specific_heat :math:`C_{pl}` Cp_l :math:`\text{J (K kg)}^{-1}` Specific heat of liquid water at 0C [6]_
34+
water_heat_vaporization :math:`L_v` Lv :math:`\text{J kg}^{-1}` Latent heat of vaporization for liquid water at 0C [7]_
35+
water_heat_fusion :math:`L_f` Lf :math:`\text{J kg}^{-1}` Latent heat of fusion for liquid water at 0C [7]_
36+
water_heat_sublimation :math:`L_s` Ls :math:`\text{J kg}^{-1}` Latent heat of sublimation for water, Lv + Lf
37+
ice_specific_heat :math:`C_{pi}` Cp_i :math:`\text{J (K kg)}^{-1}` Specific heat of ice at 0C [7]_
38+
density_ice :math:`\rho_i` rho_i :math:`\text{kg m}^{-3}` Density of ice at 0C
39+
water_triple_point_temperature :math:`T_0` T0 :math:`\text{K}` Triple-point temperature of water [2]_
40+
============================== ================ ========== ============================== ==========================================================
3941
4042
Dry Air
4143
-------

src/metpy/constants/default.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,11 @@
3737
Cp_l = water_specific_heat = units.Quantity(4.2194, 'kJ / kg / K').to('J / kg / K')
3838
Lv = water_heat_vaporization = units.Quantity(2.50084e6, 'J / kg')
3939
Lf = water_heat_fusion = units.Quantity(3.337e5, 'J / kg')
40+
Ls = water_heat_sublimation = Lv + Lf
4041
Cp_i = ice_specific_heat = units.Quantity(2090, 'J / kg / K')
4142
rho_i = density_ice = units.Quantity(917, 'kg / m^3')
4243
sat_pressure_0c = units.Quantity(6.112, 'millibar')
44+
T0 = water_triple_point_temperature = units.Quantity(273.16, 'K')
4345

4446
# Dry air
4547
Md = dry_air_molecular_weight = units.Quantity(28.96546e-3, 'kg / mol')

src/metpy/constants/nounit.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,17 @@
77
from ..units import units
88

99
Rd = default.Rd.m_as('m**2 / K / s**2')
10+
Rv = default.Rv.m_as('m**2 / K / s**2')
1011
Lv = default.Lv.m_as('m**2 / s**2')
12+
Lf = default.Lf.m_as('m**2 / s**2')
13+
Ls = default.Ls.m_as('m**2 / s**2')
1114
Cp_d = default.Cp_d.m_as('m**2 / K / s**2')
15+
Cp_l = default.Cp_l.m_as('m**2 / K / s**2')
16+
Cp_v = default.Cp_v.m_as('m**2 / K / s**2')
17+
Cp_i = default.Cp_i.m_as('m**2 / K / s**2')
1218
zero_degc = units.Quantity(0., 'degC').m_as('K')
1319
sat_pressure_0c = default.sat_pressure_0c.m_as('Pa')
1420
epsilon = default.epsilon.m_as('')
1521
kappa = default.kappa.m_as('')
1622
g = default.g.m_as('m / s**2')
23+
T0 = default.T0.m_as('K')

src/metpy/units.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@
3939
'[temperature]': 'K',
4040
'[dimensionless]': '',
4141
'[length]': 'm',
42-
'[speed]': 'm s**-1'
42+
'[speed]': 'm s**-1',
43+
'[specific_enthalpy]': 'm**2 s**-2',
44+
'[specific_heat_capacity]': 'm**2 s**-2 K-1'
4345
}
4446

4547

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

8791
# Alias geopotential meters (gpm) to just meters
8892
reg.define('@alias meter = gpm')

0 commit comments

Comments
 (0)