Skip to content

Commit 5ceaa52

Browse files
authored
Allowed collapsing coordinates with nbounds != (0, 2) (#4870)
* Allowed collapsing over coordinates with nbounds!=0,2 * Simplified test * Added further tests for other warnings * pre-commit * Used unittest's assertWarnsRegex * Added What's new? entry
1 parent ce6f8b1 commit 5ceaa52

File tree

4 files changed

+179
-8
lines changed

4 files changed

+179
-8
lines changed

docs/src/whatsnew/latest.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ This document explains the changes made to Iris for this release
4242
#. `@rcomer`_ and `@pp-mo`_ (reviewer) factored masking into the returned
4343
sum-of-weights calculation from :obj:`~iris.analysis.SUM`. (:pull:`4905`)
4444

45+
#. `@schlunma`_ fixed a bug which prevented using
46+
:meth:`iris.cube.Cube.collapsed` on coordinates whose number of bounds
47+
differs from 0 or 2. This enables the use of this method on mesh
48+
coordinates. (:issue:`4672`, :pull:`4870`)
49+
4550

4651
💣 Incompatible Changes
4752
=======================
@@ -95,4 +100,4 @@ This document explains the changes made to Iris for this release
95100
96101
97102
.. _NEP13: https://numpy.org/neps/nep-0013-ufunc-overrides.html
98-
.. _NEP18: https://numpy.org/neps/nep-0018-array-function-protocol.html
103+
.. _NEP18: https://numpy.org/neps/nep-0018-array-function-protocol.html

lib/iris/coords.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2215,12 +2215,24 @@ def serialize(x):
22152215
"Metadata may not be fully descriptive for {!r}."
22162216
)
22172217
warnings.warn(msg.format(self.name()))
2218-
elif not self.is_contiguous():
2219-
msg = (
2220-
"Collapsing a non-contiguous coordinate. "
2221-
"Metadata may not be fully descriptive for {!r}."
2222-
)
2223-
warnings.warn(msg.format(self.name()))
2218+
else:
2219+
try:
2220+
self._sanity_check_bounds()
2221+
except ValueError as exc:
2222+
msg = (
2223+
"Cannot check if coordinate is contiguous: {} "
2224+
"Metadata may not be fully descriptive for {!r}. "
2225+
"Ignoring bounds."
2226+
)
2227+
warnings.warn(msg.format(str(exc), self.name()))
2228+
self.bounds = None
2229+
else:
2230+
if not self.is_contiguous():
2231+
msg = (
2232+
"Collapsing a non-contiguous coordinate. "
2233+
"Metadata may not be fully descriptive for {!r}."
2234+
)
2235+
warnings.warn(msg.format(self.name()))
22242236

22252237
if self.has_bounds():
22262238
item = self.core_bounds()

lib/iris/tests/unit/coords/test_Coord.py

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,8 @@ def test_dim_1d(self):
332332
)
333333
for units in ["unknown", "no_unit", 1, "K"]:
334334
coord.units = units
335-
collapsed_coord = coord.collapsed()
335+
with self.assertNoWarningsRegexp():
336+
collapsed_coord = coord.collapsed()
336337
self.assertArrayEqual(
337338
collapsed_coord.points, np.mean(coord.points)
338339
)
@@ -474,6 +475,98 @@ def test_lazy_nd_points_and_bounds(self):
474475
self.assertArrayEqual(collapsed_coord.points, da.array([55]))
475476
self.assertArrayEqual(collapsed_coord.bounds, da.array([[-2, 112]]))
476477

478+
def test_numeric_nd_multidim_bounds_warning(self):
479+
self.setupTestArrays((3, 4))
480+
coord = AuxCoord(self.pts_real, bounds=self.bds_real, long_name="y")
481+
482+
msg = (
483+
"Collapsing a multi-dimensional coordinate. "
484+
"Metadata may not be fully descriptive for 'y'."
485+
)
486+
with self.assertWarnsRegex(UserWarning, msg):
487+
coord.collapsed()
488+
489+
def test_lazy_nd_multidim_bounds_warning(self):
490+
self.setupTestArrays((3, 4))
491+
coord = AuxCoord(self.pts_lazy, bounds=self.bds_lazy, long_name="y")
492+
493+
msg = (
494+
"Collapsing a multi-dimensional coordinate. "
495+
"Metadata may not be fully descriptive for 'y'."
496+
)
497+
with self.assertWarnsRegex(UserWarning, msg):
498+
coord.collapsed()
499+
500+
def test_numeric_nd_noncontiguous_bounds_warning(self):
501+
self.setupTestArrays((3))
502+
coord = AuxCoord(self.pts_real, bounds=self.bds_real, long_name="y")
503+
504+
msg = (
505+
"Collapsing a non-contiguous coordinate. "
506+
"Metadata may not be fully descriptive for 'y'."
507+
)
508+
with self.assertWarnsRegex(UserWarning, msg):
509+
coord.collapsed()
510+
511+
def test_lazy_nd_noncontiguous_bounds_warning(self):
512+
self.setupTestArrays((3))
513+
coord = AuxCoord(self.pts_lazy, bounds=self.bds_lazy, long_name="y")
514+
515+
msg = (
516+
"Collapsing a non-contiguous coordinate. "
517+
"Metadata may not be fully descriptive for 'y'."
518+
)
519+
with self.assertWarnsRegex(UserWarning, msg):
520+
coord.collapsed()
521+
522+
def test_numeric_3_bounds(self):
523+
524+
points = np.array([2.0, 6.0, 4.0])
525+
bounds = np.array([[1.0, 0.0, 3.0], [5.0, 4.0, 7.0], [3.0, 2.0, 5.0]])
526+
527+
coord = AuxCoord(points, bounds=bounds, long_name="x")
528+
529+
msg = (
530+
r"Cannot check if coordinate is contiguous: Invalid operation for "
531+
r"'x', with 3 bound\(s\). Contiguous bounds are only defined for "
532+
r"1D coordinates with 2 bounds. Metadata may not be fully "
533+
r"descriptive for 'x'. Ignoring bounds."
534+
)
535+
with self.assertWarnsRegex(UserWarning, msg):
536+
collapsed_coord = coord.collapsed()
537+
538+
self.assertFalse(collapsed_coord.has_lazy_points())
539+
self.assertFalse(collapsed_coord.has_lazy_bounds())
540+
541+
self.assertArrayAlmostEqual(collapsed_coord.points, np.array([4.0]))
542+
self.assertArrayAlmostEqual(
543+
collapsed_coord.bounds, np.array([[2.0, 6.0]])
544+
)
545+
546+
def test_lazy_3_bounds(self):
547+
548+
points = da.arange(3) * 2.0
549+
bounds = da.arange(3 * 3).reshape(3, 3)
550+
551+
coord = AuxCoord(points, bounds=bounds, long_name="x")
552+
553+
msg = (
554+
r"Cannot check if coordinate is contiguous: Invalid operation for "
555+
r"'x', with 3 bound\(s\). Contiguous bounds are only defined for "
556+
r"1D coordinates with 2 bounds. Metadata may not be fully "
557+
r"descriptive for 'x'. Ignoring bounds."
558+
)
559+
with self.assertWarnsRegex(UserWarning, msg):
560+
collapsed_coord = coord.collapsed()
561+
562+
self.assertTrue(collapsed_coord.has_lazy_points())
563+
self.assertTrue(collapsed_coord.has_lazy_bounds())
564+
565+
self.assertArrayAlmostEqual(collapsed_coord.points, da.array([2.0]))
566+
self.assertArrayAlmostEqual(
567+
collapsed_coord.bounds, da.array([[0.0, 4.0]])
568+
)
569+
477570

