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 _regrid_rectilinear_to_rectilinear__prepare(src_grid_cube, tgt_grid_cube):
    tgt_x = tgt_grid_cube.coord(axis="x")
    tgt_y = tgt_grid_cube.coord(axis="y")
    src_x = src_grid_cube.coord(axis="x")
    src_y = src_grid_cube.coord(axis="y")

    if len(src_x.shape) == 1:
        grid_x_dim = src_grid_cube.coord_dims(src_x)[0]
        grid_y_dim = src_grid_cube.coord_dims(src_y)[0]
    else:
        grid_y_dim, grid_x_dim = src_grid_cube.coord_dims(src_x)

    srcinfo = _cube_to_GridInfo(src_grid_cube)
    tgtinfo = _cube_to_GridInfo(tgt_grid_cube)

    regridder = Regridder(srcinfo, tgtinfo)

    regrid_info = RegridInfo(
        x_dim=grid_x_dim,
        y_dim=grid_y_dim,
        x_coord=tgt_x,
        y_coord=tgt_y,
        regridder=regridder,
    )

    return regrid_info
def test_global_grid():
    """Test conversion of a global grid."""
    n_lons = 6
    n_lats = 5
    lon_bounds = (-180, 180)
    lat_bounds = (-90, 90)

    cube = _grid_cube(
        n_lons,
        n_lats,
        lon_bounds,
        lat_bounds,
        circular=True,
        coord_system=GeogCS(EARTH_RADIUS),
    )
    gridinfo = _cube_to_GridInfo(cube)
    # Ensure conversion to ESMF works without error
    _ = gridinfo.make_esmf_field()

    # The following test ensures there are no overlapping cells.
    # This catches geometric/topological abnormalities that would arise from,
    # for example: switching lat/lon values, using euclidean coords vs spherical.
    rg = Regridder(gridinfo, gridinfo)
    expected_weights = scipy.sparse.identity(n_lats * n_lons)
    assert np.array_equal(expected_weights.todense(),
                          rg.weight_matrix.todense())
    assert gridinfo.crs == GeogCS(EARTH_RADIUS).as_cartopy_crs()
def _regrid_rectilinear_to_unstructured__prepare(
    src_grid_cube,
    target_mesh_cube,
    method,
    precomputed_weights=None,
    resolution=None,
):
    """
    First (setup) part of 'regrid_rectilinear_to_unstructured'.

    Check inputs and calculate the sparse regrid matrix and related info.
    The 'regrid info' returned can be re-used over many 2d slices.

    """
    grid_x = src_grid_cube.coord(axis="x")
    grid_y = src_grid_cube.coord(axis="y")
    mesh = target_mesh_cube.mesh
    location = target_mesh_cube.location
    if mesh is None:
        raise ValueError("The given cube is not defined on a mesh.")
    if method == "conservative":
        if location != "face":
            raise ValueError(
                f"Conservative regridding requires a target cube located on "
                f"the face of a cube, target cube had the {location} location."
            )
        center = False
    elif method == "bilinear":
        if location not in ["face", "node"]:
            raise ValueError(
                f"Bilinear regridding requires a target cube with a node "
                f"or face location, target cube had the {location} location."
            )
        if location == "face" and None in mesh.face_coords:
            raise ValueError(
                "Bilinear regridding requires a target cube on a face location to have "
                "a face center."
            )
        center = True
    else:
        raise ValueError(
            f"method must be either 'bilinear' or 'conservative', got '{method}'."
        )
    assert mesh is not None
    if grid_x.ndim == 1:
        (grid_x_dim,) = src_grid_cube.coord_dims(grid_x)
        (grid_y_dim,) = src_grid_cube.coord_dims(grid_y)
    else:
        grid_y_dim, grid_x_dim = src_grid_cube.coord_dims(grid_x)

    meshinfo = _mesh_to_MeshInfo(mesh, location)
    gridinfo = _cube_to_GridInfo(src_grid_cube, center=center, resolution=resolution)

    regridder = Regridder(
        gridinfo, meshinfo, method=method, precomputed_weights=precomputed_weights
    )

    regrid_info = (grid_x_dim, grid_y_dim, grid_x, grid_y, mesh, location, regridder)

    return regrid_info
