From 98706f3c9648cb7d6c5abb84ed9f8cc597e02330 Mon Sep 17 00:00:00 2001 From: James Ross Date: Tue, 18 Aug 2026 14:44:47 +0100 Subject: [PATCH 1/8] initial pass at fix # Conflicts: # src/CSET/operators/read.py --- src/CSET/operators/constraints.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/CSET/operators/constraints.py b/src/CSET/operators/constraints.py index 1900394e9..a96aae3ec 100644 --- a/src/CSET/operators/constraints.py +++ b/src/CSET/operators/constraints.py @@ -77,11 +77,28 @@ def generate_var_constraint(varname: str, **kwargs) -> iris.Constraint: return iris.AttributeConstraint(STASH=varname) # Ensure access to variable vector components for computed fields + + varname_copy = ( + list(varname) + if isinstance(varname, Iterable) and not isinstance(varname, str) + else [varname] + ) if "wind_speed_at_10m" in iter_maybe(varname): if isinstance(varname, str): varname = [varname] varname.extend(["eastward_wind_at_10m", "northward_wind_at_10m"]) varname.extend(["u_wind_at_10m", "v_wind_at_10m"]) + varname.extend(["WIND_SPEED_REQUESTED"]) + + if "eastward_wind_at_10m" in iter_maybe(varname_copy): + if isinstance(varname, str): + varname = [varname] + varname.extend(["EASTWARD_WIND_SPEED_REQUESTED"]) + + if "northward_wind_at_10m" in iter_maybe(varname_copy): + if isinstance(varname, str): + varname = [varname] + varname.extend(["NORTHWARD_WIND_SPEED_REQUESTED"]) # Case 2: Multiple varnames if isinstance(varname, (list, tuple)): From ca58b73ef24f8e9c531af2822939e6dfb6c09635 Mon Sep 17 00:00:00 2001 From: James Ross Date: Wed, 19 Aug 2026 11:00:08 +0100 Subject: [PATCH 2/8] even less hacky... --- src/CSET/operators/constraints.py | 13 ++----------- tests/operators/test_read.py | 1 - 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/CSET/operators/constraints.py b/src/CSET/operators/constraints.py index a96aae3ec..31c9701d2 100644 --- a/src/CSET/operators/constraints.py +++ b/src/CSET/operators/constraints.py @@ -88,17 +88,6 @@ def generate_var_constraint(varname: str, **kwargs) -> iris.Constraint: varname = [varname] varname.extend(["eastward_wind_at_10m", "northward_wind_at_10m"]) varname.extend(["u_wind_at_10m", "v_wind_at_10m"]) - varname.extend(["WIND_SPEED_REQUESTED"]) - - if "eastward_wind_at_10m" in iter_maybe(varname_copy): - if isinstance(varname, str): - varname = [varname] - varname.extend(["EASTWARD_WIND_SPEED_REQUESTED"]) - - if "northward_wind_at_10m" in iter_maybe(varname_copy): - if isinstance(varname, str): - varname = [varname] - varname.extend(["NORTHWARD_WIND_SPEED_REQUESTED"]) # Case 2: Multiple varnames if isinstance(varname, (list, tuple)): @@ -113,6 +102,8 @@ def generate_var_constraint(varname: str, **kwargs) -> iris.Constraint: else: varname_constraint = iris.Constraint(name=varname) + varname_constraint.varname = varname_copy + return varname_constraint diff --git a/tests/operators/test_read.py b/tests/operators/test_read.py index c59a39580..a330daa36 100644 --- a/tests/operators/test_read.py +++ b/tests/operators/test_read.py @@ -159,7 +159,6 @@ def test_read_cube(): constraint=constraints.generate_cell_methods_constraint([]), ) assert isinstance(cube, iris.cube.Cube) - # Global [latitude, longitude] example file cube = read.read_cube( "tests/test_data/air_temperature_global.nc", From 353e9d4a5d3fb3664c28a84162c922b2d2750d34 Mon Sep 17 00:00:00 2001 From: James Ross Date: Wed, 19 Aug 2026 15:13:53 +0100 Subject: [PATCH 3/8] simplifying --- src/CSET/operators/constraints.py | 8 ++---- src/CSET/operators/read.py | 45 ++++++++++++++++++++++++------- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/CSET/operators/constraints.py b/src/CSET/operators/constraints.py index 31c9701d2..357857cdb 100644 --- a/src/CSET/operators/constraints.py +++ b/src/CSET/operators/constraints.py @@ -78,12 +78,8 @@ def generate_var_constraint(varname: str, **kwargs) -> iris.Constraint: # Ensure access to variable vector components for computed fields - varname_copy = ( - list(varname) - if isinstance(varname, Iterable) and not isinstance(varname, str) - else [varname] - ) - if "wind_speed_at_10m" in iter_maybe(varname): + varname_copy = iter_maybe(varname) + if "wind_speed_at_10m" in varname_copy: if isinstance(varname, str): varname = [varname] varname.extend(["eastward_wind_at_10m", "northward_wind_at_10m"]) diff --git a/src/CSET/operators/read.py b/src/CSET/operators/read.py index 9a3c6e435..8ab50355e 100644 --- a/src/CSET/operators/read.py +++ b/src/CSET/operators/read.py @@ -195,7 +195,6 @@ def read_cubes( for cube in cubes: # Use 1 to indicate True, as booleans can't be saved in NetCDF attributes. cube.attributes["cset_comparison_base"] = 1 - # Load the rest of the models. cubes.extend(itertools.chain.from_iterable(model_cubes)) @@ -247,7 +246,8 @@ def _load_model( logger.debug("Constraint: %s", constraint) cubes = iris.load(input_files, constraint, callback=_loading_callback) # If required, compute wind_speed from components. - cubes = _compute_winds(cubes) + + cubes = _compute_winds(cubes, constraint) # Add model_name attribute to each cube to make it available at any further # step without needing to pass it as function parameter. @@ -423,7 +423,7 @@ def _loading_callback(cube: iris.cube.Cube, field, filename: str) -> iris.cube.C _lfric_time_callback(cube) _lfric_forecast_period_callback(cube) cube = _fix_no_time_coords_callback(cube) - _normalise_longname(cube) + _normalise_ML_varname(cube) return cube @@ -900,7 +900,17 @@ def _fix_lfric_cloud_base_altitude(cube: iris.cube.Cube): cube.data = dask.array.ma.masked_greater(cube.core_data(), 144.0) -def _compute_winds(cubes: iris.cube.CubeList): +def get_filter_windspeed(constraint: iris.Constraint): + """Get the windspeed filter by using the hijacked constraint.""" + if hasattr(constraint, "varname"): + return constraint.varname + else: + return None + + +def _compute_winds( + cubes: iris.cube.CubeList, constraint: iris.Constraint | None = None +): """To compute wind_speed from vector components if not available as diagnostic. Diagnostics of wind are also not always consistent between the UM @@ -915,9 +925,15 @@ def _compute_winds(cubes: iris.cube.CubeList): # the cell methods, but it may not be warranted. # # A check on UM STASH attributes is also conducted to adjust directions. + if isinstance(constraint, iris.Constraint): + filter_windspeed = get_filter_windspeed(constraint) + else: + filter_windspeed = None + u_constr = iris.Constraint("eastward_wind_at_10m") v_constr = iris.Constraint("northward_wind_at_10m") speed_constr = iris.Constraint("wind_speed_at_10m") + try: if cubes.extract(u_constr) and cubes.extract(v_constr): if len(cubes) == 2: @@ -926,15 +942,26 @@ def _compute_winds(cubes: iris.cube.CubeList): wind_only = False if len(cubes.extract(u_constr)) == 1 and not cubes.extract(speed_constr): _add_wind_speed_um(cubes) + # Convert winds in the UM to be relative to true east and true north. - if cubes.extract(u_constr) and cubes.extract(v_constr): - _convert_wind_true_dirn_um(cubes) + _convert_wind_true_dirn_um(cubes) # Return only wind_speed cube if wind_only: cubes = cubes.extract(speed_constr) + except (KeyError, AttributeError): pass + if filter_windspeed and "observed" not in cubes[0].name(): + filter_windspeed_constraint = iris.Constraint( + cube_func=lambda cube: ( + cube.long_name in filter_windspeed + or cube.standard_name in filter_windspeed + or cube.var_name in filter_windspeed + ) + ) + cubes = cubes.extract(filter_windspeed_constraint) + return cubes @@ -1108,8 +1135,8 @@ def _fix_no_time_coords_callback(cube: iris.cube.Cube): return cube -def _normalise_longname(cube: iris.cube.Cube): - """Normalise long_name to the LFRic standard list.""" +def _normalise_ML_varname(cube: iris.cube.Cube): + """Fix plev variable names to standard names.""" if cube.coords("pressure"): if cube.name() == "x_wind": cube.long_name = "zonal_wind_at_pressure_levels" @@ -1126,8 +1153,6 @@ def _normalise_longname(cube: iris.cube.Cube): cube.long_name = "eastward_wind_at_10m" if cube.name() == "y_wind" and cube.var_name == "v_wind_at_10m": cube.long_name = "northward_wind_at_10m" - if cube.name() == "air_pressure_at_sea_level": - cube.long_name = "air_pressure_at_mean_sea_level" def _check_combine_point_observations(cubes: iris.cube.CubeList): From 5e421d09514307ea8dda73ac3487bf8010cf3761 Mon Sep 17 00:00:00 2001 From: James Ross Date: Wed, 19 Aug 2026 15:14:08 +0100 Subject: [PATCH 4/8] simplifying --- src/CSET/operators/read.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CSET/operators/read.py b/src/CSET/operators/read.py index 8ab50355e..bfa306736 100644 --- a/src/CSET/operators/read.py +++ b/src/CSET/operators/read.py @@ -942,7 +942,6 @@ def _compute_winds( wind_only = False if len(cubes.extract(u_constr)) == 1 and not cubes.extract(speed_constr): _add_wind_speed_um(cubes) - # Convert winds in the UM to be relative to true east and true north. _convert_wind_true_dirn_um(cubes) # Return only wind_speed cube From 50b6e0c0b6f8c1d820df0a70a4e12d055ed2e34d Mon Sep 17 00:00:00 2001 From: James Ross Date: Wed, 19 Aug 2026 16:40:32 +0100 Subject: [PATCH 5/8] simplifying and fixing --- src/CSET/operators/constraints.py | 3 +-- src/CSET/operators/read.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/CSET/operators/constraints.py b/src/CSET/operators/constraints.py index 357857cdb..9c1a0b083 100644 --- a/src/CSET/operators/constraints.py +++ b/src/CSET/operators/constraints.py @@ -77,14 +77,13 @@ def generate_var_constraint(varname: str, **kwargs) -> iris.Constraint: return iris.AttributeConstraint(STASH=varname) # Ensure access to variable vector components for computed fields + varname_copy = iter_maybe(varname)[:] - varname_copy = iter_maybe(varname) if "wind_speed_at_10m" in varname_copy: if isinstance(varname, str): varname = [varname] varname.extend(["eastward_wind_at_10m", "northward_wind_at_10m"]) varname.extend(["u_wind_at_10m", "v_wind_at_10m"]) - # Case 2: Multiple varnames if isinstance(varname, (list, tuple)): varname_constraint = iris.Constraint( diff --git a/src/CSET/operators/read.py b/src/CSET/operators/read.py index bfa306736..53df3dab3 100644 --- a/src/CSET/operators/read.py +++ b/src/CSET/operators/read.py @@ -951,7 +951,7 @@ def _compute_winds( except (KeyError, AttributeError): pass - if filter_windspeed and "observed" not in cubes[0].name(): + if filter_windspeed: filter_windspeed_constraint = iris.Constraint( cube_func=lambda cube: ( cube.long_name in filter_windspeed From 13c374d6328fd8cda5c2f308a5100079f1ed8875 Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 20 Aug 2026 10:33:55 +0100 Subject: [PATCH 6/8] adding tests + modifying old ones --- src/CSET/operators/constraints.py | 2 +- src/CSET/operators/read.py | 29 +++--- tests/conftest.py | 40 +++++++++ tests/operators/test_read.py | 143 +++++++++++++++++++++++++++++- 4 files changed, 192 insertions(+), 22 deletions(-) diff --git a/src/CSET/operators/constraints.py b/src/CSET/operators/constraints.py index 9c1a0b083..f03ff352e 100644 --- a/src/CSET/operators/constraints.py +++ b/src/CSET/operators/constraints.py @@ -52,7 +52,7 @@ def generate_stash_constraint(stash: str, **kwargs) -> iris.AttributeConstraint: return stash_constraint -def generate_var_constraint(varname: str, **kwargs) -> iris.Constraint: +def generate_var_constraint(varname: str | list[str], **kwargs) -> iris.Constraint: """Generate constraint from variable name or STASH code. Operator that takes a CF compliant variable name string or list of names, and generates an diff --git a/src/CSET/operators/read.py b/src/CSET/operators/read.py index 53df3dab3..ec53fb157 100644 --- a/src/CSET/operators/read.py +++ b/src/CSET/operators/read.py @@ -246,7 +246,6 @@ def _load_model( logger.debug("Constraint: %s", constraint) cubes = iris.load(input_files, constraint, callback=_loading_callback) # If required, compute wind_speed from components. - cubes = _compute_winds(cubes, constraint) # Add model_name attribute to each cube to make it available at any further @@ -925,29 +924,26 @@ def _compute_winds( # the cell methods, but it may not be warranted. # # A check on UM STASH attributes is also conducted to adjust directions. - if isinstance(constraint, iris.Constraint): - filter_windspeed = get_filter_windspeed(constraint) - else: - filter_windspeed = None + + if constraint is None: + return cubes + + filter_windspeed = get_filter_windspeed(constraint) u_constr = iris.Constraint("eastward_wind_at_10m") v_constr = iris.Constraint("northward_wind_at_10m") - speed_constr = iris.Constraint("wind_speed_at_10m") + sp_constr = iris.Constraint("wind_speed_at_10m") try: - if cubes.extract(u_constr) and cubes.extract(v_constr): - if len(cubes) == 2: - wind_only = True - else: - wind_only = False - if len(cubes.extract(u_constr)) == 1 and not cubes.extract(speed_constr): + if ( + cubes.extract(u_constr) + and cubes.extract(v_constr) + and not cubes.extract(sp_constr) + ): + if "wind_speed_at_10m" in constraint.varname: _add_wind_speed_um(cubes) # Convert winds in the UM to be relative to true east and true north. _convert_wind_true_dirn_um(cubes) - # Return only wind_speed cube - if wind_only: - cubes = cubes.extract(speed_constr) - except (KeyError, AttributeError): pass @@ -960,7 +956,6 @@ def _compute_winds( ) ) cubes = cubes.extract(filter_windspeed_constraint) - return cubes diff --git a/tests/conftest.py b/tests/conftest.py index bbd604b56..e84d40703 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1355,3 +1355,43 @@ def _make_cube(data, long_name, model_name=None): return cube return _make_cube + + +@pytest.fixture() +def wind_cubelist_um(): + """UM wind cube list.""" + wind_cubelist = CubeList() + + wind_cubelist.append( + Cube( + 3.5, standard_name="x_wind", long_name="eastward_wind_at_10m", units="m s-1" + ) + ) + wind_cubelist.append( + Cube( + 3.5, + standard_name="y_wind", + long_name="northward_wind_at_10m", + units="m s-1", + ) + ) + + return wind_cubelist + + +@pytest.fixture() +def wind_cubelist_lfric(): + """LFRic wind cube list.""" + wind_cubelist = CubeList() + + wind_cubelist.append( + Cube(3.5, var_name="wspd10m", long_name="wind_speed_at_10m", units="m s-1") + ) + wind_cubelist.append( + Cube(3.5, var_name="u10m", long_name="eastward_wind_at_10m", units="m s-1") + ) + wind_cubelist.append( + Cube(3.5, var_name="v10m", long_name="northward_wind_at_10m", units="m s-1") + ) + + return wind_cubelist diff --git a/tests/operators/test_read.py b/tests/operators/test_read.py index a330daa36..8e7cfa04e 100644 --- a/tests/operators/test_read.py +++ b/tests/operators/test_read.py @@ -1306,15 +1306,14 @@ def test_check_combine_point_observations_multiple_obs(cube): def test_compute_winds(vector_cubes, tmp_working_dir): """Ensure _compute_winds calculates wind_speed from component inputs.""" + constraint = constraints.generate_var_constraint("wind_speed_at_10m") assert len(vector_cubes) == 2 vector_cubes[0].rename("eastward_wind_at_10m") vector_cubes[1].rename("northward_wind_at_10m") - output_cubes = read._compute_winds(vector_cubes) - assert len(vector_cubes) == 3 + output_cubes = read._compute_winds(vector_cubes, constraint=constraint) assert len(output_cubes) == 1 assert output_cubes.extract(iris.Constraint("wind_speed_at_10m")) assert output_cubes.extract(iris.Constraint("wind_speed_at_10m"))[0].units == "ms-1" - u = vector_cubes[0].data v = vector_cubes[1].data expected_wind = (u**2 + v**2) ** 0.5 @@ -1332,10 +1331,20 @@ def test_compute_winds_multi_vars(cube, vector_cubes, tmp_working_dir): """Ensure _compute_winds extends multiple input variable cubelist.""" # Add cube of air_temperature to CubeList containing wind components. assert len(vector_cubes) == 2 + constraint = constraints.generate_var_constraint( + [ + "air_temperature", + "wind_speed_at_10m", + "eastward_wind_at_10m", + "northward_wind_at_10m", + ] + ) vector_cubes[0].rename("eastward_wind_at_10m") vector_cubes[1].rename("northward_wind_at_10m") + output_cubes = read._compute_winds( - iris.cube.CubeList([cube, vector_cubes[0], vector_cubes[1]]) + iris.cube.CubeList([cube, vector_cubes[0], vector_cubes[1]]), + constraint=constraint, ) assert len(vector_cubes) == 2 assert len(output_cubes) == 4 @@ -1350,3 +1359,129 @@ def test_compute_winds_multi_vars(cube, vector_cubes, tmp_working_dir): expected_wind = (u**2 + v**2) ** 0.5 output_wind = output_cubes.extract(iris.Constraint("wind_speed_at_10m"))[0] assert np.allclose(output_wind.data, expected_wind, rtol=1e-6, atol=1e-2) + + +def test_compute_wind_no_constraint(wind_cubelist_um): + """No constraint given should return the cubelist unchanged.""" + cubes = read._compute_winds(wind_cubelist_um) + assert cubes == wind_cubelist_um + + +def test_wind_um(wind_cubelist_um): + """UM cubes filtered to wind speed only.""" + cubes = wind_cubelist_um.copy() + constraint = constraints.generate_var_constraint("wind_speed_at_10m") + cubes = read._compute_winds(cubes, constraint=constraint) + assert len(cubes) == 1 + speed = cubes.extract_cube(iris.Constraint("wind_speed_at_10m")) + assert speed.standard_name == "wind_speed" + + +def test_wind_um_eastward(wind_cubelist_um): + """UM cubes filtered to eastward wind only.""" + cubes = wind_cubelist_um.copy() + constraint = constraints.generate_var_constraint("eastward_wind_at_10m") + cubes = read._compute_winds(cubes, constraint=constraint) + assert len(cubes) == 1 + eastward = cubes.extract_cube(iris.Constraint("eastward_wind_at_10m")) + assert eastward.standard_name == "x_wind" + + +def test_wind_um_northward(wind_cubelist_um): + """UM cubes filtered to northward wind only.""" + cubes = wind_cubelist_um.copy() + constraint = constraints.generate_var_constraint("northward_wind_at_10m") + cubes = read._compute_winds(cubes, constraint=constraint) + assert len(cubes) == 1 + northward = cubes.extract_cube(iris.Constraint("northward_wind_at_10m")) + assert northward.standard_name == "y_wind" + + +def test_wind_um_northward_and_eastward(wind_cubelist_um): + """UM cubes filtered to both wind components.""" + cubes = wind_cubelist_um.copy() + constraint = constraints.generate_var_constraint( + ["eastward_wind_at_10m", "northward_wind_at_10m"] + ) + cubes = read._compute_winds(cubes, constraint=constraint) + assert len(cubes) == 2 + eastward = cubes.extract_cube(iris.Constraint("eastward_wind_at_10m")) + northward = cubes.extract_cube(iris.Constraint("northward_wind_at_10m")) + assert eastward.standard_name == "x_wind" + assert northward.standard_name == "y_wind" + + +def test_wind_um_all(wind_cubelist_um): + """UM cubes filtered to wind speed and both wind components.""" + cubes = wind_cubelist_um.copy() + constraint = constraints.generate_var_constraint( + ["wind_speed_at_10m", "eastward_wind_at_10m", "northward_wind_at_10m"] + ) + cubes = read._compute_winds(cubes, constraint=constraint) + assert len(cubes) == 3 + speed = cubes.extract_cube(iris.Constraint("wind_speed_at_10m")) + eastward = cubes.extract_cube(iris.Constraint("eastward_wind_at_10m")) + northward = cubes.extract_cube(iris.Constraint("northward_wind_at_10m")) + assert speed.standard_name == "wind_speed" + assert eastward.standard_name == "x_wind" + assert northward.standard_name == "y_wind" + + +def test_wind_lfric(wind_cubelist_lfric): + """LFRic cubes filtered to wind speed only.""" + cubes = wind_cubelist_lfric.copy() + constraint = constraints.generate_var_constraint("wind_speed_at_10m") + cubes = read._compute_winds(cubes, constraint=constraint) + assert len(cubes) == 1 + speed = cubes.extract_cube(iris.Constraint("wind_speed_at_10m")) + assert speed.var_name == "wspd10m" + + +def test_wind_lfric_eastward(wind_cubelist_lfric): + """LFRic cubes filtered to eastward wind only.""" + cubes = wind_cubelist_lfric.copy() + constraint = constraints.generate_var_constraint("eastward_wind_at_10m") + cubes = read._compute_winds(cubes, constraint=constraint) + assert len(cubes) == 1 + eastward = cubes.extract_cube(iris.Constraint("eastward_wind_at_10m")) + assert eastward.var_name == "u10m" + + +def test_wind_lfric_northward(wind_cubelist_lfric): + """LFRic cubes filtered to northward wind only.""" + cubes = wind_cubelist_lfric.copy() + constraint = constraints.generate_var_constraint("northward_wind_at_10m") + cubes = read._compute_winds(cubes, constraint=constraint) + assert len(cubes) == 1 + northward = cubes.extract_cube(iris.Constraint("northward_wind_at_10m")) + assert northward.var_name == "v10m" + + +def test_wind_lfric_northward_and_eastward(wind_cubelist_lfric): + """LFRic cubes filtered to both wind components.""" + cubes = wind_cubelist_lfric.copy() + constraint = constraints.generate_var_constraint( + ["eastward_wind_at_10m", "northward_wind_at_10m"] + ) + cubes = read._compute_winds(cubes, constraint=constraint) + assert len(cubes) == 2 + eastward = cubes.extract_cube(iris.Constraint("eastward_wind_at_10m")) + northward = cubes.extract_cube(iris.Constraint("northward_wind_at_10m")) + assert eastward.var_name == "u10m" + assert northward.var_name == "v10m" + + +def test_wind_lfric_all(wind_cubelist_lfric): + """LFRic cubes filtered to speed and both wind components.""" + cubes = wind_cubelist_lfric.copy() + constraint = constraints.generate_var_constraint( + ["wind_speed_at_10m", "eastward_wind_at_10m", "northward_wind_at_10m"] + ) + cubes = read._compute_winds(cubes, constraint=constraint) + assert len(cubes) == 3 + speed = cubes.extract_cube(iris.Constraint("wind_speed_at_10m")) + eastward = cubes.extract_cube(iris.Constraint("eastward_wind_at_10m")) + northward = cubes.extract_cube(iris.Constraint("northward_wind_at_10m")) + assert speed.var_name == "wspd10m" + assert eastward.var_name == "u10m" + assert northward.var_name == "v10m" From f4baf8b19dced47a7782206785befdda0f71f2f2 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 21 Aug 2026 11:50:32 +0100 Subject: [PATCH 7/8] revert change to function name --- src/CSET/operators/read.py | 4 ++-- tests/conftest.py | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/CSET/operators/read.py b/src/CSET/operators/read.py index ec53fb157..b8dda708e 100644 --- a/src/CSET/operators/read.py +++ b/src/CSET/operators/read.py @@ -422,7 +422,7 @@ def _loading_callback(cube: iris.cube.Cube, field, filename: str) -> iris.cube.C _lfric_time_callback(cube) _lfric_forecast_period_callback(cube) cube = _fix_no_time_coords_callback(cube) - _normalise_ML_varname(cube) + _normalise_longname(cube) return cube @@ -1129,7 +1129,7 @@ def _fix_no_time_coords_callback(cube: iris.cube.Cube): return cube -def _normalise_ML_varname(cube: iris.cube.Cube): +def _normalise_longname(cube: iris.cube.Cube): """Fix plev variable names to standard names.""" if cube.coords("pressure"): if cube.name() == "x_wind": diff --git a/tests/conftest.py b/tests/conftest.py index e84d40703..89f9d07f0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1395,3 +1395,15 @@ def wind_cubelist_lfric(): ) return wind_cubelist + + +@pytest.fixture() +def wind_cubelist_observed(): + """Observed wind cube list.""" + wind_cubelist = CubeList() + + wind_cubelist.append( + Cube(3.5, long_name="observed_wind_speed_at_10m", units="m s-1") + ) + + return wind_cubelist From 46277fcd085355db9aed9230924b6a76029d2e8e Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 21 Aug 2026 12:03:36 +0100 Subject: [PATCH 8/8] add small test for observed --- tests/operators/test_read.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/operators/test_read.py b/tests/operators/test_read.py index 8e7cfa04e..85fe95ef2 100644 --- a/tests/operators/test_read.py +++ b/tests/operators/test_read.py @@ -1485,3 +1485,15 @@ def test_wind_lfric_all(wind_cubelist_lfric): assert speed.var_name == "wspd10m" assert eastward.var_name == "u10m" assert northward.var_name == "v10m" + + +def test_wind_observed(wind_cubelist_observed): + """Observed cubes filtered to speed.""" + cubes = wind_cubelist_observed.copy() + constraint = constraints.generate_var_constraint( + ["observed_wind_speed_at_10m", "wind_speed_at_10m"] + ) + cubes = read._compute_winds(cubes, constraint=constraint) + assert len(cubes) == 1 + observed_speed = cubes.extract_cube(iris.Constraint("observed_wind_speed_at_10m")) + assert observed_speed