From 39b222ff7a377f4794f8ce91d1f2074bf64129b3 Mon Sep 17 00:00:00 2001 From: Haoyu Zhuang Date: Thu, 23 Jun 2022 22:55:07 -0400 Subject: [PATCH 01/15] Implement convective condensation level (CCL) Fix docstring for ccl function Fix inline comment for ccl --- docs/_templates/overrides/metpy.calc.rst | 1 + src/metpy/calc/thermo.py | 102 +++++++++++++++++++ tests/calc/test_thermo.py | 121 ++++++++++++++++++++++- 3 files changed, 223 insertions(+), 1 deletion(-) diff --git a/docs/_templates/overrides/metpy.calc.rst b/docs/_templates/overrides/metpy.calc.rst index f6eeeaf3724..063cd061d36 100644 --- a/docs/_templates/overrides/metpy.calc.rst +++ b/docs/_templates/overrides/metpy.calc.rst @@ -70,6 +70,7 @@ Soundings bulk_shear bunkers_storm_motion cape_cin + ccl critical_angle cross_totals el diff --git a/src/metpy/calc/thermo.py b/src/metpy/calc/thermo.py index a18c4243d4e..2426340d719 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -442,6 +442,108 @@ def _lcl_iter(p, p0, w, t): return lcl_p, globals()['dewpoint']._nounit(vapor_pressure._nounit(lcl_p, w)) +@exporter.export +@preprocess_and_wrap() +@check_units('[pressure]', '[temperature]', '[temperature]') +def ccl(pressure, temperature, dewpoint, height=None, mixed_layer_depth=None, which='top'): + r"""Calculate the convective condensation level (CCL). + + This function is implemented by simplifying the mixing ratio and + the saturation vapor pressure equation to + + .. math:: A(P) = ln(\frac{rP}{6.112(r+\epsilon)}) = \frac{17.67T}{T+243.5} + + where `P` is pressure, and `T` is temperature in degrees C. + + This gives us a direct relationship of how temperature changes with pressure + in the atmosphere with a constant mixing ratio. + + Parameters + ---------- + pressure : `pint.Quantity` + Atmospheric pressure profile + + temperature : `pint.Quantity` + Temperature at the levels given by `pressure` + + dewpoint : `pint.Quantity` + Dewpoint at the levels given by `pressure` + + height : `pint.Quantity`, optional + Atmospheric heights at the levels given by `pressure` + + mixed_layer_depth : `pint.Quantity`, optional + The thickness of the mixed layer as a pressure or height above the bottom + of the layer (default None). + + which: str, optional + Pick which CCL value to return; must be one of 'top', 'bottom', or 'all'. + 'top' returns the lowest-pressure CCL (default), + 'bottom' returns the highest-pressure CCL, + 'all' returns every CCL in a `Pint.Quantity` array. + + Returns + ------- + `pint.Quantity` + CCL Pressure + + `pint.Quantity` + CCL Temperature + + `pint.Quantity` + Convective Temperature + + See Also + -------- + lcl, lfc, el + + 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. + + """ + pressure, temperature, dewpoint = _remove_nans(pressure, temperature, dewpoint) + + dewpoint, temperature = dewpoint.to(units.degC), temperature.to(units.degC) + pressure = pressure.to(units.hPa) + + # If the mixed layer is not defined, take the starting dewpoint to be the + # first element of the dewpoint array and calculate the corresponding mixing ratio. + if mixed_layer_depth is None: + p_start, dewpoint_start = pressure[0], dewpoint[0] + vapor_pressure_start = saturation_vapor_pressure(dewpoint_start) + r_start = mixing_ratio(vapor_pressure_start, p_start) + + # Else, calculate the mixing ratio of the mixed layer. + else: + vapor_pressure_profile = saturation_vapor_pressure(dewpoint) + r_profile = mixing_ratio(vapor_pressure_profile, pressure) + r_start = mixed_layer(pressure, r_profile, height=height, + depth=mixed_layer_depth)[0] + + a_p = np.log(r_start * pressure / ( + mpconsts.default.sat_pressure_0c * (mpconsts.nounit.epsilon + r_start))) + # rt_profile is the temperature-pressure profile with a fixed mixing ratio + rt_profile = units.Quantity((243.5 * a_p / (17.67 - a_p)).magnitude, 'degC') + + x, y = find_intersections(pressure, rt_profile, temperature, + direction='increasing', log_x=True) + + # In the case of multiple CCLs, select which to return + if which == 'top': + x, y = x[-1], y[-1] + elif which == 'bottom': + x, y = x[0], y[0] + elif which not in ['top', 'bottom', 'all']: + raise ValueError('Invalid option for "which". Valid options are "top", "bottom", ' + 'and "all".') + + x, y = x.to(pressure.units), y.to(temperature.units) + return x, y, potential_temperature(x, y).to(temperature.units) + + @exporter.export @preprocess_and_wrap() @check_units('[pressure]', '[temperature]', '[temperature]', '[temperature]') diff --git a/tests/calc/test_thermo.py b/tests/calc/test_thermo.py index cc8e5827bfc..c900d53f0c1 100644 --- a/tests/calc/test_thermo.py +++ b/tests/calc/test_thermo.py @@ -10,7 +10,7 @@ import xarray as xr from metpy.calc import (brunt_vaisala_frequency, brunt_vaisala_frequency_squared, - brunt_vaisala_period, cape_cin, cross_totals, density, dewpoint, + 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, @@ -399,6 +399,125 @@ def test_lcl_nans(): np.nan, 18.82281982535794]) * units.degC) +def test_ccl_basic(): + """First test of CCL calculation. Data: ILX, June 17 2022 00Z.""" + pressure = np.array([993.0, 984.0, 957.0, 948.0, 925.0, 917.0, 886.0, 868.0, 850.0, + 841.0, 813.0, 806.0, 798.0, 738.0, 732.0, 723.0, 716.0, 711.0, + 700.0, 623.0, 621.0, 582.0, 541.0, 500.0, 468.0]) * units.mbar + temperature = np.array([34.6, 33.7, 31.1, 30.1, 27.8, 27.1, 24.3, 22.6, 21.4, + 20.8, 19.6, 19.4, 18.7, 13.0, 13.0, 13.4, 13.5, 13.6, + 13.0, 5.2, 5.0, 1.5, -2.4, -6.7, -10.7]) * units.degC + dewpoint = np.array([19.6, 19.4, 18.7, 18.4, 17.8, 17.5, 16.3, 15.6, 12.4, 10.8, + -0.4, -3.6, -3.8, -5.0, -6.0, -15.6, -13.2, -11.4, -11.0, + -5.8, -6.2, -14.8, -24.3, -34.7, -38.1]) * units.degC + ccl_p, ccl_t, t_c = ccl(pressure, temperature, dewpoint) + assert_almost_equal(ccl_p, 763.006048 * units.mbar, 5) + assert_almost_equal(ccl_t, 15.429946 * units.degC, 5) + assert_almost_equal(t_c, 38.616596 * units.degC, 5) + + +def test_ccl_nans(): + """Tests CCL handles nans.""" + pressure = np.array([993.0, 984.0, 957.0, np.nan, 925.0, 917.0, np.nan, 868.0, 850.0, + 841.0, 813.0, 806.0, 798.0, 738.0, 732.0, 723.0, 716.0, 711.0, + 700.0, 623.0, 621.0, 582.0, 541.0, 500.0, 468.0]) * units.mbar + temperature = np.array([34.6, np.nan, 31.1, np.nan, 27.8, 27.1, 24.3, 22.6, 21.4, + 20.8, 19.6, 19.4, 18.7, 13.0, 13.0, 13.4, 13.5, 13.6, + 13.0, 5.2, 5.0, 1.5, -2.4, -6.7, -10.7]) * units.degC + dewpoint = np.array([19.6, 19.4, 18.7, np.nan, 17.8, 17.5, 16.3, 15.6, 12.4, 10.8, + -0.4, -3.6, -3.8, -5.0, -6.0, -15.6, -13.2, -11.4, -11.0, + -5.8, -6.2, -14.8, -24.3, -34.7, -38.1]) * units.degC + ccl_p, ccl_t, t_c = ccl(pressure, temperature, dewpoint) + assert_almost_equal(ccl_p, 763.006048 * units.mbar, 5) + assert_almost_equal(ccl_t, 15.429946 * units.degC, 5) + assert_almost_equal(t_c, 38.616596 * units.degC, 5) + + +def test_ccl_unit(): + """Tests CCL pressure and temperature is returned in the correct unit.""" + pressure = (np.array([993.0, 984.0, 957.0, 948.0, 925.0, 917.0, 886.0, 868.0, 850.0, + 841.0, 813.0, 806.0, 798.0, 738.0, 732.0, 723.0, 716.0, 711.0, + 700.0, 623.0, 621.0, 582.0, 541.0, 500.0, 468.0]) * 100) * units.Pa + temperature = (np.array([34.6, 33.7, 31.1, 30.1, 27.8, 27.1, 24.3, 22.6, 21.4, + 20.8, 19.6, 19.4, 18.7, 13.0, 13.0, 13.4, 13.5, 13.6, + 13.0, 5.2, 5.0, 1.5, -2.4, -6.7, -10.7]) + 273.15) * units.kelvin + dewpoint = (np.array([19.6, 19.4, 18.7, 18.4, 17.8, 17.5, 16.3, 15.6, 12.4, 10.8, + -0.4, -3.6, -3.8, -5.0, -6.0, -15.6, -13.2, -11.4, -11.0, + -5.8, -6.2, -14.8, -24.3, -34.7, -38.1]) + 273.15) * units.kelvin + + ccl_p, ccl_t, t_c = ccl(pressure, temperature, dewpoint) + assert_almost_equal(ccl_p, (763.006048 * 100) * units.Pa, 3) + assert_almost_equal(ccl_t, (15.429946 + 273.15) * units.kelvin, 3) + assert_almost_equal(t_c, (38.616596 + 273.15) * units.kelvin, 3) + + +def test_multiple_ccl(): + """Tests the case where there are multiple CCLs. Data: BUF, May 18 2022 12Z.""" + pressure = np.array([992.0, 990.0, 983.0, 967.0, 950.0, 944.0, 928.0, 925.0, 922.0, + 883.0, 877.7, 858.0, 853.0, 850.0, 835.0, 830.0, 827.0, 826.0, + 813.6, 808.0, 799.0, 784.0, 783.3, 769.0, 760.0, 758.0, 754.0, + 753.0, 738.0, 725.7, 711.0, 704.0, 700.0, 685.0, 672.0, 646.6, + 598.6, 596.0, 587.0, 582.0, 567.0, 560.0, 555.0, 553.3, 537.0, + 526.0, 521.0, 519.0, 515.0, 500.0]) * units.mbar + temperature = np.array([6.8, 6.2, 7.8, 7.6, 7.2, 7.6, 6.6, 6.4, 6.2, 3.2, 2.8, 1.2, + 1.0, 0.8, -0.3, -0.1, 0.4, 0.6, 0.9, 1.0, 0.6, -0.3, -0.3, + -0.7, -1.5, -1.3, 0.2, 0.2, -1.1, -2.1, -3.3, -2.3, -1.7, 0.2, + -0.9, -3.0, -7.3, -7.5, -8.1, -8.3, -9.5, -10.1, -10.7, + -10.8, -12.1, -12.5, -12.7, -12.9, -13.5, -15.5]) * units.degC + dewpoint = np.array([5.1, 5.0, 4.2, 2.7, 2.2, 0.6, -2.4, -2.6, -2.8, -3.8, -3.6, + -3.1, -5.0, -4.2, -1.8, -4.3, -7.6, -6.4, -8.2, -9.0, -10.4, + -9.3, -9.6, -14.7, -11.5, -12.3, -25.8, -25.8, -19.1, -19.6, + -20.3, -42.3, -39.7, -46.8, -46.8, -46.7, -46.5, -46.5, + -52.1, -36.3, -47.5, -30.1, -29.7, -30.4, -37.1, -49.5, + -36.7, -28.9, -28.5, -22.5]) * units.degC + + ccl_p, ccl_t, t_c = ccl(pressure, temperature, dewpoint) + assert_almost_equal(ccl_p, 680.191653 * units.mbar, 5) + assert_almost_equal(ccl_t, -0.204408 * units.degC, 5) + assert_almost_equal(t_c, 31.566319 * units.degC, 5) + + ccl_p, ccl_t, t_c = ccl(pressure, temperature, dewpoint, which='bottom') + assert_almost_equal(ccl_p, 886.835325 * units.mbar, 5) + assert_almost_equal(ccl_t, 3.500840 * units.degC, 5) + assert_almost_equal(t_c, 13.158340 * units.degC, 5) + + ccl_p, ccl_t, t_c = ccl(pressure, temperature, dewpoint, which='all') + assert_array_almost_equal(ccl_p, np.array([886.835325, 680.191653]) * units.mbar, 5) + assert_array_almost_equal(ccl_t, np.array([3.500840, -0.204408]) * units.degC, 5) + assert_array_almost_equal(t_c, np.array([13.158340, 31.566319]) * units.degC, 5) + + +def test_ccl_with_ml(): + """Test CCL calculation with a specified mixed-layer depth.""" + pressure = np.array([992.0, 990.0, 983.0, 967.0, 950.0, 944.0, 928.0, 925.0, 922.0, + 883.0, 877.7, 858.0, 853.0, 850.0, 835.0, 830.0, 827.0, 826.0, + 813.6, 808.0, 799.0, 784.0, 783.3, 769.0, 760.0, 758.0, 754.0, + 753.0, 738.0, 725.7, 711.0, 704.0, 700.0, 685.0, 672.0, 646.6, + 598.6, 596.0, 587.0, 582.0, 567.0, 560.0, 555.0, 553.3, 537.0, + 526.0, 521.0, 519.0, 515.0, 500.0]) * units.mbar + temperature = np.array([6.8, 6.2, 7.8, 7.6, 7.2, 7.6, 6.6, 6.4, 6.2, 3.2, 2.8, 1.2, + 1.0, 0.8, -0.3, -0.1, 0.4, 0.6, 0.9, 1.0, 0.6, -0.3, -0.3, + -0.7, -1.5, -1.3, 0.2, 0.2, -1.1, -2.1, -3.3, -2.3, -1.7, 0.2, + -0.9, -3.0, -7.3, -7.5, -8.1, -8.3, -9.5, -10.1, -10.7, + -10.8, -12.1, -12.5, -12.7, -12.9, -13.5, -15.5]) * units.degC + dewpoint = np.array([5.1, 5.0, 4.2, 2.7, 2.2, 0.6, -2.4, -2.6, -2.8, -3.8, -3.6, + -3.1, -5.0, -4.2, -1.8, -4.3, -7.6, -6.4, -8.2, -9.0, -10.4, + -9.3, -9.6, -14.7, -11.5, -12.3, -25.8, -25.8, -19.1, -19.6, + -20.3, -42.3, -39.7, -46.8, -46.8, -46.7, -46.5, -46.5, + -52.1, -36.3, -47.5, -30.1, -29.7, -30.4, -37.1, -49.5, + -36.7, -28.9, -28.5, -22.5]) * units.degC + + ccl_p, ccl_t, t_c = ccl(pressure, temperature, dewpoint, + mixed_layer_depth=500 * units.m, which='all') + + assert_array_almost_equal(ccl_p, np.array( + [850.600930, 784.325312, 737.767377, 648.076147]) * units.mbar, 5) + assert_array_almost_equal(ccl_t, np.array( + [0.840118, -0.280299, -1.118757, -2.875716]) * units.degC, 5) + assert_array_almost_equal(t_c, np.array( + [13.804624, 19.332071, 23.576331, 32.782670]) * units.degC, 5) + + def test_lfc_basic(): """Test LFC calculation.""" levels = np.array([959., 779.2, 751.3, 724.3, 700., 269.]) * units.mbar From 881a71e93151aa2a42ca4666976d6d7b158bfeb3 Mon Sep 17 00:00:00 2001 From: Richard Zhuang <38182337+Z-Richard@users.noreply.github.com> Date: Thu, 15 Sep 2022 20:04:02 -0400 Subject: [PATCH 02/15] Update src/metpy/calc/thermo.py Co-authored-by: Ryan May --- 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 2426340d719..d89ba605555 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -537,7 +537,7 @@ def ccl(pressure, temperature, dewpoint, height=None, mixed_layer_depth=None, wh elif which == 'bottom': x, y = x[0], y[0] elif which not in ['top', 'bottom', 'all']: - raise ValueError('Invalid option for "which". Valid options are "top", "bottom", ' + raise ValueError(f'Invalid option for "which": {which}. Valid options are "top", "bottom", ' 'and "all".') x, y = x.to(pressure.units), y.to(temperature.units) From 7fee35631df55d30077b693b2a3d8f74a4644cca Mon Sep 17 00:00:00 2001 From: Richard Zhuang <38182337+Z-Richard@users.noreply.github.com> Date: Thu, 15 Sep 2022 20:04:12 -0400 Subject: [PATCH 03/15] Update src/metpy/calc/thermo.py Co-authored-by: Ryan May --- src/metpy/calc/thermo.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/metpy/calc/thermo.py b/src/metpy/calc/thermo.py index d89ba605555..dd8c9071e30 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -470,7 +470,8 @@ def ccl(pressure, temperature, dewpoint, height=None, mixed_layer_depth=None, wh Dewpoint at the levels given by `pressure` height : `pint.Quantity`, optional - Atmospheric heights at the levels given by `pressure` + Atmospheric heights at the levels given by `pressure`. Only needed when specifying a mixed layer + depth as a height. mixed_layer_depth : `pint.Quantity`, optional The thickness of the mixed layer as a pressure or height above the bottom From 7a58f20cf31b8ce654501cf5f75c8ed3a1762291 Mon Sep 17 00:00:00 2001 From: Richard Zhuang <38182337+Z-Richard@users.noreply.github.com> Date: Thu, 15 Sep 2022 20:04:37 -0400 Subject: [PATCH 04/15] Update src/metpy/calc/thermo.py Co-authored-by: Ryan May --- 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 dd8c9071e30..f1f553e7d8a 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -453,7 +453,7 @@ def ccl(pressure, temperature, dewpoint, height=None, mixed_layer_depth=None, wh .. math:: A(P) = ln(\frac{rP}{6.112(r+\epsilon)}) = \frac{17.67T}{T+243.5} - where `P` is pressure, and `T` is temperature in degrees C. + where :math:`P` is pressure, and :math:`T` is temperature in degrees C. This gives us a direct relationship of how temperature changes with pressure in the atmosphere with a constant mixing ratio. From b30280f8cb08e56d5d3168afb276629f79d51cfa Mon Sep 17 00:00:00 2001 From: Haoyu Zhuang Date: Thu, 15 Sep 2022 20:47:22 -0400 Subject: [PATCH 05/15] Fix linting issues --- src/metpy/calc/thermo.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/metpy/calc/thermo.py b/src/metpy/calc/thermo.py index f1f553e7d8a..f0d65d19a50 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -470,8 +470,8 @@ def ccl(pressure, temperature, dewpoint, height=None, mixed_layer_depth=None, wh Dewpoint at the levels given by `pressure` height : `pint.Quantity`, optional - Atmospheric heights at the levels given by `pressure`. Only needed when specifying a mixed layer - depth as a height. + Atmospheric heights at the levels given by `pressure`. + Only needed when specifying a mixed layer depth as a height. mixed_layer_depth : `pint.Quantity`, optional The thickness of the mixed layer as a pressure or height above the bottom @@ -538,8 +538,8 @@ def ccl(pressure, temperature, dewpoint, height=None, mixed_layer_depth=None, wh elif which == 'bottom': x, y = x[0], y[0] elif which not in ['top', 'bottom', 'all']: - raise ValueError(f'Invalid option for "which": {which}. Valid options are "top", "bottom", ' - 'and "all".') + raise ValueError(f'Invalid option for "which": {which}. Valid options are ' + '"top", "bottom", and "all".') x, y = x.to(pressure.units), y.to(temperature.units) return x, y, potential_temperature(x, y).to(temperature.units) From 1ba376edd5f31639a835f1305742e5775ac3c693 Mon Sep 17 00:00:00 2001 From: Richard Zhuang <38182337+Z-Richard@users.noreply.github.com> Date: Sun, 18 Sep 2022 20:43:08 -0400 Subject: [PATCH 06/15] Make requested changes for docstring Co-authored-by: Ryan May --- src/metpy/calc/thermo.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/metpy/calc/thermo.py b/src/metpy/calc/thermo.py index f0d65d19a50..7f4f5ca6c19 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -448,15 +448,9 @@ def _lcl_iter(p, p0, w, t): def ccl(pressure, temperature, dewpoint, height=None, mixed_layer_depth=None, which='top'): r"""Calculate the convective condensation level (CCL). - This function is implemented by simplifying the mixing ratio and - the saturation vapor pressure equation to - - .. math:: A(P) = ln(\frac{rP}{6.112(r+\epsilon)}) = \frac{17.67T}{T+243.5} - - where :math:`P` is pressure, and :math:`T` is temperature in degrees C. - - This gives us a direct relationship of how temperature changes with pressure - in the atmosphere with a constant mixing ratio. + This function is implemented directly based on the definition of the CCL, as in [USAF1990]_, and finding + where the ambient temperature profile intersects the line of constant mixing ratio starting at the surface, + using the surface dewpoint. Parameters ---------- From 439bb0e7d06b9e51d9fad2fff7f8fca411860176 Mon Sep 17 00:00:00 2001 From: Richard Zhuang <38182337+Z-Richard@users.noreply.github.com> Date: Sun, 18 Sep 2022 20:43:28 -0400 Subject: [PATCH 07/15] Make requested changes for rt_profile Co-authored-by: Ryan May --- src/metpy/calc/thermo.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/metpy/calc/thermo.py b/src/metpy/calc/thermo.py index 7f4f5ca6c19..43680813f43 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -518,10 +518,8 @@ def ccl(pressure, temperature, dewpoint, height=None, mixed_layer_depth=None, wh r_start = mixed_layer(pressure, r_profile, height=height, depth=mixed_layer_depth)[0] - a_p = np.log(r_start * pressure / ( - mpconsts.default.sat_pressure_0c * (mpconsts.nounit.epsilon + r_start))) # rt_profile is the temperature-pressure profile with a fixed mixing ratio - rt_profile = units.Quantity((243.5 * a_p / (17.67 - a_p)).magnitude, 'degC') + rt_profile = globals()['dewpoint'](vapor_pressure(pressure, r_start)) x, y = find_intersections(pressure, rt_profile, temperature, direction='increasing', log_x=True) From e0a60f94c5355aa3227c777cd5017f6850c3909d Mon Sep 17 00:00:00 2001 From: Haoyu Zhuang Date: Sun, 18 Sep 2022 21:29:41 -0400 Subject: [PATCH 08/15] Fix linting issues for the requested changes --- src/metpy/calc/thermo.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/metpy/calc/thermo.py b/src/metpy/calc/thermo.py index 43680813f43..66292feb1e2 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -448,8 +448,9 @@ def _lcl_iter(p, p0, w, t): def ccl(pressure, temperature, dewpoint, height=None, mixed_layer_depth=None, which='top'): r"""Calculate the convective condensation level (CCL). - This function is implemented directly based on the definition of the CCL, as in [USAF1990]_, and finding - where the ambient temperature profile intersects the line of constant mixing ratio starting at the surface, + This function is implemented directly based on the definition of the CCL, + as in [USAF1990]_, and finding where the ambient temperature profile + intersects the line of constant mixing ratio starting at the surface, using the surface dewpoint. Parameters From f72b10ce742fc96c3fa8be08fe02a8140303f9b3 Mon Sep 17 00:00:00 2001 From: Haoyu Zhuang Date: Sun, 18 Sep 2022 22:27:11 -0400 Subject: [PATCH 09/15] Fix units bug --- src/metpy/calc/thermo.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/metpy/calc/thermo.py b/src/metpy/calc/thermo.py index 66292feb1e2..7c2f1aeda51 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -502,9 +502,6 @@ def ccl(pressure, temperature, dewpoint, height=None, mixed_layer_depth=None, wh """ pressure, temperature, dewpoint = _remove_nans(pressure, temperature, dewpoint) - dewpoint, temperature = dewpoint.to(units.degC), temperature.to(units.degC) - pressure = pressure.to(units.hPa) - # If the mixed layer is not defined, take the starting dewpoint to be the # first element of the dewpoint array and calculate the corresponding mixing ratio. if mixed_layer_depth is None: From 5f14d9cd8ef085e4c63b480a60d0d786b54fde11 Mon Sep 17 00:00:00 2001 From: Haoyu Zhuang Date: Thu, 29 Sep 2022 18:16:17 -0400 Subject: [PATCH 10/15] Fix CCL units test --- tests/calc/test_thermo.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/calc/test_thermo.py b/tests/calc/test_thermo.py index c900d53f0c1..4112506f60b 100644 --- a/tests/calc/test_thermo.py +++ b/tests/calc/test_thermo.py @@ -450,6 +450,10 @@ def test_ccl_unit(): assert_almost_equal(ccl_t, (15.429946 + 273.15) * units.kelvin, 3) assert_almost_equal(t_c, (38.616596 + 273.15) * units.kelvin, 3) + assert ccl_p.units == pressure.units + assert ccl_t.units == temperature.units + assert t_c.units == temperature.units + def test_multiple_ccl(): """Tests the case where there are multiple CCLs. Data: BUF, May 18 2022 12Z.""" From fa022ddb632337ad1ba4df09e22cadeecbd93752 Mon Sep 17 00:00:00 2001 From: Haoyu Zhuang Date: Thu, 29 Sep 2022 19:06:00 -0400 Subject: [PATCH 11/15] Add CCL docstring example --- src/metpy/calc/thermo.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/metpy/calc/thermo.py b/src/metpy/calc/thermo.py index 7c2f1aeda51..8c5a3471a2c 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -499,6 +499,16 @@ def ccl(pressure, temperature, dewpoint, height=None, mixed_layer_depth=None, wh Since this function returns scalar values when given a profile, this will return Pint Quantities even when given xarray DataArray profiles. + Examples + -------- + >>> import metpy.calc as mpcalc + >>> from metpy.units import units + >>> pressure = [993, 957, 925, 886, 850, 813, 798, 732, 716, 700] * units.mbar + >>> temperature = [34.6, 31.1, 27.8, 24.3, 21.4, 19.6, 18.7, 13, 13.5, 13] * units.degC + >>> dewpoint = [19.6, 18.7, 17.8, 16.3, 12.4, -0.4, -3.8, -6, -13.2, -11] * units.degC + >>> ccl_p, ccl_t, t_c = mpcalc.ccl(pressure, temperature, dewpoint) + >>> ccl_p, t_c + (, ) """ pressure, temperature, dewpoint = _remove_nans(pressure, temperature, dewpoint) From 4e68a5147be40c3abb2c62f0509e4e4fe73786fd Mon Sep 17 00:00:00 2001 From: Haoyu Zhuang Date: Thu, 29 Sep 2022 20:45:21 -0400 Subject: [PATCH 12/15] Fix CCL docstring to signify the usage of mixed layer --- src/metpy/calc/thermo.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/metpy/calc/thermo.py b/src/metpy/calc/thermo.py index 8c5a3471a2c..9c60667b127 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -449,9 +449,9 @@ def ccl(pressure, temperature, dewpoint, height=None, mixed_layer_depth=None, wh r"""Calculate the convective condensation level (CCL). This function is implemented directly based on the definition of the CCL, - as in [USAF1990]_, and finding where the ambient temperature profile - intersects the line of constant mixing ratio starting at the surface, - using the surface dewpoint. + as in [USAF1990]_, and finding where the ambient temperature profile intersects + the line of constant mixing ratio starting at the surface, using the surface dewpoint + or the average dewpoint of a shallow layer near the surface. Parameters ---------- From 731ad31f4dc03209fc22fd12b38a40a4663bb3b1 Mon Sep 17 00:00:00 2001 From: Richard Zhuang <38182337+Z-Richard@users.noreply.github.com> Date: Thu, 29 Sep 2022 21:05:07 -0400 Subject: [PATCH 13/15] Update docstring to help discovery Co-authored-by: Ryan May --- 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 9c60667b127..714622db59a 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -446,7 +446,7 @@ def _lcl_iter(p, p0, w, t): @preprocess_and_wrap() @check_units('[pressure]', '[temperature]', '[temperature]') def ccl(pressure, temperature, dewpoint, height=None, mixed_layer_depth=None, which='top'): - r"""Calculate the convective condensation level (CCL). + r"""Calculate the convective condensation level (CCL) and convective temperature. This function is implemented directly based on the definition of the CCL, as in [USAF1990]_, and finding where the ambient temperature profile intersects From c45f58772d18bd6490ad52ab851bce881d780465 Mon Sep 17 00:00:00 2001 From: Richard Zhuang <38182337+Z-Richard@users.noreply.github.com> Date: Fri, 30 Sep 2022 07:56:28 -0400 Subject: [PATCH 14/15] Fix convective temperature with dry_lapse Co-authored-by: Ryan May --- 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 714622db59a..6a269f05422 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -542,7 +542,7 @@ def ccl(pressure, temperature, dewpoint, height=None, mixed_layer_depth=None, wh '"top", "bottom", and "all".') x, y = x.to(pressure.units), y.to(temperature.units) - return x, y, potential_temperature(x, y).to(temperature.units) + return x, y, dry_lapse(pressure[0], y, x).to(temperature.units) @exporter.export From 79805c54c5d9cbf3dc88e4da14eb289f9f93504b Mon Sep 17 00:00:00 2001 From: Haoyu Zhuang Date: Fri, 30 Sep 2022 08:18:26 -0400 Subject: [PATCH 15/15] Fix convective temperature tests --- src/metpy/calc/thermo.py | 2 +- tests/calc/test_thermo.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/metpy/calc/thermo.py b/src/metpy/calc/thermo.py index 6a269f05422..a4c68c358a6 100644 --- a/src/metpy/calc/thermo.py +++ b/src/metpy/calc/thermo.py @@ -508,7 +508,7 @@ def ccl(pressure, temperature, dewpoint, height=None, mixed_layer_depth=None, wh >>> dewpoint = [19.6, 18.7, 17.8, 16.3, 12.4, -0.4, -3.8, -6, -13.2, -11] * units.degC >>> ccl_p, ccl_t, t_c = mpcalc.ccl(pressure, temperature, dewpoint) >>> ccl_p, t_c - (, ) + (, ) """ pressure, temperature, dewpoint = _remove_nans(pressure, temperature, dewpoint) diff --git a/tests/calc/test_thermo.py b/tests/calc/test_thermo.py index 4112506f60b..99d90ee940c 100644 --- a/tests/calc/test_thermo.py +++ b/tests/calc/test_thermo.py @@ -413,7 +413,7 @@ def test_ccl_basic(): ccl_p, ccl_t, t_c = ccl(pressure, temperature, dewpoint) assert_almost_equal(ccl_p, 763.006048 * units.mbar, 5) assert_almost_equal(ccl_t, 15.429946 * units.degC, 5) - assert_almost_equal(t_c, 38.616596 * units.degC, 5) + assert_almost_equal(t_c, 37.991498 * units.degC, 5) def test_ccl_nans(): @@ -430,7 +430,7 @@ def test_ccl_nans(): ccl_p, ccl_t, t_c = ccl(pressure, temperature, dewpoint) assert_almost_equal(ccl_p, 763.006048 * units.mbar, 5) assert_almost_equal(ccl_t, 15.429946 * units.degC, 5) - assert_almost_equal(t_c, 38.616596 * units.degC, 5) + assert_almost_equal(t_c, 37.991498 * units.degC, 5) def test_ccl_unit(): @@ -448,7 +448,7 @@ def test_ccl_unit(): ccl_p, ccl_t, t_c = ccl(pressure, temperature, dewpoint) assert_almost_equal(ccl_p, (763.006048 * 100) * units.Pa, 3) assert_almost_equal(ccl_t, (15.429946 + 273.15) * units.kelvin, 3) - assert_almost_equal(t_c, (38.616596 + 273.15) * units.kelvin, 3) + assert_almost_equal(t_c, (37.991498 + 273.15) * units.kelvin, 3) assert ccl_p.units == pressure.units assert ccl_t.units == temperature.units @@ -478,17 +478,17 @@ def test_multiple_ccl(): ccl_p, ccl_t, t_c = ccl(pressure, temperature, dewpoint) assert_almost_equal(ccl_p, 680.191653 * units.mbar, 5) assert_almost_equal(ccl_t, -0.204408 * units.degC, 5) - assert_almost_equal(t_c, 31.566319 * units.degC, 5) + assert_almost_equal(t_c, 30.8678258 * units.degC, 5) ccl_p, ccl_t, t_c = ccl(pressure, temperature, dewpoint, which='bottom') assert_almost_equal(ccl_p, 886.835325 * units.mbar, 5) assert_almost_equal(ccl_t, 3.500840 * units.degC, 5) - assert_almost_equal(t_c, 13.158340 * units.degC, 5) + assert_almost_equal(t_c, 12.5020423 * units.degC, 5) ccl_p, ccl_t, t_c = ccl(pressure, temperature, dewpoint, which='all') assert_array_almost_equal(ccl_p, np.array([886.835325, 680.191653]) * units.mbar, 5) assert_array_almost_equal(ccl_t, np.array([3.500840, -0.204408]) * units.degC, 5) - assert_array_almost_equal(t_c, np.array([13.158340, 31.566319]) * units.degC, 5) + assert_array_almost_equal(t_c, np.array([12.5020423, 30.8678258]) * units.degC, 5) def test_ccl_with_ml(): @@ -519,7 +519,7 @@ def test_ccl_with_ml(): assert_array_almost_equal(ccl_t, np.array( [0.840118, -0.280299, -1.118757, -2.875716]) * units.degC, 5) assert_array_almost_equal(t_c, np.array( - [13.804624, 19.332071, 23.576331, 32.782670]) * units.degC, 5) + [13.146845, 18.661621, 22.896152, 32.081388]) * units.degC, 5) def test_lfc_basic():