Skip to content

Commit 3128d27

Browse files
authored
Merge pull request #3728 from dcamron/analytic-lcl
Romps 2017 Analytic LCL
2 parents b28b921 + 6902eb5 commit 3128d27

2 files changed

Lines changed: 204 additions & 182 deletions

File tree

src/metpy/calc/thermo.py

Lines changed: 54 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import scipy.integrate as si
1616
import scipy.optimize as so
17+
from scipy.special import lambertw
1718
import xarray as xr
1819

1920
from .exceptions import InvalidSoundingError
@@ -276,7 +277,9 @@ def water_latent_heat_melting(temperature):
276277

277278
@exporter.export
278279
@preprocess_and_wrap(wrap_like='temperature', broadcast=('temperature', 'dewpoint'))
279-
@check_units('[temperature]', '[temperature]')
280+
@process_units(input_dimensionalities={'temperature': '[temperature]',
281+
'dewpoint': '[temperature]'},
282+
output_dimensionalities='[dimensionless]')
280283
def relative_humidity_from_dewpoint(temperature, dewpoint, *, phase='liquid'):
281284
r"""Calculate the relative humidity.
282285
@@ -321,8 +324,8 @@ def relative_humidity_from_dewpoint(temperature, dewpoint, *, phase='liquid'):
321324
322325
"""
323326
validate_choice({'liquid', 'solid', 'auto'}, phase=phase)
324-
e = saturation_vapor_pressure(dewpoint, phase=phase)
325-
e_s = saturation_vapor_pressure(temperature, phase=phase)
327+
e = saturation_vapor_pressure._nounit(dewpoint, phase=phase)
328+
e_s = saturation_vapor_pressure._nounit(temperature, phase=phase)
326329
return e / e_s
327330

328331

@@ -657,7 +660,7 @@ def dt(p, t):
657660
{'pressure': '[pressure]', 'temperature': '[temperature]', 'dewpoint': '[temperature]'},
658661
('[pressure]', '[temperature]')
659662
)
660-
def lcl(pressure, temperature, dewpoint, max_iters=50, eps=1e-5):
663+
def lcl(pressure, temperature, dewpoint, max_iters=None, eps=None):
661664
r"""Calculate the lifted condensation level (LCL) from the starting point.
662665
663666
The starting state for the parcel is defined by `temperature`, `dewpoint`,
@@ -683,60 +686,57 @@ def lcl(pressure, temperature, dewpoint, max_iters=50, eps=1e-5):
683686
`pint.Quantity`
684687
LCL temperature
685688
686-
Other Parameters
687-
----------------
688-
max_iters : int, optional
689-
The maximum number of iterations to use in calculation, defaults to 50.
690-
691-
eps : float, optional
692-
The desired relative error in the calculated value, defaults to 1e-5.
693-
694689
Examples
695690
--------
696691
>>> from metpy.calc import lcl
697692
>>> from metpy.units import units
698693
>>> lcl(943 * units.hPa, 33 * units.degC, 28 * units.degC)
699-
(<Quantity(877.097306, 'hectopascal')>, <Quantity(26.7279778, 'degree_Celsius')>)
694+
(<Quantity(877.033549, 'hectopascal')>, <Quantity(26.7591908, 'degree_Celsius')>)
700695
701696
See Also
702697
--------
703698
parcel_profile
704699
705700
Notes
706701
-----
707-
This function is implemented using an iterative approach to solve for the
708-
LCL. The basic algorithm is:
702+
From [Romps2017]_, this directly solves for the temperature at the LCL, Eq 22a,
703+
704+
.. math:: T_{LCL} = c [W_{-1}(RH_l^{1/a} c \exp{c})]^{-1} T
709705
710-
1. Find the dewpoint from the LCL pressure and starting mixing ratio
711-
2. Find the LCL pressure from the starting temperature and dewpoint
712-
3. Iterate until convergence
706+
and the pressure at the LCL, Eq 22b,
713707
714-
The function is guaranteed to finish by virtue of the `max_iters` counter.
708+
.. math:: p_{LCL} = p \left( \frac{T_{LCL}}{T} \right)^{c_{pm} / R_m}
709+
710+
where :math:`a` (Eq 22d), :math:`b` (Eq 22e), and :math:`c` (Eq 22f) are derived constants,
711+
and :math:`W_{-1}` is the :math:`k=-1` branch of the Lambert :math:`W` function.
715712
716713
.. versionchanged:: 1.0
717714
Renamed ``dewpt`` parameter to ``dewpoint``
718715
719716
"""
720-
def _lcl_iter(p, p0, w, t):
721-
nonlocal nan_mask
722-
td = globals()['dewpoint']._nounit(vapor_pressure._nounit(p, w))
723-
p_new = (p0 * (td / t) ** (1. / mpconsts.nounit.kappa))
724-
nan_mask = nan_mask | np.isnan(p_new)
725-
return np.where(np.isnan(p_new), p, p_new)
717+
if max_iters or eps:
718+
_warnings.warn(
719+
'max_iters, eps arguments unused and will be deprecated in a future version.',
720+
PendingDeprecationWarning)
721+
722+
q = specific_humidity_from_dewpoint._nounit(pressure, dewpoint, phase='liquid')
723+
moist_heat_ratio = (moist_air_specific_heat_pressure._nounit(q)
724+
/ moist_air_gas_constant._nounit(q))
725+
spec_heat_diff = mpconsts.nounit.Cp_l - mpconsts.nounit.Cp_v
726+
727+
a = moist_heat_ratio + spec_heat_diff / mpconsts.nounit.Rv
728+
b = (-(mpconsts.nounit.Lv + spec_heat_diff * mpconsts.nounit.T0)
729+
/ (mpconsts.nounit.Rv * temperature))
730+
c = b / a
726731

