Skip to content

Commit dab88e8

Browse files
authored
Merge pull request #5361 from SciTools/v3.6.x
v3.6.x merge-back
2 parents bbba807 + d6c6c99 commit dab88e8

File tree

12 files changed

+818
-330
lines changed

12 files changed

+818
-330
lines changed

docs/src/whatsnew/3.6.rst

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,56 @@ This document explains the changes made to Iris for this release
5555
or feature requests for improving Iris. Enjoy!
5656

5757

58+
v3.6.1 (26 June 2023)
59+
=====================
60+
61+
.. dropdown:: v3.6.1 Patches
62+
:color: primary
63+
:icon: alert
64+
:animate: fade-in
65+
66+
📢 **Announcements**
67+
68+
Welcome and congratulations to `@sloosvel`_ who made their first contribution to
69+
Iris! 🎉
70+
71+
The patches in this release of Iris include:
72+
73+
✨ **Features**
74+
75+
#. `@rcomer`_ rewrote :func:`~iris.util.broadcast_to_shape` so it now handles
76+
lazy data. This pull-request has been included to support :pull:`5341`.
77+
(:pull:`5307`) [``pre-v3.7.0``]
78+
79+
🐛 **Bugs Fixed**
80+
81+
#. `@stephenworsley`_ fixed :meth:`~iris.cube.Cube.convert_units` to allow unit
82+
conversion of lazy data when using a `Distributed`_ scheduler.
83+
(:issue:`5347`, :pull:`5349`)
84+
85+
#. `@schlunma`_ fixed a bug in the concatenation of cubes with aux factories
86+
which could lead to a `KeyError` due to dependencies that have not been
87+
properly updated.
88+
(:issue:`5339`, :pull:`5340`)
89+
90+
#. `@schlunma`_ fixed a bug which realized all weights during weighted
91+
aggregation. Now weighted aggregation is fully lazy again.
92+
(:issue:`5338`, :pull:`5341`)
93+
94+
🚀 **Performance Enhancements**
95+
96+
#. `@sloosvel`_ improved :meth:`~iris.cube.CubeList.concatenate_cube` and
97+
:meth:`~iris.cube.CubeList.concatenate` to ensure that lazy auxiliary coordinate
98+
points and bounds are not realized. This change now allows cubes with
99+
high-resolution auxiliary coordinates to concatenate successfully whilst using a
100+
minimal in-core memory footprint.
101+
(:issue:`5115`, :pull:`5142`)
102+
103+
Note that, the above contribution labelled with ``pre-v3.7.0`` is part of the
104+
forthcoming Iris ``v3.7.0`` release, but requires to be included in this patch
105+
release.
106+
107+
58108
📢 Announcements
59109
================
60110

@@ -169,6 +219,7 @@ This document explains the changes made to Iris for this release
169219
core dev names are automatically included by the common_links.inc:
170220
171221
.. _@fnattino: https://github.com/fnattino
222+
.. _@sloosvel: https://github.com/sloosvel
172223

173224

174225
.. comment
@@ -180,4 +231,5 @@ This document explains the changes made to Iris for this release
180231
.. _PEP-0621: https://peps.python.org/pep-0621/
181232
.. _pypa/build: https://pypa-build.readthedocs.io/en/stable/
182233
.. _NEP29: https://numpy.org/neps/nep-0029-deprecation_policy.html
183-
.. _Contributor Covenant: https://www.contributor-covenant.org/version/2/1/code_of_conduct/
234+
.. _Contributor Covenant: https://www.contributor-covenant.org/version/2/1/code_of_conduct/
235+
.. _Distributed: https://distributed.dask.org/en/stable/

lib/iris/_concatenate.py

Lines changed: 90 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,15 @@ def __new__(mcs, coord, dims):
101101
102102
"""
103103
defn = coord.metadata
104-
points_dtype = coord.points.dtype
105-
bounds_dtype = coord.bounds.dtype if coord.bounds is not None else None
104+
points_dtype = coord.core_points().dtype
105+
bounds_dtype = (
106+
coord.core_bounds().dtype
107+
if coord.core_bounds() is not None
108+
else None
109+
)
106110
kwargs = {}
107111
# Add scalar flag metadata.
108-
kwargs["scalar"] = coord.points.size == 1
112+
kwargs["scalar"] = coord.core_points().size == 1
109113
# Add circular flag metadata for dimensional coordinates.
110114
if hasattr(coord, "circular"):
111115
kwargs["circular"] = coord.circular
@@ -700,18 +704,27 @@ def _cmp(coord, other):
700704
701705
"""
702706
# A candidate axis must have non-identical coordinate points.
703-
candidate_axis = not array_equal(coord.points, other.points)
707+
candidate_axis = not array_equal(
708+
coord.core_points(), other.core_points()
709+
)
704710

