Skip to content

Commit

Permalink
Allowed collapsing coordinates with nbounds != (0, 2) (SciTools#4870)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
schlunma authored and pp-mo committed Sep 26, 2022
1 parent b1d5b4f commit 0ca87e0
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 8 deletions.
7 changes: 6 additions & 1 deletion docs/src/whatsnew/latest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ This document explains the changes made to Iris for this release
#. `@rcomer`_ and `@pp-mo`_ (reviewer) factored masking into the returned
sum-of-weights calculation from :obj:`~iris.analysis.SUM`. (:pull:`4905`)

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


💣 Incompatible Changes
=======================
Expand Down Expand Up @@ -95,4 +100,4 @@ This document explains the changes made to Iris for this release
.. _NEP13: https://numpy.org/neps/nep-0013-ufunc-overrides.html
.. _NEP18: https://numpy.org/neps/nep-0018-array-function-protocol.html
.. _NEP18: https://numpy.org/neps/nep-0018-array-function-protocol.html
24 changes: 18 additions & 6 deletions lib/iris/coords.py
Original file line number Diff line number Diff line change
Expand Up @@ -2215,12 +2215,24 @@ def serialize(x):
"Metadata may not be fully descriptive for {!r}."
)
warnings.warn(msg.format(self.name()))
elif not self.is_contiguous():
msg = (
"Collapsing a non-contiguous coordinate. "
"Metadata may not be fully descriptive for {!r}."
)
warnings.warn(msg.format(self.name()))
else:
try:
self._sanity_check_bounds()
except ValueError as exc:
msg = (
"Cannot check if coordinate is contiguous: {} "
"Metadata may not be fully descriptive for {!r}. "
"Ignoring bounds."
)
warnings.warn(msg.format(str(exc), self.name()))
self.bounds = None
else:
if not self.is_contiguous():
msg = (
"Collapsing a non-contiguous coordinate. "
"Metadata may not be fully descriptive for {!r}."
)
warnings.warn(msg.format(self.name()))

if self.has_bounds():
item = self.core_bounds()
Expand Down
95 changes: 94 additions & 1 deletion lib/iris/tests/unit/coords/test_Coord.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,8 @@ def test_dim_1d(self):
)
for units in ["unknown", "no_unit", 1, "K"]:
coord.units = units
collapsed_coord = coord.collapsed()
with self.assertNoWarningsRegexp():
collapsed_coord = coord.collapsed()
self.assertArrayEqual(
collapsed_coord.points, np.mean(coord.points)
)
Expand Down Expand Up @@ -474,6 +475,98 @@ def test_lazy_nd_points_and_bounds(self):
self.assertArrayEqual(collapsed_coord.points, da.array([55]))
self.assertArrayEqual(collapsed_coord.bounds, da.array([[-2, 112]]))

def test_numeric_nd_multidim_bounds_warning(self):
self.setupTestArrays((3, 4))
coord = AuxCoord(self.pts_real, bounds=self.bds_real, long_name="y")

msg = (
"Collapsing a multi-dimensional coordinate. "
"Metadata may not be fully descriptive for 'y'."
)
with self.assertWarnsRegex(UserWarning, msg):
coord.collapsed()

def test_lazy_nd_multidim_bounds_warning(self):
self.setupTestArrays((3, 4))
coord = AuxCoord(self.pts_lazy, bounds=self.bds_lazy, long_name="y")

msg = (
"Collapsing a multi-dimensional coordinate. "
"Metadata may not be fully descriptive for 'y'."
)
with self.assertWarnsRegex(UserWarning, msg):
coord.collapsed()

def test_numeric_nd_noncontiguous_bounds_warning(self):
self.setupTestArrays((3))
coord = AuxCoord(self.pts_real, bounds=self.bds_real, long_name="y")

msg = (
"Collapsing a non-contiguous coordinate. "
"Metadata may not be fully descriptive for 'y'."
)
with self.assertWarnsRegex(UserWarning, msg):
coord.collapsed()

def test_lazy_nd_noncontiguous_bounds_warning(self):
self.setupTestArrays((3))
coord = AuxCoord(self.pts_lazy, bounds=self.bds_lazy, long_name="y")

msg = (
"Collapsing a non-contiguous coordinate. "
"Metadata may not be fully descriptive for 'y'."
)
with self.assertWarnsRegex(UserWarning, msg):
coord.collapsed()

def test_numeric_3_bounds(self):

points = np.array([2.0, 6.0, 4.0])
bounds = np.array([[1.0, 0.0, 3.0], [5.0, 4.0, 7.0], [3.0, 2.0, 5.0]])

coord = AuxCoord(points, bounds=bounds, long_name="x")

