Skip to content

Commit d0ffbfc

Browse files
authored
Merge branch 'main' into adopt-coc
2 parents d87d715 + 9f821d0 commit d0ffbfc

File tree

7 files changed

+145
-84
lines changed

7 files changed

+145
-84
lines changed

.readthedocs.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,3 @@ python:
2929
install:
3030
- method: pip
3131
path: .
32-
extra_requirements:
33-
- docs

docs/src/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ def _dotv(version):
235235
# See https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html
236236
intersphinx_mapping = {
237237
"cartopy": ("https://scitools.org.uk/cartopy/docs/latest/", None),
238+
"dask": ("https://docs.dask.org/en/stable/", None),
238239
"matplotlib": ("https://matplotlib.org/stable/", None),
239240
"numpy": ("https://numpy.org/doc/stable/", None),
240241
"python": ("https://docs.python.org/3/", None),

docs/src/sphinxext/api_rst_formatting.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,17 @@ def main_api_rst_formatting(app):
2020
print(f"[{ntpath.basename(__file__)}] Processing RST files", end="")
2121

2222
for file in src_dir.iterdir():
23-
print(f".", end="")
23+
if file.suffix == ".rst":
24+
print(f".", end="")
2425

25-
with open(file, "r") as f:
26-
lines = f.read()
26+
with open(file, "r") as f:
27+
lines = f.read()
2728

28-
lines = lines.replace(" package\n=", "\n")
29-
lines = lines.replace(" module\n=", "\n")
29+
lines = lines.replace(" package\n=", "\n")
30+
lines = lines.replace(" module\n=", "\n")
3031

31-
with open(file, "w") as f:
32-
f.write(lines)
32+
with open(file, "w") as f:
33+
f.write(lines)
3334
print("")
3435

3536
def setup(app):

docs/src/whatsnew/latest.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ This document explains the changes made to Iris for this release
3636
Also with significant input from `@fnattino`_.
3737
(:pull:`5191`)
3838

39+
#. `@rcomer`_ tweaked binary operations so that dask arrays may safely be passed
40+
to arithmetic operations and :func:`~iris.util.mask_cube`. (:pull:`4929`)
41+
3942

4043
🐛 Bugs Fixed
4144
=============

lib/iris/analysis/maths.py

Lines changed: 100 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -225,33 +225,35 @@ def _assert_is_cube(cube):
225225
@_lenient_client(services=SERVICES)
226226
def add(cube, other, dim=None, in_place=False):
227227
"""
228-
Calculate the sum of two cubes, or the sum of a cube and a
229-
coordinate or scalar value.
228+
Calculate the sum of two cubes, or the sum of a cube and a coordinate or
229+
array or scalar value.
230230
231-
When summing two cubes, they must both have the same coordinate
232-
systems & data resolution.
231+
When summing two cubes, they must both have the same coordinate systems and
232+
data resolution.
233233
234-
When adding a coordinate to a cube, they must both share the same
235-
number of elements along a shared axis.
234+
When adding a coordinate to a cube, they must both share the same number of
235+
elements along a shared axis.
236236
237-
Args:
237+
Parameters
238+
----------
238239
239-
* cube:
240-
An instance of :class:`iris.cube.Cube`.
241-
* other:
242-
An instance of :class:`iris.cube.Cube` or :class:`iris.coords.Coord`,
243-
or a number or :class:`numpy.ndarray`.
240+
cube : iris.cube.Cube
241+
First operand to add.
244242
245-
Kwargs:
243+
other: iris.cube.Cube, iris.coords.Coord, number, numpy.ndarray or dask.array.Array
244+
Second operand to add.
246245
247-
* dim:
248-
If supplying a coord with no match on the cube, you must supply
249-
the dimension to process.
250-
* in_place:
251-
Whether to create a new Cube, or alter the given "cube".
246+
dim : int, optional
247+
If `other` is a coord which does not exist on the cube, specify the
248+
dimension to which it should be mapped.
252249
253-
Returns:
254-
An instance of :class:`iris.cube.Cube`.
250+
in_place : bool, default=False
251+
If `True`, alters the input cube. Otherwise a new cube is created.
252+
253+
Returns
254+
-------
255+
256+
iris.cube.Cube
255257
256258
Notes
257259
------
@@ -280,32 +282,34 @@ def add(cube, other, dim=None, in_place=False):
280282
def subtract(cube, other, dim=None, in_place=False):
281283
"""
282284
Calculate the difference between two cubes, or the difference between
283-
a cube and a coordinate or scalar value.
285+
a cube and a coordinate or array or scalar value.
284286
285-
When subtracting two cubes, they must both have the same coordinate
286-
systems & data resolution.
287+
When differencing two cubes, they must both have the same coordinate systems
288+
and data resolution.
287289
288-
When subtracting a coordinate to a cube, they must both share the
289-
same number of elements along a shared axis.
290+
When subtracting a coordinate from a cube, they must both share the same
291+
number of elements along a shared axis.
290292
291-
Args:
293+
Parameters
294+
----------
292295
293-
* cube:
294-
An instance of :class:`iris.cube.Cube`.
295-
* other:
296-
An instance of :class:`iris.cube.Cube` or :class:`iris.coords.Coord`,
297-
or a number or :class:`numpy.ndarray`.
296+
cube : iris.cube.Cube
297+
Cube from which to subtract.
298298
299-
Kwargs:
299+
other: iris.cube.Cube, iris.coords.Coord, number, numpy.ndarray or dask.array.Array
300+
Object to subtract from the cube.
300301
301-
* dim:
302-
If supplying a coord with no match on the cube, you must supply
303-
the dimension to process.
304-
* in_place:
305-
Whether to create a new Cube, or alter the given "cube".
302+
dim : int, optional
303+
If `other` is a coord which does not exist on the cube, specify the
304+
dimension to which it should be mapped.
306305
307-
Returns:
308-
An instance of :class:`iris.cube.Cube`.
306+
in_place : bool, default=False
307+
If `True`, alters the input cube. Otherwise a new cube is created.
308+
309+
Returns
310+
-------
311+
312+
iris.cube.Cube
309313
310314
Notes
311315
------
@@ -348,8 +352,8 @@ def _add_subtract_common(
348352
operation_name - the public name of the operation (e.g. 'divide')
349353
cube - the cube whose data is used as the first argument
350354
to `operation_function`
351-
other - the cube, coord, ndarray or number whose data is
352-
used as the second argument
355+
other - the cube, coord, ndarray, dask array or number whose
356+
data is used as the second argument
353357
new_dtype - the expected dtype of the output. Used in the
354358
case of scalar masked arrays
355359
dim - dimension along which to apply `other` if it's a
@@ -384,24 +388,35 @@ def _add_subtract_common(
384388
@_lenient_client(services=SERVICES)
385389
def multiply(cube, other, dim=None, in_place=False):
386390
"""
387-
Calculate the product of a cube and another cube or coordinate.
391+
Calculate the product of two cubes, or the product of a cube and a coordinate
392+
or array or scalar value.
388393
389-
Args:
394+
When multiplying two cubes, they must both have the same coordinate systems
395+
and data resolution.
390396
391-
* cube:
392-
An instance of :class:`iris.cube.Cube`.
393-
* other:
394-
An instance of :class:`iris.cube.Cube` or :class:`iris.coords.Coord`,
395-
or a number or :class:`numpy.ndarray`.
397+
When mulplying a cube by a coordinate, they must both share the same number
398+
of elements along a shared axis.
396399
397-
Kwargs:
400+
Parameters
401+
----------
398402
399-
* dim:
400-
If supplying a coord with no match on the cube, you must supply
401-
the dimension to process.
403+
cube : iris.cube.Cube
404+
First operand to multiply.
402405
403-
Returns:
404-
An instance of :class:`iris.cube.Cube`.
406+
other: iris.cube.Cube, iris.coords.Coord, number, numpy.ndarray or dask.array.Array
407+
Second operand to multiply.
408+
409+
dim : int, optional
410+
If `other` is a coord which does not exist on the cube, specify the
411+
dimension to which it should be mapped.
412+
413+
in_place : bool, default=False
414+
If `True`, alters the input cube. Otherwise a new cube is created.
415+
416+
Returns
417+
-------
418+
419+
iris.cube.Cube
405420
406421
Notes
407422
------
@@ -461,24 +476,35 @@ def _inplace_common_checks(cube, other, math_op):
461476
@_lenient_client(services=SERVICES)
462477
def divide(cube, other, dim=None, in_place=False):
463478
"""
464-
Calculate the division of a cube by a cube or coordinate.
479+
Calculate the ratio of two cubes, or the ratio of a cube and a coordinate
480+
or array or scalar value.
465481
466-
Args:
482+
When dividing a cube by another cube, they must both have the same coordinate
483+
systems and data resolution.
467484
468-
* cube:
469-
An instance of :class:`iris.cube.Cube`.
470-
* other:
471-
An instance of :class:`iris.cube.Cube` or :class:`iris.coords.Coord`,
472-
or a number or :class:`numpy.ndarray`.
485+
When dividing a cube by a coordinate, they must both share the same number
486+
of elements along a shared axis.
473487
474-
Kwargs:
488+
Parameters
489+
----------
475490
476-
* dim:
477-
If supplying a coord with no match on the cube, you must supply
478-
the dimension to process.
491+
cube : iris.cube.Cube
492+
Numerator.
479493
480-
Returns:
481-
An instance of :class:`iris.cube.Cube`.
494+
other: iris.cube.Cube, iris.coords.Coord, number, numpy.ndarray or dask.array.Array
495+
Denominator.
496+
497+
dim : int, optional
498+
If `other` is a coord which does not exist on the cube, specify the
499+
dimension to which it should be mapped.
500+
501+
in_place : bool, default=False
502+
If `True`, alters the input cube. Otherwise a new cube is created.
503+
504+
Returns
505+
-------
506+
507+
iris.cube.Cube
482508
483509
Notes
484510
------
@@ -842,8 +868,8 @@ def _binary_op_common(
842868
operation_name - the public name of the operation (e.g. 'divide')
843869
cube - the cube whose data is used as the first argument
844870
to `operation_function`
845-
other - the cube, coord, ndarray or number whose data is
846-
used as the second argument
871+
other - the cube, coord, ndarray, dask array or number whose
872+
data is used as the second argument
847873
new_dtype - the expected dtype of the output. Used in the
848874
case of scalar masked arrays
849875
new_unit - unit for the resulting quantity
@@ -883,7 +909,10 @@ def _binary_op_common(
883909
rhs = other.core_data()
884910
else:
885911
# The rhs must be an array.
886-
rhs = np.asanyarray(other)
912+
if iris._lazy_data.is_lazy_data(other):
913+
rhs = other
914+
else:
915+
rhs = np.asanyarray(other)
887916

888917
def unary_func(lhs):
889918
data = operation_function(lhs, rhs)
@@ -1194,7 +1223,7 @@ def __call__(
11941223
Kwargs:
11951224
11961225
* other
1197-
A cube, coord, ndarray or number whose data is used as the
1226+
A cube, coord, ndarray, dask array or number whose data is used as the
11981227
second argument to the data function.
11991228
12001229
* new_name:
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Copyright Iris contributors
2+
#
3+
# This file is part of Iris and is released under the LGPL license.
4+
# See COPYING and COPYING.LESSER in the root of the repository for full
5+
# licensing details.
6+
"""Unit tests for cube arithmetic with dask arrays."""
7+
8+
# Import iris.tests first so that some things can be initialised before
9+
# importing anything else.
10+
import iris.tests as tests # isort:skip
11+
12+
from unittest import mock
13+
14+
import dask
15+
import dask.array as da
16+
17+
import iris.cube
18+
from iris.tests.unit.analysis.maths import MathsAddOperationMixin
19+
20+
21+
class TestArithDask(tests.IrisTest, MathsAddOperationMixin):
22+
@mock.patch.object(dask.base, "compute", wraps=dask.base.compute)
23+
def test_compute_not_called(self, mocked_compute):
24+
# No data should be realised when adding a cube and a dask array.
25+
cube = iris.cube.Cube(da.arange(4))
26+
array = da.ones(4)
27+
28+
self.data_op(cube, array)
29+
mocked_compute.assert_not_called()

lib/iris/util.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1977,17 +1977,17 @@ def mask_cube(cube, points_to_mask, in_place=False, dim=None):
19771977
"""
19781978
Masks any cells in the cube's data array which correspond to cells marked
19791979
``True`` (or non zero) in ``points_to_mask``. ``points_to_mask`` may be
1980-
specified as a :class:`numpy.ndarray`, :class:`iris.coords.Coord` or
1981-
:class:`iris.cube.Cube`, following the same broadcasting approach as cube
1982-
arithmetic (see :ref:`cube maths`).
1980+
specified as a :class:`numpy.ndarray`, :class:`dask.array.Array`,
1981+
:class:`iris.coords.Coord` or :class:`iris.cube.Cube`, following the same
1982+
broadcasting approach as cube arithmetic (see :ref:`cube maths`).
19831983
19841984
Parameters
19851985
----------
19861986
19871987
cube : iris.cube.Cube
19881988
Cube containing data that requires masking.
19891989
1990-
points_to_mask : numpy.ndarray, iris.coords.Coord or iris.cube.Cube
1990+
points_to_mask : numpy.ndarray, dask.array.Array, iris.coords.Coord or iris.cube.Cube
19911991
Specifies booleans (or ones and zeros) indicating which points will be masked.
19921992
19931993
in_place : bool, default=False

0 commit comments

Comments
 (0)