Skip to content

Commit

Permalink
Adding sfcWind derivation from uas and vas (#2242)
Browse files Browse the repository at this point in the history
Co-authored-by: Elizaveta Malinina <[email protected]>
Co-authored-by: Valeriu Predoi <[email protected]>
Co-authored-by: Bouwe Andela <[email protected]>
  • Loading branch information
4 people authored Feb 21, 2024
1 parent 265d2f4 commit bb7866e
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 2 deletions.
4 changes: 2 additions & 2 deletions esmvalcore/preprocessor/_derive/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
35 changes: 35 additions & 0 deletions esmvalcore/preprocessor/_derive/sfcwind.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Derivation of variable `sfcWind`."""

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 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'))

sfcwind_cube = (uas_cube**2 + vas_cube**2)**0.5

return sfcwind_cube
49 changes: 49 additions & 0 deletions tests/integration/cmor/_fixes/native6/test_era5.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand All @@ -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'),
]
]
Expand All @@ -1045,6 +1084,16 @@ 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)
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
49 changes: 49 additions & 0 deletions tests/unit/preprocessor/_derive/test_sfcwind.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""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()
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'
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])

0 comments on commit bb7866e

Please sign in to comment.