def test_Regridder_init_fail(): """Basic test for :meth:`~esmf_regrid.esmf_regridder.Regridder.__init__`.""" lon, lat, lon_bounds, lat_bounds = make_grid_args(2, 3) src_grid = GridInfo(lon, lat, lon_bounds, lat_bounds) lon, lat, lon_bounds, lat_bounds = make_grid_args(3, 2) tgt_grid = GridInfo(lon, lat, lon_bounds, lat_bounds) with pytest.raises(ValueError): _ = Regridder(src_grid, tgt_grid, method="other")
def test_make_grid(): """Basic test for :meth:`~esmf_regrid.esmf_regridder.GridInfo.make_esmf_field`.""" lon, lat, lon_bounds, lat_bounds = _make_small_grid_args() grid = GridInfo(lon, lat, lon_bounds, lat_bounds) esmf_grid = grid.make_esmf_field() esmf_grid.data[:] = 0 relative_path = ("esmf_regridder", "test_GridInfo", "small_grid.txt") fname = tests.get_result_path(relative_path) with open(fname) as fi: expected_repr = fi.read() assert esmf_grid.__repr__() == expected_repr
def test_Regridder_init(): """Basic test for :meth:`~esmf_regrid.esmf_regridder.Regridder.__init__`.""" lon, lat, lon_bounds, lat_bounds = make_grid_args(2, 3) src_grid = GridInfo(lon, lat, lon_bounds, lat_bounds) lon, lat, lon_bounds, lat_bounds = make_grid_args(3, 2) tgt_grid = GridInfo(lon, lat, lon_bounds, lat_bounds) rg = Regridder(src_grid, tgt_grid) result = rg.weight_matrix expected = _expected_weights() assert np.allclose(result.toarray(), expected.toarray())
def _cube_to_GridInfo(cube): # This is a simplified version of an equivalent function/method in PR #26. # It is anticipated that this function will be replaced by the one in PR #26. # # Returns a GridInfo object describing the horizontal grid of the cube. # This may be inherited from code written for the rectilinear regridding scheme. lon = cube.coord("longitude") lat = cube.coord("latitude") # Ensure coords come from a proper grid. assert isinstance(lon, iris.coords.DimCoord) assert isinstance(lat, iris.coords.DimCoord) # TODO: accommodate other x/y coords. # TODO: perform checks on lat/lon. # Checks may cover units, coord systems (e.g. rotated pole), contiguous bounds. if cube.coord_system() is None: crs = None else: crs = cube.coord_system().as_cartopy_crs() return GridInfo( lon.points, lat.points, _bounds_cf_to_simple_1d(lon.bounds), _bounds_cf_to_simple_1d(lat.bounds), crs=crs, circular=lon.circular, )
class TimeGridInfo: def setup(self): lon, lat, lon_bounds, lat_bounds = _make_small_grid_args() self.grid = GridInfo(lon, lat, lon_bounds, lat_bounds) def time_make_grid(self): """Basic test for :meth:`~esmf_regrid.esmf_regridder.GridInfo.make_esmf_field`.""" esmf_grid = self.grid.make_esmf_field() esmf_grid.data[:] = 0 time_make_grid.version = 1
def test_esmpy_normalisation(): """ Integration test for :meth:`~esmf_regrid.esmf_regridder.Regridder`. Checks against ESMF to ensure results are consistent. """ src_data = np.array( [ [1.0, 1.0], [1.0, 0.0], [1.0, 0.0], ], ) src_mask = np.array( [ [True, False], [False, False], [False, False], ] ) src_array = ma.array(src_data, mask=src_mask) lon, lat, lon_bounds, lat_bounds = make_grid_args(2, 3) src_grid = GridInfo(lon, lat, lon_bounds, lat_bounds) src_esmpy_grid = src_grid._make_esmf_grid() src_esmpy_grid.add_item(ESMF.GridItem.MASK, staggerloc=ESMF.StaggerLoc.CENTER) src_esmpy_grid.mask[0][...] = src_mask src_field = ESMF.Field(src_esmpy_grid) src_field.data[...] = src_data lon, lat, lon_bounds, lat_bounds = make_grid_args(3, 2) tgt_grid = GridInfo(lon, lat, lon_bounds, lat_bounds) tgt_field = tgt_grid.make_esmf_field() regridder = Regridder(src_grid, tgt_grid) regridding_kwargs = { "ignore_degenerate": True, "regrid_method": ESMF.RegridMethod.CONSERVE, "unmapped_action": ESMF.UnmappedAction.IGNORE, "factors": True, "src_mask_values": [1], } esmpy_fracarea_regridder = ESMF.Regrid( src_field, tgt_field, norm_type=ESMF.NormType.FRACAREA, **regridding_kwargs ) esmpy_dstarea_regridder = ESMF.Regrid( src_field, tgt_field, norm_type=ESMF.NormType.DSTAREA, **regridding_kwargs ) tgt_field_dstarea = esmpy_dstarea_regridder(src_field, tgt_field) result_esmpy_dstarea = tgt_field_dstarea.data result_dstarea = regridder.regrid(src_array, norm_type="dstarea") assert ma.allclose(result_esmpy_dstarea, result_dstarea) tgt_field_fracarea = esmpy_fracarea_regridder(src_field, tgt_field) result_esmpy_fracarea = tgt_field_fracarea.data result_fracarea = regridder.regrid(src_array, norm_type="fracarea") assert ma.allclose(result_esmpy_fracarea, result_fracarea)
def _cube_to_GridInfo(cube, center=False, resolution=None): # This is a simplified version of an equivalent function/method in PR #26. # It is anticipated that this function will be replaced by the one in PR #26. # # Returns a GridInfo object describing the horizontal grid of the cube. # This may be inherited from code written for the rectilinear regridding scheme. lon, lat = cube.coord(axis="x"), cube.coord(axis="y") # Checks may cover units, coord systems (e.g. rotated pole), contiguous bounds. if cube.coord_system() is None: crs = None else: crs = cube.coord_system().as_cartopy_crs() londim, latdim = len(lon.shape), len(lat.shape) assert londim == latdim assert londim in (1, 2) if londim == 1: # Ensure coords come from a proper grid. assert isinstance(lon, iris.coords.DimCoord) assert isinstance(lat, iris.coords.DimCoord) circular = lon.circular # TODO: perform checks on lat/lon. elif londim == 2: assert cube.coord_dims(lon) == cube.coord_dims(lat) assert lon.is_contiguous() assert lat.is_contiguous() # 2D coords must be AuxCoords, which do not have a circular attribute. circular = False lon_bound_array = lon.contiguous_bounds() lon_bound_array = lon.units.convert(lon_bound_array, Unit("degrees")) lat_bound_array = lat.contiguous_bounds() lat_bound_array = lat.units.convert(lat_bound_array, Unit("degrees")) if resolution is None: grid_info = GridInfo( lon.points, lat.points, lon_bound_array, lat_bound_array, crs=crs, circular=circular, center=center, ) else: grid_info = RefinedGridInfo( lon_bound_array, lat_bound_array, crs=crs, resolution=resolution, ) return grid_info
def test_regrid_with_mesh(): """Basic test for regridding with :meth:`~esmf_regrid.esmf_regridder.GridInfo.make_esmf_field`.""" mesh_args = _make_small_mesh_args() mesh = MeshInfo(*mesh_args) grid_args = make_grid_args(2, 3) grid = GridInfo(*grid_args) mesh_to_grid_regridder = Regridder(mesh, grid) mesh_input = np.array([3, 2]) grid_output = mesh_to_grid_regridder.regrid(mesh_input) expected_grid_output = np.array([ [2.671294712940605, 3.0], [2.0885553467353097, 2.9222786250561574], [2.0, 2.3397940801753307], ]) assert ma.allclose(expected_grid_output, grid_output) grid_to_mesh_regridder = Regridder(grid, mesh) grid_input = np.array([[0, 0], [1, 0], [2, 1]]) mesh_output = grid_to_mesh_regridder.regrid(grid_input) expected_mesh_output = np.array([0.1408245341331448, 1.19732762534643]) assert ma.allclose(expected_mesh_output, mesh_output) def _give_extra_dims(array): result = np.stack([array, array + 1]) result = np.stack([result, result + 10, result + 100]) return result extra_dim_mesh_input = _give_extra_dims(mesh_input) extra_dim_grid_output = mesh_to_grid_regridder.regrid(extra_dim_mesh_input) extra_dim_expected_grid_output = _give_extra_dims(expected_grid_output) assert ma.allclose(extra_dim_expected_grid_output, extra_dim_grid_output) extra_dim_grid_input = _give_extra_dims(grid_input) extra_dim_mesh_output = grid_to_mesh_regridder.regrid(extra_dim_grid_input) extra_dim_expected_mesh_output = _give_extra_dims(expected_mesh_output) assert ma.allclose(extra_dim_expected_mesh_output, extra_dim_mesh_output)
def setup(self): lon, lat, lon_bounds, lat_bounds = _make_small_grid_args() self.grid = GridInfo(lon, lat, lon_bounds, lat_bounds)
def test_Regridder_regrid(): """Basic test for :meth:`~esmf_regrid.esmf_regridder.Regridder.regrid`.""" lon, lat, lon_bounds, lat_bounds = make_grid_args(2, 3) src_grid = GridInfo(lon, lat, lon_bounds, lat_bounds) lon, lat, lon_bounds, lat_bounds = make_grid_args(3, 2) tgt_grid = GridInfo(lon, lat, lon_bounds, lat_bounds) # Set up the regridder with precomputed weights. rg = Regridder(src_grid, tgt_grid, precomputed_weights=_expected_weights()) src_array = np.array([[1.0, 1.0], [1.0, 0.0], [1.0, 0.0]]) src_masked = ma.array(src_array, mask=np.array([[1, 0], [0, 0], [0, 0]])) # Regrid with unmasked data. result_nomask = rg.regrid(src_array) expected_nomask = ma.array([ [1.0, 0.8336393373988409, 0.6674194025656824], [1.0, 0.4999999999999997, 0.0], ]) assert ma.allclose(result_nomask, expected_nomask) # Regrid with an masked array with no masked points. result_ma_nomask = rg.regrid(ma.array(src_array)) assert ma.allclose(result_ma_nomask, expected_nomask) # Regrid with a fully masked array. result_fullmask = rg.regrid(ma.array(src_array, mask=True)) expected_fulmask = ma.array(np.zeros([2, 3]), mask=True) assert ma.allclose(result_fullmask, expected_fulmask) # Regrid with a masked array containing a masked point. result_withmask = rg.regrid(src_masked) expected_withmask = ma.array([ [0.9999999999999999, 0.7503444126612077, 0.6674194025656824], [1.0, 0.4999999999999997, 0.0], ]) assert ma.allclose(result_withmask, expected_withmask) # Regrid while setting mdtol. result_half_mdtol = rg.regrid(src_masked, mdtol=0.5) expected_half_mdtol = ma.array(expected_withmask, mask=np.array([[1, 0, 1], [0, 0, 0]])) assert ma.allclose(result_half_mdtol, expected_half_mdtol) # Regrid with norm_type="dstarea". result_dstarea = rg.regrid(src_masked, norm_type="dstarea") expected_dstarea = ma.array([ [0.3325805974343169, 0.4999999999999999, 0.6674194025656823], [0.9999999999999998, 0.499931367542066, 0.0], ]) assert ma.allclose(result_dstarea, expected_dstarea) def _give_extra_dims(array): result = np.stack([array, array + 1]) result = np.stack([result, result + 10, result + 100]) return result # Regrid with multiple extra dimensions. extra_dim_src = _give_extra_dims(src_array) extra_dim_expected = _give_extra_dims(expected_nomask) extra_dim_result = rg.regrid(extra_dim_src) assert ma.allclose(extra_dim_result, extra_dim_expected) # Regrid extra dimensions with different masks. mixed_mask_src = ma.stack([src_array, src_masked]) mixed_mask_expected = np.stack([expected_nomask, expected_withmask]) mixed_mask_result = rg.regrid(mixed_mask_src) assert ma.allclose(mixed_mask_result, mixed_mask_expected) assert src_array.T.shape != src_array.shape with pytest.raises(ValueError): _ = rg.regrid(src_array.T) with pytest.raises(ValueError): _ = rg.regrid(src_masked, norm_type="INVALID")
def test_Regridder_init_small(): """ Simplified test for :meth:`~esmf_regrid.esmf_regridder.Regridder.regrid`. This test is designed to be simple enough to generate predictable weights. With predictable weights it is easier to check that the weights are associated with the correct indices. """ # The following ASCII visualisation describes the source and target grids # and the indices which ESMF assigns to their cells. # Analysis of the weights dict returned by ESMF confirms that this is # indeed the indexing which ESMF assigns these grids. # # 30 +---+---+ +-------+ # | 3 | 6 | | | # 20 +---+---+ | 2 | # | 2 | 5 | | | # 10 +---+---+ +-------+ # | 1 | 4 | | 1 | # 0 +---+---+ +-------+ # 0 10 20 0 20 def _get_points(bounds): points = (bounds[:-1] + bounds[1:]) / 2 return points lon_bounds = np.array([0, 10, 20]) lat_bounds = np.array([0, 10, 20, 30]) lon, lat = _get_points(lon_bounds), _get_points(lat_bounds) src_grid = GridInfo(lon, lat, lon_bounds, lat_bounds) assert src_grid.shape == (3, 2) assert src_grid._index_offset() == 1 lon_bounds = np.array([0, 20]) lat_bounds = np.array([0, 10, 30]) lon, lat = _get_points(lon_bounds), _get_points(lat_bounds) tgt_grid = GridInfo(lon, lat, lon_bounds, lat_bounds) assert tgt_grid.shape == (2, 1) assert tgt_grid._index_offset() == 1 rg = Regridder(src_grid, tgt_grid) result = rg.weight_matrix weights_dict = {} weights_dict["row_dst"] = ( np.array([1, 1, 1, 1, 2, 2, 2, 2], dtype=np.int32) - src_grid._index_offset()) weights_dict["col_src"] = ( np.array([1, 2, 4, 5, 2, 3, 5, 6], dtype=np.int32) - tgt_grid._index_offset()) # The following weights are calculated from grids on a sphere with great circles # defining the lines. Because of this, weights are not exactly the same as they # would be in euclidean geometry and there are some small additional weights # where the cells would not ordinarily overlap in a euclidean grid. weights_dict["weights"] = np.array([ 0.4962897, 0.0037103, # Small weight due to using great circle lines 0.4962897, 0.0037103, # Small weight due to using great circle lines 0.25484249, 0.24076863, 0.25484249, 0.24076863, ]) expected_weights = scipy.sparse.csr_matrix( (weights_dict["weights"], (weights_dict["row_dst"], weights_dict["col_src"]))) assert np.allclose(result.toarray(), expected_weights.toarray())
def test_regrid_bilinear_with_mesh(): """Basic test for regridding with :meth:`~esmf_regrid.esmf_regridder.GridInfo.make_esmf_field`.""" # We create a mesh with the following shape: # 20 ,+ ,+ 4 # / | with nodes: / | # 10 +' ,+ 1 +' ,+ 3 # | / | | / | # 0 +---+ 0 +---+ 2 # 0 10 mesh_args = _make_small_mesh_args() elem_coords = np.array([[5, 0], [5, 10]]) node_mesh = MeshInfo(*mesh_args, location="node") face_mesh = MeshInfo(*mesh_args, elem_coords=elem_coords, location="face") # We create a grid with the following shape: # 20/3 +---+ # | | # 10/3 +---+ # | | # 0 +---+ # 0 10 grid_args = [ar * 2 for ar in make_grid_args(2, 3)] grid = GridInfo(*grid_args, center=True) mesh_to_grid_regridder = Regridder(node_mesh, grid, method="bilinear") mesh_input = np.arange(5) grid_output = mesh_to_grid_regridder.regrid(mesh_input) # For a flat surface, we would expect the fractional part of these values # to be either 1/3 or 2/3. Since the actual surface lies on a sphere, and # due to the way ESMF calculates these values, the expected output is # slightly different. It's worth noting that the finer the resolution, the # closer these numbers are. Since the grids/meshes lie on coarse steps of # about 10 degrees, we can expect most cases to behave more similarly to # bilinear regridders on flat surfaces. expected_grid_output = np.array([ [0.0, 2.0], [0.6662902773937054, 2.6662902773105808], [-1, 3.333709722689418], ]) expected_grid_mask = np.array([[0, 0], [0, 0], [1, 0]]) expected_grid_output = ma.array(expected_grid_output, mask=expected_grid_mask) assert ma.allclose(expected_grid_output, grid_output) grid_to_mesh_regridder = Regridder(grid, node_mesh, method="bilinear") grid_input = np.array([[0, 0], [1, 0], [2, 1]]) mesh_output = grid_to_mesh_regridder.regrid(grid_input) expected_mesh_output = ma.array([0.0, 1.5, 0.0, 0.5, -1], mask=[0, 0, 0, 0, 1]) assert ma.allclose(expected_mesh_output, mesh_output) grid_to_face_mesh_regridder = Regridder(grid, face_mesh, method="bilinear") grid_input_2 = np.array([[0, 0], [1, 0], [4, 1]]) face_mesh_output = grid_to_face_mesh_regridder.regrid(grid_input_2) expected_face_mesh_output = np.array([0.0, 1.4888258584989558]) assert ma.allclose(expected_face_mesh_output, face_mesh_output) def _give_extra_dims(array): result = np.stack([array, array + 1]) result = np.stack([result, result + 10, result + 100]) return result extra_dim_mesh_input = _give_extra_dims(mesh_input) extra_dim_grid_output = mesh_to_grid_regridder.regrid(extra_dim_mesh_input) extra_dim_expected_grid_output = _give_extra_dims(expected_grid_output) assert ma.allclose(extra_dim_expected_grid_output, extra_dim_grid_output) extra_dim_grid_input = _give_extra_dims(grid_input) extra_dim_mesh_output = grid_to_mesh_regridder.regrid(extra_dim_grid_input) extra_dim_expected_mesh_output = _give_extra_dims(expected_mesh_output) assert ma.allclose(extra_dim_expected_mesh_output, extra_dim_mesh_output)