diff --git a/cset-workflow/includes/deterministic_plot_inflow_properties.cylc b/cset-workflow/includes/deterministic_plot_inflow_properties.cylc new file mode 100644 index 000000000..e3c8d007e --- /dev/null +++ b/cset-workflow/includes/deterministic_plot_inflow_properties.cylc @@ -0,0 +1,12 @@ +{% if DETERMINISTIC_PLOT_INFLOW_PROPERTIES %} +[runtime] + [[parallel_inflow_layer_properties]] + inherit = PARALLEL + [[[environment]]] + CSET_RECIPE_NAME = "inflow_layer_properties_plot.yaml" + + [[collate_inflow_layer_properties]] + inherit = COLLATE + [[[environment]]] + CSET_RECIPE_NAME = "inflow_layer_properties_plot.yaml" +{% endif %} diff --git a/cset-workflow/meta/rose-meta.conf b/cset-workflow/meta/rose-meta.conf index 0ab747763..85f252f66 100644 --- a/cset-workflow/meta/rose-meta.conf +++ b/cset-workflow/meta/rose-meta.conf @@ -523,3 +523,11 @@ help=Recommend looking at the input data to get these values. Uses the grid's na type=real sort-key=subsection2 compulsory=true + +[template variables=DETERMINISTIC_PLOT_INFLOW_PROPERTIES] +ns=Diagnostics +description=Extracts data required for, and calculates the inflow properties diagnostic, plotting on a map. + Required STASH m01s20i119, m01s00i025, m01s00i033. +help=See includes/deterministic_plot_inflow_properties.cylc +type=python_boolean +compulsory=true diff --git a/src/CSET/operators/convection.py b/src/CSET/operators/convection.py index 8a4ffdec4..47b6e4b31 100644 --- a/src/CSET/operators/convection.py +++ b/src/CSET/operators/convection.py @@ -20,8 +20,11 @@ """ import copy +import logging import warnings +import iris +import iris.cube import numpy as np @@ -32,16 +35,14 @@ def cape_ratio(SBCAPE, MUCAPE, MUCIN, MUCIN_thresh=-75.0): ---------- SBCAPE: Cube Surface-based convective available potential energy as calculated by the - model. - Stash: m01s20i114 + model. If using the UM please use STASH ``m01s20i114`` MUCAPE: Cube Most-unstable convective available potential energy as calculated by the - model. - Stash: m01s20i112 + model. If using the UM please use STASH ``m01s20i112`` MUCIN: Cube Most-unstable convective inhibition associated with the most-unstable - ascent as calculated by the model. - Stash: m01s20i113 + ascent as calculated by the model. If using the UM please use STASH + ``m01s20i113`` MUCIN_thresh: float, optional, default is -75. J/kg. Threshold to filter the MUCAPE by values are realistically realisable. @@ -51,13 +52,14 @@ def cape_ratio(SBCAPE, MUCAPE, MUCIN, MUCIN_thresh=-75.0): Notes ----- - This diagnostic is based on Clark et al. (2012) [1]_. It is based around the idea - that for elevated convection the convective instability is not based at the - surface. This utilises two flavours of CAPE: the surface-based CAPE (SBCAPE) - and the most-unstable CAPE (MUCAPE). The MUCAPE is filtered by the MUCIN - associated with that parcel's ascent to ensure that any CAPE can at least - theoretically be released. The default value is set at -75 J/kg but it can - be changes depending on location and users requirements. + This diagnostic is based on Clark et al. (2012) [Clarketal2012]_. It is + based around the idea that for elevated convection the convective + instability is not based at the surface. This utilises two flavours of CAPE: + the surface-based CAPE (SBCAPE) and the most-unstable CAPE (MUCAPE). The + MUCAPE is filtered by the MUCIN associated with that parcel's ascent to + ensure that any CAPE can at least theoretically be released. The default + value is set at -75 J/kg but it can be changes depending on location and + users requirements. .. math:: 1 - (\frac{SBCAPE}{MUCAPE}) @@ -73,29 +75,29 @@ def cape_ratio(SBCAPE, MUCAPE, MUCIN, MUCIN_thresh=-75.0): surface-based convection is more likely. Further details about this diagnostic for elevated convection identification - can be found in Flack et al. (2023) [2]_. + can be found in Flack et al. (2023) [FlackCAPE2023]_. Expected applicability ranges: Convective-scale models will be noisier than parametrized models as they are more responsive to the convection, and thus - it may be more sensible to view as a larger spatial average rather than - on the native resolution. + it may be more sensible to view as a larger spatial average rather than on + the native resolution. Interpretation notes: UM stash for CAPE and CIN are calculated at the end of - the timestep. Therefore this diagnostic is applicable after precipitation has - occurred, not before as is the usual interpretation of CAPE related diagnostics. + the timestep. Therefore this diagnostic is applicable after precipitation + has occurred, not before as is the usual interpretation of CAPE related + diagnostics. References ---------- - .. [1] Clark, A. J., Kain J. S., Marsh P. T., Correia J., Xue M., and Kong - F., (2012) "Forecasting tornado pathlengths using a three-dimensional - object identification algorithm applied to convection-allowing - forecasts." Weather and Forecasting, vol. 27, 1090–1113, doi: - 10.1175/WAF-D-11-00147.1 - .. [2] Flack, D.L.A., Lehnert, M., Lean, H.W., and Willington, S. (2023) - "Characteristics of Diagnostics for Identifying Elevated + .. [Clarketal2012] Clark, A. J., Kain J. S., Marsh P. T., Correia J., Xue + M., and Kong F., (2012) "Forecasting tornado pathlengths using a + three-dimensional object identification algorithm applied to + convection-allowing forecasts." Weather and Forecasting, vol. 27, + 1090–1113, doi: 10.1175/WAF-D-11-00147.1 + .. [FlackCAPE2023] Flack, D.L.A., Lehnert, M., Lean, H.W., and Willington, + S. (2023) "Characteristics of Diagnostics for Identifying Elevated Convection over the British Isles in a Convection-Allowing Model." - Weather and Forecasting, vol. 30, 1079-1094, doi: - 10.1175/WAF-D-22-0219.1 + Weather and Forecasting, vol. 30, 1079-1094, doi: 10.1175/WAF-D-22-0219.1 Examples -------- @@ -116,15 +118,18 @@ def cape_ratio(SBCAPE, MUCAPE, MUCIN, MUCIN_thresh=-75.0): >>> plt.show() """ # Load in the data into the new arrays. - SBCAPE_data = copy.deepcopy(SBCAPE.data) MUCAPE_data = copy.deepcopy(MUCAPE.data) - # Filter MUCAPE by MUCIN to all for possible (realistic) MUCAPE. - MUCAPE_data[MUCIN.data <= MUCIN_thresh] = 0.0 - # Now calculate the main diagnostic + if isinstance(MUCAPE_data, np.ma.MaskedArray): + MUCAPE_data = MUCAPE_data.filled(np.nan) + # Remove all MUCAPE below MUCIN threshold. + MUCAPE_data[MUCIN.data <= MUCIN_thresh] = np.nan with warnings.catch_warnings(): # Ignore possible divide by zero warnings, as they are replaced by NaNs. - warnings.simplefilter("ignore", RuntimeWarning) - EC_Flagb = 1 - (SBCAPE_data / MUCAPE_data) + warnings.filterwarnings("ignore", category=RuntimeWarning) + # Now calculate the main diagnostic. + EC_Flagb = 1 - (SBCAPE.data / MUCAPE_data) + if isinstance(EC_Flagb, np.ma.MaskedArray): + EC_Flagb = EC_Flagb.filled(np.nan) # Filter to reduce NaN values and -inf values for plotting ease. # There are multiple types of NaN values so need to convert them all to same type. EC_Flagb[np.isnan(EC_Flagb)] = np.nan @@ -136,3 +141,112 @@ def cape_ratio(SBCAPE, MUCAPE, MUCIN, MUCIN_thresh=-75.0): cape_ratio_cube.var_name = "cape_ratio" cape_ratio_cube.attributes.pop("STASH", None) return cape_ratio_cube + + +def inflow_layer_properties(EIB, BLheight, Orography): + r"""Filter to create a binary mask identifying elevated convection. + + Parameters + ---------- + EIB: Cube + Effective inflow layer base (precalculated or as identified by the + model). If using the UM please use STASH ``m01s20i119``. + BLheight: Cube + Boundary layer height (precalculated or as identified by the model). If + using the UM please use STASH ``m01s00i025``. + Orography: Cube + Model or actual orography, expected to be 2 dimensional. If 3 or 4 + dimensional cube given converts to 2 dimensions assuming static + orography field in ensemble realization and time. If using the UM please + use STASH ``m01s00i033``. + + Returns + ------- + Cube + + Notes + ----- + This diagnostic is based on the concept of an effective inflow layer. This + concept was first introduced by Thompson et al. (2007) [Thompsonetal2007]_. + The inflow layer defined the region of air that is most likely to be + ingested into the convective event. It is defined by thresholding the CAPE + and CIN values: CAPE > 100 J/kg and \|CIN\| < 250 J/kg. + + To turn this into a diagnostic for elevated convection the inflow layer base + is filtered against the boundary layer height. The model orography is added + to the boundary layer height to ensure reference height consistency as the + BL height is defined above ground level and the inflow layer base is defined + above sea level in the model output. + + .. math:: EIB > BLheight + Orography + + This is a binary diagnostic. It has a value of 0 to imply the environment is + suitable for surface-based convection. It has a value of 1 to indicate the + environment is suitable to produce elevated convection. + + Further details about this diagnostic for elevated convection identification + can be found in Flack et al. (2023) [Flackinf2023]_. + + Expected applicability ranges: Convective-scale models will be noisier than + parametrized models as they are more responsive to the convection, and thus + it may be more sensible to view as a larger spatial average rather than at + native resolution. + + Interpretation notes: The effective inflow layer base diagnostic from UM + STASH is dependent upon the UM CAPE and CIN diagnostics. These diagnostics + are calculated at the end of the timestep. Therefore this diagnostic is + applicable after precipitation has occurred, not before as is the usual + interpretation of CAPE related diagnostics. + + You might encounter warnings with the following text ``Orography assumed not + to vary with time or ensemble member.`` or ``Orography assumed not to vary + with time and ensemble member.`` these warnings are expected when the + orography files are not 2-dimensional, and do not cause any problems unless + ordering is not as expected. + + References + ---------- + .. [Thompsonetal2007] Thompson, R. L. Mead, C. M., and Edwards, R., (2007) + "Effective Storm-Relative Helicity and Bulk Shear in Supercell + Thunderstorm Environments." Weather and Forecasting, vol. 22, 102-115, + doi: 10.1175/WAF969.1 + .. [Flackinf2023] Flack, D.L.A., Lehnert, M., Lean, H.W., and Willington, S. + (2023) "Characteristics of Diagnostics for Identifying Elevated + Convection over the British Isles in a Convection-Allowing Model." + Weather and Forecasting, vol. 30, 1079-1094, doi: 10.1175/WAF-D-22-0219.1 + + Examples + -------- + >>> Inflow_properties=convection.inflow_layer_properties(EIB,BLheight,Orography) + >>> iplt.pcolormesh(Inflow_properties[0,:,:],cmap=mpl.cm.Purples) + >>> plt.gca().coastlines('10m') + >>> plt.colorbar() + >>> plt.clim(0,1) + >>> plt.show() + + """ + # Setup new array for output of the diagnostic. + EC_Flagd = np.zeros(EIB.shape) + # Check dimensions for Orography cube and replace with 2D array if not 2D. + if Orography.ndim == 3: + try: + Orography = Orography.slices_over("realization").next() + except iris.exceptions.CoordinateNotFoundError: + Orography = Orography.slices_over("time").next() + logging.warning("Orography assumed not to vary with time or ensemble member") + elif Orography.ndim == 4: + Orography = Orography.slices_over(("time", "realization")).next() + logging.warning("Orography assumed not to vary with time or ensemble member. ") + # Masked arrays are not respected, so convert masked values into NaNs. + if isinstance(EIB.data, np.ma.MaskedArray): + EIB.data = EIB.data.filled(np.nan) + # Change points where Effective inflow layer base is larger than boundary + # layer height to 1 implying elevated convection. + EC_Flagd[EIB.data > (BLheight.data + Orography.data)] = 1.0 + # Take the coordinates from an existing cube and replace the data. + inflow_properties_cube = EIB.copy() + inflow_properties_cube.data = EC_Flagd + # Rename and remove STASH code. + inflow_properties_cube.var_name = "inflow_layer_properties" + inflow_properties_cube.attributes.pop("STASH", None) + return inflow_properties_cube diff --git a/src/CSET/recipes/inflow_layer_properties_plot.yaml b/src/CSET/recipes/inflow_layer_properties_plot.yaml new file mode 100644 index 000000000..dcd6b0efd --- /dev/null +++ b/src/CSET/recipes/inflow_layer_properties_plot.yaml @@ -0,0 +1,38 @@ +title: Inflow layer properties plot +description: | + Extracts data required for, and calculates the Inflow properties diagnostic, plotting on a map. + +parallel: + - operator: read.read_cubes + constraint: + operator: constraints.generate_time_constraint + time_start: $VALIDITY_TIME + + - operator: convection.inflow_layer_properties + EIB: + operator: filters.filter_cubes + constraint: + operator: constraints.generate_stash_constraint + stash: m01s20i119 + BLheight: + operator: filters.filter_cubes + constraint: + operator: constraints.generate_stash_constraint + stash: m01s00i025 + Orography: + operator: filters.filter_cubes + constraint: + operator: constraints.generate_stash_constraint + stash: m01s00i033 + + - operator: write.write_cube_to_nc + filename: intermediate/inflow_layer + +collate: + - operator: read.read_cube + filename: intermediate/*.nc + + - operator: write.write_cube_to_nc + overwrite: True + + - operator: plot.postage_stamp_contour_plot diff --git a/tests/operators/test_convection.py b/tests/operators/test_convection.py index 07f87e020..37c7ae174 100644 --- a/tests/operators/test_convection.py +++ b/tests/operators/test_convection.py @@ -15,22 +15,32 @@ """Tests for convection diagnostics.""" import iris +import numpy as np import CSET.operators.convection as convection def test_cape_ratio(): """Compare with precalculated ratio.""" - precalculated = iris.load_cube("tests/test_data/convection/ECFlagB.nc") - precalculated_2 = iris.load_cube("tests/test_data/convection/ECFlagB_2.nc") SBCAPE = iris.load_cube("tests/test_data/convection/SBCAPE.nc") MUCAPE = iris.load_cube("tests/test_data/convection/MUCAPE.nc") MUCIN = iris.load_cube("tests/test_data/convection/MUCIN.nc") - assert ( - convection.cape_ratio(SBCAPE, MUCAPE, MUCIN).data.all() - == precalculated.data.all() - ) - assert ( - convection.cape_ratio(SBCAPE, MUCAPE, MUCIN, MUCIN_thresh=-1.5).data.all() - == precalculated_2.data.all() + cape_75 = convection.cape_ratio(SBCAPE, MUCAPE, MUCIN) + precalculated_75 = iris.load_cube("tests/test_data/convection/ECFlagB.nc") + assert np.allclose(cape_75.data, precalculated_75.data, atol=1e-5, equal_nan=True) + + cape_1p5 = convection.cape_ratio(SBCAPE, MUCAPE, MUCIN, MUCIN_thresh=-1.5) + precalculated_1p5 = iris.load_cube("tests/test_data/convection/ECFlagB_2.nc") + assert np.allclose(cape_1p5.data, precalculated_1p5.data, atol=1e-5, equal_nan=True) + + +def test_inflow_layer_properties(): + """Compare with precalculated properties.""" + EIB = iris.load_cube("tests/test_data/convection/EIB.nc") + BLheight = iris.load_cube("tests/test_data/convection/BLheight.nc") + Orography = iris.load_cube("tests/test_data/convection/Orography.nc") + inflow_layer_properties = convection.inflow_layer_properties( + EIB, BLheight, Orography ) + precalculated = iris.load_cube("tests/test_data/convection/ECFlagD.nc") + assert np.allclose(inflow_layer_properties.data, precalculated.data) diff --git a/tests/test_data/convection/BLheight.nc b/tests/test_data/convection/BLheight.nc new file mode 100644 index 000000000..09a9fcb70 Binary files /dev/null and b/tests/test_data/convection/BLheight.nc differ diff --git a/tests/test_data/convection/ECFlagB_2.nc b/tests/test_data/convection/ECFlagB_2.nc index 2f4f4e2ff..989b7bd49 100644 Binary files a/tests/test_data/convection/ECFlagB_2.nc and b/tests/test_data/convection/ECFlagB_2.nc differ diff --git a/tests/test_data/convection/ECFlagD.nc b/tests/test_data/convection/ECFlagD.nc new file mode 100644 index 000000000..f1dcd4a53 Binary files /dev/null and b/tests/test_data/convection/ECFlagD.nc differ diff --git a/tests/test_data/convection/EIB.nc b/tests/test_data/convection/EIB.nc new file mode 100644 index 000000000..92196370c Binary files /dev/null and b/tests/test_data/convection/EIB.nc differ diff --git a/tests/test_data/convection/Orography.nc b/tests/test_data/convection/Orography.nc new file mode 100644 index 000000000..36b94db37 Binary files /dev/null and b/tests/test_data/convection/Orography.nc differ