Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 31 additions & 7 deletions esmf_regrid/experimental/unstructured_scheme.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,11 @@ def _create_cube(data, src_cube, mesh_dim, grid_x, grid_y):
# data: a masked array containing the result of the regridding operation
# src_cube: the source cube which data is regrid from
# mesh_dim: the dimension on src_cube which the mesh belongs to
# mesh: the Mesh (or MeshCoord) object belonging to src_cube
# grid_x: the coordinate on the target cube representing the x axis
# grid_y: the coordinate on the target cube representing the y axis

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See Line 85: describes a mesh object, but this is no longer part of the _create_cube signature

new_cube = iris.cube.Cube(data)

# TODO: The following code assumes a 1D source cube and mesh_dim = 0.
# This is therefore simple code which should be updated when we start
# supporting the regridding of extra dimensions.

# TODO: The following code is rigid with respect to which dimensions
# the x coord and y coord are assigned to. We should decide if it is
# appropriate to copy the dimension ordering from the target cube
Expand All @@ -92,8 +87,37 @@ def _create_cube(data, src_cube, mesh_dim, grid_x, grid_y):

new_cube.metadata = copy.deepcopy(src_cube.metadata)

for coord in src_cube.coords(dimensions=()):
new_cube.add_aux_coord(coord.copy())
# TODO: Handle derived coordinates. The following code is taken from
# iris, the parts dealing with derived coordinates have been
# commented out for the time being.
# coord_mapping = {}

def copy_coords(src_coords, add_method):
for coord in src_coords:
dims = src_cube.coord_dims(coord)
if hasattr(coord, "mesh") or mesh_dim in dims:
continue
# Since the mesh will be replaced by a 2D grid, dims which are
# beyond the mesh_dim are increased by one.
dims = [dim if dim < mesh_dim else dim + 1 for dim in dims]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have a comment to explain this line?
I think from the continue statement above, dim will never equal mesh_dim, but explaining why it's going to be necessary to shift all later dimensions by 1 would make it clearer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this code has been copied from iris then this comment probably belongs there too!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is new.

result_coord = coord.copy()
# Add result_coord to the owner of add_method.
add_method(result_coord, dims)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I get this, but slightly confusing as we're not returning anything from this function. Is this because add_method is always going to be a method on a cube to which we are now appending these new coordinates, so the state change happens on the add_method owner?

# coord_mapping[id(coord)] = result_coord

copy_coords(src_cube.dim_coords, new_cube.add_dim_coord)
copy_coords(src_cube.aux_coords, new_cube.add_aux_coord)

# for factory in src_cube.aux_factories:
# # TODO: Regrid dependant coordinates which span mesh_dim.
# try:
# result.add_aux_factory(factory.updated(coord_mapping))
# except KeyError:
# msg = (
# "Cannot update aux_factory {!r} because of dropped"
# " coordinates.".format(factory.name())
# )
# warnings.warn(msg)

return new_cube

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Unit tests for miscellaneous helper functions in `esmf_regrid.experimental.unstructured_scheme`."""

import iris
from iris.coords import AuxCoord, DimCoord
from iris.cube import Cube
import numpy as np

from esmf_regrid.experimental.unstructured_scheme import _create_cube
Expand All @@ -11,17 +13,56 @@ def test_create_cube_2D():
data = np.ones([2, 3])

# Create a source cube with metadata and scalar coords
src_cube = iris.cube.Cube(np.zeros(5))
src_cube = Cube(np.zeros(5))
src_cube.units = "K"
src_cube.attributes = {"a": 1}
src_cube.standard_name = "air_temperature"
scalar_height = iris.coords.AuxCoord([5], units="m", standard_name="height")
scalar_time = iris.coords.DimCoord([10], units="s", standard_name="time")
scalar_height = AuxCoord([5], units="m", standard_name="height")
scalar_time = DimCoord([10], units="s", standard_name="time")
src_cube.add_aux_coord(scalar_height)
src_cube.add_aux_coord(scalar_time)

mesh_dim = 0

grid_x = DimCoord(np.arange(3), standard_name="longitude")
grid_y = DimCoord(np.arange(2), standard_name="latitude")

cube = _create_cube(data, src_cube, mesh_dim, grid_x, grid_y)
src_metadata = src_cube.metadata
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should _create_cube be handling metadata as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_create_cube already handles metadata, this is also the case in iris.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, sorry I misread this section. You're getting the source metadata to attach to the expected_cube, not to cube.


expected_cube = Cube(data)
expected_cube.metadata = src_metadata
expected_cube.add_dim_coord(grid_x, 1)
expected_cube.add_dim_coord(grid_y, 0)
expected_cube.add_aux_coord(scalar_height)
expected_cube.add_aux_coord(scalar_time)
assert expected_cube == cube


def test_create_cube_4D():
"""Test creation of 2D output grid."""
data = np.ones([4, 2, 3, 5])

# Create a source cube with metadata and scalar coords
src_cube = Cube(np.zeros([4, 5, 5]))
src_cube.units = "K"
src_cube.attributes = {"a": 1}
src_cube.standard_name = "air_temperature"
scalar_height = AuxCoord([5], units="m", standard_name="height")
scalar_time = DimCoord([10], units="s", standard_name="time")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this right? Adding a DimCoord as an add_aux_coord?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit of Iris weirdness. While DimCoord and AuxCoord are different classes, a cube also has two "boxes" to put coords in: _aux_coords_and_dims and _dim_coords_and_dims. Somewhat counterintuitively, _aux_coords_and_dims can contain DimCoords. In fact, since this is a scalar coordinate, it has to be added to _aux_coords_and_dims since it does not describe a dimension (all coords in _dim_coords_and_dims must be associated with a dimension). Scalar DimCoords will be generated when a cube is sliced, so these are worth considering and testing with.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, thanks for the explanation. With that in mind, are we comfortable these tests are sufficiently complete in their coverage for now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so, there's still some stuff like derived coordinates which we might want to add in the future, but I think this covers the main stuff.

src_cube.add_aux_coord(scalar_height)
src_cube.add_aux_coord(scalar_time)
first_coord = DimCoord(np.arange(4), standard_name="air_pressure")
src_cube.add_dim_coord(first_coord, 0)
last_coord = AuxCoord(np.arange(5), long_name="last_coord")
src_cube.add_aux_coord(last_coord, 2)
multidim_coord = AuxCoord(np.ones([4, 5]), long_name="2d_coord")
src_cube.add_aux_coord(multidim_coord, (0, 2))
ignored_coord = AuxCoord(np.arange(5), long_name="ignore")
src_cube.add_aux_coord(ignored_coord, 1)

mesh_dim = 1

grid_x = iris.coords.DimCoord(np.arange(3), standard_name="longitude")
grid_y = iris.coords.DimCoord(np.arange(2), standard_name="latitude")

Expand All @@ -30,8 +71,11 @@ def test_create_cube_2D():

expected_cube = iris.cube.Cube(data)
expected_cube.metadata = src_metadata
expected_cube.add_dim_coord(grid_x, 1)
expected_cube.add_dim_coord(grid_y, 0)
expected_cube.add_dim_coord(grid_x, 2)
expected_cube.add_dim_coord(grid_y, 1)
expected_cube.add_dim_coord(first_coord, 0)
expected_cube.add_aux_coord(last_coord, 3)
expected_cube.add_aux_coord(multidim_coord, (0, 3))
expected_cube.add_aux_coord(scalar_height)
expected_cube.add_aux_coord(scalar_time)
assert expected_cube == cube