diff --git a/lib/iris/coords.py b/lib/iris/coords.py index e2cc855de2..43ddd71790 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -521,14 +521,24 @@ def copy(self, points=None, bounds=None): raise ValueError('If bounds are specified, points must also be ' 'specified') - new_coord = copy.deepcopy(self) if points is not None: + # We do not perform a deepcopy when we supply new points so as to + # not unncessarily copy the old points. + + # Create a temp-coord to manage deep copying of a small array. + temp_coord = copy.copy(self) + temp_coord.bounds = None + # note: DataManager cannot be None or DataManager(None) for + # a deepcopy operation. + temp_coord._points_dm = DataManager(np.array((1,))) + new_coord = copy.deepcopy(temp_coord) + del(temp_coord) new_coord._points_dm = None new_coord.points = points - # Regardless of whether bounds are provided as an argument, new - # points will result in new bounds, discarding those copied from - # self. + # new points will result in new bounds. new_coord.bounds = bounds + else: + new_coord = copy.deepcopy(self) return new_coord diff --git a/lib/iris/cube.py b/lib/iris/cube.py index 64b729cf6f..977fc3d484 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -781,6 +781,27 @@ def __init__(self, data, standard_name=None, long_name=None, for cell_measure, dims in cell_measures_and_dims: self.add_cell_measure(cell_measure, dims) + # When True indexing may result in a view onto the original data array, + # to avoid unnecessary copying. + self._share_data = False + + @property + def share_data(self): + """ + Share cube data when slicing/indexing cube if True. + Setting this flag to True will realise the data payload, + if it is lazy, as lazy data cannot currently be shared across cubes. + """ + return self._share_data + + @share_data.setter + def share_data(self, value): + # If value is True: realise the data (if is hasn't already been) as + # sharing lazy data is not possible. + if value and self.has_lazy_data(): + _ = self.data + self._share_data = bool(value) + @property def metadata(self): """ @@ -2173,8 +2194,10 @@ def new_cell_measure_dims(cm_): dimension_mapping, data = iris.util._slice_data_with_keys( cube_data, keys) - # We don't want a view of the data, so take a copy of it. - data = deepcopy(data) + # We don't want a view of the data, so take a copy of it, unless + # self.share_data is True. + if not self.share_data: + data = deepcopy(data) # We can turn a masked array into a normal array if it's full. if ma.isMaskedArray(data): diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py index 14dba07b85..c58b8d43ad 100644 --- a/lib/iris/tests/unit/cube/test_Cube.py +++ b/lib/iris/tests/unit/cube/test_Cube.py @@ -25,6 +25,7 @@ from itertools import permutations +import dask.array as da import numpy as np import numpy.ma as ma @@ -66,6 +67,10 @@ def test_matrix(self): self.assertEqual(type(cube.data), np.ndarray) self.assertArrayEqual(cube.data, data) + def test_default_share_data(self): + cube = Cube(np.arange(1)) + self.assertFalse(cube.share_data) + class Test_extract(tests.IrisTest): def test_scalar_cube_exists(self): @@ -1877,6 +1882,50 @@ def test_remove_cell_measure(self): [[self.b_cell_measure, (0, 1)]]) +class Test_share_data(tests.IrisTest): + def test_setter_lazy_data(self): + cube = Cube(da.from_array(np.arange(6).reshape(2, 3), chunks=6)) + cube.share_data = True + self.assertFalse(cube.has_lazy_data()) + self.assertTrue(cube._share_data) + + def test_setter_realised_data(self): + cube = Cube(np.arange(6).reshape(2, 3)) + cube.share_data = True + self.assertFalse(cube.has_lazy_data()) + self.assertTrue(cube._share_data) + + +class Test___getitem__no_share_data(tests.IrisTest): + def test_lazy_array(self): + cube = Cube(da.from_array(np.arange(6).reshape(2, 3), chunks=6)) + cube2 = cube[1:] + self.assertTrue(cube2.has_lazy_data()) + cube.data + self.assertTrue(cube2.has_lazy_data()) + + def test_ndarray(self): + cube = Cube(np.arange(6).reshape(2, 3)) + cube2 = cube[1:] + self.assertIsNot(cube.data.base, cube2.data.base) + + +class Test___getitem__share_data(tests.IrisTest): + def test_lazy_array(self): + cube = Cube(da.from_array(np.arange(6).reshape(2, 3), chunks=6)) + cube.share_data = True + cube2 = cube[1:] + self.assertFalse(cube.has_lazy_data()) + self.assertFalse(cube2.has_lazy_data()) + self.assertIs(cube.data.base, cube2.data.base) + + def test_ndarray(self): + cube = Cube(np.arange(6).reshape(2, 3)) + cube.share_data = True + cube2 = cube[1:] + self.assertIs(cube.data.base, cube2.data.base) + + class Test__getitem_CellMeasure(tests.IrisTest): def setUp(self): cube = Cube(np.arange(6).reshape(2, 3))