diff --git a/lib/iris/_merge.py b/lib/iris/_merge.py index f6b03882d7..9ea07e54b2 100644 --- a/lib/iris/_merge.py +++ b/lib/iris/_merge.py @@ -328,7 +328,13 @@ class _CoordSignature( class _CubeSignature( namedtuple( "CubeSignature", - ["defn", "data_shape", "data_type", "cell_measures_and_dims"], + [ + "defn", + "data_shape", + "data_type", + "cell_measures_and_dims", + "ancillary_variables_and_dims", + ], ) ): """ @@ -349,6 +355,9 @@ class _CubeSignature( * cell_measures_and_dims: A list of cell_measures and dims for the cube. + * ancillary_variables_and_dims: + A list of ancillary variables and dims for the cube. + """ __slots__ = () @@ -439,8 +448,16 @@ def match(self, other, error_on_mismatch): if self.data_type != other.data_type: msg = "cube data dtype differs: {} != {}" msgs.append(msg.format(self.data_type, other.data_type)) + # Both cell_measures_and_dims and ancillary_variables_and_dims are + # ordered by the same method, it is therefore not possible for a + # mismatch to be caused by a difference in order. if self.cell_measures_and_dims != other.cell_measures_and_dims: msgs.append("cube.cell_measures differ") + if ( + self.ancillary_variables_and_dims + != other.ancillary_variables_and_dims + ): + msgs.append("cube.ancillary_variables differ") match = not bool(msgs) if error_on_mismatch and not match: @@ -1187,9 +1204,10 @@ def __init__(self, cube): self._vector_dim_coords_dims = [] self._vector_aux_coords_dims = [] - # cell measures are not merge candidates + # cell measures and ancillary variables are not merge candidates # they are checked and preserved through merge self._cell_measures_and_dims = cube._cell_measures_and_dims + self._ancillary_variables_and_dims = cube._ancillary_variables_and_dims def _report_duplicate(self, nd_indexes, group_by_nd_index): # Find the first offending source-cube with duplicate metadata. @@ -1581,11 +1599,16 @@ def _get_cube(self, data): cms_and_dims = [ (deepcopy(cm), dims) for cm, dims in self._cell_measures_and_dims ] + avs_and_dims = [ + (deepcopy(av), dims) + for av, dims in self._ancillary_variables_and_dims + ] cube = iris.cube.Cube( data, dim_coords_and_dims=dim_coords_and_dims, aux_coords_and_dims=aux_coords_and_dims, cell_measures_and_dims=cms_and_dims, + ancillary_variables_and_dims=avs_and_dims, **kwargs, ) @@ -1717,7 +1740,11 @@ def _build_signature(self, cube): """ return _CubeSignature( - cube.metadata, cube.shape, cube.dtype, cube._cell_measures_and_dims + cube.metadata, + cube.shape, + cube.dtype, + cube._cell_measures_and_dims, + cube._ancillary_variables_and_dims, ) def _add_cube(self, cube, coord_payload): diff --git a/lib/iris/tests/test_merge.py b/lib/iris/tests/test_merge.py index bd7d07e030..a6e2991fd0 100644 --- a/lib/iris/tests/test_merge.py +++ b/lib/iris/tests/test_merge.py @@ -1033,5 +1033,81 @@ def test_simple_points_merge(self): self.assertCML(r, ("cube_merge", "test_simple_attributes3.cml")) +class TestCubeMergeWithAncils(tests.IrisTest): + def _makecube(self, y, cm=False, av=False): + cube = iris.cube.Cube([0, 0]) + cube.add_dim_coord(iris.coords.DimCoord([0, 1], long_name="x"), 0) + cube.add_aux_coord(iris.coords.DimCoord(y, long_name="y")) + if cm: + cube.add_cell_measure( + iris.coords.CellMeasure([1, 1], long_name="foo"), 0, + ) + if av: + cube.add_ancillary_variable( + iris.coords.AncillaryVariable([1, 1], long_name="bar"), 0, + ) + return cube + + def test_fail_missing_cell_measure(self): + cube1 = self._makecube(0, cm=True) + cube2 = self._makecube(1) + cubes = iris.cube.CubeList([cube1, cube2]).merge() + self.assertEqual(len(cubes), 2) + + def test_fail_missing_ancillary_variable(self): + cube1 = self._makecube(0, av=True) + cube2 = self._makecube(1) + cubes = iris.cube.CubeList([cube1, cube2]).merge() + self.assertEqual(len(cubes), 2) + + def test_fail_different_cell_measure(self): + cube1 = self._makecube(0, cm=True) + cube2 = self._makecube(1) + cube2.add_cell_measure( + iris.coords.CellMeasure([2, 2], long_name="foo"), 0 + ) + cubes = iris.cube.CubeList([cube1, cube2]).merge() + self.assertEqual(len(cubes), 2) + + def test_fail_different_ancillary_variable(self): + cube1 = self._makecube(0, av=True) + cube2 = self._makecube(1) + cube2.add_ancillary_variable( + iris.coords.AncillaryVariable([2, 2], long_name="bar"), 0 + ) + cubes = iris.cube.CubeList([cube1, cube2]).merge() + self.assertEqual(len(cubes), 2) + + def test_merge_with_cell_measure(self): + cube1 = self._makecube(0, cm=True) + cube2 = self._makecube(1, cm=True) + cubes = iris.cube.CubeList([cube1, cube2]).merge() + self.assertEqual(len(cubes), 1) + self.assertEqual(cube1.cell_measures(), cubes[0].cell_measures()) + + def test_merge_with_ancillary_variable(self): + cube1 = self._makecube(0, av=True) + cube2 = self._makecube(1, av=True) + cubes = iris.cube.CubeList([cube1, cube2]).merge() + self.assertEqual(len(cubes), 1) + self.assertEqual( + cube1.ancillary_variables(), cubes[0].ancillary_variables() + ) + + def test_cell_measure_error_msg(self): + msg = "cube.cell_measures differ" + cube1 = self._makecube(0, cm=True) + cube2 = self._makecube(1) + with self.assertRaisesRegex(iris.exceptions.MergeError, msg): + _ = iris.cube.CubeList([cube1, cube2]).merge_cube() + + def test_ancillary_variable_error_msg(self): + msg = "cube.ancillary_variables differ" + cube1 = self._makecube(0, av=True) + cube2 = self._makecube(1) + with self.assertRaisesRegex(iris.exceptions.MergeError, msg): + _ = iris.cube.CubeList([cube1, cube2]).merge_cube() + + if __name__ == "__main__": tests.main()