Пример #5
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")
Пример #6
0
def test_large_mesh_validity():
    """Test validity of objects derived from a large gridlike Mesh."""
    mesh = _gridlike_mesh(40, 20)
    meshinfo = _mesh_to_MeshInfo(mesh, location="face")

    # Ensure conversion to ESMF works without error.
    _ = meshinfo.make_esmf_field()

    # The following test ensures there are no overlapping cells.
    # This catches geometric/topological abnormalities that would arise from,
    # for example: switching lat/lon values, using euclidean coords vs spherical.
    rg = Regridder(meshinfo, meshinfo)
    expected_weights = scipy.sparse.identity(meshinfo.size)
    assert np.allclose(expected_weights.todense(), rg.weight_matrix.todense())
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 test_curvilinear_grid():
    """Test conversion of a curvilinear global grid."""
    n_lons = 6
    n_lats = 5
    lon_bounds = (-180, 180)
    lat_bounds = (-90, 90)

    cube = _curvilinear_cube(
        n_lons,
        n_lats,
        lon_bounds,
        lat_bounds,
        coord_system=GeogCS(EARTH_RADIUS),
    )
    gridinfo = _cube_to_GridInfo(cube)
    # Ensure conversion to ESMF works without error
    _ = gridinfo.make_esmf_field()

    # The following test ensures there are no overlapping cells.
    # This catches geometric/topological abnormalities that would arise from,
    # for example: switching lat/lon values, using euclidean coords vs spherical.
    rg = Regridder(gridinfo, gridinfo)
    expected_weights = scipy.sparse.identity(n_lats * n_lons)
    assert np.array_equal(expected_weights.todense(),
                          rg.weight_matrix.todense())
    assert gridinfo.crs == GeogCS(EARTH_RADIUS).as_cartopy_crs()

    # While curvilinear coords do not have the "circular" attribute, the code
    # allows "circular" to be True when setting the core regridder directly.
    # This describes an ESMF object which is topologically different, but ought
    # to be geometrically equivalent to the non-circular case.
    circular_gridinfo = _cube_to_GridInfo(cube)
    circular_gridinfo.circular = True
    rg_circular = Regridder(circular_gridinfo, gridinfo)
    assert np.allclose(expected_weights.todense(),
                       rg_circular.weight_matrix.todense())
def test_local_grid():
    """Test conversion of a local grid."""
    n_lons = 6
    n_lats = 5
    lon_bounds = (-20, 20)
    lat_bounds = (20, 60)

    cube = _grid_cube(n_lons, n_lats, lon_bounds, lat_bounds)
    gridinfo = _cube_to_GridInfo(cube)
    # Ensure conversion to ESMF works without error
    _ = gridinfo.make_esmf_field()

    # The following test ensures there are no overlapping cells.
    # Note that this test fails when longitude is circular.
    rg = Regridder(gridinfo, gridinfo)
    expected_weights = scipy.sparse.identity(n_lats * n_lons)
    assert np.array_equal(expected_weights.todense(), rg.weight_matrix.todense())
def test_expanded_lons_with_mesh():
    """
    Basic test for regridding with :meth:`~esmf_regrid.esmf_regridder.RefinedGridInfo.make_esmf_field`.

    Mirrors the tests in :func:`~esmf_regrid.tests.unit.experimental.unstructured_regrid.test_MeshInfo.test_regrid_with_mesh`
    but with slightly different expected values due to increased accuracy.
    """
    mesh_args = _make_small_mesh_args()
    mesh = MeshInfo(*mesh_args)

    grid_args = make_grid_args(2, 3)
    grid = RefinedGridInfo(*grid_args[2:4], resolution=4)

    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.671534474734418, 3.0],
        [2.088765949748455, 2.922517356506756],
        [2.0, 2.340882413622917],
    ])
    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.14117205318254747, 1.1976140197893996])
    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 test_grid_with_scalars():
    """Test conversion of a grid with scalar coords."""
    n_lons = 1
    n_lats = 5
    lon_bounds = (-20, 20)
    lat_bounds = (20, 60)

    cube = _grid_cube(n_lons, n_lats, lon_bounds, lat_bounds)
    # Convert longitude to a scalar
    cube = cube[:, 0]
    assert len(cube.shape) == 1

    gridinfo = _cube_to_GridInfo(cube)
    # Ensure conversion to ESMF works without error
    _ = gridinfo.make_esmf_field()

    # The following test ensures there are no overlapping cells.
    rg = Regridder(gridinfo, gridinfo)
    expected_weights = scipy.sparse.identity(n_lats * n_lons)
    assert np.array_equal(expected_weights.todense(), rg.weight_matrix.todense())
Пример #12
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)
Пример #13
0
def _regrid_rectilinear_to_rectilinear__prepare(src_grid_cube, tgt_grid_cube):
    tgt_x, tgt_y = get_xy_dim_coords(tgt_grid_cube)
    src_x, src_y = get_xy_dim_coords(src_grid_cube)

    grid_x_dim = src_grid_cube.coord_dims(src_x)[0]
    grid_y_dim = src_grid_cube.coord_dims(src_y)[0]

    srcinfo = _cube_to_GridInfo(src_grid_cube)
    tgtinfo = _cube_to_GridInfo(tgt_grid_cube)

    regridder = Regridder(srcinfo, tgtinfo)

    regrid_info = RegridInfo(
        x_dim=grid_x_dim,
        y_dim=grid_y_dim,
        x_coord=tgt_x,
        y_coord=tgt_y,
        regridder=regridder,
    )

    return regrid_info
def test_expanded_lats_with_mesh():
    """Basic test for regridding with :meth:`~esmf_regrid.esmf_regridder.RefinedGridInfo.make_esmf_field`."""
    mesh_args = _make_small_mesh_args()
    mesh = MeshInfo(*mesh_args)

    grid = RefinedGridInfo(np.array([0, 5, 10]),
                           np.array([-90, 90]),
                           resolution=4)

    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.2024695514629724, 2.4336888097502642],
    ])
    assert ma.allclose(expected_grid_output, grid_output)

    grid_to_mesh_regridder = Regridder(grid, mesh)
    grid_input = np.array([[1, 2]])
    mesh_output = grid_to_mesh_regridder.regrid(grid_input)
    expected_mesh_output = np.array([1.7480791292591336, 1.496070008348207])
    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 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)
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())