diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 0c1b8292b5..8c666c2d5d 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -42,8 +42,9 @@ This document explains the changes made to Iris for this release 💣 Incompatible Changes ======================= -#. N/A - +#. `@bouweandela`_ changed the ``convert_units`` method on cubes and coordinates + so it also converts the values of the attributes ``"actual_range"``, + ``"valid_max"``, ``"valid_min"``, and ``"valid_range"``. (:pull:`6416`) 🚀 Performance Enhancements =========================== diff --git a/lib/iris/coords.py b/lib/iris/coords.py index ca73dcb729..9fdd20d6fc 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -747,6 +747,9 @@ def pointwise_convert(values): else: new_bounds = self.units.convert(self.bounds, unit) self.bounds = new_bounds + for key in "actual_range", "valid_max", "valid_min", "valid_range": + if key in self.attributes: + self.attributes[key] = self.units.convert(self.attributes[key], unit) self.units = unit def is_compatible(self, other, ignore=None): diff --git a/lib/iris/cube.py b/lib/iris/cube.py index 77191c3a9a..be8ddcfc47 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -1478,6 +1478,11 @@ def convert_units(self, unit: str | Unit) -> None: else: new_data = self.units.convert(self.data, unit) self.data = new_data + for key in "actual_range", "valid_max", "valid_min", "valid_range": + if key in self.attributes.locals: + self.attributes.locals[key] = self.units.convert( + self.attributes.locals[key], unit + ) self.units = unit def add_cell_method(self, cell_method: CellMethod) -> None: diff --git a/lib/iris/tests/unit/coords/test_Coord.py b/lib/iris/tests/unit/coords/test_Coord.py index 97429f58f8..ba77379a50 100644 --- a/lib/iris/tests/unit/coords/test_Coord.py +++ b/lib/iris/tests/unit/coords/test_Coord.py @@ -23,6 +23,7 @@ from iris.coords import AuxCoord, Coord, DimCoord from iris.cube import Cube from iris.exceptions import UnitConversionError +from iris.tests import _shared_utils from iris.tests.unit.coords import CoordTestMixin from iris.warnings import IrisVagueMetadataWarning @@ -1177,16 +1178,35 @@ def test_coord_3d(self): coord._sanity_check_bounds() -class Test_convert_units(tests.IrisTest): +class Test_convert_units: def test_convert_unknown_units(self): coord = iris.coords.AuxCoord(1, units="unknown") emsg = ( "Cannot convert from unknown units. " 'The "units" attribute may be set directly.' ) - with self.assertRaisesRegex(UnitConversionError, emsg): + with pytest.raises(UnitConversionError, match=emsg): coord.convert_units("degrees") + @pytest.mark.parametrize( + "attribute", + [ + "valid_min", + "valid_max", + "valid_range", + "actual_range", + ], + ) + def test_convert_attributes(self, attribute): + coord = iris.coords.AuxCoord(1, units="m") + value = np.array([0, 10]) if attribute.endswith("range") else 0 + coord.attributes[attribute] = value + coord.convert_units("ft") + converted_value = cf_units.Unit("m").convert(value, "ft") + _shared_utils.assert_array_all_close( + coord.attributes[attribute], converted_value + ) + class Test___str__(tests.IrisTest): def test_short_time_interval(self): diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py index 21fa27d2f9..0652ab9d59 100644 --- a/lib/iris/tests/unit/cube/test_Cube.py +++ b/lib/iris/tests/unit/cube/test_Cube.py @@ -3024,6 +3024,25 @@ def test_unit_multiply(self): cube.data _client.close() + @pytest.mark.parametrize( + "attribute", + [ + "valid_min", + "valid_max", + "valid_range", + "actual_range", + ], + ) + def test_convert_attributes(self, attribute): + cube = iris.cube.Cube(1, units="degrees_C") + value = np.array([0, 10]) if attribute.endswith("range") else 0 + cube.attributes.locals[attribute] = value + cube.convert_units("K") + converted_value = Unit("degrees_C").convert(value, "K") + _shared_utils.assert_array_all_close( + cube.attributes.locals[attribute], converted_value + ) + class Test__eq__data: """Partial cube equality testing, for data type only."""