Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding sfcWind derivation from uas and vas #2242

Merged
merged 15 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions esmvalcore/cmor/_fixes/native6/era5.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
malininae marked this conversation as resolved.
Show resolved Hide resolved


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."""

Expand Down
23 changes: 23 additions & 0 deletions esmvalcore/cmor/tables/custom/CMOR_sfcWind.dat
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
SOURCE: CMIP5
bouweandela marked this conversation as resolved.
Show resolved Hide resolved
!============
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:
!----------------------------------
!
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:
valeriupredoi marked this conversation as resolved.
Show resolved Hide resolved
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

Check notice on line 35 in esmvalcore/preprocessor/_derive/sfcwind.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

esmvalcore/preprocessor/_derive/sfcwind.py#L35

no newline at end of file (W292)
39 changes: 39 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 Down
39 changes: 39 additions & 0 deletions tests/unit/preprocessor/_derive/test_sfcwind.py
Original file line number Diff line number Diff line change
@@ -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])