Skip to content

Commit c023a07

Browse files
committed
Fixes to allow for non-lazy compenents.
1 parent dc4a5d9 commit c023a07

File tree

2 files changed

+49
-30
lines changed

2 files changed

+49
-30
lines changed

lib/iris/aux_factory.py

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import dask.array as da
1212
import numpy as np
1313

14-
from iris._lazy_data import concatenate
14+
from iris._lazy_data import _optimum_chunksize, concatenate, is_lazy_data
1515
from iris.common import CFVariableMixin, CoordMetadata, metadata_manager_factory
1616
import iris.coords
1717
from iris.warnings import IrisIgnoringBoundsWarning
@@ -83,18 +83,18 @@ def _calculate_array(self, *dep_arrays, **other_args):
8383
Parameters
8484
----------
8585
* dep_arrays : tuple of array-like
86-
arrays of data for each dependency.
86+
Arrays of data for each dependency.
8787
Must match the number of declared dependencies. All pre-aligned to the
8888
same number of dimensions, and matching in dimensions,
8989
i.e. all dimensions are == N(i) or 1, for each dim(i).
9090
9191
* other_args
92-
dict of keys providing class-specific additional arguments.
92+
Dict of keys providing class-specific additional arguments.
9393
9494
Returns
9595
-------
9696
array-like
97-
result (normally lazy) with same dimensions as the dependencies,
97+
A result (normally lazy) with same dimensions as the dependencies,
9898
i.e. == N(i) for all i.
9999
100100
This is the basic derived calculation, defined by each hybrid class, which
@@ -117,51 +117,66 @@ def _derive_array(self, *dep_arrays, **other_args):
117117
This routine is itself typically called by :meth:`make_coord`, to make both
118118
points and bounds.
119119
"""
120-
result = self._calculate_array(*dep_arrays, **other_args)
121120

122-
# The dims of all the given components should be the same and, **presumably**,
123-
# the same as the result ??
124-
for i_dep, (dep, name) in enumerate(zip(dep_arrays, self.dependencies.keys())):
125-
if dep.ndim != result.ndim:
126-
msg = (
127-
f"Dependency #{i_dep}, '{name}' has ndims={dep.ndim}, "
128-
"not matching result {result.ndim!r}"
129-
" (shapes {dep.shape}/{result.shape})."
130-
)
131-
raise ValueError(msg)
121+
def chunkslike(array):
122+
return array.chunksize if is_lazy_data(array) else array.shape
123+
124+
result = self._calculate_array(*dep_arrays, **other_args)
132125

133126
# See if we need to improve on the chunking of the result
134-
from iris._lazy_data import _optimum_chunksize
127+
result_chunks = chunkslike(result)
135128
adjusted_chunks = _optimum_chunksize(
136-
chunks=result.chunksize,
129+
chunks=result_chunks,
137130
shape=result.shape,
138131
dtype=result.dtype,
139132
)
140133

141-
if adjusted_chunks != result.chunksize:
134+
# Does optimum_chunksize say we should have smaller chunks in some dimensions?
135+
if np.any(adjusted_chunks < result_chunks):
136+
# co-broadcast all the deps to get same dimensions for each
137+
dep_arrays = np.broadcast_arrays(*dep_arrays)
138+
139+
# The dims of all the given components should now be the same and, *presumably*,
140+
# the same as the result ??
141+
for i_dep, (dep, name) in enumerate(
142+
zip(dep_arrays, self.dependencies.keys())
143+
):
144+
if dep.ndim != result.ndim:
145+
msg = (
146+
f"Dependency #{i_dep}, '{name}' has ndims={dep.ndim}, "
147+
f"not matching result {result.ndim!r}"
148+
f" : respective shapes {dep.shape}, {result.shape}."
149+
)
150+
raise ValueError(msg)
151+
142152
# Re-do the result calculation, re-chunking the inputs along dimensions
143153
# which it is suggested to reduce.
144154
# First make a (writable) copy of the inputs.....
145155
new_deps = []
146156
for i_dep, dep in enumerate(dep_arrays):
147157
# Reduce each dependency chunksize to the result chunksize if smaller.
148-
dep_chunks = dep.chunksize
149-
new_chunks = tuple([
150-
min(dep_chunk, adj_chunk)
151-
for dep_chunk, adj_chunk in zip(dep_chunks, adjusted_chunks)
152-
])
158+
dep_chunks = chunkslike(dep)
159+
new_chunks = tuple(
160+
[
161+
min(dep_chunk, adj_chunk)
162+
for dep_chunk, adj_chunk in zip(dep_chunks, adjusted_chunks)
163+
]
164+
)
153165
# If the dep chunksize was reduced, replace with a rechunked version.
154166
if new_chunks != dep_chunks:
167+
if not is_lazy_data(dep):
168+
# I guess this is possible ?
169+
# TODO: needs a test
170+
dep = da.from_array(dep)
155171
dep = dep.rechunk(new_chunks)
156172
new_deps.append(dep)
157173

158174
# Finally, re-do the calculation, which hopefully results in a better
159175
# overall chunking for the result
160-
result = self._calculate_array(*new_deps)
176+
result = self._calculate_array(*new_deps, **other_args)
161177

162178
return result
163179

164-
165180
@abstractmethod
166181
def make_coord(self, coord_dims_func):
167182
"""Return a new :class:`iris.coords.AuxCoord` as defined by this factory.
@@ -1108,7 +1123,9 @@ def dependencies(self):
11081123
zlev=self.zlev,
11091124
)
11101125

