Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 59 additions & 9 deletions lib/iris/analysis/maths.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# (C) British Crown Copyright 2010 - 2016, Met Office
# (C) British Crown Copyright 2010 - 2017, Met Office
#
# This file is part of Iris.
#
Expand Down Expand Up @@ -318,11 +318,36 @@ def multiply(cube, other, dim=None, in_place=False):

"""
_assert_is_cube(cube)
op = operator.imul if in_place else operator.mul
other_unit = getattr(other, 'units', '1')
new_unit = cube.units * other_unit
op = operator.imul if in_place else operator.mul
return _binary_op_common(op, 'multiply', cube, other, new_unit, dim,
in_place=in_place)

if isinstance(other, iris.cube.Cube):
# get a coordinate comparison of this cube and the cube to do the
# operation with
coord_comp = iris.analysis.coord_comparison(cube, other)
bad_coord_grps = (coord_comp['ungroupable_and_dimensioned'] +
coord_comp['resamplable'])
if bad_coord_grps:
raise ValueError('This operation cannot be performed as there are '
'differing coordinates (%s) remaining '
'which cannot be ignored.'
% ', '.join({coord_grp.name() for coord_grp
in bad_coord_grps}))
else:
coord_comp = None

new_cube = _binary_op_common(op, 'multiply', cube, other, new_unit, dim,
in_place=in_place)

if coord_comp:
# If a coordinate is to be ignored - remove it
ignore = filter(None, [coord_grp[0] for coord_grp
in coord_comp['ignorable']])
for coord in ignore:
new_cube.remove_coord(coord)

return new_cube


def divide(cube, other, dim=None, in_place=False):
Expand All @@ -348,14 +373,39 @@ def divide(cube, other, dim=None, in_place=False):

"""
_assert_is_cube(cube)
other_unit = getattr(other, 'units', '1')
new_unit = cube.units / other_unit
try:
op = operator.idiv if in_place else operator.div
except AttributeError:
op = operator.itruediv if in_place else operator.truediv
return _binary_op_common(op, 'divide', cube, other, new_unit, dim,
in_place=in_place)
other_unit = getattr(other, 'units', '1')
new_unit = cube.units / other_unit

if isinstance(other, iris.cube.Cube):
# get a coordinate comparison of this cube and the cube to do the
# operation with
coord_comp = iris.analysis.coord_comparison(cube, other)
bad_coord_grps = (coord_comp['ungroupable_and_dimensioned'] +
coord_comp['resamplable'])
if bad_coord_grps:
raise ValueError('This operation cannot be performed as there are '
'differing coordinates (%s) remaining '
'which cannot be ignored.'
% ', '.join({coord_grp.name() for coord_grp
in bad_coord_grps}))
else:
coord_comp = None

new_cube = _binary_op_common(op, 'divide', cube, other, new_unit, dim,
in_place=in_place)

if coord_comp:
# If a coordinate is to be ignored - remove it
ignore = filter(None, [coord_grp[0] for coord_grp
in coord_comp['ignorable']])
for coord in ignore:
new_cube.remove_coord(coord)

return new_cube


def exponentiate(cube, exponent, in_place=False):
Expand Down Expand Up @@ -450,7 +500,7 @@ def log2(cube, in_place=False):
* cube:
An instance of :class:`iris.cube.Cube`.

Kwargs:
Kwargs:lib/iris/tests/unit/analysis/maths/test_subtract.py

* in_place:
Whether to create a new Cube, or alter the given "cube".
Expand Down
37 changes: 36 additions & 1 deletion lib/iris/tests/unit/analysis/maths/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# (C) British Crown Copyright 2014 - 2015, Met Office
# (C) British Crown Copyright 2014 - 2017, Met Office
#
# This file is part of Iris.
#
Expand All @@ -25,7 +25,9 @@
import numpy as np

from iris.analysis import MEAN
from iris.coords import DimCoord
from iris.cube import Cube
import iris.tests as tests
import iris.tests.stock as stock


Expand Down Expand Up @@ -158,3 +160,36 @@ def test_partial_mask_not_in_place(self):

self.assertMaskedArrayEqual(com, res.data)
self.assertIsNot(res, orig_cube)


class CubeArithmeticCoordsTest(tests.IrisTest):
# This class sets up pairs of cubes to test iris' ability to reject
# arithmetic operations on coordinates which do not match.
def SetUpNonMatching(self):
# On this cube pair, the coordinates to perform operations on do not
# match in either points array or name.
data = np.zeros((3, 4))
a = DimCoord([1, 2, 3], long_name='a')
b = DimCoord([1, 2, 3, 4], long_name='b')
x = DimCoord([4, 5, 6], long_name='x')
y = DimCoord([5, 6, 7, 8], long_name='y')

nomatch1 = Cube(data, dim_coords_and_dims=[(a, 0), (b, 1)])
nomatch2 = Cube(data, dim_coords_and_dims=[(x, 0), (y, 1)])

return nomatch1, nomatch2

def SetUpReversed(self):
# On this cube pair, the coordinates to perform operations on have
# matching long names but the points array on one cube is reversed
# with respect to that on the other.
data = np.zeros((3, 4))
a1 = DimCoord([1, 2, 3], long_name='a')
b1 = DimCoord([1, 2, 3, 4], long_name='b')
a2 = DimCoord([3, 2, 1], long_name='a')
b2 = DimCoord([1, 2, 3, 4], long_name='b')

reversed1 = Cube(data, dim_coords_and_dims=[(a1, 0), (b1, 1)])
reversed2 = Cube(data, dim_coords_and_dims=[(a2, 0), (b2, 1)])

return reversed1, reversed2
15 changes: 14 additions & 1 deletion lib/iris/tests/unit/analysis/maths/test_add.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@

