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
43 changes: 39 additions & 4 deletions lib/iris/cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"""

from collections import OrderedDict

from collections.abc import (
Iterable,
Container,
Expand Down Expand Up @@ -1582,6 +1583,7 @@ def coords(
dimensions=None,
coord_system=None,
dim_coords=None,
mesh_coords=None,
):
"""
Return a list of coordinates from the :class:`Cube` that match the
Expand Down Expand Up @@ -1643,8 +1645,14 @@ def coords(
* dim_coords:
Set to ``True`` to only return coordinates that are the cube's
dimension coordinates. Set to ``False`` to only return coordinates
that are the cube's auxiliary and derived coordinates. If ``None``,
returns all coordinates.
that are the cube's auxiliary, mesh and derived coordinates.
If ``None``, returns all coordinates.

* mesh_coords:
Set to ``True`` to return only coordinates which are
:class:`~iris.experimental.ugrid.MeshCoord`\\ s.
Set to ``False`` to return only non-mesh coordinates.
If ``None``, returns all coordinates.

Returns:
A list containing zero or more coordinates matching the provided
Expand All @@ -1660,6 +1668,26 @@ def coords(
coords_and_factories += list(self.aux_coords)
coords_and_factories += list(self.aux_factories)

if mesh_coords is not None:
# Select on mesh or non-mesh.
mesh_coords = bool(mesh_coords)
# Use duck typing to avoid importing from iris.experimental.ugrid,
# which could be a circular import.
if mesh_coords:
# *only* MeshCoords
coords_and_factories = [
item
for item in coords_and_factories
if hasattr(item, "mesh")
]
else:
# *not* MeshCoords
coords_and_factories = [
item
for item in coords_and_factories
if not hasattr(item, "mesh")
]

coords_and_factories = metadata_filter(
coords_and_factories,
item=name_or_coord,
Expand Down Expand Up @@ -1727,6 +1755,7 @@ def coord(
dimensions=None,
coord_system=None,
dim_coords=None,
mesh_coords=None,
):
"""
Return a single coordinate from the :class:`Cube` that matches the
Expand Down Expand Up @@ -1793,8 +1822,14 @@ def coord(
* dim_coords:
Set to ``True`` to only return coordinates that are the cube's
dimension coordinates. Set to ``False`` to only return coordinates
that are the cube's auxiliary and derived coordinates. If ``None``,
returns all coordinates.
that are the cube's auxiliary, mesh and derived coordinates.
If ``None``, returns all coordinates.

* mesh_coords:
Set to ``True`` to return only coordinates which are
:class:`~iris.experimental.ugrid.MeshCoord`\\ s.
Set to ``False`` to return only non-mesh coordinates.
If ``None``, returns all coordinates.

Returns:
The coordinate that matches the provided criteria.
Expand Down
12 changes: 11 additions & 1 deletion lib/iris/experimental/ugrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -2896,6 +2896,10 @@ def __eq__(self, other):

return eq

# Exactly as for Coord.__hash__ : See there for why.
def __hash__(self):
return hash(id(self))

def _string_summary(self, repr_style):
# Note: bypass the immediate parent here, which is Coord, because we
# have no interest in reporting coord_system or climatological, or in
Expand Down Expand Up @@ -2927,7 +2931,13 @@ def _string_summary(self, repr_style):
)
# Add 'other' metadata that is drawn from the underlying node-coord.
# But put these *afterward*, unlike other similar classes.
for item in ("standard_name", "units", "long_name", "attributes"):
for item in (
"shape",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@pp-mo Snuck in shape here... no objections, just curious as to the reasoning

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Well it seems clear that when printing these mesh information components, we don't want to realise or print out contents.
But when debugging I saw the printouts, it seemed odd you couldn't even see the shape., unlike dask arrays which show the shape + dtype.
So I added it in (but not dtype).

Since then I realised that you + @trexfeathers had already agreed a printout format for Connectivity, which does not include shape.
So I now think that we should probably at least make those 2 the same :
What do you think + which way do you reckon we should we jump ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@pp-mo Awesome, thanks.

I was going to re-haul how coordinates in general do __str__ and __repr__ in order to keep everything lazy.

I think there is real tangible benefit there, but outside the scope of this PR

"standard_name",
"units",
"long_name",
"attributes",
):
# NOTE: order of these matches Coord.summary, but omit var_name.
val = getattr(self, item, None)
if item == "attributes":
Expand Down
94 changes: 94 additions & 0 deletions lib/iris/tests/unit/cube/test_Cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from iris.analysis import MEAN
from iris.aux_factory import HybridHeightFactory
from iris.cube import Cube
from iris.common.metadata import BaseMetadata
from iris.coords import (
AuxCoord,
DimCoord,
Expand All @@ -40,6 +41,10 @@
)
from iris._lazy_data import as_lazy_data
import iris.tests.stock as stock
from iris.tests.unit.experimental.ugrid.test_MeshCoord import (
_create_test_mesh as create_test_mesh,
_create_test_meshcoord as create_test_meshcoord,
)


class Test___init___data(tests.IrisTest):
Expand Down Expand Up @@ -1955,6 +1960,95 @@ def test__lazy(self):
self._check_copy(cube, cube.copy())


class Test_coords__mesh_coords(tests.IrisTest):
"""
Checking *only* the new "mesh_coords" keyword of the coord/coords methods.

This is *not* attached to the existing tests for this area, as they are
very old and patchy legacy tests. See: iris.tests.test_cdm.TestQueryCoord.

"""

def setUp(self):
# Create a standard test cube with a variety of types of coord.
mesh = create_test_mesh()
meshx, meshy = (
create_test_meshcoord(axis=axis, mesh=mesh) for axis in ("x", "y")
)

n_faces = meshx.shape[0]
mesh_dimco = DimCoord(
np.arange(n_faces), long_name="i_mesh_face", units="1"
)
auxco_x = AuxCoord(
np.zeros(n_faces), long_name="mesh_face_aux", units="1"
)
n_z = 2
zco = DimCoord(np.arange(n_z), long_name="level", units=1)
cube = Cube(np.zeros((n_z, n_faces)), long_name="mesh_phenom")
cube.add_dim_coord(zco, 0)
cube.add_dim_coord(mesh_dimco, 1)
for co in (meshx, meshy, auxco_x):
cube.add_aux_coord(co, 1)

self.dimco_z = zco
self.dimco_mesh = mesh_dimco
self.meshco_x = meshx
self.meshco_y = meshy
self.auxco_x = auxco_x
self.allcoords = [meshx, meshy, zco, mesh_dimco, auxco_x]
self.cube = cube

def _assert_lists_equal(self, items_a, items_b):
"""
Check that two lists of coords, cubes etc contain the same things.
Lists must contain the same items, including any repeats, but can be in
a different order.

"""
# Compare (and thus sort) by their *common* metadata.
def sortkey(item):
return BaseMetadata.from_metadata(item.metadata)

items_a = sorted(items_a, key=sortkey)
items_b = sorted(items_b, key=sortkey)
self.assertEqual(items_a, items_b)

def test_coords__all__meshcoords_default(self):
# coords() includes mesh-coords along with the others.
result = self.cube.coords()
expected = self.allcoords
self._assert_lists_equal(expected, result)

def test_coords__all__meshcoords_only(self):
# Coords(mesh_coords=True) returns only mesh-coords.
result = self.cube.coords(mesh_coords=True)
expected = [self.meshco_x, self.meshco_y]
self._assert_lists_equal(expected, result)

def test_coords__all__meshcoords_omitted(self):
# Coords(mesh_coords=False) omits the mesh-coords.
result = self.cube.coords(mesh_coords=False)
expected = set(self.allcoords) - set([self.meshco_x, self.meshco_y])
self._assert_lists_equal(expected, result)

def test_coords__axis__meshcoords(self):
# Coord (singular) with axis + mesh_coords=True
result = self.cube.coord(axis="x", mesh_coords=True)
self.assertIs(result, self.meshco_x)

def test_coords__dimcoords__meshcoords(self):
# dim_coords and mesh_coords should be mutually exclusive.
result = self.cube.coords(dim_coords=True, mesh_coords=True)
self.assertEqual(result, [])

def test_coords__nodimcoords__meshcoords(self):
# When mesh_coords=True, dim_coords=False should have no effect.
result = self.cube.coords(dim_coords=False, mesh_coords=True)
expected = [self.meshco_x, self.meshco_y]
self._assert_lists_equal(expected, result)


class Test_dtype(tests.IrisTest):
def setUp(self):
self.dtypes = (
Expand Down
2 changes: 1 addition & 1 deletion lib/iris/tests/unit/experimental/ugrid/test_MeshCoord.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ def _expected_elements_regexp(
regexp += r"Mesh\('test_mesh'\)"
else:
regexp += "<Mesh object at .*>"
regexp += ", location='face', axis='x'"
regexp += r", location='face', axis='x', shape=\(3,\)"
if standard_name:
regexp += ", standard_name='longitude'"
regexp += r", units=Unit\('degrees_east'\)"
Expand Down