1111-
def _calculate_array(self, sigma, eta, depth, depth_c, zlev, nsigma, coord_dims_func):
1126+
def _calculate_array(
1127+
self, sigma, eta, depth, depth_c, zlev, nsigma, coord_dims_func
1128+
):
11121129
# Calculate the index of the 'z' dimension in the input arrays.
11131130
# First find the cube 'z' dimension ...
11141131
[cube_z_dim] = coord_dims_func(self.dependencies["zlev"])

lib/iris/tests/unit/aux_factory/test_AuxCoordFactory.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,21 +165,23 @@ def test_lazy_coord_printing(self, sample_cube):
165165

166166
class Test_rechunk:
167167
class TestAuxFact(AuxCoordFactory):
168-
# A minimal AuxCoordFactory that enables us to test the re-chunking logic.
168+
"""A minimal AuxCoordFactory that enables us to test the re-chunking logic."""
169+
169170
def __init__(self, nx, ny, nz):
170171
def make_co(name, dims):
171172
dims = tuple(dims)
172173
pts = da.ones(dims, dtype=np.int32, chunks=dims)
173-
bds = np.stack([pts-0.5, pts+0.5], axis=-1)
174+
bds = np.stack([pts - 0.5, pts + 0.5], axis=-1)
174175
co = AuxCoord(pts, bounds=bds, long_name=name)
175176
return co
177+
176178
self.x = make_co("x", (nx, 1, 1))
177179
self.y = make_co("y", (1, ny, 1))
178180
self.z = make_co("z", (1, 1, nz))
179181

180182
@property
181183
def dependencies(self):
182-
return {'x': self.x, "y": self.y, "z": self.z}
184+
return {"x": self.x, "y": self.y, "z": self.z}
183185

184186
def _calculate_array(self, *dep_arrays, **other_args):
185187
x, y, z = dep_arrays
@@ -200,7 +202,7 @@ def make_coord(self, coord_dims_func):
200202
)
201203
return result
202204

203-
@pytest.mark.parametrize('nz', [10, 100, 1000])
205+
@pytest.mark.parametrize("nz", [10, 100, 1000])
204206
def test_rechunk(self, nz):
205207
# Test calculation which forms (NX, 1, 1) * (1, NY, 1) * (1, 1, NZ)
206208
# at different NZ sizes eventually needing to rechunk on both Y and X

0 commit comments

Comments
 (0)