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

add downward cape #3120

Merged
merged 9 commits into from
Nov 14, 2023
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
1 change: 1 addition & 0 deletions docs/_templates/overrides/metpy.calc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ Soundings
ccl
critical_angle
cross_totals
downdraft_cape
el
k_index
lcl
Expand Down
2 changes: 2 additions & 0 deletions docs/api/references.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ References
Parameters in Forecasting Severe Storms <https://ejssm.org/archives/2006/vol-1-3-2006/>`_.
*Electronic J. Severe Storms Meteor.*, **1** (3), 1-22.

.. [Emanuel1994] Emanuel, K. A., 1994: Atmospheric Convection. Oxford University Press, 592 pp.

.. [Esterheld2008] Esterheld, J. M. and D. J. Giuliano, 2008: `Discriminating between Tornadic and
Non-Tornadic Supercells: A New Hodograph Technique <https://ejssm.org/archives/2008/vol-3-2-2008/>`_.
*Electronic J. Severe Storms Meteor.*, **3** (2), 1-50.
Expand Down
127 changes: 127 additions & 0 deletions src/metpy/calc/thermo.py
Original file line number Diff line number Diff line change
Expand Up @@ -3026,6 +3026,133 @@
return cape_cin(p, t, td, ml_profile)


@exporter.export
@preprocess_and_wrap()
def downdraft_cape(pressure, temperature, dewpoint):
r"""Calculate downward CAPE (DCAPE).

Calculate the downward convective available potential energy (DCAPE) of a given upper air
profile. Downward CAPE is the maximum negative buoyancy energy available to a descending
parcel. Parcel descent is assumed to begin from the lowest equivalent potential temperature
between 700 and 500 hPa. This parcel is lowered moist adiabatically from the environmental
wet bulb temperature to the surface. This assumes the parcel remains saturated
throughout the descent.

Parameters
----------
pressure : `pint.Quantity`
Pressure profile

temperature : `pint.Quantity`
Temperature profile

dewpoint : `pint.Quantity`
Dewpoint profile

Returns
-------
dcape: `pint.Quantity`
Downward Convective Available Potential Energy (DCAPE)
down_pressure: `pint.Quantity`
Pressure levels of the descending parcel
down_parcel_trace: `pint.Quantity`
Temperatures of the descending parcel

Examples
--------
>>> from metpy.calc import dewpoint_from_relative_humidity, downdraft_cape
>>> from metpy.units import units
>>> # pressure
>>> p = [1008., 1000., 950., 900., 850., 800., 750., 700., 650., 600.,
... 550., 500., 450., 400., 350., 300., 250., 200.,
... 175., 150., 125., 100., 80., 70., 60., 50.,
... 40., 30., 25., 20.] * units.hPa
>>> # temperature
>>> T = [29.3, 28.1, 25.5, 20.9, 18.4, 15.9, 13.1, 10.1, 6.7, 3.1,
... -0.5, -4.5, -9.0, -14.8, -21.5, -29.7, -40.0, -52.4,
... -59.2, -66.5, -74.1, -78.5, -76.0, -71.6, -66.7, -61.3,
... -56.3, -51.7, -50.7, -47.5] * units.degC
>>> # relative humidity
>>> rh = [.85, .75, .56, .39, .82, .72, .75, .86, .65, .22, .52,
... .66, .64, .20, .05, .75, .76, .45, .25, .48, .76, .88,
... .56, .88, .39, .67, .15, .04, .94, .35] * units.dimensionless
>>> # calculate dewpoint
>>> Td = dewpoint_from_relative_humidity(T, rh)
>>> downdraft_cape(p, T, Td)
(<Quantity(1222.67968, 'joule / kilogram')>, <Quantity([1008. 1000. 950.
900. 850. 800. 750. 700. 650. 600.], 'hectopascal')>, <Quantity([17.50959548
17.20643425 15.237249 13.12607097 10.85045704 8.38243809 5.68671014 2.71808363
-0.58203825 -4.29053485], 'degree_Celsius')>)

See Also
--------
cape_cin, surface_based_cape_cin, most_unstable_cape_cin, mixed_layer_cape_cin

Notes
-----
Formula adopted from [Emanuel1994]_.

.. math:: \text{DCAPE} = -R_d \int_{SFC}^{p_\text{top}}
(T_{{v}_{env}} - T_{{v}_{parcel}}) d\text{ln}(p)


* :math:`DCAPE` is downward convective available potential energy
* :math:`SFC` is the level of the surface or beginning of parcel path
* :math:`p_\text{top}` is pressure of the start of descent path
* :math:`R_d` is the gas constant
* :math:`T_{{v}_{env}}` is environment virtual temperature
* :math:`T_{{v}_{parcel}}` is the parcel virtual temperature
* :math:`p` is atmospheric pressure

Only functions on 1D profiles (not higher-dimension vertical cross sections or grids).
Since this function returns scalar values when given a profile, this will return Pint
Quantities even when given xarray DataArray profiles.



"""
pressure, temperature, dewpoint = _remove_nans(pressure, temperature, dewpoint)
if not len(pressure) == len(temperature) == len(dewpoint):
raise ValueError('Provided pressure, temperature,'
'and dewpoint must be the same length')

