From 7ba2bdd95f3271fad61ebea05d1fb215f3e5b40c Mon Sep 17 00:00:00 2001 From: Carwyn Pelley Date: Fri, 16 Jun 2017 10:56:41 +0100 Subject: [PATCH 1/3] TEST: Failing unittest --- lib/iris/tests/unit/cube/test_Cube.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py index 14dba07b85..965efb388f 100644 --- a/lib/iris/tests/unit/cube/test_Cube.py +++ b/lib/iris/tests/unit/cube/test_Cube.py @@ -2003,6 +2003,15 @@ def test_bad_transpose_order(self): with self.assertRaisesRegexp(ValueError, exp_emsg): self.cube.transpose([1]) + def test_transpose_multidim_coords(self): + # Ensure that multidimensional coordinates are transposed where + # necessary so that their dimension mapping remains ordered in the + # way they were in the source originally. + aux_coord = iris.coords.AuxCoord(np.zeros((2, 4)), long_name='bing') + self.cube.add_aux_coord(aux_coord, (1, 2)) + self.cube.transpose((2, 0, 1)) + self.assertEqual(self.cube.coord('bing').shape, (4, 2)) + if __name__ == '__main__': tests.main() From 5eb759fce7a267a4379593b27c32e7c9e0aab031 Mon Sep 17 00:00:00 2001 From: Carwyn Pelley Date: Fri, 16 Jun 2017 13:39:11 +0100 Subject: [PATCH 2/3] Enforce aux coord increasing order on transpose --- lib/iris/cube.py | 17 ++++++++++++++++- lib/iris/tests/unit/cube/test_Cube.py | 16 ++++++++++------ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/lib/iris/cube.py b/lib/iris/cube.py index 5843179318..a2c9e7b4f6 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -36,6 +36,7 @@ import dask.array as da import numpy as np import numpy.ma as ma +from scipy.stats import rankdata from iris._cube_coord_common import CFVariableMixin import iris._concatenate @@ -2808,7 +2809,21 @@ def remap_dim_coord(coord_and_dim): def remap_aux_coord(coord_and_dims): coord, dims = coord_and_dims - return coord, tuple(dim_mapping[dim] for dim in dims) + new_order = tuple(dim_mapping[dim] for dim in dims) + new_rank = (rankdata(new_order, method='ordinal') - 1).tolist() + new_order_inc = tuple(set(new_order)) + + # Enforce increasing order for mapping. + points = coord.points.transpose(new_rank) + bounds = None + if coord.has_bounds(): + bounds = coord.bounds.transpose(new_rank) + + self.remove_coord(coord) + new_coord = coord.copy(points, bounds=bounds) + self.add_aux_coord(new_coord, new_order_inc) + return new_coord, new_order_inc + self._aux_coords_and_dims = list(map(remap_aux_coord, self._aux_coords_and_dims)) diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py index 965efb388f..756a9ca35c 100644 --- a/lib/iris/tests/unit/cube/test_Cube.py +++ b/lib/iris/tests/unit/cube/test_Cube.py @@ -2005,12 +2005,16 @@ def test_bad_transpose_order(self): def test_transpose_multidim_coords(self): # Ensure that multidimensional coordinates are transposed where - # necessary so that their dimension mapping remains ordered in the - # way they were in the source originally. - aux_coord = iris.coords.AuxCoord(np.zeros((2, 4)), long_name='bing') - self.cube.add_aux_coord(aux_coord, (1, 2)) - self.cube.transpose((2, 0, 1)) - self.assertEqual(self.cube.coord('bing').shape, (4, 2)) + # necessary so that their dimension mapping is in increasing order. + data = np.zeros((1, 2, 3, 4, 5)) + cube = Cube(data) + aux_coord = iris.coords.AuxCoord(np.zeros((2, 4, 1)), long_name='bing') + cube.add_aux_coord(aux_coord, (1, 3, 0)) + cube.transpose((3, 4, 2, 1, 0)) + + coord = cube.coord('bing') + self.assertEqual(coord.shape, (4, 2, 1)) + self.assertEqual(cube.coord_dims(coord), (0, 3, 4)) if __name__ == '__main__': From 383bdeaf2e72b70768102440a4d78beecf4971d4 Mon Sep 17 00:00:00 2001 From: Carwyn Pelley Date: Fri, 16 Jun 2017 16:44:28 +0100 Subject: [PATCH 3/3] ENH: Aux coord sanitise --- .../unit/util/test_sanitise_auxcoords.py | 50 +++++++++++++++++++ lib/iris/util.py | 31 ++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 lib/iris/tests/unit/util/test_sanitise_auxcoords.py diff --git a/lib/iris/tests/unit/util/test_sanitise_auxcoords.py b/lib/iris/tests/unit/util/test_sanitise_auxcoords.py new file mode 100644 index 0000000000..6202159ec3 --- /dev/null +++ b/lib/iris/tests/unit/util/test_sanitise_auxcoords.py @@ -0,0 +1,50 @@ +# (C) British Crown Copyright 2017, Met Office +# +# This file is part of Iris. +# +# Iris is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Iris is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Iris. If not, see . +"""Test function :func:`iris.util.sanitise_auxcoords`.""" + +from __future__ import (absolute_import, division, print_function) +from six.moves import (filter, input, map, range, zip) # noqa + +# import iris tests first so that some things can be initialised before +# importing anything else +import iris.tests as tests + +import iris +import numpy as np + +from iris.util import sanitise_auxcoords + + +class TestAll(tests.IrisTest): + def test(self): + # Ensure that multidimensional coordinates are transposed where + # necessary so that their dimension mapping is in increasing order. + data = np.zeros((1, 2, 3, 4, 5, 6)) + cube = iris.cube.Cube(data) + aux_coord = iris.coords.AuxCoord(np.zeros((3, 1, 5, 2)), + long_name='bing') + cube.add_aux_coord(aux_coord, (2, 0, 4, 1)) + + sanitise_auxcoords(cube) + + coord = cube.coord('bing') + self.assertEqual(coord.shape, (1, 2, 3, 5)) + self.assertEqual(cube.coord_dims(coord), (0, 1, 2, 4)) + + +if __name__ == '__main__': + tests.main() diff --git a/lib/iris/util.py b/lib/iris/util.py index 340f2ee4fa..d0760d631c 100644 --- a/lib/iris/util.py +++ b/lib/iris/util.py @@ -37,12 +37,43 @@ import cf_units import numpy as np import numpy.ma as ma +from scipy.stats import rankdata from iris._deprecation import warn_deprecated import iris import iris.exceptions +def sanitise_auxcoords(cube): + """ + Enforce increasing dimension mappings for all coordinates. + + Helper function to transpose multidimensional coordinates as neccessary to + enforce increasing order dimension mappings. + + Args: + + * cube + An instance of :class:`iris.cube.Cube` + + """ + for coord in cube.aux_coords: + if coord.ndim > 1: + dims = cube.coord_dims(coord) + dim_rank = (rankdata(dims, method='ordinal') - 1).tolist() + if dim_rank != range(coord.ndim): + # Sanitise coordinate + new_order = range(len(dim_rank)) + transpose_indx = [dim_rank.index(val) for val in new_order] + points = coord.points.transpose(transpose_indx) + bounds = None + if coord.has_bounds(): + bounds = coord.bounds.transpose(transpose_indx) + new_coord = coord.copy(points, bounds=bounds) + cube.remove_coord(coord) + cube.add_aux_coord(new_coord, sorted(dims)) + + def broadcast_weights(weights, array, dims): """ Broadcast a weights array to the shape of another array.