Skip to content

Commit 61e30cd

Browse files
authored
Merge pull request #6669 from trexfeathers/v3-13-experiment
Sort-of-merge-back: `v3.12.3` into `v3.13.x`
2 parents 5cdcc16 + 2b09495 commit 61e30cd

File tree

18 files changed

+242
-29
lines changed

18 files changed

+242
-29
lines changed

docs/src/whatsnew/3.12.rst

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ v3.12.2 (09 May 2025)
6969
:color: primary
7070
:icon: alert
7171
:animate: fade-in
72-
:open:
7372

7473
The patches in this release of Iris include:
7574

@@ -78,6 +77,23 @@ v3.12.2 (09 May 2025)
7877
operations for many users.
7978

8079

80+
v3.12.3 (22 Aug 2025)
81+
=====================
82+
83+
.. dropdown:: v3.12.3 Patches
84+
:color: primary
85+
:icon: alert
86+
:animate: fade-in
87+
:open:
88+
89+
The patches in this release of Iris include:
90+
91+
#. Improved compatibility with NumPy >= v1.25 for array comparisons, in
92+
response to deprecations around un-broadcastable arrays. Delivered in a
93+
patch release to meet a time-critical need for a specific user.
94+
:ref:`See the full entry for more<3_12_3_array_comparison>`.
95+
96+
8197
📢 Announcements
8298
================
8399

@@ -217,6 +233,14 @@ v3.12.2 (09 May 2025)
217233
#. `@trexfeathers`_ refactored Iris loading and saving to make it compatible
218234
with Dask version ``2025.4.0`` and above. (:pull:`6451`)
219235

236+
.. _3_12_3_array_comparison:
237+
238+
#. `@trexfeathers`_ and `@ukmo-ccbunney`_ adapted array comparison in response
239+
to NumPy v1.25 deprecating comparison of un-broadcastable arrays. It is
240+
hoped that users will see no difference in behaviour, but please get in touch
241+
if you notice anything. See `NumPy v1.25 expired deprecations`_ and
242+
`numpy#22707`_ for more. (:pull:`6665`)
243+
220244

221245
📚 Documentation
222246
================
@@ -273,3 +297,5 @@ v3.12.2 (09 May 2025)
273297
274298
.. _SPEC 0: https://scientific-python.org/specs/spec-0000/
275299
.. _Running setuptools commands: https://setuptools.pypa.io/en/latest/deprecated/commands.html
300+
.. _NumPy v1.25 expired deprecations: https://numpy.org/doc/stable/release/1.25.0-notes.html#expired-deprecations
301+
.. _numpy#22707: https://github.com/numpy/numpy/pull/22707

lib/iris/_concatenate.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from xxhash import xxh3_64
1717