705711
if candidate_axis:
706712
# Ensure both have equal availability of bounds.
707-
result = (coord.bounds is None) == (other.bounds is None)
713+
result = (coord.core_bounds() is None) == (
714+
other.core_bounds() is None
715+
)
708716
else:
709-
if coord.bounds is not None and other.bounds is not None:
717+
if (
718+
coord.core_bounds() is not None
719+
and other.core_bounds() is not None
720+
):
710721
# Ensure equality of bounds.
711-
result = array_equal(coord.bounds, other.bounds)
722+
result = array_equal(coord.core_bounds(), other.core_bounds())
712723
else:
713724
# Ensure both have equal availability of bounds.
714-
result = coord.bounds is None and other.bounds is None
725+
result = (
726+
coord.core_bounds() is None and other.core_bounds() is None
727+
)
715728

716729
return result, candidate_axis
717730

@@ -851,9 +864,13 @@ def concatenate(self):
851864
# Concatenate the new dimension coordinate.
852865
dim_coords_and_dims = self._build_dim_coordinates()
853866

854-
# Concatenate the new auxiliary coordinates.
867+
# Concatenate the new auxiliary coordinates (does NOT include
868+
# scalar coordinates!).
855869
aux_coords_and_dims = self._build_aux_coordinates()
856870

871+
# Concatenate the new scalar coordinates.
872+
scalar_coords = self._build_scalar_coordinates()
873+
857874
# Concatenate the new cell measures
858875
cell_measures_and_dims = self._build_cell_measures()
859876

@@ -862,18 +879,21 @@ def concatenate(self):
862879

863880
# Concatenate the new aux factories
864881
aux_factories = self._build_aux_factories(
865-
dim_coords_and_dims, aux_coords_and_dims
882+
dim_coords_and_dims, aux_coords_and_dims, scalar_coords
866883
)
867884

868885
# Concatenate the new data payload.
869886
data = self._build_data()
870887