# Get layer between 500 and 700 hPa
p_layer, t_layer, td_layer = get_layer(pressure, temperature, dewpoint,
bottom=700 * units.hPa,
depth=200 * units.hPa, interpolate=True)
theta_e = equivalent_potential_temperature(p_layer, t_layer, td_layer)

# Find parcel with minimum thetae in the layer
min_idx = np.argmin(theta_e)
parcel_start_p = p_layer[min_idx]

parcel_start_td = td_layer[min_idx]
parcel_start_wb = wet_bulb_temperature(parcel_start_p, t_layer[min_idx], parcel_start_td)

# Descend parcel moist adiabatically to surface

Check warning on line 3132 in src/metpy/calc/thermo.py

View check run for this annotation

Codecov / codecov/patch

src/metpy/calc/thermo.py#L3132

Added line #L3132 was not covered by tests
down_pressure = pressure[pressure >= parcel_start_p].to(units.hPa)
down_parcel_trace = moist_lapse(down_pressure, parcel_start_wb,
reference_pressure=parcel_start_p)

# Find virtual temperature of parcel and environment
parcel_virt_temp = virtual_temperature_from_dewpoint(down_pressure, down_parcel_trace,
down_parcel_trace)
env_virt_temp = virtual_temperature_from_dewpoint(down_pressure,
temperature[pressure >= parcel_start_p],
dewpoint[pressure >= parcel_start_p])

# calculate differences (remove units for NumPy)
diff = (env_virt_temp - parcel_virt_temp).to(units.degK).magnitude
lnp = np.log(down_pressure.magnitude)

# Find DCAPE
dcape = -(mpconsts.Rd
* units.Quantity(np.trapz(diff, lnp), 'K')
).to(units('J/kg'))

return dcape, down_pressure, down_parcel_trace


@exporter.export
@preprocess_and_wrap()
@check_units('[pressure]', '[temperature]', '[temperature]')
Expand Down
26 changes: 24 additions & 2 deletions tests/calc/test_thermo.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
from metpy.calc import (brunt_vaisala_frequency, brunt_vaisala_frequency_squared,
brunt_vaisala_period, cape_cin, ccl, cross_totals, density, dewpoint,
dewpoint_from_relative_humidity, dewpoint_from_specific_humidity,
dry_lapse, dry_static_energy, el, equivalent_potential_temperature,
exner_function, gradient_richardson_number, InvalidSoundingError,
downdraft_cape, dry_lapse, dry_static_energy, el,
equivalent_potential_temperature, exner_function,
gradient_richardson_number, InvalidSoundingError,
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,
Expand Down Expand Up @@ -1554,6 +1555,27 @@ def test_mixed_layer_cape_cin(multiple_intersections):
assert_almost_equal(mlcin, -13.4809966289 * units('joule / kilogram'), 2)


def test_dcape():
"""Test the calculation of DCAPE."""
pressure = [1008., 1000., 950., 900., 850., 800., 750., 700., 650., 600.,
550., 500., 450., 400., 350., 300., 250., 200.,
175., 150., 125., 100., 80., 70., 60., 50.,
40., 30., 25., 20.] * units.hPa
temperature = [29.3, 28.1, 25.5, 20.9, 18.4, 15.9, 13.1, 10.1, 6.7, 3.1,
-0.5, -4.5, -9.0, -14.8, -21.5, -29.7, -40.0, -52.4,
-59.2, -66.5, -74.1, -78.5, -76.0, -71.6, -66.7, -61.3,
-56.3, -51.7, -50.7, -47.5] * units.degC
dewpoint = [26.5, 23.3, 16.1, 6.4, 15.3, 10.9, 8.8, 7.9, 0.6,
-16.6, -9.2, -9.9, -14.6, -32.8, -51.2, -32.7, -42.6, -58.9,
-69.5, -71.7, -75.9, -79.3, -79.7, -72.5, -73.3, -64.3, -70.6,
-75.8, -51.2, -56.4] * units.degC
dcape, down_press, down_t = downdraft_cape(pressure, temperature, dewpoint)
assert_almost_equal(dcape, 1222 * units('joule / kilogram'), 0)
assert_array_almost_equal(down_press, pressure[:10], 0)
assert_almost_equal(down_t, [17.5, 17.2, 15.2, 13.1, 10.9, 8.4,
5.7, 2.7, -0.6, -4.3] * units.degC, 1)


def test_mixed_layer():
"""Test the mixed layer calculation."""
pressure = np.array([959., 779.2, 751.3, 724.3, 700., 269.]) * units.hPa
Expand Down