Skip to content
Draft
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
49 changes: 40 additions & 9 deletions lib/iris/mesh/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -3364,13 +3364,24 @@ def __init__(
Index origin; default is ``0``.

"""
if np.asanyarray(indices).ndim > 1:
raise ValueError(
f"`indices` must be 1D. Got {np.asanyarray(indices).ndim} dimensions."
)

if not isinstance(mesh, MeshXY):
raise TypeError(f"`mesh` must be `MeshXY`. Got {type(mesh)}")

if location not in _MeshXYMixin.ELEMENTS:
msg = f"`location` must be in {_MeshXYMixin.ELEMENTS}. Got {location}"
raise ValueError(msg)

if location == "face" and mesh.topology_dimension < 2:
raise ValueError(
f"`location` cannot be 'face' for a mesh with topology_dimension < 2. "
f"Got {location} and topology_dimension={mesh.topology_dimension}"
)

if start_index not in [0, 1]:
raise ValueError(f"`start_index must be 0 or 1. Got {start_index}")

Expand Down Expand Up @@ -3453,10 +3464,14 @@ def _calculate_node_bool_index(self):
case "node":
# self.indices is a user-supplied index array over the nodes; convert
# it to a fixed-shape boolean membership mask.
monotonic, direction = iris.util.monotonic(
self.indices, strict=True, return_direction=True
)
if not (monotonic and direction == 1):
if len(self.indices) > 1:
monotonic, direction = iris.util.monotonic(
self.indices, strict=True, return_direction=True
)
increasing = monotonic and direction == 1
else:
increasing = True
if not increasing:
# TODO: boolean 'mask' array precludes non-monotonic indexing,
# but is only needed to support connectivity construction, and
# only causes problems for coordinate construction. Separate
Expand All @@ -3483,9 +3498,13 @@ def _calculate_node_bool_index(self):
and c.connected == "node"
)
]
# Doesn't matter if connectivity is transposed or not in this case.
# TODO: implement lazy_indices() and core_indices() for _MeshIndexSet
conn_indices = connectivity.core_indices()[self.indices]
# Respect the connectivity's location_axis before selecting the
# requested edges/faces. This preserves the connectivity's stored
# orientation while still handling transposed connectivities.
conn_indices = connectivity.indices_by_location(
connectivity.core_indices()
)[self.indices]
al = da if _lazy.is_lazy_data(conn_indices) else np
# Flatten and drop masked padding (ragged connectivities) by scattering
# membership into a fixed-shape boolean mask.
Expand Down Expand Up @@ -3611,6 +3630,18 @@ def as_mesh(self) -> MeshXY:
kwargs: Kwargs = {"mesh_id": id(self.mesh), "frozen": True}
coord_man = self.mesh._coord_manager.indexed(*indices, **kwargs)
conn_man = self.mesh._connectivity_manager.indexed(*indices, **kwargs)
has_edges = hasattr(conn_man, "edge_node")
has_faces = hasattr(conn_man, "face_node")
if has_faces:
topology_dimension = 2
elif has_edges:
topology_dimension = 1
else:
message = (
f"Cannot create a MeshXY from a MeshIndexSet with no edge or face "
f"connectivities. This is a *{self.location}* MeshIndexSet."
)
raise NotImplementedError(message)

def _coords_and_axes(
location: Literal["node", "edge", "face"],
Expand All @@ -3619,11 +3650,11 @@ def _coords_and_axes(
return [(getattr(coords, f"{location}_{axis}"), axis) for axis in self.AXES]

return MeshXY(
topology_dimension=self.topology_dimension,
topology_dimension=topology_dimension,
node_coords_and_axes=_coords_and_axes("node"),
connectivities=[conn for conn in conn_man.all_members if conn is not None],
edge_coords_and_axes=_coords_and_axes("edge"),
face_coords_and_axes=_coords_and_axes("face"),
edge_coords_and_axes=_coords_and_axes("edge") if has_edges else None,
face_coords_and_axes=_coords_and_axes("face") if has_faces else None,
standard_name=self.standard_name,
long_name=self.long_name,
var_name=self.var_name,
Expand Down
4 changes: 3 additions & 1 deletion lib/iris/tests/stock/mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ def sample_mesh(

if n_faces == 0:
face_coords_and_axes = None
topology_dimension = 1
else:
topology_dimension = 2
# Define a rather arbitrary face-nodes connectivity.
# Some nodes are left out, because n_faces*n_bounds < n_nodes.
conns = arr.arange(n_faces * nodes_per_face, dtype=int)
Expand All @@ -119,7 +121,7 @@ def sample_mesh(
face_coords_and_axes = [(face_x, "x"), (face_y, "y")]

mesh = MeshXY(
topology_dimension=2,
topology_dimension=topology_dimension,
node_coords_and_axes=[(node_x, "x"), (node_y, "y")],
connectivities=connectivities,
edge_coords_and_axes=edge_coords_and_axes,
Expand Down
12 changes: 9 additions & 3 deletions lib/iris/tests/test_coord_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,10 +419,14 @@ def test_dim_coord_restrictions(self):
with pytest.raises(ValueError, match="must be scalar or 1-dim"):
iris.coords.DimCoord([[1, 2, 3], [4, 5, 6]])
# monotonic points
with pytest.raises(ValueError, match="must be strictly monotonic"):
with pytest.raises(
iris.exceptions.MonotonicityError, match="must be strictly monotonic"
):
iris.coords.DimCoord([1, 2, 99, 4, 5])
# monotonic bounds
with pytest.raises(ValueError, match="direction of monotonicity"):
with pytest.raises(
iris.exceptions.MonotonicityError, match="direction of monotonicity"
):
iris.coords.DimCoord([1, 2, 3], bounds=[[1, 12], [2, 9], [3, 6]])
# masked points
emsg = "points array must not be masked"
Expand Down Expand Up @@ -778,7 +782,9 @@ def test_guess_bounds(self):
coord = iris.coords.AuxCoord.from_coord(coord)
coord.points = points
coord.bounds = None
with pytest.raises(ValueError, match="Need monotonic points"):
with pytest.raises(
iris.exceptions.MonotonicityError, match="Need monotonic points"
):
coord.guess_bounds()


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def test_interpolate_non_monotonic(self):
iris.coords.AuxCoord([0, 3, 2], long_name="non-monotonic"), 1
)
msg = "Cannot interpolate over the non-monotonic coordinate non-monotonic."
with pytest.raises(ValueError, match=msg):
with pytest.raises(iris.exceptions.MonotonicityError, match=msg):
RectilinearInterpolator(self.cube, ["non-monotonic"], LINEAR, EXTRAPOLATE)


Expand Down
Loading
Loading