diff --git a/README.md b/README.md index 2f46877e0..d84752778 100644 --- a/README.md +++ b/README.md @@ -51,3 +51,5 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +GitHub Copilot was used in the development of this software. diff --git a/cset-workflow/extra-meta/colorbar_dict_alphabetical.json b/cset-workflow/extra-meta/colorbar_dict_alphabetical.json index 480bf2551..99b2986f6 100644 --- a/cset-workflow/extra-meta/colorbar_dict_alphabetical.json +++ b/cset-workflow/extra-meta/colorbar_dict_alphabetical.json @@ -166,5 +166,10 @@ "wind_speed_at_10m": { "min": 0.0, "max": 30.0 + }, + "air_potential_temperature": { + "min": 230, + "max": 340, + "cmap": "jet" } } diff --git a/cset-workflow/includes/lfric_deterministic_domain_mean_vertical_profile_series.cylc b/cset-workflow/includes/lfric_deterministic_domain_mean_vertical_profile_series.cylc index 37b4c5b6e..28766f6d6 100644 --- a/cset-workflow/includes/lfric_deterministic_domain_mean_vertical_profile_series.cylc +++ b/cset-workflow/includes/lfric_deterministic_domain_mean_vertical_profile_series.cylc @@ -1,16 +1,16 @@ {% if LFRIC_DOMAIN_MEAN_VERTICAL_PROFILE_SERIES %} [runtime] -{% for model_field in LFRIC_PRESSURE_LEVEL_MODEL_FIELDS %} +{% for model_field in LFRIC_MODEL_LEVEL_MODEL_FIELDS %} [[pre_process_lfric_domain_mean_vertical_profile_series_{{model_field}}]] inherit = PARALLEL [[[environment]]] CSET_RECIPE_NAME = "lfric_generic_domain_mean_vertical_profile_series.yaml" - CSET_ADDOPTS = "--VARNAME='{{model_field}}' --PLEVEL='{{LFRIC_PRESSURE_LEVELS}}'" + CSET_ADDOPTS = "--VARNAME='{{model_field}}' --MLEVEL='{{LFRIC_MODEL_LEVELS}}'" [[collate_lfric_domain_mean_vertical_profile_series_{{model_field}}]] inherit = COLLATE [[[environment]]] CSET_RECIPE_NAME = "lfric_generic_domain_mean_vertical_profile_series.yaml" - CSET_ADDOPTS = "--VARNAME='{{model_field}}' --PLEVEL='{{LFRIC_PRESSURE_LEVELS}}}'" + CSET_ADDOPTS = "--VARNAME='{{model_field}}' --MLEVEL='{{LFRIC_MODEL_LEVELS}}'" {% endfor %} {% endif %} diff --git a/cset-workflow/includes/lfric_plot_spatial_mlevel_model_field.cylc b/cset-workflow/includes/lfric_plot_spatial_mlevel_model_field.cylc new file mode 100644 index 000000000..432e73f83 --- /dev/null +++ b/cset-workflow/includes/lfric_plot_spatial_mlevel_model_field.cylc @@ -0,0 +1,18 @@ +{% if LFRIC_PLOT_SPATIAL_MODEL_LEVEL_MODEL_FIELD %} +[runtime] +{% for model_field in LFRIC_MODEL_LEVEL_MODEL_FIELDS %} +{% for mlevel in LFRIC_MODEL_LEVELS %} + [[process_lfric_generic_mlevel_spatial_plot_sequence_{{model_field}}_{{mlevel}}]] + inherit = PARALLEL + [[[environment]]] + CSET_RECIPE_NAME = "lfric_generic_mlevel_spatial_plot_sequence.yaml" + CSET_ADDOPTS = "--VARNAME='{{model_field}}' --MLEVEL='{{mlevel}}'" + + [[collate_lfric_generic_mlevel_spatial_plot_sequence_{{model_field}}_{{mlevel}}]] + inherit = COLLATE + [[[environment]]] + CSET_RECIPE_NAME = "lfric_generic_mlevel_spatial_plot_sequence.yaml" + CSET_ADDOPTS = "--VARNAME='{{model_field}}' --MLEVEL='{{mlevel}}'" +{% endfor %} +{% endfor %} +{% endif %} diff --git a/cset-workflow/includes/lfric_plot_spatial_plevel_model_field.cylc b/cset-workflow/includes/lfric_plot_spatial_plevel_model_field.cylc new file mode 100644 index 000000000..f55518213 --- /dev/null +++ b/cset-workflow/includes/lfric_plot_spatial_plevel_model_field.cylc @@ -0,0 +1,18 @@ +{% if LFRIC_PLOT_SPATIAL_PRESSURE_LEVEL_MODEL_FIELD %} +[runtime] +{% for model_field in LFRIC_PRESSURE_LEVEL_MODEL_FIELDS %} +{% for plevel in LFRIC_PRESSURE_LEVELS %} + [[process_lfric_generic_plevel_spatial_plot_sequence_{{model_field}}_{{plevel}}]] + inherit = PARALLEL + [[[environment]]] + CSET_RECIPE_NAME = "lfric_generic_plevel_spatial_plot_sequence.yaml" + CSET_ADDOPTS = "--VARNAME='{{model_field}}' --PLEVEL='{{plevel}}'" + + [[collate_lfric_generic_plevel_spatial_plot_sequence_{{model_field}}_{{plevel}}]] + inherit = COLLATE + [[[environment]]] + CSET_RECIPE_NAME = "lfric_generic_plevel_spatial_plot_sequence.yaml" + CSET_ADDOPTS = "--VARNAME='{{model_field}}' --PLEVEL='{{plevel}}'" +{% endfor %} +{% endfor %} +{% endif %} diff --git a/cset-workflow/meta/rose-meta.conf b/cset-workflow/meta/rose-meta.conf index 6a627c80a..6caf42ec5 100644 --- a/cset-workflow/meta/rose-meta.conf +++ b/cset-workflow/meta/rose-meta.conf @@ -472,13 +472,21 @@ trigger=template variables=LFRIC_PRESSURE_LEVEL_MODEL_FIELDS: True; compulsory=true sort-key=pressure3 +[template variables=LFRIC_PLOT_SPATIAL_MODEL_LEVEL_MODEL_FIELD] +ns=Diagnostics +description=Create plots for the specified model level fields. +help=See includes/lfric_plot_spatial_mlevel_model_field.cylc +type=python_boolean +compulsory=true +sort-key=model1 + [template variables=LFRIC_DOMAIN_MEAN_VERTICAL_PROFILE_SERIES] ns=Diagnostics description=Domain averaged vertical profile for each validity time. -help=See includes/lfric_deterministic_domain_mean_vertical_profile_series.cylc +help=See includes/lfric_deterministic_domain_mean_vertical_profile_series.cylc. Currently vertical profiles for LFRic only work with LFRIC_MODEL_LEVELS as we currently don't have LFRIC data with a pressure or height coordinate. type=python_boolean compulsory=true -sort-key=pressure3 +sort-key=model1 [template variables=LFRIC_PRESSURE_LEVEL_MODEL_FIELDS] ns=Diagnostics @@ -488,6 +496,14 @@ type=python_list compulsory=true sort-key=pressure3 +[template variables=LFRIC_MODEL_LEVEL_MODEL_FIELDS] +ns=Diagnostics +description=List of standard names of model fields on model levels to plot. +help=Include a list of variable names in python list format["var1","var2"]. +type=python_list +compulsory=true +sort-key=model1 + [template variables=LFRIC_PRESSURE_LEVELS] ns=Diagnostics description=List of pressure levels to generate plots for. @@ -496,6 +512,14 @@ type=python_list compulsory=true sort-key=pressure3 +[template variables=LFRIC_MODEL_LEVELS] +ns=Diagnostics +description=List of model level numbers to generate plots for. +help=Include an integer or list of integers of model level numbers in python list format [1,2,3,4,5]. +type=python_list +compulsory=true +sort-key=model1 + [template variables=PLOT_SPATIAL_PRESSURE_LEVEL_MODEL_FIELD] ns=Diagnostics diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 770f4895e..8104cfd47 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -15,7 +15,13 @@ Unreleased .. Highlight any user facing changes. E.g: .. "* `@gh-user`_ did foo to bar in :pr:`9999`. This enables baz." -* `@Sylviabohnenstengel`_ documentation: add info on quick pytesting in :pr: `696` +* `@Sylviabohnenstengel`_ documentation: add info on quick pytesting in :pr:`696` +* `@Sylviabohnenstengel`_ add constraint operator for lfric full_levels and half_levels +* `@Sylviabohnenstengel`_ introduced lfric_model_level and lfric_model_level_field to rose meta +* `@Sylviabohnenstengel`_ expand plot operator add plotting lfric vertical profiles on model levels +* `@Sylviabohnenstengel`_ expand plot operator add plotting on model levels to spatial plot operator +* `@Sylviabohnenstengel`_ added new recipe for plotting vertical profiles on model levels for lfric. +* `@Sylviabohnenstengel`_ added new recipe for plotting spatial lfric data on model levels. 24.6.0 (2024-06-17) ------------------- diff --git a/src/CSET/operators/constraints.py b/src/CSET/operators/constraints.py index 285daddb8..8935cbf6d 100644 --- a/src/CSET/operators/constraints.py +++ b/src/CSET/operators/constraints.py @@ -14,8 +14,8 @@ """Operators to generate constraints to filter with.""" +from collections.abc import Iterable from datetime import datetime -from typing import Union import iris import iris.cube @@ -64,67 +64,45 @@ def generate_var_constraint(varname: str, **kwargs) -> iris.Constraint: return varname_constraint -def generate_model_level_constraint( - model_level_number: Union[int, str], **kwargs +def generate_level_constraint( + coordinate: str, levels: int | list[int], **kwargs ) -> iris.Constraint: - """Generate constraint for a particular model level number. + """Generate constraint for particular levels on the specified coordinate. - Operator that takes a CF compliant model_level_number string, and uses iris to - generate a constraint to be passed into the read operator to minimize the - CubeList the read operator loads and speed up loading. + Operator that generates a constraint to constrain to specific model or + pressure levels. If no levels are specified then any cube with the specified + coordinate is rejected. + + Typically ``coordinate`` will be ``"pressure"`` or ``"model_level_number"`` + for UM, or ``"full_levels"`` or ``"half_levels"`` for LFRic. Arguments --------- - model_level_number: str - CF compliant model level number. + coordinate: str + Level coordinate name about which to constraint. + levels: int | list[int] + CF compliant levels. Returns ------- - model_level_number_constraint: iris.Constraint + constraint: iris.Constraint """ - # Cast to int in case a string is given. - model_level_number = int(model_level_number) - model_level_number_constraint = iris.Constraint( - model_level_number=model_level_number - ) - return model_level_number_constraint - - -def generate_pressure_level_constraint( - pressure_levels: Union[int, list[int]], **kwargs -) -> iris.Constraint: - """Generate constraint for the specified pressure_levels. + # Ensure is iterable. + if not isinstance(levels, Iterable): + levels = [levels] - If no pressure levels are specified then any cube with a pressure coordinate - is rejected. + # When no levels specified reject cube with level coordinate. + if len(levels) == 0: - Arguments - --------- - pressure_levels: int|list - List of integer pressure levels in hPa either as single integer - for a single level or a list of multiple integers. + def no_levels(cube): + # Reject cubes for which coordinate exists. + return not bool(cube.coords(coordinate)) - Returns - ------- - pressure_constraint: iris.Constraint - """ - # If pressure_level is an integer it is converted into a list. - if isinstance(pressure_levels, int): - pressure_levels = [pressure_levels] - if len(pressure_levels) == 0: - # If none specified reject cubes with pressure level coordinate. - def no_pressure_coordinate(cube): - try: - cube.coord("pressure") - except iris.exceptions.CoordinateNotFoundError: - return True - return False - - pressure_constraint = iris.Constraint(cube_func=no_pressure_coordinate) - else: - pressure_constraint = iris.Constraint(pressure=pressure_levels) + return iris.Constraint(cube_func=no_levels) - return pressure_constraint + # Filter the coordinate to the desired levels. + # Dictionary unpacking is used to provide programmatic keyword arguments. + return iris.Constraint(**{coordinate: levels}) def generate_cell_methods_constraint(cell_methods: list, **kwargs) -> iris.Constraint: diff --git a/src/CSET/operators/plot.py b/src/CSET/operators/plot.py index 64a48a7c4..3f6b48fcc 100644 --- a/src/CSET/operators/plot.py +++ b/src/CSET/operators/plot.py @@ -334,6 +334,7 @@ def _plot_and_save_vertical_line_series( cube: iris.cube.Cube, coord: iris.coords.Coord, filename: str, + series_coordinate: str, title: str, vmin: float, vmax: float, @@ -349,6 +350,8 @@ def _plot_and_save_vertical_line_series( Coordinate to plot on y-axis. filename: str Filename of the plot to write. + series_coordinate: str + Coordinate to use as vertical axis. title: str Plot title. vmin: float @@ -360,27 +363,40 @@ def _plot_and_save_vertical_line_series( fig = plt.figure(figsize=(8, 8), facecolor="w", edgecolor="k") iplt.plot(cube, coord, "o-") ax = plt.gca() - ax.invert_yaxis() - ax.set_yscale("log") - - # Define y-ticks and labels for pressure log axis - y_tick_labels = [ - "1000", - "850", - "700", - "500", - "300", - "200", - "100", - "50", - "30", - "20", - "10", - ] - y_ticks = [1000, 850, 700, 500, 300, 200, 100, 50, 30, 20, 10] - - # Set y-axis limits and ticks - ax.set_ylim(1100, 100) + + # Special handling for pressure level data. + if series_coordinate == "pressure": + # Invert y-axis and set to log scale. + ax.invert_yaxis() + ax.set_yscale("log") + + # Define y-ticks and labels for pressure log axis. + y_tick_labels = [ + "1000", + "850", + "700", + "500", + "300", + "200", + "100", + "50", + "30", + "20", + "10", + ] + y_ticks = [1000, 850, 700, 500, 300, 200, 100, 50, 30, 20, 10] + + # Set y-axis limits and ticks. + ax.set_ylim(1100, 100) + + # test if series_coordinate is model level data. The um data uses model_level_number + # and lfric uses full_levels as coordinate. + elif series_coordinate in ("model_level_number", "full_levels", "half_levels"): + # Define y-ticks and labels for vertical axis. + y_ticks = cube.coord(series_coordinate).points + y_tick_labels = [str(int(i)) for i in y_ticks] + ax.set_ylim(min(y_ticks), max(y_ticks)) + ax.set_yticks(y_ticks) ax.set_yticklabels(y_tick_labels) @@ -566,7 +582,7 @@ def plot_line_series( def plot_vertical_line_series( cube: iris.cube.Cube, filename: str = None, - series_coordinate: str = "pressure", + series_coordinate: str = "model_level_number", sequence_coordinate: str = "time", # line_coordinate: str = "realization", **kwargs, @@ -586,7 +602,9 @@ def plot_vertical_line_series( Name of the plot to write, used as a prefix for plot sequences. Defaults to the recipe name. series_coordinate: str, optional - Coordinate to plot on the y-axis. Defaults to ``pressure``. + Coordinate to plot on the y-axis. Can be ``pressure`` or + ``model_level_number`` for UM, or ``full_levels`` or ``half_levels`` + for LFRic. Defaults to ``model_level_number``. This coordinate must exist in the cube. sequence_coordinate: str, optional Coordinate about which to make a plot sequence. Defaults to ``"time"``. @@ -650,6 +668,7 @@ def plot_vertical_line_series( cube_slice, coord, plot_filename, + series_coordinate, title=title, vmin=vmin, vmax=vmax, diff --git a/src/CSET/operators/read.py b/src/CSET/operators/read.py index 1201d3864..50fbbfce8 100644 --- a/src/CSET/operators/read.py +++ b/src/CSET/operators/read.py @@ -75,6 +75,7 @@ def read_cube( If the constraint doesn't produce a single cube. """ cubes = read_cubes(loadpath, constraint, filename_pattern) + # TODO: Fix coordinate name to enable full level and half level merging. # Check filtered cubes is a CubeList containing one cube. if len(cubes) == 1: return cubes[0] diff --git a/src/CSET/recipes/generic_domain_mean_vertical_profile_series.yaml b/src/CSET/recipes/generic_domain_mean_vertical_profile_series.yaml index 602a3e902..dc5c893a6 100644 --- a/src/CSET/recipes/generic_domain_mean_vertical_profile_series.yaml +++ b/src/CSET/recipes/generic_domain_mean_vertical_profile_series.yaml @@ -11,8 +11,9 @@ parallel: operator: constraints.generate_var_constraint varname: $VARNAME pressure_level_constraint: - operator: constraints.generate_pressure_level_constraint - pressure_levels: $PLEVEL + operator: constraints.generate_level_constraint + coordinate: "pressure" + levels: $PLEVEL validity_time_constraint: operator: constraints.generate_time_constraint time_start: $VALIDITY_TIME diff --git a/src/CSET/recipes/generic_plevel_spatial_plot_sequence.yaml b/src/CSET/recipes/generic_plevel_spatial_plot_sequence.yaml index 0e028fec0..347ab5bd7 100644 --- a/src/CSET/recipes/generic_plevel_spatial_plot_sequence.yaml +++ b/src/CSET/recipes/generic_plevel_spatial_plot_sequence.yaml @@ -14,8 +14,9 @@ parallel: operator: constraints.generate_time_constraint time_start: $VALIDITY_TIME pressure_level_constraint: - operator: constraints.generate_pressure_level_constraint - pressure_levels: $PLEVEL + operator: constraints.generate_level_constraint + coordinate: "pressure" + levels: $PLEVEL - operator: write.write_cube_to_nc diff --git a/src/CSET/recipes/generic_surface_domain_mean_time_series.yaml b/src/CSET/recipes/generic_surface_domain_mean_time_series.yaml index a9ed8a23c..3cf495a09 100644 --- a/src/CSET/recipes/generic_surface_domain_mean_time_series.yaml +++ b/src/CSET/recipes/generic_surface_domain_mean_time_series.yaml @@ -14,8 +14,9 @@ parallel: operator: constraints.generate_cell_methods_constraint cell_methods: [] pressure_level_constraint: - operator: constraints.generate_pressure_level_constraint - pressure_levels: [] + operator: constraints.generate_level_constraint + coordinate: "pressure" + levels: [] validity_time_constraint: operator: constraints.generate_time_constraint time_start: $VALIDITY_TIME diff --git a/src/CSET/recipes/generic_surface_spatial_plot_sequence.yaml b/src/CSET/recipes/generic_surface_spatial_plot_sequence.yaml index e123b37e0..0c9311852 100644 --- a/src/CSET/recipes/generic_surface_spatial_plot_sequence.yaml +++ b/src/CSET/recipes/generic_surface_spatial_plot_sequence.yaml @@ -13,8 +13,9 @@ parallel: operator: constraints.generate_cell_methods_constraint cell_methods: [] pressure_level_constraint: - operator: constraints.generate_pressure_level_constraint - pressure_levels: [] + operator: constraints.generate_level_constraint + coordinate: "pressure" + levels: [] validity_time_constraint: operator: constraints.generate_time_constraint time_start: $VALIDITY_TIME diff --git a/src/CSET/recipes/lfric_generic_domain_mean_vertical_profile_series.yaml b/src/CSET/recipes/lfric_generic_domain_mean_vertical_profile_series.yaml index a7b9a72cc..52da8be3d 100644 --- a/src/CSET/recipes/lfric_generic_domain_mean_vertical_profile_series.yaml +++ b/src/CSET/recipes/lfric_generic_domain_mean_vertical_profile_series.yaml @@ -1,6 +1,6 @@ category: Quick Look title: Domain mean $VARNAME vertical profile as series -description: Plots a time series of the vertical profile of domain mean $VARNAME using a log pressure coordinate. +description: Plots a time series of the vertical profile of domain mean $VARNAME using the model_level_number coordinate. # Pre-processing steps. parallel: @@ -10,9 +10,10 @@ parallel: varname_constraint: operator: constraints.generate_var_constraint varname: $VARNAME - pressure_level_constraint: - operator: constraints.generate_pressure_level_constraint - pressure_levels: $PLEVEL + model_level_constraint: + operator: constraints.generate_level_constraint + coordinate: "full_levels" + levels: $MLEVEL validity_time_constraint: operator: constraints.generate_time_constraint time_start: $VALIDITY_TIME @@ -23,7 +24,7 @@ parallel: # Save domain meaned variable to a file per validity time. - operator: write.write_cube_to_nc - filename: intermediate/pressure_level_domain_mean + filename: intermediate/model_level_domain_mean # Collation steps. # Reads in intermediate cube and plots it. @@ -31,9 +32,9 @@ collate: - operator: read.read_cube filename_pattern: intermediate/*.nc -# plot the vertical line series + # Plot the vertical line series - operator: plot.plot_vertical_line_series - series_coordinate: pressure + series_coordinate: full_levels sequence_coordinate: time # Make a single NetCDF with all the data inside it. diff --git a/src/CSET/recipes/lfric_generic_mlevel_spatial_plot_sequence.yaml b/src/CSET/recipes/lfric_generic_mlevel_spatial_plot_sequence.yaml new file mode 100644 index 000000000..501e53757 --- /dev/null +++ b/src/CSET/recipes/lfric_generic_mlevel_spatial_plot_sequence.yaml @@ -0,0 +1,31 @@ +category: Quick Look +title: $VARNAME $MLEVEL Level Spatial Plot +description: | + Extracts and plots the $VARNAME from a file at model level $MLEVEL. + +parallel: + - operator: read.read_cubes + constraint: + operator: constraints.combine_constraints + variable_constraint: + operator: constraints.generate_var_constraint + varname: $VARNAME + validity_time_constraint: + operator: constraints.generate_time_constraint + time_start: $VALIDITY_TIME + model_level_constraint: + operator: constraints.generate_level_constraint + coordinate: "model_level_number" + levels: $MLEVEL + - operator: write.write_cube_to_nc + filename: intermediate/model_level_field + +collate: + - operator: read.read_cube + filename_pattern: intermediate/*.nc + + - operator: plot.spatial_contour_plot + sequence_coordinate: time + + - operator: write.write_cube_to_nc + overwrite: True diff --git a/src/CSET/recipes/lfric_generic_plevel_spatial_plot_sequence.yaml b/src/CSET/recipes/lfric_generic_plevel_spatial_plot_sequence.yaml new file mode 100644 index 000000000..4cc7105cf --- /dev/null +++ b/src/CSET/recipes/lfric_generic_plevel_spatial_plot_sequence.yaml @@ -0,0 +1,32 @@ +category: Quick Look +title: $VARNAME $PLEVEL Level Spatial Plot +description: | + Extracts and plots the $VARNAME from a file at pressure level $PLEVEL. + +parallel: + - operator: read.read_cubes + constraint: + operator: constraints.combine_constraints + variable_constraint: + operator: constraints.generate_var_constraint + varname: $VARNAME + pressure_level_constraint: + operator: constraints.generate_level_constraint + coordinate: "pressure" + levels: $PLEVEL + validity_time_constraint: + operator: constraints.generate_time_constraint + time_start: $VALIDITY_TIME + + - operator: write.write_cube_to_nc + filename: intermediate/pressure_level_field + +collate: + - operator: read.read_cube + filename_pattern: intermediate/*.nc + + - operator: plot.spatial_contour_plot + sequence_coordinate: time + + - operator: write.write_cube_to_nc + overwrite: True diff --git a/src/CSET/recipes/lfric_generic_surface_domain_mean_time_series.yaml b/src/CSET/recipes/lfric_generic_surface_domain_mean_time_series.yaml index f7e309102..9f144e65a 100644 --- a/src/CSET/recipes/lfric_generic_surface_domain_mean_time_series.yaml +++ b/src/CSET/recipes/lfric_generic_surface_domain_mean_time_series.yaml @@ -11,8 +11,9 @@ parallel: operator: constraints.generate_var_constraint varname: $VARNAME pressure_level_constraint: - operator: constraints.generate_pressure_level_constraint - pressure_levels: [] + operator: constraints.generate_level_constraint + coordinate: "pressure" + levels: [] validity_time_constraint: operator: constraints.generate_time_constraint time_start: $VALIDITY_TIME diff --git a/src/CSET/recipes/lfric_generic_surface_spatial_plot_sequence.yaml b/src/CSET/recipes/lfric_generic_surface_spatial_plot_sequence.yaml index 301fa9a9f..499bf64cd 100644 --- a/src/CSET/recipes/lfric_generic_surface_spatial_plot_sequence.yaml +++ b/src/CSET/recipes/lfric_generic_surface_spatial_plot_sequence.yaml @@ -10,8 +10,9 @@ parallel: operator: constraints.generate_var_constraint varname: $VARNAME pressure_level_constraint: - operator: constraints.generate_pressure_level_constraint - pressure_levels: [] + operator: constraints.generate_level_constraint + coordinate: "pressure" + levels: [] validity_time_constraint: operator: constraints.generate_time_constraint time_start: $VALIDITY_TIME diff --git a/tests/operators/test_constraints.py b/tests/operators/test_constraints.py index 17cd19fc1..74bb88026 100644 --- a/tests/operators/test_constraints.py +++ b/tests/operators/test_constraints.py @@ -33,15 +33,6 @@ def test_generate_var_constraint(): assert repr(var_constraint) == expected_var_constraint -def test_generate_model_level_constraint(): - """Generate iris cube constraint for model level number.""" - var_constraint = constraints.generate_model_level_constraint("2") - expected_model_level_constraint = ( - "Constraint(coord_values={'model_level_number': 2})" - ) - assert repr(var_constraint) == expected_model_level_constraint - - def test_generate_cell_methods_constraint(): """Generate iris cube constraint for cell methods.""" cell_methods_constraint = constraints.generate_cell_methods_constraint([]) @@ -68,30 +59,32 @@ def test_generate_time_constraint(): assert expected_time_constraint in repr(time_constraint) -def test_generate_pressure_level_constraint_single_level(): - """Generate constraint for a single pressure level.""" - pressure_constraint = constraints.generate_pressure_level_constraint( - pressure_levels=1000 +def test_generate_level_constraint_single_level(): + """Generate constraint for a single level.""" + pressure_constraint = constraints.generate_level_constraint( + coordinate="pressure", levels=1000 ) expected_pressure_constraint = "Constraint(coord_values={'pressure': [1000]})" assert expected_pressure_constraint in repr(pressure_constraint) -def test_generate_pressure_level_constraint_multi_level(): +def test_generate_level_constraint_multi_level(): """Generate constraint for multiple pressure levels.""" - pressure_constraint = constraints.generate_pressure_level_constraint( - pressure_levels=[200, 800] + pressure_constraint = constraints.generate_level_constraint( + coordinate="pressure", levels=[200, 800] ) expected_pressure_constraint = "Constraint(coord_values={'pressure': [200, 800]})" assert expected_pressure_constraint in repr(pressure_constraint) -def test_generate_pressure_level_constraint_no_pressure(): +def test_generate_level_constraint_no_pressure(): """Generate constraint for not having pressure levels.""" - pressure_constraint = constraints.generate_pressure_level_constraint( - pressure_levels=[] + pressure_constraint = constraints.generate_level_constraint( + coordinate="pressure", levels=[] + ) + expected_pressure_constraint = ( + "Constraint(cube_func=.no_levels at" ) - expected_pressure_constraint = "Constraint(cube_func=.no_pressure_coordinate at" assert expected_pressure_constraint in repr(pressure_constraint) diff --git a/tests/operators/test_filters.py b/tests/operators/test_filters.py index 6bbf9dbb3..c7c737c83 100644 --- a/tests/operators/test_filters.py +++ b/tests/operators/test_filters.py @@ -48,20 +48,19 @@ def test_filter_cubes_multiple_returned_exception(cubes): filters.filter_cubes(cubes, constraint) -def test_filter_cubes_pressure_coord(cube): - """Test a cube without a pressure coordinate is passed through.""" - pressure_constraint = constraints.generate_pressure_level_constraint( - pressure_levels=[] +def test_filter_cubes_no_level_coord(cube): + """Test a cube without the level coordinate is passed through.""" + pressure_constraint = constraints.generate_level_constraint( + coordinate="pressure", levels=[] ) assert filters.filter_cubes(cube, pressure_constraint) -def test_filter_cubes_pressure_coord_none_returned(cube): +def test_filter_cubes_no_level_coord_none_returned(cube): """Test exception when pressure coordinate is excluded.""" - pressure_constraint = constraints.generate_pressure_level_constraint( - pressure_levels=[] + pressure_constraint = constraints.generate_level_constraint( + coordinate="pressure", levels=[] ) - cube = cube.copy() cube.add_aux_coord(iris.coords.DimCoord(100, var_name="pressure")) with pytest.raises(ValueError): filters.filter_cubes(cube, pressure_constraint) diff --git a/tests/operators/test_plots.py b/tests/operators/test_plots.py index 657fab4d3..df29962c1 100644 --- a/tests/operators/test_plots.py +++ b/tests/operators/test_plots.py @@ -134,7 +134,9 @@ def test_plot_vertical_line_series_no_series_coordinate( """Error when cube is missing series coordinate (pressure).""" vertical_profile_cube.remove_coord("pressure") with pytest.raises(ValueError, match="Cube must have a pressure coordinate."): - plot.plot_vertical_line_series(vertical_profile_cube) + plot.plot_vertical_line_series( + vertical_profile_cube, series_coordinate="pressure" + ) def test_plot_vertical_line_series_no_sequence_coordinate( @@ -143,4 +145,6 @@ def test_plot_vertical_line_series_no_sequence_coordinate( """Error when cube is missing sequence coordinate (time).""" vertical_profile_cube.remove_coord("time") with pytest.raises(ValueError, match="Cube must have a time coordinate."): - plot.plot_vertical_line_series(vertical_profile_cube) + plot.plot_vertical_line_series( + vertical_profile_cube, series_coordinate="pressure" + ) diff --git a/tests/test_data/air_temperature_1000_hpa_level_histogram_plot.nc b/tests/test_data/air_temperature_1000_hpa_level_histogram_plot.nc new file mode 100644 index 000000000..5e5659243 Binary files /dev/null and b/tests/test_data/air_temperature_1000_hpa_level_histogram_plot.nc differ