Skip to content

Commit

Permalink
Merge pull request #638 from MetOffice/637_lfric_extension_to_vertica…
Browse files Browse the repository at this point in the history
…l_profile

LFRic extension to vertical profile
  • Loading branch information
jfrost-mo authored Jul 1, 2024
2 parents c9d3688 + 10f69ee commit 1091d80
Show file tree
Hide file tree
Showing 23 changed files with 264 additions and 127 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
5 changes: 5 additions & 0 deletions cset-workflow/extra-meta/colorbar_dict_alphabetical.json
Original file line number Diff line number Diff line change
Expand Up @@ -166,5 +166,10 @@
"wind_speed_at_10m": {
"min": 0.0,
"max": 30.0
},
"air_potential_temperature": {
"min": 230,
"max": 340,
"cmap": "jet"
}
}
Original file line number Diff line number Diff line change
@@ -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 %}
18 changes: 18 additions & 0 deletions cset-workflow/includes/lfric_plot_spatial_mlevel_model_field.cylc
Original file line number Diff line number Diff line change
@@ -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 %}
18 changes: 18 additions & 0 deletions cset-workflow/includes/lfric_plot_spatial_plevel_model_field.cylc
Original file line number Diff line number Diff line change
@@ -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 %}
28 changes: 26 additions & 2 deletions cset-workflow/meta/rose-meta.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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
Expand Down
8 changes: 7 additions & 1 deletion docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
-------------------
Expand Down
76 changes: 27 additions & 49 deletions src/CSET/operators/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
65 changes: 42 additions & 23 deletions src/CSET/operators/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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)

Expand Down Expand Up @@ -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,
Expand All @@ -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"``.
Expand Down Expand Up @@ -650,6 +668,7 @@ def plot_vertical_line_series(
cube_slice,
coord,
plot_filename,
series_coordinate,
title=title,
vmin=vmin,
vmax=vmax,
Expand Down
1 change: 1 addition & 0 deletions src/CSET/operators/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions src/CSET/recipes/generic_plevel_spatial_plot_sequence.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 3 additions & 2 deletions src/CSET/recipes/generic_surface_domain_mean_time_series.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 1091d80

Please sign in to comment.