From 35b6fa61a59f513c4e8b67626527e538a63e9031 Mon Sep 17 00:00:00 2001 From: Elizaveta Malinina Date: Tue, 10 Oct 2023 18:37:28 +0000 Subject: [PATCH 01/11] adding function to derive sfcWind from uas and vas (not wirking) --- .../cmor/tables/custom/CMOR_sfcWind.dat | 23 +++++++++++ esmvalcore/preprocessor/_derive/sfcWind.py | 39 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 esmvalcore/cmor/tables/custom/CMOR_sfcWind.dat create mode 100644 esmvalcore/preprocessor/_derive/sfcWind.py diff --git a/esmvalcore/cmor/tables/custom/CMOR_sfcWind.dat b/esmvalcore/cmor/tables/custom/CMOR_sfcWind.dat new file mode 100644 index 0000000000..db987b071a --- /dev/null +++ b/esmvalcore/cmor/tables/custom/CMOR_sfcWind.dat @@ -0,0 +1,23 @@ +SOURCE: CMIP5 +!============ +variable_entry: sfcWind +!============ +modeling_realm: atmos +!---------------------------------- +! Variable attributes: +!---------------------------------- +standard_name: wind_speed +units: m s-1 +cell_methods: area: mean time: point +cell_measures: area: areacella +long_name: Near-Surface Wind Speed +comment: This is the wind speed computed from the mean u and v components of wind +!---------------------------------- +! Additional variable information: +!---------------------------------- +dimensions: longitude latitude time +type: real +valid_min: +valid_max: +!---------------------------------- +! \ No newline at end of file diff --git a/esmvalcore/preprocessor/_derive/sfcWind.py b/esmvalcore/preprocessor/_derive/sfcWind.py new file mode 100644 index 0000000000..f361ee37b9 --- /dev/null +++ b/esmvalcore/preprocessor/_derive/sfcWind.py @@ -0,0 +1,39 @@ +"""Derivation of variable `sfcWind`.""" + +import cf_units +import iris +import numpy as np +from iris import NameConstraint + +from ._baseclass import DerivedVariableBase + +class DerivedVariable(DerivedVariableBase): + """Derivation of variable `sfcWind`.""" + + @staticmethod + def required(project): + """Declare the variables needed for derivation.""" + required = [ + { + 'short_name': 'uas' + }, + { + 'short_name': 'vas' + }, + ] + return required + + @staticmethod + def calculate(cubes): + """Compute surface near-surface wind speed + from eastward and northward components.""" + + uas_cube = cubes.extract_cube(NameConstraint(var_name='uas')) + vas_cube = cubes.extract_cube(NameConstraint(var_name='vas')) + + sqrt_f = iris.analysis.maths.IFunc(np.sqrt, + lambda cube: cf_units.Unit('m s-1')) + + sfcWind_cube = sqrt_f(uas_cube**2 + vas_cube**2) + + return sfcWind_cube \ No newline at end of file From 7bc940f57fa354fa552402e667fa03c4dff9a21c Mon Sep 17 00:00:00 2001 From: Elizaveta Malinina Date: Mon, 23 Oct 2023 23:12:10 +0000 Subject: [PATCH 02/11] adding sfcWind derivation and testing --- .../cmor/tables/custom/CMOR_sfcWind.dat | 4 +- esmvalcore/preprocessor/_derive/__init__.py | 4 +- .../_derive/{sfcWind.py => sfcwind.py} | 18 ++++----- .../unit/preprocessor/_derive/test_sfcwind.py | 39 +++++++++++++++++++ 4 files changed, 50 insertions(+), 15 deletions(-) rename esmvalcore/preprocessor/_derive/{sfcWind.py => sfcwind.py} (64%) create mode 100644 tests/unit/preprocessor/_derive/test_sfcwind.py diff --git a/esmvalcore/cmor/tables/custom/CMOR_sfcWind.dat b/esmvalcore/cmor/tables/custom/CMOR_sfcWind.dat index db987b071a..b8af9a39f0 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_sfcWind.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_sfcWind.dat @@ -8,14 +8,14 @@ modeling_realm: atmos !---------------------------------- standard_name: wind_speed units: m s-1 -cell_methods: area: mean time: point +cell_methods: area: mean time: mean cell_measures: area: areacella long_name: Near-Surface Wind Speed comment: This is the wind speed computed from the mean u and v components of wind !---------------------------------- ! Additional variable information: !---------------------------------- -dimensions: longitude latitude time +dimensions: longitude latitude time type: real valid_min: valid_max: diff --git a/esmvalcore/preprocessor/_derive/__init__.py b/esmvalcore/preprocessor/_derive/__init__.py index 6ef2ec9d2a..27bffa38d7 100644 --- a/esmvalcore/preprocessor/_derive/__init__.py +++ b/esmvalcore/preprocessor/_derive/__init__.py @@ -52,11 +52,11 @@ def get_required(short_name, project): List of dictionaries (including at least the key `short_name`). """ - if short_name not in ALL_DERIVED_VARIABLES: + if short_name.lower() not in ALL_DERIVED_VARIABLES: raise NotImplementedError( f"Cannot derive variable '{short_name}', no derivation script " f"available") - DerivedVariable = ALL_DERIVED_VARIABLES[short_name] # noqa: N806 + DerivedVariable = ALL_DERIVED_VARIABLES[short_name.lower()] # noqa: N806 variables = deepcopy(DerivedVariable().required(project)) return variables diff --git a/esmvalcore/preprocessor/_derive/sfcWind.py b/esmvalcore/preprocessor/_derive/sfcwind.py similarity index 64% rename from esmvalcore/preprocessor/_derive/sfcWind.py rename to esmvalcore/preprocessor/_derive/sfcwind.py index f361ee37b9..26768f5b45 100644 --- a/esmvalcore/preprocessor/_derive/sfcWind.py +++ b/esmvalcore/preprocessor/_derive/sfcwind.py @@ -1,12 +1,10 @@ """Derivation of variable `sfcWind`.""" -import cf_units -import iris -import numpy as np from iris import NameConstraint from ._baseclass import DerivedVariableBase + class DerivedVariable(DerivedVariableBase): """Derivation of variable `sfcWind`.""" @@ -25,15 +23,13 @@ def required(project): @staticmethod def calculate(cubes): - """Compute surface near-surface wind speed - from eastward and northward components.""" - + """Compute near-surface wind speed. + + Wind speed derived from eastward and northward components. + """ uas_cube = cubes.extract_cube(NameConstraint(var_name='uas')) vas_cube = cubes.extract_cube(NameConstraint(var_name='vas')) - sqrt_f = iris.analysis.maths.IFunc(np.sqrt, - lambda cube: cf_units.Unit('m s-1')) - - sfcWind_cube = sqrt_f(uas_cube**2 + vas_cube**2) + sfcwind_cube = (uas_cube**2 + vas_cube**2)**0.5 - return sfcWind_cube \ No newline at end of file + return sfcwind_cube \ No newline at end of file diff --git a/tests/unit/preprocessor/_derive/test_sfcwind.py b/tests/unit/preprocessor/_derive/test_sfcwind.py new file mode 100644 index 0000000000..1d0b655896 --- /dev/null +++ b/tests/unit/preprocessor/_derive/test_sfcwind.py @@ -0,0 +1,39 @@ +"""Test derivation of ``sfcwind``.""" +import numpy as np +import pytest +from iris.cube import CubeList + +from esmvalcore.preprocessor._derive import sfcwind + +from .test_shared import get_cube + + +@pytest.fixture +def cubes(): + """Input cubes for derivation of ``sfcwind``.""" + uas_cube = get_cube([[[3.0]]], + air_pressure_coord=False, + standard_name='eastward_wind', + var_name='uas', + units='m s-1') + vas_cube = get_cube([[[4.0]]], + air_pressure_coord=False, + standard_name='northward_wind', + var_name='vas', + units='m s-1') + return CubeList([uas_cube, vas_cube]) + + +def test_sfcwind_calculate(cubes): + """Test function ``calculate``.""" + derived_var = sfcwind.DerivedVariable() + out_cube = derived_var.calculate(cubes) + assert out_cube.shape == (1, 1, 1) + assert out_cube.units == 'm s-1' + assert out_cube.coords('time') + assert out_cube.coords('latitude') + assert out_cube.coords('longitude') + np.testing.assert_allclose(out_cube.data, [[[5.0]]]) + np.testing.assert_allclose(out_cube.coord('time').points, [0.0]) + np.testing.assert_allclose(out_cube.coord('latitude').points, [45.0]) + np.testing.assert_allclose(out_cube.coord('longitude').points, [10.0]) From e6086a78a6a1e9d95c978b3dded8711141e5f199 Mon Sep 17 00:00:00 2001 From: Elizaveta Malinina Date: Mon, 23 Oct 2023 23:13:05 +0000 Subject: [PATCH 03/11] add uas and vas cmor check --- esmvalcore/cmor/_fixes/native6/era5.py | 20 +++++++++ .../cmor/_fixes/native6/test_era5.py | 41 ++++++++++++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/esmvalcore/cmor/_fixes/native6/era5.py b/esmvalcore/cmor/_fixes/native6/era5.py index d76fd21701..9e1b68ac2c 100644 --- a/esmvalcore/cmor/_fixes/native6/era5.py +++ b/esmvalcore/cmor/_fixes/native6/era5.py @@ -334,6 +334,26 @@ def fix_metadata(self, cubes): return cubes +class Uas(Fix): + """Fixes for uas.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + for cube in cubes: + fix_hourly_time_coordinate(cube) + return cubes + + +class Vas(Fix): + """Fixes for vas.""" + + def fix_metadata(self, cubes): + """Fix metadata.""" + for cube in cubes: + fix_hourly_time_coordinate(cube) + return cubes + + class Zg(Fix): """Fixes for Geopotential.""" diff --git a/tests/integration/cmor/_fixes/native6/test_era5.py b/tests/integration/cmor/_fixes/native6/test_era5.py index 6b8a4922b7..7efabef8f9 100644 --- a/tests/integration/cmor/_fixes/native6/test_era5.py +++ b/tests/integration/cmor/_fixes/native6/test_era5.py @@ -999,6 +999,44 @@ def uas_cmor_e1hr(): return iris.cube.CubeList([cube]) +def vas_era5_hourly(): + time = _era5_time('hourly') + cube = iris.cube.Cube( + _era5_data('hourly'), + long_name='10m_v_component_of_wind', + var_name='v10', + units='m s-1', + dim_coords_and_dims=[ + (time, 0), + (_era5_latitude(), 1), + (_era5_longitude(), 2), + ], + ) + return iris.cube.CubeList([cube]) + + +def vas_cmor_e1hr(): + cmor_table = CMOR_TABLES['native6'] + vardef = cmor_table.get_variable('E1hr', 'vas') + time = _cmor_time('E1hr') + data = _cmor_data('E1hr') + cube = iris.cube.Cube( + data.astype('float32'), + long_name=vardef.long_name, + var_name=vardef.short_name, + standard_name=vardef.standard_name, + units=Unit(vardef.units), + dim_coords_and_dims=[ + (time, 0), + (_cmor_latitude(), 1), + (_cmor_longitude(), 2), + ], + attributes={'comment': COMMENT}, + ) + cube.add_aux_coord(_cmor_aux_height(10.)) + return iris.cube.CubeList([cube]) + + VARIABLES = [ pytest.param(a, b, c, d, id=c + '_' + d) for (a, b, c, d) in [ (cl_era5_monthly(), cl_cmor_amon(), 'cl', 'Amon'), @@ -1022,6 +1060,7 @@ def uas_cmor_e1hr(): (tasmax_era5_hourly(), tasmax_cmor_e1hr(), 'tasmax', 'E1hr'), (tasmin_era5_hourly(), tasmin_cmor_e1hr(), 'tasmin', 'E1hr'), (uas_era5_hourly(), uas_cmor_e1hr(), 'uas', 'E1hr'), + (vas_era5_hourly(), vas_cmor_e1hr(), 'vas', 'E1hr'), (zg_era5_monthly(), zg_cmor_amon(), 'zg', 'Amon'), ] ] @@ -1047,4 +1086,4 @@ def test_cmorization(era5_cubes, cmor_cubes, var, mip): coord.bounds = np.round(coord.bounds, decimals=7) print('cmor_cube:', cmor_cube) print('fixed_cube:', fixed_cube) - assert fixed_cube == cmor_cube + assert fixed_cube == cmor_cube \ No newline at end of file From 852a23ead6ff9515d43b8feed94ea1d5b984dd32 Mon Sep 17 00:00:00 2001 From: Valeriu Predoi Date: Tue, 24 Oct 2023 14:43:00 +0100 Subject: [PATCH 04/11] no newline --- esmvalcore/preprocessor/_derive/sfcwind.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esmvalcore/preprocessor/_derive/sfcwind.py b/esmvalcore/preprocessor/_derive/sfcwind.py index 26768f5b45..2af241f517 100644 --- a/esmvalcore/preprocessor/_derive/sfcwind.py +++ b/esmvalcore/preprocessor/_derive/sfcwind.py @@ -32,4 +32,4 @@ def calculate(cubes): sfcwind_cube = (uas_cube**2 + vas_cube**2)**0.5 - return sfcwind_cube \ No newline at end of file + return sfcwind_cube From f86ed361ffd0cded3f6a49371223f25b4bda2d0a Mon Sep 17 00:00:00 2001 From: Valeriu Predoi Date: Tue, 24 Oct 2023 14:43:50 +0100 Subject: [PATCH 05/11] no newline --- tests/integration/cmor/_fixes/native6/test_era5.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/cmor/_fixes/native6/test_era5.py b/tests/integration/cmor/_fixes/native6/test_era5.py index 7efabef8f9..2c07eaaf70 100644 --- a/tests/integration/cmor/_fixes/native6/test_era5.py +++ b/tests/integration/cmor/_fixes/native6/test_era5.py @@ -1086,4 +1086,4 @@ def test_cmorization(era5_cubes, cmor_cubes, var, mip): coord.bounds = np.round(coord.bounds, decimals=7) print('cmor_cube:', cmor_cube) print('fixed_cube:', fixed_cube) - assert fixed_cube == cmor_cube \ No newline at end of file + assert fixed_cube == cmor_cube From fe10c7a710df87cbdc6b276ec689af615c0d019a Mon Sep 17 00:00:00 2001 From: Valeriu Predoi Date: Tue, 24 Oct 2023 14:46:36 +0100 Subject: [PATCH 06/11] no newline --- esmvalcore/cmor/tables/custom/CMOR_sfcWind.dat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esmvalcore/cmor/tables/custom/CMOR_sfcWind.dat b/esmvalcore/cmor/tables/custom/CMOR_sfcWind.dat index b8af9a39f0..856fb3b8f6 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_sfcWind.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_sfcWind.dat @@ -20,4 +20,4 @@ type: real valid_min: valid_max: !---------------------------------- -! \ No newline at end of file +! From 852526a57247d8d94c2a6fbf7926b7e5a9b90ebd Mon Sep 17 00:00:00 2001 From: Valeriu Predoi Date: Tue, 24 Oct 2023 16:35:01 +0100 Subject: [PATCH 07/11] allow for extra information to be printed out should cube egality fails the test --- tests/integration/cmor/_fixes/native6/test_era5.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/integration/cmor/_fixes/native6/test_era5.py b/tests/integration/cmor/_fixes/native6/test_era5.py index 2c07eaaf70..4068d97547 100644 --- a/tests/integration/cmor/_fixes/native6/test_era5.py +++ b/tests/integration/cmor/_fixes/native6/test_era5.py @@ -1086,4 +1086,13 @@ def test_cmorization(era5_cubes, cmor_cubes, var, mip): coord.bounds = np.round(coord.bounds, decimals=7) print('cmor_cube:', cmor_cube) print('fixed_cube:', fixed_cube) + print('cmor_cube data:', cmor_cube.data) + print('fixed_cube data:', fixed_cube.data) + print("cmor_cube coords:") + for coord in cmor_cube.coords(): + print(coord) + print("\n") + print("fixed_cube coords:") + for coord in fixed_cube.coords(): + print(coord) assert fixed_cube == cmor_cube From 7cb3f8972810101f56d359c43bfe125d98f21867 Mon Sep 17 00:00:00 2001 From: Valeriu Predoi Date: Wed, 25 Oct 2023 12:50:12 +0100 Subject: [PATCH 08/11] spit out more info if the test fails --- tests/integration/cmor/_fixes/native6/test_era5.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/cmor/_fixes/native6/test_era5.py b/tests/integration/cmor/_fixes/native6/test_era5.py index 4068d97547..70b432541d 100644 --- a/tests/integration/cmor/_fixes/native6/test_era5.py +++ b/tests/integration/cmor/_fixes/native6/test_era5.py @@ -1084,6 +1084,7 @@ def test_cmorization(era5_cubes, cmor_cubes, var, mip): coord.points = np.round(coord.points, decimals=7) if coord.bounds is not None: coord.bounds = np.round(coord.bounds, decimals=7) + print("Test results for variable/MIP: ", var, mip) print('cmor_cube:', cmor_cube) print('fixed_cube:', fixed_cube) print('cmor_cube data:', cmor_cube.data) From 603d3400f87be8313923c4888b5a6e891e61ff87 Mon Sep 17 00:00:00 2001 From: Elizaveta Malinina Date: Thu, 26 Oct 2023 21:59:41 +0000 Subject: [PATCH 09/11] removing the time fixes for Uas and Vas --- esmvalcore/cmor/_fixes/native6/era5.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/esmvalcore/cmor/_fixes/native6/era5.py b/esmvalcore/cmor/_fixes/native6/era5.py index 9e1b68ac2c..d76fd21701 100644 --- a/esmvalcore/cmor/_fixes/native6/era5.py +++ b/esmvalcore/cmor/_fixes/native6/era5.py @@ -334,26 +334,6 @@ def fix_metadata(self, cubes): return cubes -class Uas(Fix): - """Fixes for uas.""" - - def fix_metadata(self, cubes): - """Fix metadata.""" - for cube in cubes: - fix_hourly_time_coordinate(cube) - return cubes - - -class Vas(Fix): - """Fixes for vas.""" - - def fix_metadata(self, cubes): - """Fix metadata.""" - for cube in cubes: - fix_hourly_time_coordinate(cube) - return cubes - - class Zg(Fix): """Fixes for Geopotential.""" From 4b67bd73ab8ed7f72a8851ecaab8ed65cd755d29 Mon Sep 17 00:00:00 2001 From: Valeriu Predoi Date: Mon, 30 Oct 2023 14:52:21 +0000 Subject: [PATCH 10/11] add to test for full coverage --- tests/unit/preprocessor/_derive/test_sfcwind.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/unit/preprocessor/_derive/test_sfcwind.py b/tests/unit/preprocessor/_derive/test_sfcwind.py index 1d0b655896..a249c4fe9c 100644 --- a/tests/unit/preprocessor/_derive/test_sfcwind.py +++ b/tests/unit/preprocessor/_derive/test_sfcwind.py @@ -27,6 +27,16 @@ def cubes(): def test_sfcwind_calculate(cubes): """Test function ``calculate``.""" derived_var = sfcwind.DerivedVariable() + required_vars = derived_var.required("CMIP5") + expected_required_vars = [ + { + 'short_name': 'uas' + }, + { + 'short_name': 'vas' + }, + ] + assert required_vars == expected_required_vars out_cube = derived_var.calculate(cubes) assert out_cube.shape == (1, 1, 1) assert out_cube.units == 'm s-1' From 66cfcfba62e06f75a1951d4b90287d9d506b9e41 Mon Sep 17 00:00:00 2001 From: Bouwe Andela Date: Wed, 21 Feb 2024 15:07:37 +0100 Subject: [PATCH 11/11] Remove custom CMOR table entry as it is no longer needed --- .../cmor/tables/custom/CMOR_sfcWind.dat | 23 ------------------- 1 file changed, 23 deletions(-) delete mode 100644 esmvalcore/cmor/tables/custom/CMOR_sfcWind.dat diff --git a/esmvalcore/cmor/tables/custom/CMOR_sfcWind.dat b/esmvalcore/cmor/tables/custom/CMOR_sfcWind.dat deleted file mode 100644 index 856fb3b8f6..0000000000 --- a/esmvalcore/cmor/tables/custom/CMOR_sfcWind.dat +++ /dev/null @@ -1,23 +0,0 @@ -SOURCE: CMIP5 -!============ -variable_entry: sfcWind -!============ -modeling_realm: atmos -!---------------------------------- -! Variable attributes: -!---------------------------------- -standard_name: wind_speed -units: m s-1 -cell_methods: area: mean time: mean -cell_measures: area: areacella -long_name: Near-Surface Wind Speed -comment: This is the wind speed computed from the mean u and v components of wind -!---------------------------------- -! Additional variable information: -!---------------------------------- -dimensions: longitude latitude time -type: real -valid_min: -valid_max: -!---------------------------------- -!