1- # (C) British Crown Copyright 2017, Met Office
1+ # (C) British Crown Copyright 2017 - 2018 , Met Office
22#
33# This file is part of Iris.
44#
@@ -102,6 +102,39 @@ def as_lazy_data(data, chunks=None, asarray=False):
102102 return data
103103
104104
105+ def _co_realise_lazy_arrays (arrays ):
106+ """
107+ Compute multiple lazy arrays and return a list of real values.
108+
109+ All the arrays are computed together, so they can share results for common
110+ graph elements.
111+
112+ Casts all results with `np.asanyarray`, and converts any MaskedConstants
113+ appearing into masked arrays, to ensure that all return values are
114+ writeable NumPy array objects.
115+
116+ Any non-lazy arrays are passed through, as they are by `da.compute`.
117+ They undergo the same result standardisation.
118+
119+ """
120+ computed_arrays = da .compute (* arrays )
121+ results = []
122+ for lazy_in , real_out in zip (arrays , computed_arrays ):
123+ # Ensure we always have arrays.
124+ # Note : in some cases dask (and numpy) will return a scalar
125+ # numpy.int/numpy.float object rather than an ndarray.
126+ # Recorded in https://github.com/dask/dask/issues/2111.
127+ real_out = np .asanyarray (real_out )
128+ if isinstance (real_out , ma .core .MaskedConstant ):
129+ # Convert any masked constants into NumPy masked arrays.
130+ # NOTE: in this case, also apply the original lazy-array dtype, as
131+ # masked constants *always* have dtype float64.
132+ real_out = ma .masked_array (real_out .data , mask = real_out .mask ,
133+ dtype = lazy_in .dtype )
134+ results .append (real_out )
135+ return results
136+
137+
105138def as_concrete_data (data ):
106139 """
107140 Return the actual content of a lazy array, as a numpy array.
@@ -120,14 +153,7 @@ def as_concrete_data(data):
120153
121154 """
122155 if is_lazy_data (data ):
123- # Realise dask array, ensuring the data result is always a NumPy array.
124- # In some cases dask may return a scalar numpy.int/numpy.float object
125- # rather than a numpy.ndarray object.
126- # Recorded in https://github.com/dask/dask/issues/2111.
127- dtype = data .dtype
128- data = np .asanyarray (data .compute ())
129- if isinstance (data , ma .core .MaskedConstant ):
130- data = ma .masked_array (data .data , dtype = dtype , mask = data .mask )
156+ data , = _co_realise_lazy_arrays ([data ])
131157
132158 return data
133159
@@ -158,3 +184,39 @@ def multidim_lazy_stack(stack):
158184 result = da .stack ([multidim_lazy_stack (subarray )
159185 for subarray in stack ])
160186 return result
187+
188+
189+ def co_realise_cubes (* cubes ):
190+ """
191+ Fetch 'real' data for multiple cubes, in a shared calculation.
192+
193+ This computes any lazy data, equivalent to accessing each `cube.data`.
194+ However, lazy calculations and data fetches can be shared between the
195+ computations, improving performance.
196+
197+ Args:
198+
199+ * cubes (list of :class:`~iris.cube.Cube`):
200+ Arguments, each of which is a cube to be realised.
201+
202+ For example::
203+
204+ # Form stats.
205+ a_std = cube_a.collapsed(['x', 'y'], iris.analysis.STD_DEV)
206+ b_std = cube_b.collapsed(['x', 'y'], iris.analysis.STD_DEV)
207+ ab_mean_diff = (cube_b - cube_a).collapsed(['x', 'y'],
208+ iris.analysis.MEAN)
209+ std_err = (a_std * a_std + b_std * b_std) ** 0.5
210+
211+ # Compute stats together (to avoid multiple data passes).
212+ iris.co_realise_cubes(a_std, b_std, ab_mean_diff, std_err)
213+
214+
215+ .. Note::
216+
217+ Cubes with non-lazy data may also be passed, with no ill effect.
218+
219+ """
220+ results = _co_realise_lazy_arrays ([cube .core_data () for cube in cubes ])
221+ for cube , result in zip (cubes , results ):
222+ cube .data = result
0 commit comments