from iris.analysis.maths import add
from iris.tests.unit.analysis.maths import \
CubeArithmeticBroadcastingTestMixin, CubeArithmeticMaskingTestMixin
CubeArithmeticBroadcastingTestMixin, CubeArithmeticMaskingTestMixin, \
CubeArithmeticCoordsTest


@tests.skip_data
Expand All @@ -54,5 +55,17 @@ def cube_func(self):
return add


class TestCoordMatch(CubeArithmeticCoordsTest):
def test_no_match(self):
cube1, cube2 = self.SetUpNonMatching()
with self.assertRaises(ValueError):
add(cube1, cube2)

def test_reversed_points(self):
cube1, cube2 = self.SetUpReversed()
with self.assertRaises(ValueError):
add(cube1, cube2)


if __name__ == "__main__":
tests.main()
15 changes: 14 additions & 1 deletion lib/iris/tests/unit/analysis/maths/test_divide.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
from iris.analysis.maths import divide
from iris.cube import Cube
from iris.tests.unit.analysis.maths import \
CubeArithmeticBroadcastingTestMixin, CubeArithmeticMaskingTestMixin
CubeArithmeticBroadcastingTestMixin, CubeArithmeticMaskingTestMixin, \
CubeArithmeticCoordsTest


@tests.skip_data
Expand Down Expand Up @@ -90,5 +91,17 @@ def test_masked_div_zero(self):
self.assertMaskedArrayEqual(com, res, strict=True)


class TestCoordMatch(CubeArithmeticCoordsTest):
def test_no_match(self):
cube1, cube2 = self.SetUpNonMatching()
with self.assertRaises(ValueError):
divide(cube1, cube2)

def test_reversed_points(self):
cube1, cube2 = self.SetUpReversed()
with self.assertRaises(ValueError):
divide(cube1, cube2)


if __name__ == "__main__":
tests.main()
15 changes: 14 additions & 1 deletion lib/iris/tests/unit/analysis/maths/test_multiply.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@

from iris.analysis.maths import multiply
from iris.tests.unit.analysis.maths import \
CubeArithmeticBroadcastingTestMixin, CubeArithmeticMaskingTestMixin
CubeArithmeticBroadcastingTestMixin, CubeArithmeticMaskingTestMixin, \
CubeArithmeticCoordsTest


@tests.skip_data
Expand All @@ -54,5 +55,17 @@ def cube_func(self):
return multiply


class TestCoordMatch(CubeArithmeticCoordsTest):
def test_no_match(self):
cube1, cube2 = self.SetUpNonMatching()
with self.assertRaises(ValueError):
multiply(cube1, cube2)

def test_reversed_points(self):
cube1, cube2 = self.SetUpReversed()
with self.assertRaises(ValueError):
multiply(cube1, cube2)


if __name__ == "__main__":
tests.main()
14 changes: 13 additions & 1 deletion lib/iris/tests/unit/analysis/maths/test_subtract.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@

from iris.analysis.maths import subtract
from iris.tests.unit.analysis.maths import \
CubeArithmeticBroadcastingTestMixin, CubeArithmeticMaskingTestMixin
CubeArithmeticBroadcastingTestMixin, CubeArithmeticMaskingTestMixin, \
CubeArithmeticCoordsTest


@tests.skip_data
Expand All @@ -54,5 +55,16 @@ def cube_func(self):
return subtract


class TestCoordMatch(CubeArithmeticCoordsTest):
def test_no_match(self):
cube1, cube2 = self.SetUpNonMatching()
with self.assertRaises(ValueError):
subtract(cube1, cube2)

def test_reversed_points(self):
cube1, cube2 = self.SetUpReversed()
with self.assertRaises(ValueError):
subtract(cube1, cube2)

if __name__ == "__main__":
tests.main()
36 changes: 20 additions & 16 deletions lib/iris/tests/unit/analysis/stats/test_pearsonr.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# (C) British Crown Copyright 2014 - 2015, Met Office
# (C) British Crown Copyright 2014 - 2017, Met Office
#
# This file is part of Iris.
#
Expand Down Expand Up @@ -59,13 +59,15 @@ def test_incompatible_cubes(self):
'longitude')

def test_compatible_cubes(self):
r = stats.pearsonr(self.cube_a, self.cube_b, ['latitude', 'longitude'])
self.assertArrayAlmostEqual(r.data, [0.81114936,
0.81690538,
0.79833135,
0.81118674,
0.79745386,
0.81278484])
with self.assertRaises(ValueError):
r = stats.pearsonr(self.cube_a, self.cube_b,
['latitude', 'longitude'])
self.assertArrayAlmostEqual(r.data, [0.81114936,
0.81690538,
0.79833135,
0.81118674,
0.79745386,
0.81278484])

def test_broadcast_cubes(self):
r1 = stats.pearsonr(self.cube_a, self.cube_b[0, :, :],
Expand All @@ -80,14 +82,16 @@ def test_broadcast_cubes(self):
self.assertArrayEqual(r2.data, np.array(r_by_slice))

def test_compatible_cubes_weighted(self):
r = stats.pearsonr(self.cube_a, self.cube_b, ['latitude', 'longitude'],
self.weights)
self.assertArrayAlmostEqual(r.data, [0.79106045,
0.79989169,
0.78826918,
0.79925855,
0.79011544,
0.80115837])
with self.assertRaises(ValueError):
r = stats.pearsonr(self.cube_a, self.cube_b,
['latitude', 'longitude'],
self.weights)
self.assertArrayAlmostEqual(r.data, [0.79106045,
0.79989169,
0.78826918,
0.79925855,
0.79011544,
0.80115837])

def test_broadcast_cubes_weighted(self):
r = stats.pearsonr(self.cube_a, self.cube_b[0, :, :],
Expand Down