示例#1
0
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")
示例#2
0
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,
    )
示例#5
0
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
示例#8
0
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)
示例#9
0
 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)