478571
class Test_is_compatible(tests.IrisTest):
479572
def setUp(self):

lib/iris/tests/unit/cube/test_Cube.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,67 @@ def test_no_lat_weighted_aggregator_mixed(self):
565565
self._assert_nowarn_collapse_without_weight(coords, warn)
566566

567567

568+
class Test_collapsed_coord_with_3_bounds(tests.IrisTest):
569+
def setUp(self):
570+
self.cube = Cube([1, 2])
571+
572+
bounds = [[0.0, 1.0, 2.0], [2.0, 3.0, 4.0]]
573+
lat = AuxCoord([1.0, 2.0], bounds=bounds, standard_name="latitude")
574+
lon = AuxCoord([1.0, 2.0], bounds=bounds, standard_name="longitude")
575+
576+
self.cube.add_aux_coord(lat, 0)
577+
self.cube.add_aux_coord(lon, 0)
578+
579+
def _assert_warn_cannot_check_contiguity(self, warn):
580+
# Ensure that warning is raised.
581+
for coord in ["latitude", "longitude"]:
582+
msg = (
583+
f"Cannot check if coordinate is contiguous: Invalid "
584+
f"operation for '{coord}', with 3 bound(s). Contiguous "
585+
f"bounds are only defined for 1D coordinates with 2 "
586+
f"bounds. Metadata may not be fully descriptive for "
587+
f"'{coord}'. Ignoring bounds."
588+
)
589+
self.assertIn(mock.call(msg), warn.call_args_list)
590+
591+
def _assert_cube_as_expected(self, cube):
592+
"""Ensure that cube data and coordiantes are as expected."""
593+
self.assertArrayEqual(cube.data, np.array(3))
594+
595+
lat = cube.coord("latitude")
596+
self.assertArrayAlmostEqual(lat.points, np.array([1.5]))
597+
self.assertArrayAlmostEqual(lat.bounds, np.array([[1.0, 2.0]]))
598+
599+
lon = cube.coord("longitude")
600+
self.assertArrayAlmostEqual(lon.points, np.array([1.5]))
601+
self.assertArrayAlmostEqual(lon.bounds, np.array([[1.0, 2.0]]))
602+
603+
def test_collapsed_lat_with_3_bounds(self):
604+
"""Collapse latitude with 3 bounds."""
605+
with mock.patch("warnings.warn") as warn:
606+
collapsed_cube = self.cube.collapsed("latitude", iris.analysis.SUM)
607+
self._assert_warn_cannot_check_contiguity(warn)
608+
self._assert_cube_as_expected(collapsed_cube)
609+
610+
def test_collapsed_lon_with_3_bounds(self):
611+
"""Collapse longitude with 3 bounds."""
612+
with mock.patch("warnings.warn") as warn:
613+
collapsed_cube = self.cube.collapsed(
614+
"longitude", iris.analysis.SUM
615+
)
616+
self._assert_warn_cannot_check_contiguity(warn)
617+
self._assert_cube_as_expected(collapsed_cube)
618+
619+
def test_collapsed_lat_lon_with_3_bounds(self):
620+
"""Collapse latitude and longitude with 3 bounds."""
621+
with mock.patch("warnings.warn") as warn:
622+
collapsed_cube = self.cube.collapsed(
623+
["latitude", "longitude"], iris.analysis.SUM
624+
)
625+
self._assert_warn_cannot_check_contiguity(warn)
626+
self._assert_cube_as_expected(collapsed_cube)
627+
628+
568629
class Test_summary(tests.IrisTest):
569630
def setUp(self):
570631
self.cube = Cube(0)

0 commit comments

Comments
 (0)