def test_mask_handling(): """ Test masked data handling for :func:`esmf_regrid.experimental.unstructured_scheme.GridToMeshESMFRegridder`. Tests masked data handling for multiple valid values for mdtol. """ tgt = _flat_mesh_cube() n_lons = 6 n_lats = 5 lon_bounds = (-180, 180) lat_bounds = (-90, 90) src = _grid_cube(n_lons, n_lats, lon_bounds, lat_bounds, circular=True) data = np.ones([n_lats, n_lons]) mask = np.zeros([n_lats, n_lons]) mask[0, 0] = 1 masked_data = ma.array(data, mask=mask) src.data = masked_data regridder_0 = GridToMeshESMFRegridder(src, tgt, mdtol=0) regridder_05 = GridToMeshESMFRegridder(src, tgt, mdtol=0.05) regridder_1 = GridToMeshESMFRegridder(src, tgt, mdtol=1) result_0 = regridder_0(src) result_05 = regridder_05(src) result_1 = regridder_1(src) expected_data = np.ones(tgt.shape) expected_0 = ma.array(expected_data) expected_05 = ma.array(expected_data, mask=[0, 0, 1, 0, 0, 0]) expected_1 = ma.array(expected_data, mask=[1, 0, 1, 0, 0, 0]) assert ma.allclose(expected_0, result_0.data) assert ma.allclose(expected_05, result_05.data) assert ma.allclose(expected_1, result_1.data)
def test_invalid_method(): """ Test initialisation of :func:`esmf_regrid.experimental.unstructured_scheme.GridToMeshESMFRegridder`. Checks that an error is raised when the method is invalid. """ n_lons = 6 n_lats = 5 lon_bounds = (-180, 180) lat_bounds = (-90, 90) face_tgt = _gridlike_mesh_cube(n_lons, n_lats, location="face") edge_tgt = _gridlike_mesh_cube(n_lons, n_lats, location="edge") node_tgt = _gridlike_mesh_cube(n_lons, n_lats, location="node") src = _grid_cube(n_lons, n_lats, lon_bounds, lat_bounds, circular=True) with pytest.raises(ValueError): _ = GridToMeshESMFRegridder(src, face_tgt, method="other") with pytest.raises(ValueError) as excinfo: _ = GridToMeshESMFRegridder(src, node_tgt, method="conservative") expected_message = ( "Conservative regridding requires a target cube located on " "the face of a cube, target cube had the node location.") assert expected_message in str(excinfo.value) with pytest.raises(ValueError) as excinfo: _ = GridToMeshESMFRegridder(src, edge_tgt, method="bilinear") expected_message = ( "Bilinear regridding requires a target cube with a node " "or face location, target cube had the edge location.") assert expected_message in str(excinfo.value)
def test_invalid_resolution(): """ Test initialisation of :func:`esmf_regrid.experimental.unstructured_scheme.GridToMeshESMFRegridder`. Checks that an error is raised when the resolution is invalid. """ n_lons = 6 n_lats = 5 lon_bounds = (-180, 180) lat_bounds = (-90, 90) tgt = _gridlike_mesh_cube(n_lons, n_lats, location="face") src = _grid_cube(n_lons, n_lats, lon_bounds, lat_bounds, circular=True) with pytest.raises(ValueError) as excinfo: _ = GridToMeshESMFRegridder(src, tgt, method="conservative", resolution=-1) expected_message = "resolution must be a positive integer." assert expected_message in str(excinfo.value) with pytest.raises(ValueError) as excinfo: _ = GridToMeshESMFRegridder(src, tgt, method="bilinear", resolution=4) expected_message = "resolution can only be set for conservative regridding." assert expected_message in str(excinfo.value)
def test_laziness(): """Test that regridding is lazy when source data is lazy.""" n_lons = 12 n_lats = 10 h = 4 lon_bounds = (-180, 180) lat_bounds = (-90, 90) mesh = _gridlike_mesh(n_lons, n_lats) src_data = np.arange(n_lats * n_lons * h).reshape([n_lats, n_lons, h]) src_data = da.from_array(src_data, chunks=[3, 5, 2]) src = Cube(src_data) grid = _grid_cube(n_lons, n_lats, lon_bounds, lat_bounds, circular=True) src.add_dim_coord(grid.coord("latitude"), 0) src.add_dim_coord(grid.coord("longitude"), 1) mesh_coord_x, mesh_coord_y = mesh.to_MeshCoords("face") tgt_data = np.zeros([n_lats * n_lons]) tgt = Cube(tgt_data) tgt.add_aux_coord(mesh_coord_x, 0) tgt.add_aux_coord(mesh_coord_y, 0) rg = GridToMeshESMFRegridder(src, tgt) assert src.has_lazy_data() result = rg(src) assert result.has_lazy_data() out_chunks = result.lazy_data().chunks expected_chunks = ((120, ), (2, 2)) assert out_chunks == expected_chunks assert np.allclose(result.data, src_data.reshape([-1, h]))
def test_mismatched_grids(): """ Test error handling in calling of :func:`esmf_regrid.experimental.unstructured_scheme.GridToMeshESMFRegridder`. Checks that an error is raised when the regridder is called with a cube whose grid does not match with the one used when initialising the regridder. """ tgt = _flat_mesh_cube() n_lons = 6 n_lats = 5 lon_bounds = (-180, 180) lat_bounds = (-90, 90) src = _grid_cube(n_lons, n_lats, lon_bounds, lat_bounds, circular=True) regridder = GridToMeshESMFRegridder(src, tgt) n_lons_other = 3 n_lats_other = 10 src_other = _grid_cube(n_lons_other, n_lats_other, lon_bounds, lat_bounds, circular=True) with pytest.raises(ValueError): _ = regridder(src_other)
def setup_cache(self): SYNTH_DATA_DIR = Path().cwd() / "tmp_data" SYNTH_DATA_DIR.mkdir(exist_ok=True) file = str(SYNTH_DATA_DIR.joinpath("chunked_cube.nc")) lon_bounds = (-180, 180) lat_bounds = (-90, 90) n_lons_src = 100 n_lats_src = 200 n_lons_tgt = 20 n_lats_tgt = 40 h = 2000 grid = _grid_cube(n_lons_src, n_lats_src, lon_bounds, lat_bounds) chunk_size = [n_lats_src, n_lons_src, 10] src_data = da.ones([n_lats_src, n_lons_src, h], chunks=chunk_size) src = Cube(src_data) src.add_dim_coord(grid.coord("latitude"), 0) src.add_dim_coord(grid.coord("longitude"), 1) tgt = _gridlike_mesh_cube(n_lons_tgt, n_lats_tgt) iris.save(src, file, chunksizes=chunk_size) # Construct regridder with a loaded version of the grid for consistency. loaded_src = iris.load_cube(file) regridder = GridToMeshESMFRegridder(loaded_src, tgt) return regridder, file
def _make_grid_to_mesh_regridder(method="conservative", resolution=None, grid_dims=1, circular=True): src_lons = 3 src_lats = 4 tgt_lons = 5 tgt_lats = 6 if circular: lon_bounds = (-180, 180) else: lon_bounds = (-180, 170) lat_bounds = (-90, 90) if grid_dims == 1: src = _grid_cube(src_lons, src_lats, lon_bounds, lat_bounds, circular=circular) else: src = _curvilinear_cube(src_lons, src_lats, lon_bounds, lat_bounds) src.coord("longitude").var_name = "longitude" src.coord("latitude").var_name = "latitude" if method == "bilinear": location = "node" else: location = "face" tgt = _gridlike_mesh_cube(tgt_lons, tgt_lats, location=location) rg = GridToMeshESMFRegridder(src, tgt, method=method, mdtol=0.5, resolution=resolution) return rg, src
def test_default_mdtol(): """ Test initialisation of :func:`esmf_regrid.experimental.unstructured_scheme.GridToMeshESMFRegridder`. Checks that default mdtol values are as expected. """ n_lons = 6 n_lats = 5 lon_bounds = (-180, 180) lat_bounds = (-90, 90) tgt = _gridlike_mesh_cube(n_lons, n_lats) src = _grid_cube(n_lons, n_lats, lon_bounds, lat_bounds, circular=True) rg_con = GridToMeshESMFRegridder(src, tgt, method="conservative") assert rg_con.mdtol == 1 rg_bi = GridToMeshESMFRegridder(src, tgt, method="bilinear") assert rg_bi.mdtol == 0
def setup_cache(self): from esmf_regrid.experimental.io import save_regridder SYNTH_DATA_DIR = Path().cwd() / "tmp_data" SYNTH_DATA_DIR.mkdir(exist_ok=True) destination_file = str(SYNTH_DATA_DIR.joinpath("destination.nc")) file_dict = {"destination": destination_file} for tp in self.params[0]: ( lon_bounds, lat_bounds, n_lons_src, n_lats_src, n_lons_tgt, n_lats_tgt, _, alt_coord_system, ) = self.get_args(tp) src_grid = _grid_cube( n_lons_src, n_lats_src, lon_bounds, lat_bounds, alt_coord_system=alt_coord_system, ) tgt_grid = _grid_cube( n_lons_tgt, n_lats_tgt, lon_bounds, lat_bounds, alt_coord_system=alt_coord_system, ) src_mesh_cube = _gridlike_mesh_cube( n_lons_src, n_lats_src, ) tgt_mesh_cube = _gridlike_mesh_cube( n_lons_tgt, n_lats_tgt, ) rg_dict = {} rg_dict["mesh_to_grid"] = MeshToGridESMFRegridder( src_mesh_cube, tgt_grid) rg_dict["grid_to_mesh"] = GridToMeshESMFRegridder( src_grid, tgt_mesh_cube) for rgt in self.params[1]: regridder = rg_dict[rgt] source_file = str( SYNTH_DATA_DIR.joinpath(f"source_{tp}_{rgt}.nc")) save_regridder(regridder, source_file) file_dict[(tp, rgt)] = source_file return file_dict
def test_invalid_mdtol(): """ Test initialisation of :func:`esmf_regrid.experimental.unstructured_scheme.GridToMeshESMFRegridder`. Checks that an error is raised when mdtol is out of range. """ tgt = _flat_mesh_cube() n_lons = 6 n_lats = 5 lon_bounds = (-180, 180) lat_bounds = (-90, 90) src = _grid_cube(n_lons, n_lats, lon_bounds, lat_bounds, circular=True) with pytest.raises(ValueError): _ = GridToMeshESMFRegridder(src, tgt, mdtol=2) with pytest.raises(ValueError): _ = GridToMeshESMFRegridder(src, tgt, mdtol=-1)
def test_bilinear(): """ Basic test for :func:`esmf_regrid.experimental.unstructured_scheme.GridToMeshESMFRegridder`. Tests with method="bilinear". """ n_lons = 6 n_lats = 5 lon_bounds = (-180, 180) lat_bounds = (-90, 90) src = _grid_cube(n_lons, n_lats, lon_bounds, lat_bounds, circular=True) face_tgt = _gridlike_mesh_cube(n_lons, n_lats, location="face") node_tgt = _gridlike_mesh_cube(n_lons, n_lats, location="node") src = _add_metadata(src) src.data[:] = 1 # Ensure all data in the source is one. face_regridder = GridToMeshESMFRegridder(src, face_tgt, method="bilinear") node_regridder = GridToMeshESMFRegridder(src, node_tgt, method="bilinear") assert face_regridder.regridder.method == "bilinear" assert node_regridder.regridder.method == "bilinear" face_expected_data = np.ones_like(face_tgt.data) node_expected_data = np.ones_like(node_tgt.data) face_result = face_regridder(src) node_result = node_regridder(src) # Lenient check for data. assert np.allclose(face_expected_data, face_result.data) assert np.allclose(node_expected_data, node_result.data) # Check metadata and scalar coords. face_expected_cube = _add_metadata(face_tgt) node_expected_cube = _add_metadata(node_tgt) face_expected_cube.data = face_result.data node_expected_cube.data = node_result.data assert face_expected_cube == face_result assert node_expected_cube == node_result
def test_multidim_cubes(): """ Test for :func:`esmf_regrid.experimental.unstructured_scheme.regrid_rectilinear_to_unstructured`. Tests with multidimensional cubes. The source cube contains coordinates on the dimensions before and after the grid dimensions. """ tgt = _flat_mesh_cube() mesh = tgt.mesh mesh_length = mesh.connectivity(contains_face=True).shape[0] n_lons = 6 n_lats = 5 lon_bounds = (-180, 180) lat_bounds = (-90, 90) grid = _grid_cube(n_lons, n_lats, lon_bounds, lat_bounds, circular=True) h = 2 t = 3 height = DimCoord(np.arange(h), standard_name="height") time = DimCoord(np.arange(t), standard_name="time") src_data = np.empty([t, n_lats, n_lons, h]) src_data[:] = np.arange(t * h).reshape([t, h])[:, np.newaxis, np.newaxis, :] cube = Cube(src_data) cube.add_dim_coord(grid.coord("latitude"), 1) cube.add_dim_coord(grid.coord("longitude"), 2) cube.add_dim_coord(time, 0) cube.add_dim_coord(height, 3) regridder = GridToMeshESMFRegridder(grid, tgt) result = regridder(cube) # Lenient check for data. expected_data = np.empty([t, mesh_length, h]) expected_data[:] = np.arange(t * h).reshape(t, h)[:, np.newaxis, :] assert np.allclose(expected_data, result.data) mesh_coord_x, mesh_coord_y = mesh.to_MeshCoords("face") expected_cube = Cube(expected_data) expected_cube.add_dim_coord(time, 0) expected_cube.add_aux_coord(mesh_coord_x, 1) expected_cube.add_aux_coord(mesh_coord_y, 1) expected_cube.add_dim_coord(height, 2) # Check metadata and scalar coords. result.data = expected_data assert expected_cube == result
def test_resolution(): """ Test for :func:`esmf_regrid.experimental.unstructured_scheme.GridToMeshESMFRegridder`. Tests for the resolution keyword. """ tgt = _flat_mesh_cube() n_lons = 6 n_lats = 5 lon_bounds = (-180, 180) lat_bounds = (-90, 90) grid = _grid_cube(n_lons, n_lats, lon_bounds, lat_bounds, circular=True) resolution = 8 result = GridToMeshESMFRegridder(grid, tgt, resolution=resolution) assert result.resolution == resolution assert result.regridder.src.resolution == resolution
def test_flat_cubes(): """ Basic test for :func:`esmf_regrid.experimental.unstructured_scheme.GridToMeshESMFRegridder`. Tests with flat cubes as input (a 2D grid cube and a 1D mesh cube). """ tgt = _flat_mesh_cube() n_lons = 6 n_lats = 5 lon_bounds = (-180, 180) lat_bounds = (-90, 90) src = _grid_cube(n_lons, n_lats, lon_bounds, lat_bounds, circular=True) # Ensure data in the target grid is different to the expected data. # i.e. target grid data is all zero, expected data is all one tgt.data[:] = 0 src = _add_metadata(src) src.data[:] = 1 # Ensure all data in the source is one. regridder = GridToMeshESMFRegridder(src, tgt) result = regridder(src) src_T = src.copy() src_T.transpose() result_transposed = regridder(src_T) expected_data = np.ones([n_lats, n_lons]) expected_cube = _add_metadata(tgt) # Lenient check for data. assert np.allclose(expected_data, result.data) assert np.allclose(expected_data, result_transposed.data) # Check metadata and scalar coords. expected_cube.data = result.data assert expected_cube == result expected_cube.data = result_transposed.data assert expected_cube == result_transposed