1818
from iris._lazy_data import concatenate as concatenate_arrays
19+
from iris.common.metadata import hexdigest
1920
import iris.coords
2021
from iris.coords import AncillaryVariable, AuxCoord, CellMeasure, DimCoord
2122
import iris.cube
@@ -792,7 +793,7 @@ def _coordinate_differences(self, other, attr, reason="metadata"):
792793
diff_names = []
793794
for self_key, self_value in self_dict.items():
794795
other_value = other_dict[self_key]
795-
if self_value != other_value:
796+
if hexdigest(self_value) != hexdigest(other_value):
796797
diff_names.append(self_key)
797798
result = (
798799
" " + reason,

lib/iris/_constraints.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,8 @@ def __init__(self, **attributes):
531531
super().__init__(cube_func=self._cube_func)
532532

533533
def __eq__(self, other):
534+
# Note: equality means that NumPy arrays are not supported for
535+
# AttributeConstraints (get the truth ambiguity error).
534536
eq = (
535537
isinstance(other, AttributeConstraint)
536538
and self._attributes == other._attributes
@@ -553,6 +555,8 @@ def _cube_func(self, cube):
553555
match = False
554556
break
555557
else:
558+
# Note: equality means that NumPy arrays are not supported
559+
# for AttributeConstraints (get the truth ambiguity error).
556560
if cube_attr != value:
557561
match = False
558562
break

lib/iris/common/mixin.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ def __eq__(self, other):
115115
match = set(self.keys()) == set(other.keys())
116116
if match:
117117
for key, value in self.items():
118+
# TODO: should this use the iris.common.metadata approach of
119+
# using hexdigest? Might be a breaking change for some corner
120+
# cases, so would need a major release.
118121
match = np.array_equal(
119122
np.array(value, ndmin=1), np.array(other[key], ndmin=1)
120123
)

lib/iris/coords.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -772,7 +772,9 @@ def is_compatible(self, other, ignore=None):
772772
ignore = (ignore,)
773773
common_keys = common_keys.difference(ignore)
774774
for key in common_keys:
775-
if np.any(self.attributes[key] != other.attributes[key]):
775+
if not iris.util._attribute_equal(
776+
self.attributes[key], other.attributes[key]
777+
):
776778
compatible = False
777779
break
778780

lib/iris/cube.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1446,7 +1446,9 @@ def is_compatible(
14461446
ignore = (ignore,)
14471447
common_keys = common_keys.difference(ignore)
14481448
for key in common_keys:
1449-
if np.any(self.attributes[key] != other.attributes[key]):
1449+
if not iris.util._attribute_equal(
1450+
self.attributes[key], other.attributes[key]
1451+
):
14501452
compatible = False
14511453
break
14521454

lib/iris/fileformats/_structured_array_identification.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,9 @@ def __eq__(self, other):
111111

112112
result = NotImplemented
113113
if stride is not None or arr is not None:
114-
result = stride == self.stride and np.all(self.unique_ordered_values == arr)
114+
result = stride == self.stride and np.array_equal(
115+
self.unique_ordered_values, arr
116+
)
115117
return result
116118

117119
def __ne__(self, other):
@@ -284,7 +286,7 @@ def from_array(cls, arr):
284286
# Do one last sanity check - does the array we've just described
285287
# actually compute the correct array?
286288
constructed_array = structure.construct_array(arr.size)
287-
if not np.all(constructed_array == arr):
289+
if not np.array_equal(constructed_array, arr):
288290
structure = None
289291

290292
return structure

lib/iris/fileformats/netcdf/saver.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2785,13 +2785,7 @@ def save(
27852785
# Fnd any global attributes which are not the same on *all* cubes.
27862786
def attr_values_equal(val1, val2):
27872787
# An equality test which also works when some values are numpy arrays (!)
2788-
# As done in :meth:`iris.common.mixin.LimitedAttributeDict.__eq__`.
2789-
match = val1 == val2
2790-
try:
2791-
match = bool(match)
2792-
except ValueError:
2793-
match = match.all()
2794-
return match
2788+
return iris.util._attribute_equal(val1, val2)
27952789

27962790
cube0 = cubes[0]
27972791
invalid_globals = set(
@@ -2878,7 +2872,9 @@ def attr_values_equal(val1, val2):
28782872
common_keys.intersection_update(keys)
28792873
different_value_keys = []
28802874
for key in common_keys:
2881-
if np.any(attributes[key] != cube.attributes[key]):
2875+
if not iris.util._attribute_equal(
2876+
attributes[key], cube.attributes[key]
2877+
):
28822878
different_value_keys.append(key)
28832879
common_keys.difference_update(different_value_keys)
28842880
local_keys.update(different_value_keys)

lib/iris/fileformats/pp_load_rules.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,15 @@ def _convert_vertical_coords(
139139
)
140140
coords_and_dims.append((coord, dim))
141141

142+
# Common calc for Depth
143+
try:
144+
svd_lev_eq = brsvd1 == brlev
145+
except ValueError:
146+
# In case of broadcasting errors.
147+
svd_lev_eq = False
148+
142149
# Depth - unbound.
143-
if (len(lbcode) != 5) and (lbvc == 2) and np.all(brsvd1 == brlev):
150+
if (len(lbcode) != 5) and (lbvc == 2) and np.all(svd_lev_eq):
144151
coord = _dim_or_aux(
145152
blev,
146153
standard_name="depth",
@@ -150,7 +157,7 @@ def _convert_vertical_coords(
150157
coords_and_dims.append((coord, dim))
151158

152159
# Depth - bound.
153-
if (len(lbcode) != 5) and (lbvc == 2) and np.all(brsvd1 != brlev):
160+
if (len(lbcode) != 5) and (lbvc == 2) and np.all(~svd_lev_eq):
154161
coord = _dim_or_aux(
155162
blev,
156163
standard_name="depth",
@@ -164,10 +171,10 @@ def _convert_vertical_coords(
164171
if (
165172
(len(lbcode) != 5)
166173
and (lbvc == 2)
167-
and (np.any(brsvd1 == brlev) and np.any(brsvd1 != brlev))
174+
and (np.any(svd_lev_eq) and np.any(~svd_lev_eq))
168175
):
169-
lower = np.where(brsvd1 == brlev, blev, brsvd1)
170-
upper = np.where(brsvd1 == brlev, blev, brlev)
176+
lower = np.where(svd_lev_eq, blev, brsvd1)
177+
upper = np.where(svd_lev_eq, blev, brlev)
171178
coord = _dim_or_aux(
172179
blev,
173180
standard_name="depth",
@@ -189,7 +196,7 @@ def _convert_vertical_coords(
189196
units="1",
190197
)
191198
coords_and_dims.append((coord, dim))
192-
elif np.any(brsvd1 != brlev):
199+
elif np.any(~svd_lev_eq):
193200
# UM populates metadata CORRECTLY,
194201
# so treat it as the expected (bounded) soil depth.
195202
coord = _dim_or_aux(

lib/iris/tests/test_coding_standards.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,10 @@ def last_change_by_fname():
223223

224224
# Call "git whatchanged" to get the details of all the files and when
225225
# they were last changed.
226+
# TODO: whatchanged is deprecated, find an alternative Git command.
226227
output = subprocess.check_output(
227-
["git", "whatchanged", "--pretty=TIME:%ct"], cwd=IRIS_REPO_DIRPATH
228+
["git", "whatchanged", "--pretty=TIME:%ct", "--i-still-use-this"],
229+
cwd=IRIS_REPO_DIRPATH,
228230
)
229231

230232
output = output.decode().split("\n")

0 commit comments

Comments
 (0)