727-
# Handle nans by creating a mask that gets set by our _lcl_iter function if it
728-
# ever encounters a nan, at which point pressure is set to p, stopping iteration.
729-
nan_mask = False
730-
w = mixing_ratio._nounit(saturation_vapor_pressure._nounit(dewpoint), pressure)
731-
lcl_p = so.fixed_point(_lcl_iter, pressure, args=(pressure, w, temperature),
732-
xtol=eps, maxiter=max_iters)
733-
lcl_p = np.where(nan_mask, np.nan, lcl_p)
732+
w_minus1 = lambertw(
733+
(relative_humidity_from_dewpoint._nounit(temperature, dewpoint, phase='liquid')
734+
** (1 / a) * c * np.exp(c)), k=-1).real
734735

735-
# np.isclose needed if surface is LCL due to precision error with np.log in dewpoint.
736-
# Causes issues with parcel_profile_with_lcl if removed. Issue #1187
737-
lcl_p = np.where(np.isclose(lcl_p, pressure), pressure, lcl_p)
736+
t_lcl = c / w_minus1 * temperature
737+
p_lcl = pressure * (t_lcl / temperature) ** moist_heat_ratio
738738

739-
return lcl_p, globals()['dewpoint']._nounit(vapor_pressure._nounit(lcl_p, w))
739+
return p_lcl, t_lcl
740740

741741