871888
# Build the new cube.
889+
all_aux_coords_and_dims = aux_coords_and_dims + [
890+
(scalar_coord, ()) for scalar_coord in scalar_coords
891+
]
872892
kwargs = cube_signature.defn._asdict()
873893
cube = iris.cube.Cube(
874894
data,
875895
dim_coords_and_dims=dim_coords_and_dims,
876-
aux_coords_and_dims=aux_coords_and_dims,
896+
aux_coords_and_dims=all_aux_coords_and_dims,
877897
cell_measures_and_dims=cell_measures_and_dims,
878898
ancillary_variables_and_dims=ancillary_variables_and_dims,
879899
aux_factories=aux_factories,
@@ -1095,7 +1115,7 @@ def _build_aux_coordinates(self):
10951115
# Concatenate the points together.
10961116
dim = dims.index(self.axis)
10971117
points = [
1098-
skton.signature.aux_coords_and_dims[i].coord.points
1118+
skton.signature.aux_coords_and_dims[i].coord.core_points()
10991119
for skton in skeletons
11001120
]
11011121
points = np.concatenate(tuple(points), axis=dim)
@@ -1104,7 +1124,9 @@ def _build_aux_coordinates(self):
11041124
bnds = None
11051125
if coord.has_bounds():
11061126
bnds = [
1107-
skton.signature.aux_coords_and_dims[i].coord.bounds
1127+
skton.signature.aux_coords_and_dims[
1128+
i
1129+
].coord.core_bounds()
11081130
for skton in skeletons
11091131
]
11101132
bnds = np.concatenate(tuple(bnds), axis=dim)
@@ -1132,12 +1154,22 @@ def _build_aux_coordinates(self):
11321154

11331155
aux_coords_and_dims.append((coord.copy(), dims))
11341156

1135-
# Generate all the scalar coordinates for the new concatenated cube.
1136-
for coord in cube_signature.scalar_coords:
1137-
aux_coords_and_dims.append((coord.copy(), ()))
1138-
11391157
return aux_coords_and_dims
11401158

1159+
def _build_scalar_coordinates(self):
1160+
"""
1161+
Generate the scalar coordinates for the new concatenated cube.
1162+
1163+
Returns:
1164+
A list of scalar coordinates.
1165+
1166+
"""
1167+
scalar_coords = []
1168+
for coord in self._cube_signature.scalar_coords:
1169+
scalar_coords.append(coord.copy())
1170+
1171+
return scalar_coords
1172+
11411173
def _build_cell_measures(self):
11421174
"""
11431175
Generate the cell measures with associated dimension(s)
@@ -1216,7 +1248,9 @@ def _build_ancillary_variables(self):
12161248

12171249
return ancillary_variables_and_dims
12181250

1219-
def _build_aux_factories(self, dim_coords_and_dims, aux_coords_and_dims):
1251+
def _build_aux_factories(
1252+
self, dim_coords_and_dims, aux_coords_and_dims, scalar_coords
1253+
):
12201254
"""
12211255
Generate the aux factories for the new concatenated cube.
12221256
@@ -1230,6 +1264,9 @@ def _build_aux_factories(self, dim_coords_and_dims, aux_coords_and_dims):
12301264
A list of auxiliary coordinates and dimension(s) tuple pairs from
12311265
the concatenated cube.
12321266
1267+
* scalar_coords:
1268+
A list of scalar coordinates from the concatenated cube.
1269+
12331270
Returns:
12341271
A list of :class:`iris.aux_factory.AuxCoordFactory`.
12351272
@@ -1240,35 +1277,44 @@ def _build_aux_factories(self, dim_coords_and_dims, aux_coords_and_dims):
12401277
old_aux_coords = [a[0] for a in cube_signature.aux_coords_and_dims]
12411278
new_dim_coords = [d[0] for d in dim_coords_and_dims]
12421279
new_aux_coords = [a[0] for a in aux_coords_and_dims]
1243-
scalar_coords = cube_signature.scalar_coords
1280+
old_scalar_coords = cube_signature.scalar_coords
1281+
new_scalar_coords = scalar_coords
12441282

12451283
aux_factories = []
12461284

12471285
# Generate all the factories for the new concatenated cube.
1248-
for i, (coord, dims, factory) in enumerate(
1249-
cube_signature.derived_coords_and_dims
1250-
):
1251-
# Check whether the derived coordinate of the factory spans the
1252-
# nominated dimension of concatenation.
1253-
if self.axis in dims:
1254-
# Update the dependencies of the factory with coordinates of
1255-
# the concatenated cube. We need to check all coordinate types
1256-
# here (dim coords, aux coords, and scalar coords).
1257-
new_dependencies = {}
1258-
for old_dependency in factory.dependencies.values():
1259-
if old_dependency in old_dim_coords:
1260-
dep_idx = old_dim_coords.index(old_dependency)
1261-
new_dependency = new_dim_coords[dep_idx]
1262-
elif old_dependency in old_aux_coords:
1263-
dep_idx = old_aux_coords.index(old_dependency)
1264-
new_dependency = new_aux_coords[dep_idx]
1265-
else:
1266-
dep_idx = scalar_coords.index(old_dependency)
1267-
new_dependency = scalar_coords[dep_idx]
1268-
new_dependencies[id(old_dependency)] = new_dependency
1286+
for _, _, factory in cube_signature.derived_coords_and_dims:
1287+
# Update the dependencies of the factory with coordinates of
1288+
# the concatenated cube. We need to check all coordinate types
1289+
# here (dim coords, aux coords, and scalar coords).
1290+
1291+
# Note: in contrast to other _build_... methods of this class, we
1292+
# do NOT need to distinguish between aux factories that span the
1293+
# nominated concatenation axis and aux factories that do not. The
1294+
# reason is that ALL aux factories need to be updated with the new
1295+
# coordinates of the concatenated cube (passed to this function via
1296+
# dim_coords_and_dims, aux_coords_and_dims, scalar_coords [these
1297+
# contain ALL new coordinates, not only the ones spanning the
1298+
# concatenation dimension]), so no special treatment for the aux
1299+
# factories that span the concatenation dimension is necessary. If
1300+
# not all aux factories are properly updated with references to the
1301+
# new coordinates, this may lead to KeyErrors (see
1302+
# https://github.com/SciTools/iris/issues/5339).
1303+
new_dependencies = {}
1304+
for old_dependency in factory.dependencies.values():
1305+
if old_dependency in old_dim_coords:
1306+
dep_idx = old_dim_coords.index(old_dependency)
1307+
new_dependency = new_dim_coords[dep_idx]
1308+
elif old_dependency in old_aux_coords:
1309+
dep_idx = old_aux_coords.index(old_dependency)
1310+
new_dependency = new_aux_coords[dep_idx]
1311+
else:
1312+
dep_idx = old_scalar_coords.index(old_dependency)
1313+
new_dependency = new_scalar_coords[dep_idx]
1314+
new_dependencies[id(old_dependency)] = new_dependency
12691315

1270-
# Create new factory with the updated dependencies.
1271-
factory = factory.updated(new_dependencies)
1316+
# Create new factory with the updated dependencies.
1317+
factory = factory.updated(new_dependencies)
12721318

12731319
aux_factories.append(factory)
12741320

@@ -1307,7 +1353,7 @@ def _build_dim_coordinates(self):
13071353

13081354
# Concatenate the points together for the nominated dimension.
13091355
points = [
1310-
skeleton.signature.dim_coords[dim_ind].points
1356+
skeleton.signature.dim_coords[dim_ind].core_points()
13111357
for skeleton in skeletons
13121358
]
13131359
points = np.concatenate(tuple(points))
@@ -1316,7 +1362,7 @@ def _build_dim_coordinates(self):
13161362
bounds = None
13171363
if self._cube_signature.dim_coords[dim_ind].has_bounds():
13181364
bounds = [
1319-
skeleton.signature.dim_coords[dim_ind].bounds
1365+
skeleton.signature.dim_coords[dim_ind].core_bounds()
13201366
for skeleton in skeletons
13211367
]
13221368
bounds = np.concatenate(tuple(bounds))

0 commit comments

Comments
 (0)