Skip to content

Commit

Permalink
Add support for string coordinates (#657)
Browse files Browse the repository at this point in the history
  • Loading branch information
Javier Vegas-Regidor authored Jul 1, 2020
1 parent 3dc653b commit 3f5340a
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 35 deletions.
7 changes: 6 additions & 1 deletion esmvalcore/cmor/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,8 @@ def _check_coord_monotonicity_and_direction(self, cmor, coord, var_name):
"""Check monotonicity and direction of coordinate."""
if coord.ndim > 1:
return
if coord.dtype.kind == 'U':
return
if not coord.is_monotonic():
self.report_critical(self._is_msg, var_name, 'monotonic')
if len(coord.points) == 1:
Expand Down Expand Up @@ -567,7 +569,10 @@ def _set_range_in_0_360(array):
def _check_requested_values(self, coord, coord_info, var_name):
"""Check requested values."""
if coord_info.requested:
cmor_points = [float(val) for val in coord_info.requested]
try:
cmor_points = [float(val) for val in coord_info.requested]
except ValueError:
cmor_points = coord_info.requested
coord_points = list(coord.points)
for point in cmor_points:
if point not in coord_points:
Expand Down
93 changes: 59 additions & 34 deletions tests/unit/cmor/test_cmor_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,33 +201,28 @@ def test_check_no_multiple_coords_same_stdname(self):

def test_check_bad_standard_name_auto_fix(self):
"""Test check pass for a bad standard_name with automatic fixes."""
self.cube = self.get_cube(self.var_info)
self.cube.standard_name = 'wind_speed'
self._check_cube(automatic_fixes=True)
self._check_cube()

def test_check_bad_standard_name(self):
"""Test check fails for a bad short_name."""
self.cube = self.get_cube(self.var_info)
self.cube.standard_name = 'wind_speed'
self._check_fails_in_metadata(automatic_fixes=False)

def test_check_bad_long_name_auto_fix(self):
"""Test check pass for a bad standard_name with automatic fixes."""
self.cube = self.get_cube(self.var_info)
self.cube.long_name = 'bad_name'
self._check_cube(automatic_fixes=True)
self._check_cube()

def test_check_bad_long_name_auto_fix_report_warning(self):
"""Test check pass for a bad standard_name with automatic fixes."""
self.cube = self.get_cube(self.var_info)
self.cube.long_name = 'bad_name'
self._check_warnings_on_metadata(automatic_fixes=True)

def test_check_bad_long_name(self):
"""Test check fails for a bad short_name."""
self.cube = self.get_cube(self.var_info)
self.cube.long_name = 'bad_name'
self._check_fails_in_metadata()

Expand Down Expand Up @@ -256,7 +251,6 @@ def test_check_with_no_positive_cmip5(self):

def test_check_with_no_positive_cmip6(self):
"""Check CMIP6 variable with no positive attribute report warning."""
self.cube = self.get_cube(self.var_info)
self.var_info.positive = 'up'
self.var_info.table_type = 'CMIP6'
self._check_warnings_on_metadata()
Expand Down Expand Up @@ -301,21 +295,18 @@ def test_rank_unstructured_grid(self):
def test_check_bad_var_standard_name_strict_flag(self):
"""Test check fails for a bad variable standard_name with
--cmor-check strict."""
self.cube = self.get_cube(self.var_info)
self.cube.standard_name = 'wind_speed'
self._check_fails_in_metadata(automatic_fixes=False)

def test_check_bad_var_long_name_strict_flag(self):
"""Test check fails for a bad variable long_name with
--cmor-check strict."""
self.cube = self.get_cube(self.var_info)
self.cube.long_name = "Near-Surface Wind Speed"
self._check_fails_in_metadata(automatic_fixes=False)

def test_check_bad_var_units_strict_flag(self):
"""Test check fails for a bad variable units with
--cmor-check strict."""
self.cube = self.get_cube(self.var_info)
self.cube.units = "kg"
self._check_fails_in_metadata(automatic_fixes=False)

Expand Down Expand Up @@ -364,31 +355,27 @@ def test_check_missing_time_strict_flag(self):
def test_check_missing_coord_strict_flag(self):
"""Test check fails for missing coord other than lat and lon
with --cmor-check strict"""
self.cube = self.get_cube(self.var_info)
self.var_info.coordinates = {'height2m': CoordinateInfoMock('height2m')
}
self._check_fails_in_metadata(automatic_fixes=False)

def test_check_bad_var_standard_name_relaxed_flag(self):
"""Test check reports warning for a bad variable standard_name with
--cmor-check relaxed."""
self.cube = self.get_cube(self.var_info)
self.cube.standard_name = 'wind_speed'
self._check_warnings_on_metadata(automatic_fixes=False,
check_level=CheckLevels.RELAXED)

def test_check_bad_var_long_name_relaxed_flag(self):
"""Test check reports warning for a bad variable long_name with
--cmor-check relaxed."""
self.cube = self.get_cube(self.var_info)
self.cube.long_name = "Near-Surface Wind Speed"
self._check_warnings_on_metadata(automatic_fixes=False,
check_level=CheckLevels.RELAXED)

def test_check_bad_var_units_relaxed_flag(self):
"""Test check reports warning for a bad variable units with
--cmor-check relaxed."""
self.cube = self.get_cube(self.var_info)
self.cube.units = "kg"
self._check_warnings_on_metadata(automatic_fixes=False,
check_level=CheckLevels.RELAXED)
Expand Down Expand Up @@ -444,7 +431,6 @@ def test_check_missing_time_relaxed_flag(self):
def test_check_missing_coord_relaxed_flag(self):
"""Test check reports warning for missing coord other than lat and lon
with --cmor-check relaxed"""
self.cube = self.get_cube(self.var_info)
self.var_info.coordinates = {'height2m': CoordinateInfoMock('height2m')
}
self._check_warnings_on_metadata(automatic_fixes=False,
Expand All @@ -453,23 +439,20 @@ def test_check_missing_coord_relaxed_flag(self):
def test_check_bad_var_standard_name_none_flag(self):
"""Test check reports warning for a bad variable standard_name with
--cmor-check ignore."""
self.cube = self.get_cube(self.var_info)
self.cube.standard_name = 'wind_speed'
self._check_warnings_on_metadata(automatic_fixes=False,
check_level=CheckLevels.IGNORE)

def test_check_bad_var_long_name_none_flag(self):
"""Test check reports warning for a bad variable long_name with
--cmor-check ignore."""
self.cube = self.get_cube(self.var_info)
self.cube.long_name = "Near-Surface Wind Speed"
self._check_warnings_on_metadata(automatic_fixes=False,
check_level=CheckLevels.IGNORE)

def test_check_bad_var_units_none_flag(self):
"""Test check reports warning for a bad variable unit with
--cmor-check ignore."""
self.cube = self.get_cube(self.var_info)
self.cube.units = "kg"
self._check_warnings_on_metadata(automatic_fixes=False,
check_level=CheckLevels.IGNORE)
Expand Down Expand Up @@ -528,7 +511,6 @@ def test_check_missing_time_none_flag(self):
def test_check_missing_coord_none_flag(self):
"""Test check reports warning for missing coord other than lat, lon and
time with --cmor-check ignore"""
self.cube = self.get_cube(self.var_info)
self.var_info.coordinates = {'height2m': CoordinateInfoMock('height2m')
}
self._check_warnings_on_metadata(automatic_fixes=False,
Expand Down Expand Up @@ -574,6 +556,25 @@ def test_non_requested(self):
checker.check_metadata()
self.assertTrue(checker.has_warnings())

def test_requested_str_values(self):
"""
Warning if requested values are not present.
Check issue a warning if a values requested
for a coordinate are not correct in the metadata step
"""
region_coord = CoordinateInfoMock('basin')
region_coord.standard_name = 'region'
region_coord.units = ''
region_coord.requested = [
"atlantic_arctic_ocean",
"indian_pacific_ocean",
"global_ocean"
]
self.var_info.coordinates['region'] = region_coord
self.cube = self.get_cube(self.var_info)
self._check_cube()

def test_non_increasing(self):
"""Fail in metadata if increasing coordinate is decreasing."""
coord = self.cube.coord('latitude')
Expand Down Expand Up @@ -940,13 +941,13 @@ def get_cube(self,
if not frequency:
frequency = var_info.frequency
for dim_spec in var_info.coordinates.values():
coord = self._create_coord_from_spec(dim_spec, set_time_units,
frequency)
if isinstance(coord, iris.coords.DimCoord):
coord = self._create_coord_from_spec(
dim_spec, set_time_units, frequency)
if dim_spec.value:
scalar_coords.append(coord)
else:
coords.append((coord, index))
index += 1
elif isinstance(coord, iris.coords.AuxCoord):
scalar_coords.append(coord)

valid_min, valid_max = self._get_valid_limits(var_info)
var_data = (np.ones(len(coords) * [20], 'f') *
Expand All @@ -971,7 +972,10 @@ def get_cube(self,
cube.attributes['positive'] = var_info.positive

for coord, i in coords:
cube.add_dim_coord(coord, i)
if isinstance(coord, iris.coords.DimCoord):
cube.add_dim_coord(coord, i)
else:
cube.add_aux_coord(coord, i)

for coord in scalar_coords:
cube.add_aux_coord(coord)
Expand Down Expand Up @@ -1008,9 +1012,14 @@ def _create_coord_from_spec(self, coord_spec, set_time_units, frequency):
if coord_spec.value:
return self._construct_scalar_coord(coord_spec)

if coord_spec.requested:
try:
float(coord_spec.requested[0])
except ValueError:
return self._construct_array_coord(coord_spec, aux=True)
return self._construct_array_coord(coord_spec)

def _construct_array_coord(self, dim_spec):
def _construct_array_coord(self, dim_spec, aux=False):
if dim_spec.units.startswith("days since "):
values = self._get_time_values(dim_spec)
unit = Unit(dim_spec.units, calendar='360_day')
Expand All @@ -1019,19 +1028,35 @@ def _construct_array_coord(self, dim_spec):
unit = Unit(dim_spec.units)
# Set up attributes dictionary
coord_atts = {'stored_direction': dim_spec.stored_direction}
coord = iris.coords.DimCoord(
values,
standard_name=dim_spec.standard_name,
long_name=dim_spec.long_name,
var_name=dim_spec.out_name,
attributes=coord_atts,
units=unit,
)
coord.guess_bounds()
if aux:
coord = iris.coords.AuxCoord(
values,
standard_name=dim_spec.standard_name,
long_name=dim_spec.long_name,
var_name=dim_spec.out_name,
attributes=coord_atts,
units=unit,
)
else:
coord = iris.coords.DimCoord(
values,
standard_name=dim_spec.standard_name,
long_name=dim_spec.long_name,
var_name=dim_spec.out_name,
attributes=coord_atts,
units=unit,
)
coord.guess_bounds()
return coord

@staticmethod
def _get_values(dim_spec):
if dim_spec.requested:
try:
float(dim_spec.requested[0])
except ValueError:
return dim_spec.requested + [
f'Value{x}' for x in range(len(dim_spec.requested), 20)]
valid_min = dim_spec.valid_min
if valid_min:
valid_min = float(valid_min)
Expand Down

0 comments on commit 3f5340a

Please sign in to comment.