742742
@exporter.export
@@ -912,7 +912,7 @@ def lfc(pressure, temperature, dewpoint, parcel_temperature_profile=None, dewpoi
912912
>>> Td = dewpoint_from_relative_humidity(T, rh)
913913
>>> # calculate LFC
914914
>>> lfc(p, T, Td)
915-
(<Quantity(967.153333, 'hectopascal')>, <Quantity(25.7463935, 'degree_Celsius')>)
915+
(<Quantity(967.309996, 'hectopascal')>, <Quantity(25.778387, 'degree_Celsius')>)
916916
917917
See Also
918918
--------
@@ -1128,7 +1128,7 @@ def el(pressure, temperature, dewpoint, parcel_temperature_profile=None, which='
11281128
>>> prof = parcel_profile(p, T[0], Td[0]).to('degC')
11291129
>>> # calculate EL
11301130
>>> el(p, T, Td, prof)
1131-
(<Quantity(112.330791, 'hectopascal')>, <Quantity(-76.2072049, 'degree_Celsius')>)
1131+
(<Quantity(112.252054, 'hectopascal')>, <Quantity(-76.2210312, 'degree_Celsius')>)
11321132
11331133
See Also
11341134
--------
@@ -1223,12 +1223,12 @@ def parcel_profile(pressure, temperature, dewpoint):
12231223
>>> Td = dewpoint_from_relative_humidity(T, rh)
12241224
>>> # computer parcel temperature
12251225
>>> parcel_profile(p, T[0], Td[0]).to('degC')
1226-
<Quantity([ 29.3 28.61221952 25.16541995 23.4015316 21.52129482
1227-
19.50726582 17.33772233 14.98517962 12.41415852 9.57784164
1228-
6.41283006 2.83063069 -1.29662004 -6.16091134 -12.0618903
1229-
-19.47976398 -29.16872218 -42.15825511 -50.22699729 -59.51873404
1230-
-70.22585491 -82.71535699 -94.46901701 -101.15645164 -108.5667789
1231-
-116.92064777 -126.57020709 -138.13646516 -144.98953102 -152.90542218], 'degree_Celsius')>
1226+
<Quantity([ 29.3 28.61221952 25.17408111 23.41044641 21.53049669
1227+
19.51679547 17.34763012 14.99552875 12.4250297 9.58933992
1228+
6.4250951 2.84385238 -1.28217807 -6.14487817 -12.0437512
1229+
-19.45887455 -29.14459155 -42.13147376 -50.19971377 -59.49169312
1230+
-70.19973455 -82.69068969 -94.44583924 -101.13413664 -108.54542355
1231+
-116.90037584 -126.55118719 -138.11894611 -144.97290122 -152.88981956], 'degree_Celsius')>
12321232
12331233
See Also
12341234
--------
@@ -2786,7 +2786,7 @@ def cape_cin(pressure, temperature, dewpoint, parcel_profile, which_lfc='bottom'
27862786
>>> prof = parcel_profile(p, T[0], Td[0]).to('degC')
27872787
>>> # calculate surface based CAPE/CIN
27882788
>>> cape_cin(p, T, Td, prof)
2789-
(<Quantity(4818.6911, 'joule / kilogram')>, <Quantity(0, 'joule / kilogram')>)
2789+
(<Quantity(4830.74608, 'joule / kilogram')>, <Quantity(0, 'joule / kilogram')>)
27902790
27912791
See Also
27922792
--------
@@ -3427,7 +3427,7 @@ def most_unstable_cape_cin(pressure, temperature, dewpoint, **kwargs):
34273427
>>> Td = dewpoint_from_relative_humidity(T, rh)
34283428
>>> # calculate most unstbale CAPE/CIN
34293429
>>> most_unstable_cape_cin(p, T, Td)
3430-
(<Quantity(4818.6911, 'joule / kilogram')>, <Quantity(0, 'joule / kilogram')>)
3430+
(<Quantity(4830.74608, 'joule / kilogram')>, <Quantity(0, 'joule / kilogram')>)
34313431
34323432
See Also
34333433
--------
@@ -3503,7 +3503,7 @@ def mixed_layer_cape_cin(pressure, temperature, dewpoint, **kwargs):
35033503
>>> # calculate dewpoint
35043504
>>> Td = dewpoint_from_relative_humidity(T, rh)
35053505
>>> mixed_layer_cape_cin(p, T, Td, depth=50 * units.hPa)
3506-
(<Quantity(691.939088, 'joule / kilogram')>, <Quantity(0, 'joule / kilogram')>)
3506+
(<Quantity(678.967843, 'joule / kilogram')>, <Quantity(0, 'joule / kilogram')>)
35073507
35083508
See Also
35093509
--------
@@ -3586,10 +3586,10 @@ def downdraft_cape(pressure, temperature, dewpoint):
35863586
>>> # calculate dewpoint
35873587
>>> Td = dewpoint_from_relative_humidity(T, rh)
35883588
>>> downdraft_cape(p, T, Td)
3589-
(<Quantity(1222.00699, 'joule / kilogram')>, <Quantity([1008. 1000. 950.
3590-
900. 850. 800. 750. 700. 650. 600.], 'hectopascal')>, <Quantity([17.51354413
3591-
17.21031876 15.24077208 13.12928933 10.85340448 8.38511286 5.6890572 2.71997016
3592-
-0.58085433 -4.29043969], 'degree_Celsius')>)
3589+
(<Quantity(1220.67097, 'joule / kilogram')>, <Quantity([1008. 1000. 950.
3590+
900. 850. 800. 750. 700. 650. 600.], 'hectopascal')>, <Quantity([17.52017969
3591+
17.21699119 15.24769167 13.13648889 10.86092343 8.39299889 5.69736825 2.72877681
3592+
-0.57146657 -4.28036902], 'degree_Celsius')>)
35933593
35943594
See Also
35953595
--------
@@ -4387,7 +4387,7 @@ def wet_bulb_temperature(pressure, temperature, dewpoint):
43874387
>>> from metpy.calc import wet_bulb_temperature
43884388
>>> from metpy.units import units
43894389
>>> wet_bulb_temperature(993 * units.hPa, 32 * units.degC, 15 * units.degC)
4390-
<Quantity(20.3746466, 'degree_Celsius')>
4390+
<Quantity(20.3937601, 'degree_Celsius')>
43914391
43924392
See Also
43934393
--------
@@ -4824,7 +4824,7 @@ def lifted_index(pressure, temperature, parcel_profile, vertical_dim=0):
48244824
>>>
48254825
>>> # Calculate lifted index using our mixed profile
48264826
>>> lifted_index(press, temp, mixed_prof)
4827-
<Quantity([2.51432431], 'delta_degree_Celsius')>
4827+
<Quantity([2.54198585], 'delta_degree_Celsius')>
48284828
48294829
See Also
48304830
--------
@@ -5227,7 +5227,7 @@ def showalter_index(pressure, temperature, dewpoint):
52275227
>>> Td = dewpoint_from_relative_humidity(T, rh)
52285228
>>> # compute the showalter index
52295229
>>> showalter_index(p, T, Td)
5230-
<Quantity([0.5107429], 'delta_degree_Celsius')>
5230+
<Quantity([0.51436699], 'delta_degree_Celsius')>
52315231
52325232
"""
52335233
# find the measured temperature and dew point temperature at 850 hPa.

0 commit comments

Comments
 (0)