From 57204cecdfc45d587d5440b9d70363a7abd0eda9 Mon Sep 17 00:00:00 2001 From: Steve Decker Date: Thu, 14 Dec 2023 09:51:49 -0500 Subject: [PATCH 1/3] Check for duplicate levels in isentropic_interpolation_as_dataset Fixes #3309 by provided the user with a warning when interpolation to a duplicate isentropic surface is requested. This is likely a mistake on the part of the user, but by issuing a warning rather than an error, computations can proceed if the duplication was really intended. The code used here is an adaptation of the duplicate-level check found in _parcel_profile_helper elsewhere in thermo.py. --- src/metpy/calc/thermo.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/metpy/calc/thermo.py b/src/metpy/calc/thermo.py index eeba567cd6a..0f36e33efd9 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -2826,6 +2826,13 @@ def isentropic_interpolation_as_dataset( # Ensure matching coordinates by broadcasting all_args = xr.broadcast(temperature, *args) + # Check for duplicate isentropic levels, which can be problematic (Issue #3309) + unique, counts = np.unique(levels.m, return_counts=True) + if np.any(counts > 1): + _warnings.warn(f'Duplicate level(s) {unique[counts > 1]} provided. ' + 'The output Dataset includes duplicate levels as a result. ' + 'This may cause xarray to crash when working with this Dataset!') + # Obtain result as list of Quantities ret = isentropic_interpolation( levels, From 5abc85340fa40570e1ee07b7ba1b1fdfab8d41eb Mon Sep 17 00:00:00 2001 From: Steve Decker Date: Thu, 14 Dec 2023 10:04:03 -0500 Subject: [PATCH 2/3] Test duplicate-level check --- tests/calc/test_thermo.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/calc/test_thermo.py b/tests/calc/test_thermo.py index 118902b3f27..f301f67ebfd 100644 --- a/tests/calc/test_thermo.py +++ b/tests/calc/test_thermo.py @@ -1458,6 +1458,29 @@ def test_isentropic_interpolation_as_dataset(): assert result['isentropic_level'].attrs == expected['isentropic_level'].attrs +def test_isentropic_interpolation_as_dataset_duplicate(): + """Test duplicate-level check in isentropic_interpolation_as_dataset.""" + data = xr.Dataset( + { + 'temperature': ( + ('isobaric', 'y', 'x'), + [[[296.]], [[292.]], [[290.]], [[288.]]] * units.K + ), + 'rh': ( + ('isobaric', 'y', 'x'), + [[[100.]], [[80.]], [[40.]], [[20.]]] * units.percent + ) + }, + coords={ + 'isobaric': (('isobaric',), [1000., 950., 900., 850.], {'units': 'hPa'}), + 'time': '2020-01-01T00:00Z' + } + ) + isentlev = [296., 296.] * units.kelvin + with pytest.warns(UserWarning): + _ = isentropic_interpolation_as_dataset(isentlev, data['temperature'], data['rh']) + + @pytest.mark.parametrize('array_class', (units.Quantity, masked_array)) def test_surface_based_cape_cin(array_class): """Test the surface-based CAPE and CIN calculation.""" From 34ac58ebf107a128eaf7444cf2e7cdeb101606b9 Mon Sep 17 00:00:00 2001 From: Ryan May Date: Thu, 14 Dec 2023 13:10:45 -0700 Subject: [PATCH 3/3] MNT: Refactor isentropic_interpolation tests for xarray Move common test data to a fixture to reduce noise in tests. --- tests/calc/test_thermo.py | 67 +++++++++++++++------------------------ 1 file changed, 25 insertions(+), 42 deletions(-) diff --git a/tests/calc/test_thermo.py b/tests/calc/test_thermo.py index f301f67ebfd..5e7aea34c9c 100644 --- a/tests/calc/test_thermo.py +++ b/tests/calc/test_thermo.py @@ -1386,29 +1386,10 @@ def test_isentropic_pressure_4d(): assert_almost_equal(isentprs[1][:, 1, ], truerh, 3) -def test_isentropic_interpolation_dataarray(): - """Test calculation of isentropic interpolation with xarray dataarrays.""" - temp = xr.DataArray([[[296.]], [[292.]], [[290.]], [[288.]]] * units.K, - dims=('isobaric', 'y', 'x'), - coords={'isobaric': (('isobaric',), [1000., 950., 900., 850.], - {'units': 'hPa'}), - 'time': '2020-01-01T00:00Z'}) - - rh = xr.DataArray([[[100.]], [[80.]], [[40.]], [[20.]]] * units.percent, - dims=('isobaric', 'y', 'x'), coords={ - 'isobaric': (('isobaric',), [1000., 950., 900., 850.], {'units': 'hPa'}), - 'time': '2020-01-01T00:00Z'}) - - isentlev = [296., 297.] * units.kelvin - press, rh_interp = isentropic_interpolation(isentlev, temp.isobaric, temp, rh) - - assert_array_almost_equal(press, np.array([[[1000.]], [[936.213]]]) * units.hPa, 3) - assert_array_almost_equal(rh_interp, np.array([[[100.]], [[69.19706]]]) * units.percent, 3) - - -def test_isentropic_interpolation_as_dataset(): - """Test calculation of isentropic interpolation with xarray.""" - data = xr.Dataset( +@pytest.fixture +def xarray_isentropic_data(): + """Generate test xarray dataset for interpolation functions.""" + return xr.Dataset( { 'temperature': ( ('isobaric', 'y', 'x'), @@ -1424,8 +1405,25 @@ def test_isentropic_interpolation_as_dataset(): 'time': '2020-01-01T00:00Z' } ) + + +def test_isentropic_interpolation_dataarray(xarray_isentropic_data): + """Test calculation of isentropic interpolation with xarray dataarrays.""" + rh = xarray_isentropic_data.rh + temp = xarray_isentropic_data.temperature + isentlev = [296., 297.] * units.kelvin - result = isentropic_interpolation_as_dataset(isentlev, data['temperature'], data['rh']) + press, rh_interp = isentropic_interpolation(isentlev, temp.isobaric, temp, rh) + + assert_array_almost_equal(press, np.array([[[1000.]], [[936.213]]]) * units.hPa, 3) + assert_array_almost_equal(rh_interp, np.array([[[100.]], [[69.19706]]]) * units.percent, 3) + + +def test_isentropic_interpolation_as_dataset(xarray_isentropic_data): + """Test calculation of isentropic interpolation with xarray.""" + isentlev = [296., 297.] * units.kelvin + result = isentropic_interpolation_as_dataset(isentlev, xarray_isentropic_data.temperature, + xarray_isentropic_data.rh) expected = xr.Dataset( { 'pressure': ( @@ -1458,27 +1456,12 @@ def test_isentropic_interpolation_as_dataset(): assert result['isentropic_level'].attrs == expected['isentropic_level'].attrs -def test_isentropic_interpolation_as_dataset_duplicate(): +def test_isentropic_interpolation_as_dataset_duplicate(xarray_isentropic_data): """Test duplicate-level check in isentropic_interpolation_as_dataset.""" - data = xr.Dataset( - { - 'temperature': ( - ('isobaric', 'y', 'x'), - [[[296.]], [[292.]], [[290.]], [[288.]]] * units.K - ), - 'rh': ( - ('isobaric', 'y', 'x'), - [[[100.]], [[80.]], [[40.]], [[20.]]] * units.percent - ) - }, - coords={ - 'isobaric': (('isobaric',), [1000., 950., 900., 850.], {'units': 'hPa'}), - 'time': '2020-01-01T00:00Z' - } - ) isentlev = [296., 296.] * units.kelvin with pytest.warns(UserWarning): - _ = isentropic_interpolation_as_dataset(isentlev, data['temperature'], data['rh']) + _ = isentropic_interpolation_as_dataset(isentlev, xarray_isentropic_data.temperature, + xarray_isentropic_data.rh) @pytest.mark.parametrize('array_class', (units.Quantity, masked_array))