msg = (
r"Cannot check if coordinate is contiguous: Invalid operation for "
r"'x', with 3 bound\(s\). Contiguous bounds are only defined for "
r"1D coordinates with 2 bounds. Metadata may not be fully "
r"descriptive for 'x'. Ignoring bounds."
)
with self.assertWarnsRegex(UserWarning, msg):
collapsed_coord = coord.collapsed()

self.assertFalse(collapsed_coord.has_lazy_points())
self.assertFalse(collapsed_coord.has_lazy_bounds())

self.assertArrayAlmostEqual(collapsed_coord.points, np.array([4.0]))
self.assertArrayAlmostEqual(
collapsed_coord.bounds, np.array([[2.0, 6.0]])
)

def test_lazy_3_bounds(self):

points = da.arange(3) * 2.0
bounds = da.arange(3 * 3).reshape(3, 3)

coord = AuxCoord(points, bounds=bounds, long_name="x")

msg = (
r"Cannot check if coordinate is contiguous: Invalid operation for "
r"'x', with 3 bound\(s\). Contiguous bounds are only defined for "
r"1D coordinates with 2 bounds. Metadata may not be fully "
r"descriptive for 'x'. Ignoring bounds."
)
with self.assertWarnsRegex(UserWarning, msg):
collapsed_coord = coord.collapsed()

self.assertTrue(collapsed_coord.has_lazy_points())
self.assertTrue(collapsed_coord.has_lazy_bounds())

self.assertArrayAlmostEqual(collapsed_coord.points, da.array([2.0]))
self.assertArrayAlmostEqual(
collapsed_coord.bounds, da.array([[0.0, 4.0]])
)


class Test_is_compatible(tests.IrisTest):
def setUp(self):
Expand Down
61 changes: 61 additions & 0 deletions lib/iris/tests/unit/cube/test_Cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,67 @@ def test_no_lat_weighted_aggregator_mixed(self):
self._assert_nowarn_collapse_without_weight(coords, warn)


class Test_collapsed_coord_with_3_bounds(tests.IrisTest):
def setUp(self):
self.cube = Cube([1, 2])

bounds = [[0.0, 1.0, 2.0], [2.0, 3.0, 4.0]]
lat = AuxCoord([1.0, 2.0], bounds=bounds, standard_name="latitude")
lon = AuxCoord([1.0, 2.0], bounds=bounds, standard_name="longitude")

self.cube.add_aux_coord(lat, 0)
self.cube.add_aux_coord(lon, 0)

def _assert_warn_cannot_check_contiguity(self, warn):
# Ensure that warning is raised.
for coord in ["latitude", "longitude"]:
msg = (
f"Cannot check if coordinate is contiguous: Invalid "
f"operation for '{coord}', with 3 bound(s). Contiguous "
f"bounds are only defined for 1D coordinates with 2 "
f"bounds. Metadata may not be fully descriptive for "
f"'{coord}'. Ignoring bounds."
)
self.assertIn(mock.call(msg), warn.call_args_list)

def _assert_cube_as_expected(self, cube):
"""Ensure that cube data and coordiantes are as expected."""
self.assertArrayEqual(cube.data, np.array(3))

lat = cube.coord("latitude")
self.assertArrayAlmostEqual(lat.points, np.array([1.5]))
self.assertArrayAlmostEqual(lat.bounds, np.array([[1.0, 2.0]]))

lon = cube.coord("longitude")
self.assertArrayAlmostEqual(lon.points, np.array([1.5]))
self.assertArrayAlmostEqual(lon.bounds, np.array([[1.0, 2.0]]))

def test_collapsed_lat_with_3_bounds(self):
"""Collapse latitude with 3 bounds."""
with mock.patch("warnings.warn") as warn:
collapsed_cube = self.cube.collapsed("latitude", iris.analysis.SUM)
self._assert_warn_cannot_check_contiguity(warn)
self._assert_cube_as_expected(collapsed_cube)

def test_collapsed_lon_with_3_bounds(self):
"""Collapse longitude with 3 bounds."""
with mock.patch("warnings.warn") as warn:
collapsed_cube = self.cube.collapsed(
"longitude", iris.analysis.SUM
)
self._assert_warn_cannot_check_contiguity(warn)
self._assert_cube_as_expected(collapsed_cube)

def test_collapsed_lat_lon_with_3_bounds(self):
"""Collapse latitude and longitude with 3 bounds."""
with mock.patch("warnings.warn") as warn:
collapsed_cube = self.cube.collapsed(
["latitude", "longitude"], iris.analysis.SUM
)
self._assert_warn_cannot_check_contiguity(warn)
self._assert_cube_as_expected(collapsed_cube)


class Test_summary(tests.IrisTest):
def setUp(self):
self.cube = Cube(0)
Expand Down

0 comments on commit 0ca87e0

Please sign in to comment.