From 16dd3e609330cc3479c8689243b0faa88e4772e7 Mon Sep 17 00:00:00 2001 From: Sam Gardner Date: Thu, 13 Jul 2023 14:41:07 -0500 Subject: [PATCH 01/10] add corfidi MCS motion --- docs/_templates/overrides/metpy.calc.rst | 1 + docs/api/references.rst | 6 ++ src/metpy/calc/indices.py | 93 ++++++++++++++++++++++++ tests/calc/test_indices.py | 12 ++- 4 files changed, 111 insertions(+), 1 deletion(-) diff --git a/docs/_templates/overrides/metpy.calc.rst b/docs/_templates/overrides/metpy.calc.rst index c5920a81505..c227f4209be 100644 --- a/docs/_templates/overrides/metpy.calc.rst +++ b/docs/_templates/overrides/metpy.calc.rst @@ -71,6 +71,7 @@ Soundings bulk_shear bunkers_storm_motion + corfidi_storm_motion cape_cin ccl critical_angle diff --git a/docs/api/references.rst b/docs/api/references.rst index e4df24baa3e..75d4ed95849 100644 --- a/docs/api/references.rst +++ b/docs/api/references.rst @@ -39,6 +39,12 @@ References doi:`10.1175/1520-0434(2000)015\<0061:PSMUAN\>2.0.CO;2 2.0.CO;2>`_. +.. [Corfidi2003] Corfidi, S. F., 2003: Cold Pools and MCS Propagation: + Forecasting the Motion of Downwind-Developing MCSs. + *Wea. Forecasting.*, **18**, 997–1017, + doi:`10.1175/1520-0434(2003)018\<0997:CPAMPF\>2.0.CO;2 + 2.0.CO;2>`_. + .. [CODATA2018] Tiesinga, Eite, Peter J. Mohr, David B. Newell, and Barry N. Taylor, 2020: The 2018 CODATA Recommended Values of the Fundamental Physical Constants (Web Version 8.1). Database developed by J. Baker, M. Douma, and S. Kotochigova. diff --git a/src/metpy/calc/indices.py b/src/metpy/calc/indices.py index ef258242e8f..f0a5c57b277 100644 --- a/src/metpy/calc/indices.py +++ b/src/metpy/calc/indices.py @@ -4,6 +4,7 @@ """Contains calculation of various derived indices.""" import numpy as np +from .basic import wind_speed from .thermo import mixing_ratio, saturation_vapor_pressure from .tools import _remove_nans, get_layer from .. import constants as mpconsts @@ -336,6 +337,98 @@ def bunkers_storm_motion(pressure, u, v, height): return right_mover, left_mover, wind_mean +@exporter.export +@preprocess_and_wrap() +@check_units('[pressure]', '[speed]', '[speed]', '[length]') +def corfidi_storm_motion(pressure, u, v, height): + r"""Calculate upwind- and downwind-developing MCS storm motions using the Corfidi method. + + Method described by ([Corfidi2003]_): + + * Cloud-layer winds, estimated as the mean of the wind from 850 hPa to 300 hPa + * Convergence along the cold pool, defined as the negative of the low-level jet + (estimated as maximum in winds below 1.5 km AGL) + * The vector sum of the above is used to describe upwind propagating MCSes + * Downwind propagating MCSes are taken as propagating along the sum of the cloud-layer wind + and negative of the low level jet, therefore the cloud-level winds are doubled + to re-add storm motion + + Parameters + ---------- + pressure : `pint.Quantity` + Pressure from full profile + + u : `pint.Quantity` + Full profile of the U-component of the wind + + v : `pint.Quantity` + Full profile of the V-component of the wind + + height : `pint.Quantity` + Full profile of height + + Returns + ------- + upwind_prop: (`pint.Quantity`, `pint.Quantity`) + Scalar U- and V- components of Corfidi upwind propagating MCS motion + + downwind_prop: (`pint.Quantity`, `pint.Quantity`) + Scalar U- and V- components of Corfidi downwind propagating MCS motion + + Examples + -------- + >>> from metpy.calc import corfidi_storm_motion, wind_components + >>> from metpy.units import units + >>> p = [1000, 925, 850, 700, 500, 400] * units.hPa + >>> h = [250, 700, 1500, 3100, 5720, 7120] * units.meters + >>> wdir = [165, 180, 190, 210, 220, 250] * units.degree + >>> sped = [5, 15, 20, 30, 50, 60] * units.knots + >>> u, v = wind_components(sped, wdir) + >>> corfidi_storm_motion(p, u, v, h) + (, + ) + + Notes + ----- + 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. + + """ + # convert height to AGL + height = height - height[0] + + # convert u/v wind to wind speed and direction + wind_magnitude = wind_speed(u, v) + # find inverse of low-level jet + lowlevel_index = np.argmin(height <= units.Quantity(1500, 'meter')) + llj_index = np.argmax(wind_magnitude[:lowlevel_index]) + llj_inverse = units.Quantity.from_list((-u[llj_index], -v[llj_index])) + + # cloud layer mean wind + # don't select outside bounds of given data + if pressure[0] < units.Quantity(850, 'hectopascal'): + bottom = pressure[0] + else: + bottom = units.Quantity(850, 'hectopascal') + if pressure[-1] > units.Quantity(300, 'hectopascal'): + depth = bottom - pressure[-1] + else: + depth = units.Quantity(550, 'hectopascal') + cloud_layer_winds = weighted_continuous_average(pressure, u, v, height=height, + bottom=bottom, + depth=depth) + + cloud_layer_winds = units.Quantity.from_list(cloud_layer_winds) + + # calculate corfidi vectors + upwind = cloud_layer_winds + llj_inverse + + downwind = 2*cloud_layer_winds + llj_inverse + + return upwind, downwind + + @exporter.export @preprocess_and_wrap() @check_units('[pressure]', '[speed]', '[speed]') diff --git a/tests/calc/test_indices.py b/tests/calc/test_indices.py index b2283c3e7db..ddb3f7d1e53 100644 --- a/tests/calc/test_indices.py +++ b/tests/calc/test_indices.py @@ -9,7 +9,7 @@ import pytest import xarray as xr -from metpy.calc import (bulk_shear, bunkers_storm_motion, critical_angle, +from metpy.calc import (bulk_shear, bunkers_storm_motion, corfidi_storm_motion, critical_angle, mean_pressure_weighted, precipitable_water, significant_tornado, supercell_composite, weighted_continuous_average) from metpy.testing import assert_almost_equal, assert_array_almost_equal, get_upper_air_data @@ -176,6 +176,16 @@ def test_bunkers_motion(): assert_almost_equal(motion.flatten(), truth, 8) +def test_corfidi_motion(): + """Test corfidi MCS motion with observed sounding.""" + data = get_upper_air_data(datetime(2016, 5, 22, 0), 'DDC') + motion = concatenate(corfidi_storm_motion(data['pressure'], + data['u_wind'], data['v_wind'], + data['height'])) + truth = [4.935900604467806, -29.210861180440986, + 23.21058679863669, -21.773710150231544] * units('kt') + assert_almost_equal(motion.flatten(), truth, 8) + def test_bulk_shear(): """Test bulk shear with observed sounding.""" data = get_upper_air_data(datetime(2016, 5, 22, 0), 'DDC') From 453a977c2c86bfdd964f59714bbdeeb6642e9998 Mon Sep 17 00:00:00 2001 From: Sam Gardner Date: Sat, 15 Jul 2023 15:33:57 -0500 Subject: [PATCH 02/10] better coverage for corfidi unit test --- tests/calc/test_indices.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/calc/test_indices.py b/tests/calc/test_indices.py index ddb3f7d1e53..27d204eab48 100644 --- a/tests/calc/test_indices.py +++ b/tests/calc/test_indices.py @@ -179,12 +179,24 @@ def test_bunkers_motion(): def test_corfidi_motion(): """Test corfidi MCS motion with observed sounding.""" data = get_upper_air_data(datetime(2016, 5, 22, 0), 'DDC') - motion = concatenate(corfidi_storm_motion(data['pressure'], + motion_full = concatenate(corfidi_storm_motion(data['pressure'], data['u_wind'], data['v_wind'], data['height'])) - truth = [4.935900604467806, -29.210861180440986, + truth_full = [4.935900604467806, -29.210861180440986, 23.21058679863669, -21.773710150231544] * units('kt') - assert_almost_equal(motion.flatten(), truth, 8) + assert_almost_equal(motion_full.flatten(), truth_full, 8) + motion_no_bottom = concatenate(corfidi_storm_motion(data['pressure'][6:], + data['u_wind'][6:], data['v_wind'][6:], + data['height'][6:])) + truth_no_bottom = [6.0042903160, -31.9279062924, + 25.3473662217, -27.2078003741] * units('kt') + assert_almost_equal(motion_no_bottom.flatten(), truth_no_bottom, 8) + motion_no_top = concatenate(corfidi_storm_motion(data['pressure'][:37], + data['u_wind'][:37], data['v_wind'][:37], + data['height'][:37])) + truth_no_top = [4.6170542714, -27.7322457064, + 22.5728941325, -18.8164792021] * units('kt') + assert_almost_equal(motion_no_top.flatten(), truth_no_top, 8) def test_bulk_shear(): """Test bulk shear with observed sounding.""" From 5fbcb184b2ed49f1cd36499b592f1e22b4884a41 Mon Sep 17 00:00:00 2001 From: Sam Gardner Date: Sat, 15 Jul 2023 16:00:54 -0500 Subject: [PATCH 03/10] fix spacing --- src/metpy/calc/indices.py | 6 +++--- tests/calc/test_indices.py | 17 +++++++++-------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/metpy/calc/indices.py b/src/metpy/calc/indices.py index f0a5c57b277..4baebedbf95 100644 --- a/src/metpy/calc/indices.py +++ b/src/metpy/calc/indices.py @@ -416,15 +416,15 @@ def corfidi_storm_motion(pressure, u, v, height): else: depth = units.Quantity(550, 'hectopascal') cloud_layer_winds = weighted_continuous_average(pressure, u, v, height=height, - bottom=bottom, - depth=depth) + bottom=bottom, + depth=depth) cloud_layer_winds = units.Quantity.from_list(cloud_layer_winds) # calculate corfidi vectors upwind = cloud_layer_winds + llj_inverse - downwind = 2*cloud_layer_winds + llj_inverse + downwind = 2 * cloud_layer_winds + llj_inverse return upwind, downwind diff --git a/tests/calc/test_indices.py b/tests/calc/test_indices.py index 27d204eab48..348bb2bae40 100644 --- a/tests/calc/test_indices.py +++ b/tests/calc/test_indices.py @@ -180,24 +180,25 @@ def test_corfidi_motion(): """Test corfidi MCS motion with observed sounding.""" data = get_upper_air_data(datetime(2016, 5, 22, 0), 'DDC') motion_full = concatenate(corfidi_storm_motion(data['pressure'], - data['u_wind'], data['v_wind'], - data['height'])) + data['u_wind'], data['v_wind'], + data['height'])) truth_full = [4.935900604467806, -29.210861180440986, - 23.21058679863669, -21.773710150231544] * units('kt') + 23.21058679863669, -21.773710150231544] * units('kt') assert_almost_equal(motion_full.flatten(), truth_full, 8) motion_no_bottom = concatenate(corfidi_storm_motion(data['pressure'][6:], - data['u_wind'][6:], data['v_wind'][6:], - data['height'][6:])) + data['u_wind'][6:], data['v_wind'][6:], + data['height'][6:])) truth_no_bottom = [6.0042903160, -31.9279062924, 25.3473662217, -27.2078003741] * units('kt') assert_almost_equal(motion_no_bottom.flatten(), truth_no_bottom, 8) motion_no_top = concatenate(corfidi_storm_motion(data['pressure'][:37], - data['u_wind'][:37], data['v_wind'][:37], - data['height'][:37])) + data['u_wind'][:37], data['v_wind'][:37], + data['height'][:37])) truth_no_top = [4.6170542714, -27.7322457064, - 22.5728941325, -18.8164792021] * units('kt') + 22.5728941325, -18.8164792021] * units('kt') assert_almost_equal(motion_no_top.flatten(), truth_no_top, 8) + def test_bulk_shear(): """Test bulk shear with observed sounding.""" data = get_upper_air_data(datetime(2016, 5, 22, 0), 'DDC') From e9b469b31266ced63885088293539484192e1481 Mon Sep 17 00:00:00 2001 From: Sam Gardner Date: Wed, 9 Aug 2023 00:24:43 +0000 Subject: [PATCH 04/10] remove nans, use pressure-weighted mean --- src/metpy/calc/indices.py | 4 +++- tests/calc/test_indices.py | 23 +++++++++++++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/metpy/calc/indices.py b/src/metpy/calc/indices.py index 4baebedbf95..78e98385cf8 100644 --- a/src/metpy/calc/indices.py +++ b/src/metpy/calc/indices.py @@ -395,6 +395,8 @@ def corfidi_storm_motion(pressure, u, v, height): Quantities even when given xarray DataArray profiles. """ + # remove nans from input + pressure, u, v, height = _remove_nans(pressure, u, v, height) # convert height to AGL height = height - height[0] @@ -415,7 +417,7 @@ def corfidi_storm_motion(pressure, u, v, height): depth = bottom - pressure[-1] else: depth = units.Quantity(550, 'hectopascal') - cloud_layer_winds = weighted_continuous_average(pressure, u, v, height=height, + cloud_layer_winds = mean_pressure_weighted(pressure, u, v, height=height, bottom=bottom, depth=depth) diff --git a/tests/calc/test_indices.py b/tests/calc/test_indices.py index 348bb2bae40..b684c522936 100644 --- a/tests/calc/test_indices.py +++ b/tests/calc/test_indices.py @@ -182,21 +182,32 @@ def test_corfidi_motion(): motion_full = concatenate(corfidi_storm_motion(data['pressure'], data['u_wind'], data['v_wind'], data['height'])) - truth_full = [4.935900604467806, -29.210861180440986, - 23.21058679863669, -21.773710150231544] * units('kt') + truth_full = [4.38681947, -26.16100158, + 22.11242453, -15.67399095] * units('kt') assert_almost_equal(motion_full.flatten(), truth_full, 8) motion_no_bottom = concatenate(corfidi_storm_motion(data['pressure'][6:], data['u_wind'][6:], data['v_wind'][6:], data['height'][6:])) - truth_no_bottom = [6.0042903160, -31.9279062924, - 25.3473662217, -27.2078003741] * units('kt') + truth_no_bottom = [5.70216267, -29.03497766, + 24.74311094, -21.42194311] * units('kt') assert_almost_equal(motion_no_bottom.flatten(), truth_no_bottom, 8) motion_no_top = concatenate(corfidi_storm_motion(data['pressure'][:37], data['u_wind'][:37], data['v_wind'][:37], data['height'][:37])) - truth_no_top = [4.6170542714, -27.7322457064, - 22.5728941325, -18.8164792021] * units('kt') + truth_no_top = [4.1892675, -25.20826346, + 21.71732059, -13.76851471] * units('kt') assert_almost_equal(motion_no_top.flatten(), truth_no_top, 8) + u_with_nans = data['u_wind'] + u_with_nans[6:10] = np.nan + v_with_nans = data['v_wind'] + v_with_nans[6:10] = np.nan + motion_with_nans = concatenate(corfidi_storm_motion(data['pressure'], + u_with_nans, v_with_nans, + data['height'])) + truth_with_nans = [6.6604286, -26.30560547, + 23.79507672, -16.0832665] * units('kt') + assert_almost_equal(motion_with_nans.flatten(), truth_with_nans, 8) + def test_bulk_shear(): From e26a9c90a354cc33c611f706994dccb5131e078f Mon Sep 17 00:00:00 2001 From: Sam Gardner Date: Wed, 9 Aug 2023 00:39:52 +0000 Subject: [PATCH 05/10] codestyle --- src/metpy/calc/indices.py | 4 ++-- tests/calc/test_indices.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/metpy/calc/indices.py b/src/metpy/calc/indices.py index 78e98385cf8..52f00acd9ea 100644 --- a/src/metpy/calc/indices.py +++ b/src/metpy/calc/indices.py @@ -418,8 +418,8 @@ def corfidi_storm_motion(pressure, u, v, height): else: depth = units.Quantity(550, 'hectopascal') cloud_layer_winds = mean_pressure_weighted(pressure, u, v, height=height, - bottom=bottom, - depth=depth) + bottom=bottom, + depth=depth) cloud_layer_winds = units.Quantity.from_list(cloud_layer_winds) diff --git a/tests/calc/test_indices.py b/tests/calc/test_indices.py index b684c522936..78e4475ac31 100644 --- a/tests/calc/test_indices.py +++ b/tests/calc/test_indices.py @@ -207,7 +207,6 @@ def test_corfidi_motion(): truth_with_nans = [6.6604286, -26.30560547, 23.79507672, -16.0832665] * units('kt') assert_almost_equal(motion_with_nans.flatten(), truth_with_nans, 8) - def test_bulk_shear(): From 3be4f1dfaa5e7f4abed6ba01cf06d6c7d14675f4 Mon Sep 17 00:00:00 2001 From: Sam Gardner Date: Wed, 9 Aug 2023 21:16:18 +0000 Subject: [PATCH 06/10] update example --- src/metpy/calc/indices.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/metpy/calc/indices.py b/src/metpy/calc/indices.py index 52f00acd9ea..535b0bd7f07 100644 --- a/src/metpy/calc/indices.py +++ b/src/metpy/calc/indices.py @@ -382,11 +382,11 @@ def corfidi_storm_motion(pressure, u, v, height): >>> p = [1000, 925, 850, 700, 500, 400] * units.hPa >>> h = [250, 700, 1500, 3100, 5720, 7120] * units.meters >>> wdir = [165, 180, 190, 210, 220, 250] * units.degree - >>> sped = [5, 15, 20, 30, 50, 60] * units.knots - >>> u, v = wind_components(sped, wdir) + >>> speed = [5, 15, 20, 30, 50, 60] * units.knots + >>> u, v = wind_components(speed, wdir) >>> corfidi_storm_motion(p, u, v, h) - (, - ) + (, + ) Notes ----- From d0d86ae7919bd564fbbc803ee9b43e766e4d3f80 Mon Sep 17 00:00:00 2001 From: Sam Gardner Date: Mon, 30 Oct 2023 00:11:44 -0500 Subject: [PATCH 07/10] look for maximum below 850hPa, allow u_llj and v_llj override --- src/metpy/calc/indices.py | 60 ++++++++++++++++++++++++++------------ tests/calc/test_indices.py | 48 ++++++++++++++++++------------ 2 files changed, 72 insertions(+), 36 deletions(-) diff --git a/src/metpy/calc/indices.py b/src/metpy/calc/indices.py index 535b0bd7f07..f033b1c15a7 100644 --- a/src/metpy/calc/indices.py +++ b/src/metpy/calc/indices.py @@ -339,15 +339,15 @@ def bunkers_storm_motion(pressure, u, v, height): @exporter.export @preprocess_and_wrap() -@check_units('[pressure]', '[speed]', '[speed]', '[length]') -def corfidi_storm_motion(pressure, u, v, height): +@check_units('[pressure]', '[speed]', '[speed]', u_llj='[speed]', v_llj='[speed]') +def corfidi_storm_motion(pressure, u, v, *, u_llj=None, v_llj=None): r"""Calculate upwind- and downwind-developing MCS storm motions using the Corfidi method. Method described by ([Corfidi2003]_): * Cloud-layer winds, estimated as the mean of the wind from 850 hPa to 300 hPa * Convergence along the cold pool, defined as the negative of the low-level jet - (estimated as maximum in winds below 1.5 km AGL) + (estimated as maximum in winds below 850 hPa unless specified in u_llj and v_llj) * The vector sum of the above is used to describe upwind propagating MCSes * Downwind propagating MCSes are taken as propagating along the sum of the cloud-layer wind and negative of the low level jet, therefore the cloud-level winds are doubled @@ -364,8 +364,11 @@ def corfidi_storm_motion(pressure, u, v, height): v : `pint.Quantity` Full profile of the V-component of the wind - height : `pint.Quantity` - Full profile of height + u_llj : `pint.Quantity`, optional + U-component of low-level jet + + v_llj : `pint.Quantity`, optional + V-component of low-level jet Returns ------- @@ -380,13 +383,22 @@ def corfidi_storm_motion(pressure, u, v, height): >>> from metpy.calc import corfidi_storm_motion, wind_components >>> from metpy.units import units >>> p = [1000, 925, 850, 700, 500, 400] * units.hPa - >>> h = [250, 700, 1500, 3100, 5720, 7120] * units.meters >>> wdir = [165, 180, 190, 210, 220, 250] * units.degree >>> speed = [5, 15, 20, 30, 50, 60] * units.knots >>> u, v = wind_components(speed, wdir) - >>> corfidi_storm_motion(p, u, v, h) + >>> corfidi_storm_motion(p, u, v) (, ) + >>> # Example with manually specified low-level jet as max wind below 1500m + >>> import numpy as np + >>> h = [100, 250, 700, 1500, 3100, 5720] * units.meters + >>> lowest1500_index = np.argmin(h <= units.Quantity(1500, 'meter')) + >>> llj_index = np.argmax(speed[:lowest1500_index]) + >>> llj_u, llj_v = u[llj_index], v[llj_index] + >>> corfidi_storm_motion(p, u, v, u_llj=llj_u, v_llj=llj_v) + (, + ) + Notes ----- @@ -395,17 +407,29 @@ def corfidi_storm_motion(pressure, u, v, height): Quantities even when given xarray DataArray profiles. """ - # remove nans from input - pressure, u, v, height = _remove_nans(pressure, u, v, height) - # convert height to AGL - height = height - height[0] + # If user specifies only one component of low-level jet, raise + if (u_llj is None) ^ (v_llj is None): + raise ValueError('Must specify both u_llj and v_llj or neither') - # convert u/v wind to wind speed and direction - wind_magnitude = wind_speed(u, v) - # find inverse of low-level jet - lowlevel_index = np.argmin(height <= units.Quantity(1500, 'meter')) - llj_index = np.argmax(wind_magnitude[:lowlevel_index]) - llj_inverse = units.Quantity.from_list((-u[llj_index], -v[llj_index])) + # remove nans from input + pressure, u, v = _remove_nans(pressure, u, v) + + # If LLJ specified, use that + if u_llj is not None and v_llj is not None: + # find inverse of low-level jet + llj_inverse = units.Quantity.from_list((-1*u_llj, -1*v_llj)) + # If pressure values contain values below 850 hPa, find low-level jet + elif np.max(pressure) >= units.Quantity(850, 'hectopascal'): + # convert u/v wind to wind speed and direction + wind_magnitude = wind_speed(u, v) + # find inverse of low-level jet + lowlevel_index = np.argmin(pressure >= units.Quantity(850, 'hectopascal')) + llj_index = np.argmax(wind_magnitude[:lowlevel_index]) + llj_inverse = units.Quantity.from_list((-u[llj_index], -v[llj_index])) + # If LLJ not specified and maximum pressure value is above 850 hPa, raise + else: + raise ValueError('Must specify low-level jet or ' + 'specify pressure values below 850 hPa') # cloud layer mean wind # don't select outside bounds of given data @@ -417,7 +441,7 @@ def corfidi_storm_motion(pressure, u, v, height): depth = bottom - pressure[-1] else: depth = units.Quantity(550, 'hectopascal') - cloud_layer_winds = mean_pressure_weighted(pressure, u, v, height=height, + cloud_layer_winds = mean_pressure_weighted(pressure, u, v, bottom=bottom, depth=depth) diff --git a/tests/calc/test_indices.py b/tests/calc/test_indices.py index 78e4475ac31..dd21afe191e 100644 --- a/tests/calc/test_indices.py +++ b/tests/calc/test_indices.py @@ -180,33 +180,45 @@ def test_corfidi_motion(): """Test corfidi MCS motion with observed sounding.""" data = get_upper_air_data(datetime(2016, 5, 22, 0), 'DDC') motion_full = concatenate(corfidi_storm_motion(data['pressure'], - data['u_wind'], data['v_wind'], - data['height'])) - truth_full = [4.38681947, -26.16100158, - 22.11242453, -15.67399095] * units('kt') + data['u_wind'], data['v_wind'])) + truth_full = [20.60174457, -22.38741441, + 38.32734963, -11.90040377] * units('kt') assert_almost_equal(motion_full.flatten(), truth_full, 8) - motion_no_bottom = concatenate(corfidi_storm_motion(data['pressure'][6:], - data['u_wind'][6:], data['v_wind'][6:], - data['height'][6:])) - truth_no_bottom = [5.70216267, -29.03497766, - 24.74311094, -21.42194311] * units('kt') - assert_almost_equal(motion_no_bottom.flatten(), truth_no_bottom, 8) + + motion_override = concatenate(corfidi_storm_motion(data['pressure'], + data['u_wind'], data['v_wind'], + u_llj=0*units('kt'), + v_llj=0*units('kt'))) + truth_override = [17.72560506, 10.48701063, + 35.45121012, 20.97402126] * units('kt') + assert_almost_equal(motion_override.flatten(), truth_override, 8) + + with pytest.raises(ValueError): + corfidi_storm_motion(data['pressure'][6:], data['u_wind'][6:], data['v_wind'][6:]) + motion_no_top = concatenate(corfidi_storm_motion(data['pressure'][:37], - data['u_wind'][:37], data['v_wind'][:37], - data['height'][:37])) - truth_no_top = [4.1892675, -25.20826346, - 21.71732059, -13.76851471] * units('kt') + data['u_wind'][:37], data['v_wind'][:37])) + truth_no_top = [20.40419260, -21.43467629, + 37.93224569, -9.99492754] * units('kt') assert_almost_equal(motion_no_top.flatten(), truth_no_top, 8) + u_with_nans = data['u_wind'] u_with_nans[6:10] = np.nan v_with_nans = data['v_wind'] v_with_nans[6:10] = np.nan motion_with_nans = concatenate(corfidi_storm_motion(data['pressure'], - u_with_nans, v_with_nans, - data['height'])) - truth_with_nans = [6.6604286, -26.30560547, - 23.79507672, -16.0832665] * units('kt') + u_with_nans, v_with_nans)) + truth_with_nans = [20.01078763, -22.65208606, + 37.14543575, -12.42974709] * units('kt') assert_almost_equal(motion_with_nans.flatten(), truth_with_nans, 8) + + with pytest.raises(ValueError): + corfidi_storm_motion(data['pressure'], data['u_wind'], + data['v_wind'], u_llj=10 * units('kt')) + + with pytest.raises(ValueError): + corfidi_storm_motion(data['pressure'], data['u_wind'], + data['v_wind'], v_llj=10 * units('kt')) def test_bulk_shear(): From 76a5da32bff38390c27ed03e851404495b68020e Mon Sep 17 00:00:00 2001 From: Sam Gardner Date: Mon, 30 Oct 2023 00:34:40 -0500 Subject: [PATCH 08/10] fix climate --- src/metpy/calc/indices.py | 2 +- tests/calc/test_indices.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/metpy/calc/indices.py b/src/metpy/calc/indices.py index f033b1c15a7..3c8fa2bf95a 100644 --- a/src/metpy/calc/indices.py +++ b/src/metpy/calc/indices.py @@ -417,7 +417,7 @@ def corfidi_storm_motion(pressure, u, v, *, u_llj=None, v_llj=None): # If LLJ specified, use that if u_llj is not None and v_llj is not None: # find inverse of low-level jet - llj_inverse = units.Quantity.from_list((-1*u_llj, -1*v_llj)) + llj_inverse = units.Quantity.from_list((-1 * u_llj, -1 * v_llj)) # If pressure values contain values below 850 hPa, find low-level jet elif np.max(pressure) >= units.Quantity(850, 'hectopascal'): # convert u/v wind to wind speed and direction diff --git a/tests/calc/test_indices.py b/tests/calc/test_indices.py index dd21afe191e..bcc36b67311 100644 --- a/tests/calc/test_indices.py +++ b/tests/calc/test_indices.py @@ -186,22 +186,22 @@ def test_corfidi_motion(): assert_almost_equal(motion_full.flatten(), truth_full, 8) motion_override = concatenate(corfidi_storm_motion(data['pressure'], - data['u_wind'], data['v_wind'], - u_llj=0*units('kt'), - v_llj=0*units('kt'))) + data['u_wind'], data['v_wind'], + u_llj=0 * units('kt'), + v_llj=0 * units('kt'))) truth_override = [17.72560506, 10.48701063, 35.45121012, 20.97402126] * units('kt') assert_almost_equal(motion_override.flatten(), truth_override, 8) - + with pytest.raises(ValueError): corfidi_storm_motion(data['pressure'][6:], data['u_wind'][6:], data['v_wind'][6:]) - + motion_no_top = concatenate(corfidi_storm_motion(data['pressure'][:37], data['u_wind'][:37], data['v_wind'][:37])) truth_no_top = [20.40419260, -21.43467629, 37.93224569, -9.99492754] * units('kt') assert_almost_equal(motion_no_top.flatten(), truth_no_top, 8) - + u_with_nans = data['u_wind'] u_with_nans[6:10] = np.nan v_with_nans = data['v_wind'] @@ -211,11 +211,11 @@ def test_corfidi_motion(): truth_with_nans = [20.01078763, -22.65208606, 37.14543575, -12.42974709] * units('kt') assert_almost_equal(motion_with_nans.flatten(), truth_with_nans, 8) - + with pytest.raises(ValueError): corfidi_storm_motion(data['pressure'], data['u_wind'], data['v_wind'], u_llj=10 * units('kt')) - + with pytest.raises(ValueError): corfidi_storm_motion(data['pressure'], data['u_wind'], data['v_wind'], v_llj=10 * units('kt')) From 48b8e59459c4db3a4f185f25007610639a03486b Mon Sep 17 00:00:00 2001 From: Sam Gardner Date: Tue, 14 Nov 2023 17:51:11 -0600 Subject: [PATCH 09/10] don't conglomerate tests --- tests/calc/test_indices.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/tests/calc/test_indices.py b/tests/calc/test_indices.py index 6fbace61136..361edcd3dbd 100644 --- a/tests/calc/test_indices.py +++ b/tests/calc/test_indices.py @@ -185,6 +185,10 @@ def test_corfidi_motion(): 38.32734963, -11.90040377] * units('kt') assert_almost_equal(motion_full.flatten(), truth_full, 8) + +def test_corfidi_motion_override_llj(): + """Test corfidi MCS motion with overridden LLJ""" + data = get_upper_air_data(datetime(2016, 5, 22, 0), 'DDC') motion_override = concatenate(corfidi_storm_motion(data['pressure'], data['u_wind'], data['v_wind'], u_llj=0 * units('kt'), @@ -193,15 +197,35 @@ def test_corfidi_motion(): 35.45121012, 20.97402126] * units('kt') assert_almost_equal(motion_override.flatten(), truth_override, 8) + with pytest.raises(ValueError): + corfidi_storm_motion(data['pressure'], data['u_wind'], + data['v_wind'], u_llj=10 * units('kt')) + + with pytest.raises(ValueError): + corfidi_storm_motion(data['pressure'], data['u_wind'], + data['v_wind'], v_llj=10 * units('kt')) + + +def test_corfidi_corfidi_llj_unaivalable(): + """Test corfidi MCS motion where the LLJ is unailable.""" + data = get_upper_air_data(datetime(2016, 5, 22, 0), 'DDC') with pytest.raises(ValueError): corfidi_storm_motion(data['pressure'][6:], data['u_wind'][6:], data['v_wind'][6:]) + +def test_corfidi_corfidi_cloudlayer_trimmed(): + """Test corfidi MCS motion where sounding does not include the entire cloud layer.""" + data = get_upper_air_data(datetime(2016, 5, 22, 0), 'DDC') motion_no_top = concatenate(corfidi_storm_motion(data['pressure'][:37], data['u_wind'][:37], data['v_wind'][:37])) truth_no_top = [20.40419260, -21.43467629, 37.93224569, -9.99492754] * units('kt') assert_almost_equal(motion_no_top.flatten(), truth_no_top, 8) + +def test_corfidi_motion_with_nans(): + """Test corfidi MCS motion with observed sounding with nans.""" + data = get_upper_air_data(datetime(2016, 5, 22, 0), 'DDC') u_with_nans = data['u_wind'] u_with_nans[6:10] = np.nan v_with_nans = data['v_wind'] @@ -212,14 +236,6 @@ def test_corfidi_motion(): 37.14543575, -12.42974709] * units('kt') assert_almost_equal(motion_with_nans.flatten(), truth_with_nans, 8) - with pytest.raises(ValueError): - corfidi_storm_motion(data['pressure'], data['u_wind'], - data['v_wind'], u_llj=10 * units('kt')) - - with pytest.raises(ValueError): - corfidi_storm_motion(data['pressure'], data['u_wind'], - data['v_wind'], v_llj=10 * units('kt')) - def test_bunkers_motion_with_nans(): """Test Bunkers storm motion with observed sounding.""" From 50e6b6b94723fced1c2eb408306f90c9c56b8e52 Mon Sep 17 00:00:00 2001 From: Sam Gardner Date: Tue, 14 Nov 2023 17:52:34 -0600 Subject: [PATCH 10/10] codestyle :) --- tests/calc/test_indices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/calc/test_indices.py b/tests/calc/test_indices.py index 361edcd3dbd..1a5ab52ebcd 100644 --- a/tests/calc/test_indices.py +++ b/tests/calc/test_indices.py @@ -187,7 +187,7 @@ def test_corfidi_motion(): def test_corfidi_motion_override_llj(): - """Test corfidi MCS motion with overridden LLJ""" + """Test corfidi MCS motion with overridden LLJ.""" data = get_upper_air_data(datetime(2016, 5, 22, 0), 'DDC') motion_override = concatenate(corfidi_storm_motion(data['pressure'], data['u_wind'], data['v_wind'],