From c62a09376e33e9971b87d6e1749f88b2c2718376 Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Mon, 9 Mar 2020 14:35:34 +0100 Subject: [PATCH 1/2] Added fixes for tos of BCC models --- esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1.py | 28 ++++--- esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1_m.py | 59 +------------- esmvalcore/cmor/_fixes/cmip6/bcc_csm2_mr.py | 33 ++++++++ esmvalcore/cmor/_fixes/cmip6/bcc_esm1.py | 6 ++ .../cmor/_fixes/cmip5/test_bcc_csm1_1.py | 65 ++++++++++++++- .../cmor/_fixes/cmip5/test_bcc_csm1_1_m.py | 15 +++- .../cmor/_fixes/cmip6/test_bcc_csm2_mr.py | 81 +++++++++++++++++++ .../cmor/_fixes/cmip6/test_bcc_esm1.py | 21 +++++ 8 files changed, 236 insertions(+), 72 deletions(-) create mode 100644 esmvalcore/cmor/_fixes/cmip6/bcc_csm2_mr.py create mode 100644 esmvalcore/cmor/_fixes/cmip6/bcc_esm1.py create mode 100644 tests/integration/cmor/_fixes/cmip6/test_bcc_csm2_mr.py create mode 100644 tests/integration/cmor/_fixes/cmip6/test_bcc_esm1.py diff --git a/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1.py b/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1.py index aa1941cf56..0f1baa2f24 100644 --- a/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1.py +++ b/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1.py @@ -17,6 +17,7 @@ def fix_data(self, cube): Parameters ---------- cube: iris.cube.Cube + Input cube to fix. Returns ------- @@ -27,10 +28,12 @@ def fix_data(self, cube): rlon = cube.coord('grid_longitude').points # Transform grid latitude/longitude to array indices [0, 1, 2, ...] - rlat_to_idx = InterpolatedUnivariateSpline( - rlat, np.arange(len(rlat)), k=1) - rlon_to_idx = InterpolatedUnivariateSpline( - rlon, np.arange(len(rlon)), k=1) + rlat_to_idx = InterpolatedUnivariateSpline(rlat, + np.arange(len(rlat)), + k=1) + rlon_to_idx = InterpolatedUnivariateSpline(rlon, + np.arange(len(rlon)), + k=1) rlat_idx_bnds = rlat_to_idx(cube.coord('grid_latitude').bounds) rlon_idx_bnds = rlon_to_idx(cube.coord('grid_longitude').bounds) @@ -38,16 +41,17 @@ def fix_data(self, cube): lat_vertices = [] lon_vertices = [] for (i, j) in [(0, 0), (0, 1), (1, 1), (1, 0)]: - (rlat_v, rlon_v) = np.meshgrid( - rlat_idx_bnds[:, i], rlon_idx_bnds[:, j], indexing='ij') + (rlat_v, rlon_v) = np.meshgrid(rlat_idx_bnds[:, i], + rlon_idx_bnds[:, j], + indexing='ij') lat_vertices.append( - map_coordinates( - cube.coord('latitude').points, [rlat_v, rlon_v], - mode='nearest')) + map_coordinates(cube.coord('latitude').points, + [rlat_v, rlon_v], + mode='nearest')) lon_vertices.append( - map_coordinates( - cube.coord('longitude').points, [rlat_v, rlon_v], - mode='wrap')) + map_coordinates(cube.coord('longitude').points, + [rlat_v, rlon_v], + mode='wrap')) lat_vertices = np.array(lat_vertices) lon_vertices = np.array(lon_vertices) lat_vertices = np.moveaxis(lat_vertices, 0, -1) diff --git a/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1_m.py b/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1_m.py index 266299c4f1..11956558fe 100644 --- a/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1_m.py +++ b/esmvalcore/cmor/_fixes/cmip5/bcc_csm1_1_m.py @@ -1,61 +1,6 @@ - """Fixes for bcc-csm1-1-m.""" -import numpy as np -from scipy.interpolate import InterpolatedUnivariateSpline -from scipy.ndimage import map_coordinates - -from ..fix import Fix +from .bcc_csm1_1 import Tos as BaseTos -class Tos(Fix): +class Tos(BaseTos): """Fixes for tos.""" - - def fix_data(self, cube): - """Fix data. - - Calculate missing latitude/longitude boundaries using interpolation. - - Parameters - ---------- - cube: iris.cube.Cube - - Returns - ------- - iris.cube.Cube - - """ - rlat = cube.coord('grid_latitude').points - rlon = cube.coord('grid_longitude').points - - # Transform grid latitude/longitude to array indices [0, 1, 2, ...] - rlat_to_idx = InterpolatedUnivariateSpline( - rlat, np.arange(len(rlat)), k=1) - rlon_to_idx = InterpolatedUnivariateSpline( - rlon, np.arange(len(rlon)), k=1) - rlat_idx_bnds = rlat_to_idx(cube.coord('grid_latitude').bounds) - rlon_idx_bnds = rlon_to_idx(cube.coord('grid_longitude').bounds) - - # Calculate latitude/longitude vertices by interpolation - lat_vertices = [] - lon_vertices = [] - for (i, j) in [(0, 0), (0, 1), (1, 1), (1, 0)]: - (rlat_v, rlon_v) = np.meshgrid( - rlat_idx_bnds[:, i], rlon_idx_bnds[:, j], indexing='ij') - lat_vertices.append( - map_coordinates( - cube.coord('latitude').points, [rlat_v, rlon_v], - mode='nearest')) - lon_vertices.append( - map_coordinates( - cube.coord('longitude').points, [rlat_v, rlon_v], - mode='wrap')) - lat_vertices = np.array(lat_vertices) - lon_vertices = np.array(lon_vertices) - lat_vertices = np.moveaxis(lat_vertices, 0, -1) - lon_vertices = np.moveaxis(lon_vertices, 0, -1) - - # Copy vertices to cube - cube.coord('latitude').bounds = lat_vertices - cube.coord('longitude').bounds = lon_vertices - - return cube diff --git a/esmvalcore/cmor/_fixes/cmip6/bcc_csm2_mr.py b/esmvalcore/cmor/_fixes/cmip6/bcc_csm2_mr.py new file mode 100644 index 0000000000..b0313e1eb9 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/bcc_csm2_mr.py @@ -0,0 +1,33 @@ +"""Fixes for BCC-CSM2-MR model.""" +from ..cmip5.bcc_csm1_1 import Tos as BaseTos + + +class Tos(BaseTos): + """Fixes for tos.""" + + def fix_metadata(self, cubes): + """Rename ``var_name`` of 1D-``latitude`` and 1D-``longitude``. + + Parameters + ---------- + cubes : iris.cube.CubeList + Input cubes. + + Returns + ------- + iris.cube.CubeList + + """ + cube = self.get_cube_from_list(cubes) + lat_coord = cube.coord('latitude', dimensions=(1, )) + lon_coord = cube.coord('longitude', dimensions=(2, )) + lat_coord.standard_name = None + lat_coord.long_name = 'grid_latitude' + lat_coord.var_name = 'i' + lat_coord.units = '1' + lon_coord.standard_name = None + lon_coord.long_name = 'grid_longitude' + lon_coord.var_name = 'j' + lon_coord.units = '1' + lon_coord.circular = False + return cubes diff --git a/esmvalcore/cmor/_fixes/cmip6/bcc_esm1.py b/esmvalcore/cmor/_fixes/cmip6/bcc_esm1.py new file mode 100644 index 0000000000..e05c4940a1 --- /dev/null +++ b/esmvalcore/cmor/_fixes/cmip6/bcc_esm1.py @@ -0,0 +1,6 @@ +"""Fixes for BCC-ESM1 model.""" +from .bcc_csm2_mr import Tos as BaseTos + + +class Tos(BaseTos): + """Fixes for tos.""" diff --git a/tests/integration/cmor/_fixes/cmip5/test_bcc_csm1_1.py b/tests/integration/cmor/_fixes/cmip5/test_bcc_csm1_1.py index 30c0c44466..f6ccf8d6bf 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_bcc_csm1_1.py +++ b/tests/integration/cmor/_fixes/cmip5/test_bcc_csm1_1.py @@ -7,7 +7,70 @@ class TestTos(unittest.TestCase): """Test tos fixes.""" + def test_get(self): - """Test fix get""" + """Test fix get.""" self.assertListEqual( Fix.get_fixes('CMIP5', 'BCC-CSM1-1', 'Amon', 'tos'), [Tos(None)]) + + +def test_tos_fix_data(): + """Test ``fix_data`` for ``tos``.""" + grid_lat = iris.coords.DimCoord( + [20.0, 40.0], + bounds=[[10.0, 30.0], [30.0, 50.0]], + var_name='rlat', + standard_name='grid_latitude', + ) + grid_lon = iris.coords.DimCoord( + [10.0, 20.0, 30.0], + bounds=[[5.0, 15.0], [15.0, 25.0], [25.0, 35.0]], + var_name='rlon', + standard_name='grid_longitude', + ) + latitude = iris.coords.AuxCoord( + [[-40.0, -20.0, 0.0], [-20.0, 0.0, 20.0]], + var_name='lat', + standard_name='latitude', + units='degrees_north', + ) + longitude = iris.coords.AuxCoord( + [[100.0, 140.0, 180.0], [80.0, 100.0, 120.0]], + var_name='lon', + standard_name='longitude', + units='degrees_east', + ) + + # Create cube without bounds + cube = iris.cube.Cube( + np.full((2, 3), 300.0), + var_name='tos', + units='K', + dim_coords_and_dims=[(grid_lat, 0), (grid_lon, 1)], + aux_coords_and_dims=[(latitude, (0, 1)), (longitude, (0, 1))], + ) + assert cube.coord('latitude').bounds is None + assert cube.coord('longitude').bounds is None + + # Apply fix + fix = Tos(None) + fixed_cube = fix.fix_data(cube) + assert fixed_cube is cube + assert fixed_cube.coord('latitude').bounds is not None + assert fixed_cube.coord('longitude').bounds is not None + latitude_bounds = np.array([[[-40, -33.75, -23.75, -30.0], + [-33.75, -6.25, 3.75, -23.75], + [-6.25, -1.02418074021670e-14, 10.0, 3.75]], + [[-30.0, -23.75, -13.75, -20.0], + [-23.75, 3.75, 13.75, -13.75], + [3.75, 10.0, 20.0, 13.75]]]) + np.testing.assert_allclose(fixed_cube.coord('latitude').bounds, + latitude_bounds) + longitude_bounds = np.array([[[140.625, 99.375, 99.375, 140.625], + [99.375, 140.625, 140.625, 99.375], + [140.625, 99.375, 99.375, 140.625]], + [[140.625, 99.375, 99.375, 140.625], + [99.375, 140.625, 140.625, 99.375], + [140.625, 99.375, 99.375, 140.625]]]) + np.testing.assert_allclose(fixed_cube.coord('longitude').bounds, + longitude_bounds) diff --git a/tests/integration/cmor/_fixes/cmip5/test_bcc_csm1_1_m.py b/tests/integration/cmor/_fixes/cmip5/test_bcc_csm1_1_m.py index 01f427f478..efcec0b79c 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_bcc_csm1_1_m.py +++ b/tests/integration/cmor/_fixes/cmip5/test_bcc_csm1_1_m.py @@ -7,7 +7,18 @@ class TestTos(unittest.TestCase): """Test tos fixes.""" + def test_get(self): - """Test fix get""" + """Test fix get.""" self.assertListEqual( - Fix.get_fixes('CMIP5', 'BCC-CSM1-1-M', 'Amon', 'tos'), [Tos(None)]) + Fix.get_fixes('CMIP5', 'bcc-csm1-1-m', 'Amon', 'tos'), [Tos(None)]) + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip5.bcc_csm1_1_m.BaseTos.fix_data', + autospec=True) +def test_tos_fix_data(mock_base_fix_data): + """Test ``fix_data`` for ``tos``.""" + fix = Tos(None) + fix.fix_data('cubes') + mock_base_fix_data.assert_called_once_with(fix, 'cubes') diff --git a/tests/integration/cmor/_fixes/cmip6/test_bcc_csm2_mr.py b/tests/integration/cmor/_fixes/cmip6/test_bcc_csm2_mr.py new file mode 100644 index 0000000000..c29e84cdb2 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_bcc_csm2_mr.py @@ -0,0 +1,81 @@ +"""Test fixes for BCC-CSM2-MR.""" +import unittest + +import iris + +from esmvalcore.cmor._fixes.cmip6.bcc_csm2_mr import Tos +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_tos_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'BCC-CSM2-MR', 'Omon', 'tos') + assert fix == [Tos(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip6.bcc_csm2_mr.BaseTos.fix_data', + autospec=True) +def test_tos_fix_data(mock_base_fix_data): + """Test ``fix_data`` for ``tos``.""" + fix = Tos(None) + fix.fix_data('cubes') + mock_base_fix_data.assert_called_once_with(fix, 'cubes') + + +def test_tos_fix_metadata(): + """Test ``fix_metadata`` for ``tos``.""" + grid_lat = iris.coords.DimCoord([1.0], + var_name='lat', + standard_name='latitude', + long_name='latitude', + units='degrees_north', + attributes={'1D': '1'}) + grid_lon = iris.coords.DimCoord([1.0], + var_name='lon', + standard_name='longitude', + long_name='longitude', + units='degrees_east', + circular=True, + attributes={'1D': '1'}) + latitude = iris.coords.AuxCoord([[0.0]], + var_name='lat', + standard_name='latitude', + long_name='latitude', + units='degrees_north') + longitude = iris.coords.AuxCoord([[0]], + var_name='lon', + standard_name='longitude', + long_name='longitude', + units='degrees_east') + cube = iris.cube.Cube( + [[[0.0]]], + var_name='tos', + long_name='sea_surface_temperature', + dim_coords_and_dims=[(grid_lat.copy(), 1), (grid_lon.copy(), 2)], + aux_coords_and_dims=[(latitude.copy(), (1, 2)), + (longitude.copy(), (1, 2))], + ) + cubes = iris.cube.CubeList([cube, iris.cube.Cube(0.0)]) + fix = Tos(None) + fixed_cubes = fix.fix_metadata(cubes) + tos_cube = fixed_cubes.extract_strict('sea_surface_temperature') + + # No duplicates anymore + assert len(tos_cube.coords('latitude')) == 1 + assert len(tos_cube.coords('longitude')) == 1 + + # Latitude + grid_lat = tos_cube.coord('grid_latitude') + assert grid_lat.var_name == 'i' + assert grid_lat.long_name == 'grid_latitude' + assert grid_lat.standard_name is None + assert grid_lat.units == '1' + + # Longitude + grid_lon = tos_cube.coord('grid_longitude') + assert grid_lon.var_name == 'j' + assert grid_lon.long_name == 'grid_longitude' + assert grid_lon.standard_name is None + assert grid_lon.units == '1' + assert not grid_lon.circular diff --git a/tests/integration/cmor/_fixes/cmip6/test_bcc_esm1.py b/tests/integration/cmor/_fixes/cmip6/test_bcc_esm1.py new file mode 100644 index 0000000000..c3479b0d45 --- /dev/null +++ b/tests/integration/cmor/_fixes/cmip6/test_bcc_esm1.py @@ -0,0 +1,21 @@ +"""Test fixes for BCC-ESM1.""" +import unittest + +from esmvalcore.cmor._fixes.cmip6.bcc_esm1 import Tos +from esmvalcore.cmor._fixes.fix import Fix + + +def test_get_tos_fix(): + """Test getting of fix.""" + fix = Fix.get_fixes('CMIP6', 'BCC-ESM1', 'Omon', 'tos') + assert fix == [Tos(None)] + + +@unittest.mock.patch( + 'esmvalcore.cmor._fixes.cmip6.bcc_esm1.BaseTos.fix_metadata', + autospec=True) +def test_tos_fix_metadata(mock_base_fix_metadata): + """Test ``fix_metadata`` for ``tos``.""" + fix = Tos(None) + fix.fix_metadata('cubes') + mock_base_fix_metadata.assert_called_once_with(fix, 'cubes') From 66cbdad2b46fe13d0baabd6d159d477133ea487a Mon Sep 17 00:00:00 2001 From: Manuel Schlund Date: Mon, 9 Mar 2020 14:53:17 +0100 Subject: [PATCH 2/2] Fixed tests --- tests/integration/cmor/_fixes/cmip5/test_bcc_csm1_1.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/integration/cmor/_fixes/cmip5/test_bcc_csm1_1.py b/tests/integration/cmor/_fixes/cmip5/test_bcc_csm1_1.py index f6ccf8d6bf..28c838f3de 100644 --- a/tests/integration/cmor/_fixes/cmip5/test_bcc_csm1_1.py +++ b/tests/integration/cmor/_fixes/cmip5/test_bcc_csm1_1.py @@ -1,6 +1,9 @@ """Test Access1-0 fixes.""" import unittest +import iris +import numpy as np + from esmvalcore.cmor.fix import Fix from esmvalcore.cmor._fixes.cmip5.bcc_csm1_1 import Tos