From 1ffb3e53405fa77c07f2a8de1aaa9a169e9b2cc6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 13:14:23 +0000 Subject: [PATCH 01/16] CI: (deps): Bump dask from 2023.12.0 to 2023.12.1 in /ci Bumps [dask](https://github.com/dask/dask) from 2023.12.0 to 2023.12.1. - [Changelog](https://github.com/dask/dask/blob/main/docs/release-procedure.md) - [Commits](https://github.com/dask/dask/compare/2023.12.0...2023.12.1) --- updated-dependencies: - dependency-name: dask dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- ci/extra_requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/extra_requirements.txt b/ci/extra_requirements.txt index 53dabb31c3d..ad4380880e4 100644 --- a/ci/extra_requirements.txt +++ b/ci/extra_requirements.txt @@ -1,3 +1,3 @@ cartopy==0.22.0 -dask==2023.12.0 +dask==2023.12.1 shapely==2.0.2 From e0817f3962d9923d3d33e094905fa3741ea110bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Dec 2023 13:52:28 +0000 Subject: [PATCH 02/16] CI: (deps): Bump coverage from 7.3.3 to 7.3.4 in /ci Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.3.3 to 7.3.4. - [Release notes](https://github.com/nedbat/coveragepy/releases) - [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst) - [Commits](https://github.com/nedbat/coveragepy/compare/7.3.3...7.3.4) --- updated-dependencies: - dependency-name: coverage dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- ci/test_requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/test_requirements.txt b/ci/test_requirements.txt index 3fa0a52627f..45508143c4d 100644 --- a/ci/test_requirements.txt +++ b/ci/test_requirements.txt @@ -2,4 +2,4 @@ packaging==23.2 pytest==7.4.3 pytest-mpl==0.16.1 netCDF4==1.6.5 -coverage==7.3.3 +coverage==7.3.4 From e3aaea7a1e2d59d55abaa27dd2135437c3c87ee6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Dec 2023 13:15:40 +0000 Subject: [PATCH 03/16] CI: (deps): Bump ruff from 0.1.8 to 0.1.9 in /ci Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.8 to 0.1.9. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.1.8...v0.1.9) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- ci/linting_requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/linting_requirements.txt b/ci/linting_requirements.txt index 173ccafd40a..62c4b383739 100644 --- a/ci/linting_requirements.txt +++ b/ci/linting_requirements.txt @@ -1,4 +1,4 @@ -ruff==0.1.8 +ruff==0.1.9 flake8==6.1.0 pycodestyle==2.11.1 From 0cdfc31f0f0a99dcee8c275ae73eb1cb8f933552 Mon Sep 17 00:00:00 2001 From: Ryan May Date: Fri, 22 Dec 2023 14:12:14 -0700 Subject: [PATCH 04/16] MNT: Avoid pandas warning Apparently with Pandas 2.2.0 (tested in rc0) freq='H' is deprecated, in favor of freq='h'. Since this works across our versions, go ahead and change. --- tests/calc/test_basic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/calc/test_basic.py b/tests/calc/test_basic.py index c685f69e1fb..4dcaa563ccb 100644 --- a/tests/calc/test_basic.py +++ b/tests/calc/test_basic.py @@ -709,14 +709,14 @@ def test_smooth_window_1d_dataarray(): temperature = xr.DataArray( [37., 32., 34., 29., 28., 24., 26., 24., 27., 30.], dims=('time',), - coords={'time': pd.date_range('2020-01-01', periods=10, freq='H')}, + coords={'time': pd.date_range('2020-01-01', periods=10, freq='h')}, attrs={'units': 'degF'}) smoothed = smooth_window(temperature, window=np.ones(3) / 3, normalize_weights=False) truth = xr.DataArray( [37., 34.33333333, 31.66666667, 30.33333333, 27., 26., 24.66666667, 25.66666667, 27., 30.] * units.degF, dims=('time',), - coords={'time': pd.date_range('2020-01-01', periods=10, freq='H')} + coords={'time': pd.date_range('2020-01-01', periods=10, freq='h')} ) xr.testing.assert_allclose(smoothed, truth) From 4a41e4c31e669b699f30761e3b28cbe3d126d17b Mon Sep 17 00:00:00 2001 From: Alexander Lakocy Date: Sun, 16 Jul 2023 13:23:23 -0500 Subject: [PATCH 05/16] ENH: Add Galvez-Davison Index calculation --- src/metpy/calc/thermo.py | 210 ++++++++++++++++++++++++++++++++++++++ tests/calc/test_thermo.py | 107 ++++++++++++++++++- 2 files changed, 315 insertions(+), 2 deletions(-) diff --git a/src/metpy/calc/thermo.py b/src/metpy/calc/thermo.py index 2a166f874ea..870ac1fe653 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -14,6 +14,7 @@ find_intersections, first_derivative, get_layer) from .. import _warnings, constants as mpconsts from ..cbook import broadcast_indices +from ..constants import Cp_d from ..interpolate.one_dimension import interpolate_1d from ..package_tools import Exporter from ..units import check_units, concatenate, process_units, units @@ -4497,6 +4498,215 @@ def k_index(pressure, temperature, dewpoint, vertical_dim=0): return ((t850 - t500) + td850 - (t700 - td700)).to(units.degC) +@exporter.export +@add_vertical_dim_from_xarray +@preprocess_and_wrap(broadcast=('pressure', 'temperature', 'dewpoint')) +@check_units('[pressure]', '[temperature]', '[temperature]') +def galvez_davison_index(pressure, temperature, dewpoint, vertical_dim=0): + """ + Calculate GDI from the pressure temperature and dewpoint. + + GDI formula derived from [Galvez2015](https://www.wpc.ncep.noaa.gov/international/gdi/GDI_Manuscript_V20150910.pdf)_: + + .. math:: GDI = CBI + MWI + II + TC + + where: + + * :math:`CBI` is the Column Buoyancy Index + * :math:`MWI` is the Mid-tropospheric Warming Index + * :math:`II` is the Inversion Index + * :math:`TC` is the Terrain Correction [optional] + + Calculation of the GDI relies on temperatures and mixing ratios at 950, + 850, 700, and 500 hPa. These four levels define three layers: A) Boundary, + B) Trade Wind Inversion (TWI), C) Mid-Troposphere. + + ---------------------------------------------------------------------------------- + GDI Value | Expected Convective Regime + ---------------------------------------------------------------------------------- + >=45 | Scattered to widespread thunderstorms likely. + 35 to 45 | Scattered thunderstorms and/or scattered to widespread rain showers. + 25 to 35 | Isolated to scattered thunderstorms and/or scattered showers. + 15 to 25 | Isolated thunderstorms and/or isolated to scattered showers. + 5 to 10 | Isolated to scattered showers. + <5 | Strong TWI likely, light rain possible. + ---------------------------------------------------------------------------------- + + Parameters + ---------- + pressure : `pint.Quantity` + Pressure level(s), in order from highest to lowest pressure + + temperature : `pint.Quantity` + Temperature corresponding to pressure + + dewpoint : `pint.Quantity` + Dewpoint temperature corresponding to pressure + + vertical_dim : int, optional + The axis corresponding to vertical, defaults to 0. Automatically determined from + xarray DataArray arguments. + + Returns + ------- + `pint.Quantity` + GDI Index + + Examples + -------- + >>> from metpy.calc import dewpoint_from_relative_humidity, k_index + >>> 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, 23.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, .65, .36, .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) + >>> galvez_davison_index(p, T, Td) + + """ + # Calculate mixing ratio from dewpoint in two steps + relative_humidity = relative_humidity_from_dewpoint( + temperature, dewpoint + ) + mixing_ratio = mixing_ratio_from_relative_humidity( + pressure, temperature, relative_humidity + ) + + # Calculate potential temperature + potential_temp = potential_temperature(pressure, temperature) + + if np.any(np.max(pressure, axis=vertical_dim) < 950 * units.hectopascal): + indices_without_950 = np.where( + np.max(pressure, axis=vertical_dim) < 950 * units.hectopascal + ) + raise ValueError( + f'Data not provided for 950hPa or higher pressure. ' + f'GDI requires 950hPa temperature and dewpoint data, ' + f'see referenced paper section 3.d. in docstring for discussion of' + f' extrapolating sounding data below terrain surface in high-' + f'elevation regions.\nIndices without a 950hPa or higher datapoint' + f':\n{indices_without_950}' + f'\nMax provided pressures:' + f'\n{np.max(pressure, axis=0)[indices_without_950]}' + ) + # Convert temperature before interpolation + temperature_k = temperature.to('kelvin') + potential_temp_k = potential_temp.to('kelvin') + + # Interpolate to find temperature and mixing ratio at 950, 850, 700, and 500 hPa + ( + (t950, t850, t700, t500), + (r950, r850, r700, r500), + (th950, th850, th700, th500) + ) = interpolate_1d( + units.Quantity([950, 850, 700, 500], 'hPa'), + pressure, temperature_k, mixing_ratio, potential_temp_k, + axis=vertical_dim, + ) + + th_a = th950 + r_a = r950 + + th_b = 0.5 * (th850 + th700) + r_b = 0.5 * (r850 + r700) + + th_c = th500 + r_c = r500 + + alpha = -10 * units.kelvin # Empirical adjustment + + # Latent heat of vaporization of water - different from MetPy constant + # Using different value, from paper, since GDI unlikely to be used to + # derive other metrics + Lo = 2.69E6 * (units.joule / units.kilogram) + + # Temperature math from here on requires kelvin units + eptp_a = th_a * np.exp((Lo * r_a) / (Cp_d * t850)) + eptp_b = th_b * np.exp((Lo * r_b) / (Cp_d * t850)) + alpha + eptp_c = th_c * np.exp((Lo * r_c) / (Cp_d * t850)) + alpha + + if t950.size == 1: + is_array = False + else: + is_array = True + + # Calculate C.B.I. + beta = 303 * units.kelvin # Empirical adjustment + l_e = eptp_a - beta # Low-troposphere EPT + m_e = eptp_c - beta # Mid-troposphere EPT + + # Gamma unit - likely a typo from the paper, should be units of K^(-2) to + # result in dimensionless CBI + gamma = 6.5e-2 * (1 / units.kelvin) + zero_kelvin = 0 * units.kelvin + + if is_array: + # Replace conditional in paper for array compatibility. + # Will set CBI for any l_e < 0 to 0 + l_e[l_e <= zero_kelvin] = zero_kelvin + else: + l_e = max(l_e, zero_kelvin) + column_buoyancy_index = gamma * (l_e * m_e) + # Convert to magnitude and dimensionless to avoid unit issue from typo + column_buoyancy_index = column_buoyancy_index.magnitude + + # Calculate Mid-tropospheric Warming Index + tau = 263.15 * units.kelvin # Threshhold + mu = -7 * (1 / units.kelvin) # Empirical adjustment + + t_diff = t500 - tau + if is_array: + t_diff[t_diff <= zero_kelvin] = zero_kelvin + else: + t_diff = max(t_diff, zero_kelvin) + mid_tropospheric_warming_index = mu * t_diff + mid_tropospheric_warming_index = mid_tropospheric_warming_index.magnitude + + # Calculate Inversion Index + sigma = 1.5 * (1 / units.kelvin) # Empirical scaling constant + s = t950 - t700 + d = eptp_b - eptp_a + + inv_sum = s + d + if is_array: + inv_sum[inv_sum >= zero_kelvin] = zero_kelvin + else: + inv_sum = min(inv_sum, zero_kelvin) + + inversion_index = sigma * inv_sum + inversion_index = inversion_index.magnitude + + # Calculate Terrain Correction + p_3 = 18 + p_2 = 9000 * units.hectopascal + p_1 = 500 * units.hectopascal + p_sfc = pressure[0] + terrain_correction = p_3 - (p_2 / (p_sfc - p_1)) + + # Convert all to 'dimensionless' + column_buoyancy_index *= units.dimensionless + mid_tropospheric_warming_index *= units.dimensionless + inversion_index *= units.dimensionless + terrain_correction *= units.dimensionless + + # Calculate G.D.I. + return (column_buoyancy_index + + mid_tropospheric_warming_index + + inversion_index + + terrain_correction) + + @exporter.export @add_vertical_dim_from_xarray @preprocess_and_wrap( diff --git a/tests/calc/test_thermo.py b/tests/calc/test_thermo.py index ea97646d49d..5e8cfe3e7e7 100644 --- a/tests/calc/test_thermo.py +++ b/tests/calc/test_thermo.py @@ -37,9 +37,9 @@ vertical_velocity_pressure, virtual_potential_temperature, virtual_temperature, virtual_temperature_from_dewpoint, wet_bulb_potential_temperature, wet_bulb_temperature) -from metpy.calc.thermo import _find_append_zero_crossings +from metpy.calc.thermo import _find_append_zero_crossings, galvez_davison_index from metpy.testing import (assert_almost_equal, assert_array_almost_equal, assert_nan, - version_check) + version_check, wet_bulb_temperature) from metpy.units import is_quantity, masked_array, units @@ -2309,6 +2309,50 @@ def index_xarray_data(): coords={'isobaric': pressure, 'time': ['2020-01-01T00:00Z']}) +@pytest.fixture() +def index_xarray_data_expanded(): + """Create data for testing that index calculations work with xarray data. + + Expanded for Galvez Davison Index calculation, which requires 950hPa pressure + """ + pressure = xr.DataArray( + [950., 850., 700., 500.], dims=('isobaric',), attrs={'units': 'hPa'} + ) + temp = xr.DataArray([[[[306., 305., 304.], [303., 302., 301.]], + [[296., 295., 294.], [293., 292., 291.]], + [[286., 285., 284.], [283., 282., 281.]], + [[276., 275., 274.], [273., 272., 271.]]]] * units.K, + dims=('time', 'isobaric', 'y', 'x')) + + profile = xr.DataArray([[[[299., 298., 297.], [296., 295., 294.]], + [[289., 288., 287.], [286., 285., 284.]], + [[279., 278., 277.], [276., 275., 274.]], + [[269., 268., 267.], [266., 265., 264.]]]] * units.K, + dims=('time', 'isobaric', 'y', 'x')) + + dewp = xr.DataArray([[[[304., 303., 302.], [301., 300., 299.]], + [[294., 293., 292.], [291., 290., 289.]], + [[284., 283., 282.], [281., 280., 279.]], + [[274., 273., 272.], [271., 270., 269.]]]] * units.K, + dims=('time', 'isobaric', 'y', 'x')) + + dirw = xr.DataArray([[[[135., 135., 135.], [135., 135., 135.]], + [[180., 180., 180.], [180., 180., 180.]], + [[225., 225., 225.], [225., 225., 225.]], + [[270., 270., 270.], [270., 270., 270.]]]] * units.degree, + dims=('time', 'isobaric', 'y', 'x')) + + speed = xr.DataArray([[[[15., 15., 15.], [15., 15., 15.]], + [[20., 20., 20.], [20., 20., 20.]], + [[25., 25., 25.], [25., 25., 25.]], + [[50., 50., 50.], [50., 50., 50.]]]] * units.knots, + dims=('time', 'isobaric', 'y', 'x')) + + return xr.Dataset({'temperature': temp, 'profile': profile, 'dewpoint': dewp, + 'wind_direction': dirw, 'wind_speed': speed}, + coords={'isobaric': pressure, 'time': ['2020-01-01T00:00Z']}) + + def test_lifted_index(): """Test the Lifted Index calculation.""" pressure = np.array([1014., 1000., 997., 981.2, 947.4, 925., 914.9, 911., @@ -2398,6 +2442,65 @@ def test_k_index_xarray(index_xarray_data): np.array([[[312., 311., 310.], [309., 308., 307.]]]) * units.K) +def test_gdi(): + """Test the Galvez Davison Index calculation.""" + pressure = np.array([1014., 1000., 997., 981.2, 947.4, 925., 914.9, 911., + 902., 883., 850., 822.3, 816., 807., 793.2, 770., + 765.1, 753., 737.5, 737., 713., 700., 688., 685., + 680., 666., 659.8, 653., 643., 634., 615., 611.8, + 566.2, 516., 500., 487., 484.2, 481., 475., 460., + 400.]) * units.hPa + temperature = np.array([24.2, 24.2, 24., 23.1, 21., 19.6, 18.7, 18.4, + 19.2, 19.4, 17.2, 15.3, 14.8, 14.4, 13.4, 11.6, + 11.1, 10., 8.8, 8.8, 8.2, 7., 5.6, 5.6, + 5.6, 4.4, 3.8, 3.2, 3., 3.2, 1.8, 1.5, + -3.4, -9.3, -11.3, -13.1, -13.1, -13.1, -13.7, -15.1, + -23.5]) * units.degC + dewpoint = np.array([23.2, 23.1, 22.8, 22., 20.2, 19., 17.6, 17., + 16.8, 15.5, 14., 11.7, 11.2, 8.4, 7., 4.6, + 5., 6., 4.2, 4.1, -1.8, -2., -1.4, -0.4, + -3.4, -5.6, -4.3, -2.8, -7., -25.8, -31.2, -31.4, + -34.1, -37.3, -32.3, -34.1, -37.3, -41.1, -37.7, -58.1, + -57.5]) * units.degC + + gdi = galvez_davison_index(pressure, temperature, dewpoint) + + # Compare with value from hand calculation + assert_almost_equal(gdi, 6.635, decimal=1) + + +def test_gdi_xarray(index_xarray_data_expanded): + """Test the GDI calculation with a grid of xarray data.""" + result = galvez_davison_index( + index_xarray_data_expanded.isobaric, + index_xarray_data_expanded.temperature, + index_xarray_data_expanded.dewpoint + ) + + assert_array_almost_equal( + result, + np.array( + [[ + [191.3749159, 158.8527877, 131.0999557], + [107.5620913, 87.7547436, 71.2538119] + ]] + ) * units.dimensionless + ) + + +def test_gdi_no_950_raises_valueerror(index_xarray_data): + """GDI requires a 950hPa or higher measurement. + + Ensure error is raised if this data is not provided. + """ + with pytest.raises(ValueError): + galvez_davison_index( + index_xarray_data.isobaric, + index_xarray_data.temperature, + index_xarray_data.dewpoint + ) + + def test_gradient_richardson_number(): """Test gradient Richardson number calculation.""" theta = units('K') * np.asarray([254.5, 258.3, 262.2]) From 4087a0812818fd62d988bf7fe5402f5c9984291c Mon Sep 17 00:00:00 2001 From: Alexander Lakocy Date: Sun, 16 Jul 2023 15:40:59 -0500 Subject: [PATCH 06/16] ENH: Add documentation for galvez_davison_index --- docs/_templates/overrides/metpy.calc.rst | 1 + docs/api/references.rst | 4 ++ .../calculations/Sounding_Calculations.py | 1 + src/metpy/calc/thermo.py | 38 +++++++++++-------- 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/docs/_templates/overrides/metpy.calc.rst b/docs/_templates/overrides/metpy.calc.rst index 57a438210f9..e5e7a4f787f 100644 --- a/docs/_templates/overrides/metpy.calc.rst +++ b/docs/_templates/overrides/metpy.calc.rst @@ -79,6 +79,7 @@ Soundings cross_totals downdraft_cape el + galvez_davison_index k_index lcl lfc diff --git a/docs/api/references.rst b/docs/api/references.rst index 787cc6581da..53c7ed5fa5b 100644 --- a/docs/api/references.rst +++ b/docs/api/references.rst @@ -86,6 +86,10 @@ References Services and Supporting Research, 2003. `FCM-R19-2003 <../_static/FCM-R19-2003-WindchillReport.pdf>`_, 75 pp. +.. [Galvez2015] Galvez, J. M. and Michel Davison, 2015. “The Gálvez-Davison Index for Tropical + Convection.” `GDI_Manuscript_V20161021 + `_. + .. [Galway1956] Galway, J. G., 1956: The Lifted Index as a Predictor of Latent Instability. *American Meteorology Society*, doi:`10.1175/1520-0477-37.10.528 diff --git a/examples/calculations/Sounding_Calculations.py b/examples/calculations/Sounding_Calculations.py index fad52818007..45dad18fcee 100644 --- a/examples/calculations/Sounding_Calculations.py +++ b/examples/calculations/Sounding_Calculations.py @@ -96,6 +96,7 @@ def effective_layer(p, t, td, h, height_layer=False): # Compute common sounding index parameters ctotals = mpcalc.cross_totals(p, T, Td) kindex = mpcalc.k_index(p, T, Td) +gdi = mpcalc.galvez_davison_index(p, T, Td) showalter = mpcalc.showalter_index(p, T, Td) total_totals = mpcalc.total_totals_index(p, T, Td) vert_totals = mpcalc.vertical_totals(p, T) diff --git a/src/metpy/calc/thermo.py b/src/metpy/calc/thermo.py index 870ac1fe653..cbe9e3943f0 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -4506,7 +4506,11 @@ def galvez_davison_index(pressure, temperature, dewpoint, vertical_dim=0): """ Calculate GDI from the pressure temperature and dewpoint. - GDI formula derived from [Galvez2015](https://www.wpc.ncep.noaa.gov/international/gdi/GDI_Manuscript_V20150910.pdf)_: + Calculation of the GDI relies on temperatures and mixing ratios at 950, + 850, 700, and 500 hPa. These four levels define three layers: A) Boundary, + B) Trade Wind Inversion (TWI), C) Mid-Troposphere. + + GDI formula derived from [Galvez2015]_: .. math:: GDI = CBI + MWI + II + TC @@ -4517,20 +4521,24 @@ def galvez_davison_index(pressure, temperature, dewpoint, vertical_dim=0): * :math:`II` is the Inversion Index * :math:`TC` is the Terrain Correction [optional] - Calculation of the GDI relies on temperatures and mixing ratios at 950, - 850, 700, and 500 hPa. These four levels define three layers: A) Boundary, - B) Trade Wind Inversion (TWI), C) Mid-Troposphere. - - ---------------------------------------------------------------------------------- - GDI Value | Expected Convective Regime - ---------------------------------------------------------------------------------- - >=45 | Scattered to widespread thunderstorms likely. - 35 to 45 | Scattered thunderstorms and/or scattered to widespread rain showers. - 25 to 35 | Isolated to scattered thunderstorms and/or scattered showers. - 15 to 25 | Isolated thunderstorms and/or isolated to scattered showers. - 5 to 10 | Isolated to scattered showers. - <5 | Strong TWI likely, light rain possible. - ---------------------------------------------------------------------------------- + .. list-table:: GDI Values & Corresponding Convective Regimes + :widths: 15 75 + :header-rows: 1 + + * - GDI Value + - Expected Convective Regime + * - >=45 + - Scattered to widespread thunderstorms likely. + * - 35 to 45 + - Scattered thunderstorms and/or scattered to widespread rain showers. + * - 25 to 35 + - Isolated to scattered thunderstorms and/or scattered showers. + * - 15 to 25 + - Isolated thunderstorms and/or isolated to scattered showers. + * - 5 to 10 + - Isolated to scattered showers. + * - <5 + - Strong TWI likely, light rain possible. Parameters ---------- From 70271cbbe1d1360197b0c178b27615e5293b2eaa Mon Sep 17 00:00:00 2001 From: Alexander Lakocy Date: Sun, 16 Jul 2023 19:09:57 -0500 Subject: [PATCH 07/16] FIX: Correct whitespace issues and typos --- docs/api/references.rst | 4 ++-- src/metpy/calc/thermo.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/api/references.rst b/docs/api/references.rst index 53c7ed5fa5b..dacd4189546 100644 --- a/docs/api/references.rst +++ b/docs/api/references.rst @@ -86,8 +86,8 @@ References Services and Supporting Research, 2003. `FCM-R19-2003 <../_static/FCM-R19-2003-WindchillReport.pdf>`_, 75 pp. -.. [Galvez2015] Galvez, J. M. and Michel Davison, 2015. “The Gálvez-Davison Index for Tropical - Convection.” `GDI_Manuscript_V20161021 +.. [Galvez2015] Galvez, J. M. and Michel Davison, 2015. “The Gálvez-Davison Index for Tropical + Convection.” `GDI_Manuscript_V20161021 `_. .. [Galway1956] Galway, J. G., 1956: The Lifted Index as a Predictor of Latent Instability. diff --git a/src/metpy/calc/thermo.py b/src/metpy/calc/thermo.py index cbe9e3943f0..83049c02b96 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -4637,12 +4637,12 @@ def galvez_davison_index(pressure, temperature, dewpoint, vertical_dim=0): # Latent heat of vaporization of water - different from MetPy constant # Using different value, from paper, since GDI unlikely to be used to # derive other metrics - Lo = 2.69E6 * (units.joule / units.kilogram) + l_0 = 2.69E6 * (units.joule / units.kilogram) # Temperature math from here on requires kelvin units - eptp_a = th_a * np.exp((Lo * r_a) / (Cp_d * t850)) - eptp_b = th_b * np.exp((Lo * r_b) / (Cp_d * t850)) + alpha - eptp_c = th_c * np.exp((Lo * r_c) / (Cp_d * t850)) + alpha + eptp_a = th_a * np.exp((l_0 * r_a) / (Cp_d * t850)) + eptp_b = th_b * np.exp((l_0 * r_b) / (Cp_d * t850)) + alpha + eptp_c = th_c * np.exp((l_0 * r_c) / (Cp_d * t850)) + alpha if t950.size == 1: is_array = False @@ -4670,7 +4670,7 @@ def galvez_davison_index(pressure, temperature, dewpoint, vertical_dim=0): column_buoyancy_index = column_buoyancy_index.magnitude # Calculate Mid-tropospheric Warming Index - tau = 263.15 * units.kelvin # Threshhold + tau = 263.15 * units.kelvin # Threshold mu = -7 * (1 / units.kelvin) # Empirical adjustment t_diff = t500 - tau From 560f542dd5a68e70d5af012ef80959814ff99413 Mon Sep 17 00:00:00 2001 From: kgoebber Date: Thu, 21 Sep 2023 11:50:26 -0500 Subject: [PATCH 08/16] make changes to GDI calc --- .../calculations/Sounding_Calculations.py | 7 ++- src/metpy/calc/thermo.py | 62 +++++++------------ tests/calc/test_thermo.py | 28 ++++++--- 3 files changed, 51 insertions(+), 46 deletions(-) diff --git a/examples/calculations/Sounding_Calculations.py b/examples/calculations/Sounding_Calculations.py index 45dad18fcee..0791a382ab0 100644 --- a/examples/calculations/Sounding_Calculations.py +++ b/examples/calculations/Sounding_Calculations.py @@ -88,6 +88,11 @@ def effective_layer(p, t, td, h, height_layer=False): sped = df['speed'].values * units.knot height = df['height'].values * units.meter +########################################### +# Compute needed variables from our data file and attach units +relhum = mpcalc.relative_humidity_from_dewpoint(T, Td) +mixrat = mpcalc.mixing_ratio_from_relative_humidity(p, T, relhum) + ########################################### # Compute the wind components u, v = mpcalc.wind_components(sped, wdir) @@ -96,7 +101,7 @@ def effective_layer(p, t, td, h, height_layer=False): # Compute common sounding index parameters ctotals = mpcalc.cross_totals(p, T, Td) kindex = mpcalc.k_index(p, T, Td) -gdi = mpcalc.galvez_davison_index(p, T, Td) +gdi = mpcalc.galvez_davison_index(p, T, mixrat, p[0]) showalter = mpcalc.showalter_index(p, T, Td) total_totals = mpcalc.total_totals_index(p, T, Td) vert_totals = mpcalc.vertical_totals(p, T) diff --git a/src/metpy/calc/thermo.py b/src/metpy/calc/thermo.py index 83049c02b96..c5990208b28 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -4500,11 +4500,12 @@ def k_index(pressure, temperature, dewpoint, vertical_dim=0): @exporter.export @add_vertical_dim_from_xarray -@preprocess_and_wrap(broadcast=('pressure', 'temperature', 'dewpoint')) -@check_units('[pressure]', '[temperature]', '[temperature]') -def galvez_davison_index(pressure, temperature, dewpoint, vertical_dim=0): +@preprocess_and_wrap(broadcast=('pressure', 'temperature', 'mixing_ratio')) +@check_units('[pressure]', '[temperature]', '[dimensionless]', '[pressure]') +def galvez_davison_index(pressure, temperature, mixing_ratio, surface_pressure, + vertical_dim=0): """ - Calculate GDI from the pressure temperature and dewpoint. + Calculate GDI from the pressure, temperature, mixing ratio, and surface pressure. Calculation of the GDI relies on temperatures and mixing ratios at 950, 850, 700, and 500 hPa. These four levels define three layers: A) Boundary, @@ -4543,13 +4544,16 @@ def galvez_davison_index(pressure, temperature, dewpoint, vertical_dim=0): Parameters ---------- pressure : `pint.Quantity` - Pressure level(s), in order from highest to lowest pressure + Pressure level(s) temperature : `pint.Quantity` Temperature corresponding to pressure - dewpoint : `pint.Quantity` - Dewpoint temperature corresponding to pressure + mixing_ratio : `pint.Quantity` + Mixing ratio values corresponding to pressure + + surface_pressure : `pint.Quantity` + Pressure of the surface. vertical_dim : int, optional The axis corresponding to vertical, defaults to 0. Automatically determined from @@ -4578,19 +4582,11 @@ def galvez_davison_index(pressure, temperature, dewpoint, vertical_dim=0): >>> rh = [.85, .65, .36, .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) - >>> galvez_davison_index(p, T, Td) + >>> # calculate mixing ratio + >>> mixrat = mixing_ratio_from_relative_humidity(p, T, rh) + >>> galvez_davison_index(p, T, mixrat, p[0]) """ - # Calculate mixing ratio from dewpoint in two steps - relative_humidity = relative_humidity_from_dewpoint( - temperature, dewpoint - ) - mixing_ratio = mixing_ratio_from_relative_humidity( - pressure, temperature, relative_humidity - ) - # Calculate potential temperature potential_temp = potential_temperature(pressure, temperature) @@ -4632,12 +4628,12 @@ def galvez_davison_index(pressure, temperature, dewpoint, vertical_dim=0): th_c = th500 r_c = r500 - alpha = -10 * units.kelvin # Empirical adjustment + alpha = units.Quantity(-10, 'K') # Empirical adjustment # Latent heat of vaporization of water - different from MetPy constant # Using different value, from paper, since GDI unlikely to be used to # derive other metrics - l_0 = 2.69E6 * (units.joule / units.kilogram) + l_0 = units.Quantity(2.69E6, 'J/kg') # Temperature math from here on requires kelvin units eptp_a = th_a * np.exp((l_0 * r_a) / (Cp_d * t850)) @@ -4650,14 +4646,14 @@ def galvez_davison_index(pressure, temperature, dewpoint, vertical_dim=0): is_array = True # Calculate C.B.I. - beta = 303 * units.kelvin # Empirical adjustment + beta = units.Quantity(303, 'K') # Empirical adjustment l_e = eptp_a - beta # Low-troposphere EPT m_e = eptp_c - beta # Mid-troposphere EPT # Gamma unit - likely a typo from the paper, should be units of K^(-2) to - # result in dimensionless CBI - gamma = 6.5e-2 * (1 / units.kelvin) - zero_kelvin = 0 * units.kelvin + # result in dimensionless CBI + gamma = units.Quantity(6.5e-2, '1/K^2') + zero_kelvin = units.Quantity(0, 'K') if is_array: # Replace conditional in paper for array compatibility. @@ -4666,12 +4662,10 @@ def galvez_davison_index(pressure, temperature, dewpoint, vertical_dim=0): else: l_e = max(l_e, zero_kelvin) column_buoyancy_index = gamma * (l_e * m_e) - # Convert to magnitude and dimensionless to avoid unit issue from typo - column_buoyancy_index = column_buoyancy_index.magnitude # Calculate Mid-tropospheric Warming Index - tau = 263.15 * units.kelvin # Threshold - mu = -7 * (1 / units.kelvin) # Empirical adjustment + tau = units.Quantity(263.15, 'K') # Threshold + mu = units.Quantity(-7, '1/K') # Empirical adjustment t_diff = t500 - tau if is_array: @@ -4679,10 +4673,9 @@ def galvez_davison_index(pressure, temperature, dewpoint, vertical_dim=0): else: t_diff = max(t_diff, zero_kelvin) mid_tropospheric_warming_index = mu * t_diff - mid_tropospheric_warming_index = mid_tropospheric_warming_index.magnitude # Calculate Inversion Index - sigma = 1.5 * (1 / units.kelvin) # Empirical scaling constant + sigma = units.Quantity(1.5, '1/K') # Empirical scaling constant s = t950 - t700 d = eptp_b - eptp_a @@ -4693,21 +4686,14 @@ def galvez_davison_index(pressure, temperature, dewpoint, vertical_dim=0): inv_sum = min(inv_sum, zero_kelvin) inversion_index = sigma * inv_sum - inversion_index = inversion_index.magnitude # Calculate Terrain Correction p_3 = 18 p_2 = 9000 * units.hectopascal p_1 = 500 * units.hectopascal - p_sfc = pressure[0] + p_sfc = surface_pressure terrain_correction = p_3 - (p_2 / (p_sfc - p_1)) - # Convert all to 'dimensionless' - column_buoyancy_index *= units.dimensionless - mid_tropospheric_warming_index *= units.dimensionless - inversion_index *= units.dimensionless - terrain_correction *= units.dimensionless - # Calculate G.D.I. return (column_buoyancy_index + mid_tropospheric_warming_index diff --git a/tests/calc/test_thermo.py b/tests/calc/test_thermo.py index 5e8cfe3e7e7..94f90153437 100644 --- a/tests/calc/test_thermo.py +++ b/tests/calc/test_thermo.py @@ -2463,7 +2463,9 @@ def test_gdi(): -34.1, -37.3, -32.3, -34.1, -37.3, -41.1, -37.7, -58.1, -57.5]) * units.degC - gdi = galvez_davison_index(pressure, temperature, dewpoint) + relative_humidity = relative_humidity_from_dewpoint(temperature, dewpoint) + mixrat = mixing_ratio_from_relative_humidity(pressure, temperature, relative_humidity) + gdi = galvez_davison_index(pressure, temperature, mixrat, pressure[0]) # Compare with value from hand calculation assert_almost_equal(gdi, 6.635, decimal=1) @@ -2471,10 +2473,16 @@ def test_gdi(): def test_gdi_xarray(index_xarray_data_expanded): """Test the GDI calculation with a grid of xarray data.""" + pressure = index_xarray_data_expanded.isobaric + temperature = index_xarray_data_expanded.temperature + dewpoint = index_xarray_data_expanded.dewpoint + relative_humidity = relative_humidity_from_dewpoint(temperature, dewpoint) + mixrat = mixing_ratio_from_relative_humidity(pressure, temperature, relative_humidity) result = galvez_davison_index( - index_xarray_data_expanded.isobaric, - index_xarray_data_expanded.temperature, - index_xarray_data_expanded.dewpoint + pressure, + temperature, + mixrat, + pressure[0] ) assert_array_almost_equal( @@ -2494,10 +2502,16 @@ def test_gdi_no_950_raises_valueerror(index_xarray_data): Ensure error is raised if this data is not provided. """ with pytest.raises(ValueError): + pressure = index_xarray_data.isobaric + temperature = index_xarray_data.temperature + dewpoint = index_xarray_data.dewpoint + relative_humidity = relative_humidity_from_dewpoint(temperature, dewpoint) + mixrat = mixing_ratio_from_relative_humidity(pressure, temperature, relative_humidity) galvez_davison_index( - index_xarray_data.isobaric, - index_xarray_data.temperature, - index_xarray_data.dewpoint + pressure, + temperature, + mixrat, + pressure[0] ) From b0ca3160694ac155a755efb53820643d203ae22e Mon Sep 17 00:00:00 2001 From: kgoebber Date: Thu, 21 Sep 2023 20:45:35 -0500 Subject: [PATCH 09/16] update test dataset date --- tests/calc/test_thermo.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/calc/test_thermo.py b/tests/calc/test_thermo.py index 94f90153437..d1ac288f46b 100644 --- a/tests/calc/test_thermo.py +++ b/tests/calc/test_thermo.py @@ -39,7 +39,7 @@ wet_bulb_potential_temperature, wet_bulb_temperature) from metpy.calc.thermo import _find_append_zero_crossings, galvez_davison_index from metpy.testing import (assert_almost_equal, assert_array_almost_equal, assert_nan, - version_check, wet_bulb_temperature) + version_check) from metpy.units import is_quantity, masked_array, units @@ -2311,9 +2311,9 @@ def index_xarray_data(): @pytest.fixture() def index_xarray_data_expanded(): - """Create data for testing that index calculations work with xarray data. + """Create expanded data for testing that index calculations work with xarray data. - Expanded for Galvez Davison Index calculation, which requires 950hPa pressure + Specifically for Galvez Davison Index calculation, which requires 950hPa pressure """ pressure = xr.DataArray( [950., 850., 700., 500.], dims=('isobaric',), attrs={'units': 'hPa'} @@ -2350,7 +2350,7 @@ def index_xarray_data_expanded(): return xr.Dataset({'temperature': temp, 'profile': profile, 'dewpoint': dewp, 'wind_direction': dirw, 'wind_speed': speed}, - coords={'isobaric': pressure, 'time': ['2020-01-01T00:00Z']}) + coords={'isobaric': pressure, 'time': ['2023-01-01T00:00Z']}) def test_lifted_index(): From 9a75f23ce6cf665a70736ae58c0eb05c24e064ff Mon Sep 17 00:00:00 2001 From: kgoebber Date: Sat, 16 Dec 2023 13:14:53 -0600 Subject: [PATCH 10/16] update test values --- src/metpy/calc/thermo.py | 14 +++++++------- tests/calc/test_thermo.py | 8 ++------ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/metpy/calc/thermo.py b/src/metpy/calc/thermo.py index c5990208b28..87a7ed7e1c7 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -14,7 +14,6 @@ find_intersections, first_derivative, get_layer) from .. import _warnings, constants as mpconsts from ..cbook import broadcast_indices -from ..constants import Cp_d from ..interpolate.one_dimension import interpolate_1d from ..package_tools import Exporter from ..units import check_units, concatenate, process_units, units @@ -4631,14 +4630,15 @@ def galvez_davison_index(pressure, temperature, mixing_ratio, surface_pressure, alpha = units.Quantity(-10, 'K') # Empirical adjustment # Latent heat of vaporization of water - different from MetPy constant - # Using different value, from paper, since GDI unlikely to be used to - # derive other metrics - l_0 = units.Quantity(2.69E6, 'J/kg') + # Using different value, from MetPy, since GDI unlikely to be used to + # derive other metrics and a more appropriate value for tropical + # atmosphere. + l_0 = units.Quantity(2.69e6, 'J/kg') # Temperature math from here on requires kelvin units - eptp_a = th_a * np.exp((l_0 * r_a) / (Cp_d * t850)) - eptp_b = th_b * np.exp((l_0 * r_b) / (Cp_d * t850)) + alpha - eptp_c = th_c * np.exp((l_0 * r_c) / (Cp_d * t850)) + alpha + eptp_a = th_a * np.exp((l_0 * r_a) / (mpconsts.Cp_d * t850)) + eptp_b = th_b * np.exp((l_0 * r_b) / (mpconsts.Cp_d * t850)) + alpha + eptp_c = th_c * np.exp((l_0 * r_c) / (mpconsts.Cp_d * t850)) + alpha if t950.size == 1: is_array = False diff --git a/tests/calc/test_thermo.py b/tests/calc/test_thermo.py index d1ac288f46b..971993cc72e 100644 --- a/tests/calc/test_thermo.py +++ b/tests/calc/test_thermo.py @@ -2487,12 +2487,8 @@ def test_gdi_xarray(index_xarray_data_expanded): assert_array_almost_equal( result, - np.array( - [[ - [191.3749159, 158.8527877, 131.0999557], - [107.5620913, 87.7547436, 71.2538119] - ]] - ) * units.dimensionless + np.array([[[189.5890429, 157.4307982, 129.9739099], + [106.6763526, 87.0637477, 70.7202505]]]) ) From a5210e567acc3fe7608a277273ce38edef11bec5 Mon Sep 17 00:00:00 2001 From: kgoebber Date: Sun, 17 Dec 2023 07:21:47 -0600 Subject: [PATCH 11/16] fix doc example --- src/metpy/calc/thermo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metpy/calc/thermo.py b/src/metpy/calc/thermo.py index 87a7ed7e1c7..78d36bacc07 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -4584,7 +4584,7 @@ def galvez_davison_index(pressure, temperature, mixing_ratio, surface_pressure, >>> # calculate mixing ratio >>> mixrat = mixing_ratio_from_relative_humidity(p, T, rh) >>> galvez_davison_index(p, T, mixrat, p[0]) - + """ # Calculate potential temperature potential_temp = potential_temperature(pressure, temperature) From fbe977eea191a03a260b8ebdb9eef5b92901c7d5 Mon Sep 17 00:00:00 2001 From: Drew Camron Date: Tue, 26 Dec 2023 11:58:53 -0700 Subject: [PATCH 12/16] Reorganize GDI Apply subjective code style cleanup to reduce repetition. Test 950 level check sooner. Collapse array dimension handling. --- src/metpy/calc/thermo.py | 100 +++++++++++++-------------------------- 1 file changed, 33 insertions(+), 67 deletions(-) diff --git a/src/metpy/calc/thermo.py b/src/metpy/calc/thermo.py index 78d36bacc07..ede6a26f2f2 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -4565,7 +4565,7 @@ def galvez_davison_index(pressure, temperature, mixing_ratio, surface_pressure, Examples -------- - >>> from metpy.calc import dewpoint_from_relative_humidity, k_index + >>> from metpy.calc import mixing_ratio_from_relative_humidity >>> from metpy.units import units >>> # pressure >>> p = [1008., 1000., 950., 900., 850., 800., 750., 700., 650., 600., @@ -4586,9 +4586,6 @@ def galvez_davison_index(pressure, temperature, mixing_ratio, surface_pressure, >>> galvez_davison_index(p, T, mixrat, p[0]) """ - # Calculate potential temperature - potential_temp = potential_temperature(pressure, temperature) - if np.any(np.max(pressure, axis=vertical_dim) < 950 * units.hectopascal): indices_without_950 = np.where( np.max(pressure, axis=vertical_dim) < 950 * units.hectopascal @@ -4603,96 +4600,65 @@ def galvez_davison_index(pressure, temperature, mixing_ratio, surface_pressure, f'\nMax provided pressures:' f'\n{np.max(pressure, axis=0)[indices_without_950]}' ) - # Convert temperature before interpolation - temperature_k = temperature.to('kelvin') - potential_temp_k = potential_temp.to('kelvin') - # Interpolate to find temperature and mixing ratio at 950, 850, 700, and 500 hPa + potential_temp = potential_temperature(pressure, temperature) + + # Interpolate to appropriate level with appropriate units ( (t950, t850, t700, t500), (r950, r850, r700, r500), (th950, th850, th700, th500) ) = interpolate_1d( units.Quantity([950, 850, 700, 500], 'hPa'), - pressure, temperature_k, mixing_ratio, potential_temp_k, + pressure, temperature.to('K'), mixing_ratio, potential_temp.to('K'), axis=vertical_dim, ) - th_a = th950 - r_a = r950 - - th_b = 0.5 * (th850 + th700) - r_b = 0.5 * (r850 + r700) - - th_c = th500 - r_c = r500 - - alpha = units.Quantity(-10, 'K') # Empirical adjustment - - # Latent heat of vaporization of water - different from MetPy constant - # Using different value, from MetPy, since GDI unlikely to be used to - # derive other metrics and a more appropriate value for tropical - # atmosphere. + # L_v definition preserved from referenced paper + # Value differs heavily from metpy.constants.Lv in tropical context + # and using MetPy value affects resulting GDI l_0 = units.Quantity(2.69e6, 'J/kg') - # Temperature math from here on requires kelvin units - eptp_a = th_a * np.exp((l_0 * r_a) / (mpconsts.Cp_d * t850)) - eptp_b = th_b * np.exp((l_0 * r_b) / (mpconsts.Cp_d * t850)) + alpha - eptp_c = th_c * np.exp((l_0 * r_c) / (mpconsts.Cp_d * t850)) + alpha - - if t950.size == 1: - is_array = False - else: - is_array = True + # Calculate adjusted equivalent potential temperatures + alpha = units.Quantity(-10, 'K') + eptp_a = th950 * np.exp(l_0 * r950 / (mpconsts.Cp_d * t850)) + eptp_b = ((th850 + th700) / 2 * + np.exp(l_0 * (r850 + r700) / 2 / (mpconsts.Cp_d * t850)) + alpha) + eptp_c = th500 * np.exp(l_0 * r500 / (mpconsts.Cp_d * t850)) + alpha - # Calculate C.B.I. - beta = units.Quantity(303, 'K') # Empirical adjustment - l_e = eptp_a - beta # Low-troposphere EPT - m_e = eptp_c - beta # Mid-troposphere EPT + # Calculate Column Buoyanci Index (CBI) + # Apply threshold to low and mid levels + beta = units.Quantity(303, 'K') + l_e = eptp_a - beta + m_e = eptp_c - beta # Gamma unit - likely a typo from the paper, should be units of K^(-2) to # result in dimensionless CBI gamma = units.Quantity(6.5e-2, '1/K^2') - zero_kelvin = units.Quantity(0, 'K') - - if is_array: - # Replace conditional in paper for array compatibility. - # Will set CBI for any l_e < 0 to 0 - l_e[l_e <= zero_kelvin] = zero_kelvin - else: - l_e = max(l_e, zero_kelvin) - column_buoyancy_index = gamma * (l_e * m_e) - # Calculate Mid-tropospheric Warming Index - tau = units.Quantity(263.15, 'K') # Threshold - mu = units.Quantity(-7, '1/K') # Empirical adjustment + column_buoyancy_index = np.atleast_1d(gamma * l_e * m_e) + column_buoyancy_index[l_e <= 0] = 0 + # Calculate Mid-tropospheric Warming Index (MWI) + # Apply threshold to 500-hPa temperature + tau = units.Quantity(263.15, 'K') t_diff = t500 - tau - if is_array: - t_diff[t_diff <= zero_kelvin] = zero_kelvin - else: - t_diff = max(t_diff, zero_kelvin) - mid_tropospheric_warming_index = mu * t_diff - # Calculate Inversion Index - sigma = units.Quantity(1.5, '1/K') # Empirical scaling constant + mu = units.Quantity(-7, '1/K') # Empirical adjustment + mid_tropospheric_warming_index = np.atleast_1d(mu * t_diff) + mid_tropospheric_warming_index[t_diff <= 0] = 0 + + # Calculate Inversion Index (II) s = t950 - t700 d = eptp_b - eptp_a - inv_sum = s + d - if is_array: - inv_sum[inv_sum >= zero_kelvin] = zero_kelvin - else: - inv_sum = min(inv_sum, zero_kelvin) - inversion_index = sigma * inv_sum + sigma = units.Quantity(1.5, '1/K') # Empirical scaling constant + inversion_index = np.atleast_1d(sigma * inv_sum) + inversion_index[inv_sum >= 0] = 0 # Calculate Terrain Correction - p_3 = 18 - p_2 = 9000 * units.hectopascal - p_1 = 500 * units.hectopascal - p_sfc = surface_pressure - terrain_correction = p_3 - (p_2 / (p_sfc - p_1)) + terrain_correction = 18 - 9000 / (surface_pressure.m_as('hPa') - 500) # Calculate G.D.I. return (column_buoyancy_index From 8e070a0aa7c0c175f1f33eb870eee093ef626ace Mon Sep 17 00:00:00 2001 From: Drew Camron Date: Tue, 26 Dec 2023 11:59:37 -0700 Subject: [PATCH 13/16] Test on Quantity arrays and profile --- tests/calc/test_thermo.py | 40 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/tests/calc/test_thermo.py b/tests/calc/test_thermo.py index 971993cc72e..4cd9c60749b 100644 --- a/tests/calc/test_thermo.py +++ b/tests/calc/test_thermo.py @@ -2476,12 +2476,13 @@ def test_gdi_xarray(index_xarray_data_expanded): pressure = index_xarray_data_expanded.isobaric temperature = index_xarray_data_expanded.temperature dewpoint = index_xarray_data_expanded.dewpoint - relative_humidity = relative_humidity_from_dewpoint(temperature, dewpoint) - mixrat = mixing_ratio_from_relative_humidity(pressure, temperature, relative_humidity) + mixing_ratio = mixing_ratio_from_relative_humidity( + pressure, temperature, relative_humidity_from_dewpoint(temperature, dewpoint)) + result = galvez_davison_index( pressure, temperature, - mixrat, + mixing_ratio, pressure[0] ) @@ -2492,6 +2493,39 @@ def test_gdi_xarray(index_xarray_data_expanded): ) +def test_gdi_arrays(index_xarray_data_expanded): + """Test GDI on 3-D Quantity arrays with an array of surface pressure.""" + ds = index_xarray_data_expanded.isel(time=0).squeeze() + pressure = ds.isobaric.metpy.unit_array[:, None, None] + temperature = ds.temperature.metpy.unit_array + dewpoint = ds.dewpoint.metpy.unit_array + mixing_ratio = mixing_ratio_from_relative_humidity( + pressure, temperature, relative_humidity_from_dewpoint(temperature, dewpoint)) + surface_pressure = units.Quantity( + np.broadcast_to(pressure.m, temperature.shape), pressure.units)[0] + + result = galvez_davison_index(pressure, temperature, mixing_ratio, surface_pressure) + + assert_array_almost_equal( + result, + np.array([[189.5890429, 157.4307982, 129.9739099], + [106.6763526, 87.0637477, 70.7202505]]) + ) + + +def test_gdi_profile(index_xarray_data_expanded): + """Test GDI calculation on an individual profile.""" + ds = index_xarray_data_expanded.isel(time=0, y=0, x=0) + pressure = ds.isobaric.metpy.unit_array + temperature = ds.temperature.metpy.unit_array + dewpoint = ds.dewpoint.metpy.unit_array + mixing_ratio = mixing_ratio_from_relative_humidity( + pressure, temperature, relative_humidity_from_dewpoint(temperature, dewpoint)) + + assert_almost_equal(galvez_davison_index(pressure, temperature, mixing_ratio, pressure[0]), + 189.5890429, 4) + + def test_gdi_no_950_raises_valueerror(index_xarray_data): """GDI requires a 950hPa or higher measurement. From 83436e8a25d4d209bbce2156f92aec1264eb24d0 Mon Sep 17 00:00:00 2001 From: Drew Camron Date: Tue, 26 Dec 2023 12:48:00 -0700 Subject: [PATCH 14/16] Return scalar on profile --- src/metpy/calc/thermo.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/metpy/calc/thermo.py b/src/metpy/calc/thermo.py index ede6a26f2f2..4e7382daabf 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -4622,8 +4622,8 @@ def galvez_davison_index(pressure, temperature, mixing_ratio, surface_pressure, # Calculate adjusted equivalent potential temperatures alpha = units.Quantity(-10, 'K') eptp_a = th950 * np.exp(l_0 * r950 / (mpconsts.Cp_d * t850)) - eptp_b = ((th850 + th700) / 2 * - np.exp(l_0 * (r850 + r700) / 2 / (mpconsts.Cp_d * t850)) + alpha) + eptp_b = ((th850 + th700) / 2 + * np.exp(l_0 * (r850 + r700) / 2 / (mpconsts.Cp_d * t850)) + alpha) eptp_c = th500 * np.exp(l_0 * r500 / (mpconsts.Cp_d * t850)) + alpha # Calculate Column Buoyanci Index (CBI) @@ -4661,10 +4661,15 @@ def galvez_davison_index(pressure, temperature, mixing_ratio, surface_pressure, terrain_correction = 18 - 9000 / (surface_pressure.m_as('hPa') - 500) # Calculate G.D.I. - return (column_buoyancy_index - + mid_tropospheric_warming_index - + inversion_index - + terrain_correction) + gdi = (column_buoyancy_index + + mid_tropospheric_warming_index + + inversion_index + + terrain_correction) + + if gdi.size == 1: + return gdi[0] + else: + return gdi @exporter.export From 7a5e4cc34e62e632017335ca31b343cd79602538 Mon Sep 17 00:00:00 2001 From: Ryan May Date: Wed, 27 Dec 2023 23:27:38 -0700 Subject: [PATCH 15/16] CI: Fix CI for release Can't use fetch-tags on a tag apparently. See actions/checkout#1467. --- .github/workflows/docs.yml | 4 +++- .github/workflows/release.yml | 3 --- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f6e25f0ef99..2e7bc8061c7 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -40,7 +40,9 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 150 - fetch-tags: true + + - name: Get tags + run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Install using PyPI uses: ./.github/actions/install-pypi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index af6cfcb781d..4b62e76b758 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,9 +11,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - with: - fetch-depth: 10 - fetch-tags: true - name: Set up Python id: setup From bf7b91302357f08a371dd4beadfad87b83502799 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Dec 2023 13:06:44 +0000 Subject: [PATCH 16/16] CI: (deps): Bump coverage from 7.3.4 to 7.4.0 in /ci Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.3.4 to 7.4.0. - [Release notes](https://github.com/nedbat/coveragepy/releases) - [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst) - [Commits](https://github.com/nedbat/coveragepy/compare/7.3.4...7.4.0) --- updated-dependencies: - dependency-name: coverage dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- ci/test_requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/test_requirements.txt b/ci/test_requirements.txt index 45508143c4d..21fd0b867d3 100644 --- a/ci/test_requirements.txt +++ b/ci/test_requirements.txt @@ -2,4 +2,4 @@ packaging==23.2 pytest==7.4.3 pytest-mpl==0.16.1 netCDF4==1.6.5 -coverage==7.3.4 +coverage==7.4.0