Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
3 changes: 1 addition & 2 deletions benchmarks/benchmarks/unit_style/ugrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,8 @@ def setup(self, n_faces, lazy=False):
)

def get_coords_and_axes(location):
search_kwargs = {f"include_{location}s": True}
return [
(source_mesh.coord(axis=axis, **search_kwargs), axis)
(source_mesh.coord(axis=axis, location=location), axis)
for axis in ("x", "y")
]

Expand Down
79 changes: 71 additions & 8 deletions lib/iris/experimental/ugrid/mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -1487,6 +1487,7 @@ def coord(
var_name=None,
attributes=None,
axis=None,
location=None,
include_nodes=None,
include_edges=None,
include_faces=None,
Expand Down Expand Up @@ -1537,11 +1538,13 @@ def coord(
The desired coordinate axis, see :func:`~iris.util.guess_coord_axis`.
If ``None``, does not check for ``axis``. Accepts the values ``X``,
``Y``, ``Z`` and ``T`` (case-insensitive).
include_node : bool, optional
location : str, optional
The desired location. Accepts the values ``node``, ``edge`` or ``face``.
include_nodes : bool, optional
Include all ``node`` coordinates in the list of objects to be matched.
include_edge : bool, optional
include_edges : bool, optional
Include all ``edge`` coordinates in the list of objects to be matched.
include_face : bool, optional
include_faces : bool, optional
Include all ``face`` coordinates in the list of objects to be matched.

Returns
Expand All @@ -1551,6 +1554,30 @@ def coord(
that matched the given criteria.

"""
if location is not None:
if location not in ["node", "edge", "face"]:
raise ValueError(
f"Expected location to be one of `node`, `edge` or `face`, got `{location}`"
)
include_nodes_new = location == "node"
include_edges_new = location == "edge"
include_faces_new = location == "face"
if include_nodes not in [None, include_nodes_new]:
msg = f"Value of `include_nodes` ({include_nodes}) is incompatible with `location` ({location})"
raise ValueError(msg)
else:
include_nodes = include_nodes_new
if include_edges not in [None, include_edges_new]:
msg = f"Value of `include_edges` ({include_edges}) is incompatible with `location` ({location})"
raise ValueError(msg)
else:
include_edges = include_edges_new
if include_faces not in [None, include_faces_new]:
msg = f"Value of `include_faces` ({include_faces}) is incompatible with `location` ({location})"
raise ValueError(msg)
else:
include_faces = include_faces_new

result = self._coord_manager.filter(
item=item,
standard_name=standard_name,
Expand All @@ -1572,6 +1599,7 @@ def coords(
var_name=None,
attributes=None,
axis=None,
location=None,
include_nodes=None,
include_edges=None,
include_faces=None,
Expand Down Expand Up @@ -1617,11 +1645,14 @@ def coords(
The desired coordinate axis, see :func:`~iris.util.guess_coord_axis`.
If ``None``, does not check for ``axis``. Accepts the values ``X``,
``Y``, ``Z`` and ``T`` (case-insensitive).
include_node : bool, optional
location : str, list of str, optional
The desired location or list of locations. Accepts the values ``node``,
``edge`` or ``face``.
include_nodes : bool, optional
Include all ``node`` coordinates in the list of objects to be matched.
include_edge : bool, optional
include_edges : bool, optional
Include all ``edge`` coordinates in the list of objects to be matched.
include_face : bool, optional
include_faces : bool, optional
Include all ``face`` coordinates in the list of objects to be matched.

Returns
Expand All @@ -1631,6 +1662,39 @@ def coords(
:class:`Mesh` that matched the given criteria.

"""
if location is not None:
if isinstance(location, str):
_location = [location]
elif isinstance(location, Iterable):
_location = location
else:
raise TypeError(
f"Expected location to be string or an Iterable, got {type(location)}"
)
for loc in _location:
if loc not in ["node", "edge", "face"]:
raise ValueError(
f"Expected location to contain only `node`, `edge` or `face`, got `{location}`"
)
include_nodes_new = "node" in _location
include_edges_new = "edge" in _location
include_faces_new = "face" in _location
if include_nodes not in [None, include_nodes_new]:
msg = f"Value of `include_nodes` ({include_nodes}) is incompatible with `location` ({location})"
raise ValueError(msg)
else:
include_nodes = include_nodes_new
if include_edges not in [None, include_edges_new]:
msg = f"Value of `include_edges` ({include_edges}) is incompatible with `location` ({location})"
raise ValueError(msg)
else:
include_edges = include_edges_new
if include_faces not in [None, include_faces_new]:
msg = f"Value of `include_faces` ({include_faces}) is incompatible with `location` ({location})"
raise ValueError(msg)
else:
include_faces = include_faces_new

result = self._coord_manager.filters(
item=item,
standard_name=standard_name,
Expand Down Expand Up @@ -2737,8 +2801,7 @@ def __init__(
use_metadict = node_metadict.copy()
if location != "node":
# Location is either "edge" or "face" - get the relevant coord.
kwargs = {f"include_{location}s": True, "axis": axis}
location_coord = self.mesh.coord(**kwargs)
location_coord = self.mesh.coord(axis=axis, location=location)

# Take the MeshCoord metadata from the 'location' coord.
use_metadict = location_coord.metadata._asdict()
Expand Down
44 changes: 44 additions & 0 deletions lib/iris/tests/unit/experimental/ugrid/mesh/test_Mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,25 @@ def test_coord(self):
func = self.mesh.coord
exception = CoordinateNotFoundError
self.assertRaisesRegex(exception, ".*but found 2", func, include_nodes=True)
self.assertRaisesRegex(exception, ".*but found 2", func, location="node")
self.assertRaisesRegex(exception, ".*but found none", func, axis="t")
self.assertRaisesRegex(
ValueError,
"include_edges.*incompatible with.*node",
func,
location="node",
include_edges=True,
)
self.assertRaisesRegex(
ValueError,
"include_nodes.*incompatible with.*node",
func,
location="node",
include_nodes=False,
)
self.assertRaisesRegex(
ValueError, "Expected location.*got `foo`", func, location="foo"
)

def test_coords(self):
# General results. Method intended for inheritance.
Expand All @@ -238,6 +256,9 @@ def test_coords(self):
{"long_name": "long_name"},
{"var_name": "node_lon"},
{"attributes": {"test": 1}},
{"include_nodes": True},
{"location": "node"},
{"location": ["node", "edge"]},
)

fake_coord = AuxCoord([0])
Expand All @@ -248,6 +269,10 @@ def test_coords(self):
{"long_name": "foo"},
{"var_name": "foo"},
{"attributes": {"test": 2}},
{"include_nodes": False},
{"include_edges": True},
{"location": "face"},
{"location": ["face", "edge"]},
)

func = self.mesh.coords
Expand All @@ -256,6 +281,25 @@ def test_coords(self):
for kwargs in negative_kwargs:
self.assertNotIn(self.NODE_LON, func(**kwargs))

func = self.mesh.coords
self.assertRaisesRegex(
ValueError,
"include_edges.*incompatible with.*node",
func,
location=["node", "face"],
include_edges=True,
)
self.assertRaisesRegex(
ValueError,
"include_nodes.*incompatible with.*node",
func,
location=["node", "face"],
include_nodes=False,
)
self.assertRaisesRegex(
ValueError, "Expected location.*got.*foo", func, location=["node", "foo"]
)

def test_coords_elements(self):
# topology_dimension-specific results. Method intended to be overridden.
all_expected = {
Expand Down
8 changes: 2 additions & 6 deletions lib/iris/tests/unit/experimental/ugrid/mesh/test_MeshCoord.py
Original file line number Diff line number Diff line change
Expand Up @@ -795,12 +795,8 @@ def setup_mesh(self, location, axis):
mesh = sample_mesh()

# Modify the metadata of specific coordinates used in this test.
def select_coord(location, axis):
kwargs = {f"include_{location}s": True, "axis": axis}
return mesh.coord(**kwargs)

node_coord = select_coord("node", axis)
location_coord = select_coord(location, axis)
node_coord = mesh.coord(axis=axis, location="node")
location_coord = mesh.coord(axis=axis, location=location)
for i_place, coord in enumerate((node_coord, location_coord)):
coord.standard_name = "longitude" if axis == "x" else "latitude"
coord.units = "degrees"
Expand Down