diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 9b9dcff946..c601f6741a 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -53,6 +53,9 @@ This document explains the changes made to Iris for this release :func:`~iris.cube.Cube.slices` to work with dataless cubes. (:issue:`6725`, :pull:`6724`) +#. `@ESadek-MO`_ added functionality to allow :func:`~iris.cube.Cube.rolling_window` and + :func:`~iris.cube.Cube.intersection` to work with dataless cubes. (:pull:`6757`) + 🐛 Bugs Fixed ============= diff --git a/lib/iris/cube.py b/lib/iris/cube.py index c9f1072307..a140d6bc0e 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -3276,8 +3276,6 @@ def intersection(self, *args, **kwargs) -> Cube: which intersects with the requested coordinate intervals. """ - if self.is_dataless(): - raise iris.exceptions.DatalessError("intersection") result = self ignore_bounds = kwargs.pop("ignore_bounds", False) threshold = kwargs.pop("threshold", 0) @@ -3349,9 +3347,17 @@ def make_chunk(key): if len(chunks) == 1: result = chunks[0] else: - chunk_data = [chunk.core_data() for chunk in chunks] - data = _lazy.concatenate(chunk_data, axis=dim) - result = iris.cube.Cube(data) + if self.is_dataless(): + old_shape = list(self.shape) + newlen = sum(chunk.coord(coord).shape[0] for chunk in chunks) + old_shape[dim] = newlen + new_shape = tuple(old_shape) + data = None + else: + chunk_data = [chunk.core_data() for chunk in chunks] + data = _lazy.concatenate(chunk_data, axis=dim) + new_shape = None + result = iris.cube.Cube(data=data, shape=new_shape) result.metadata = deepcopy(self.metadata) # Record a mapping from old coordinate IDs to new coordinates, @@ -4952,8 +4958,7 @@ def rolling_window( """ # noqa: D214, D406, D407, D410, D411 # Update weights kwargs (if necessary) to handle different types of # weights - if self.is_dataless(): - raise iris.exceptions.DatalessError("rolling_window") + dataless = self.is_dataless() weights_info = None if kwargs.get("weights") is not None: weights_info = _Weights(kwargs["weights"], self) @@ -4992,13 +4997,13 @@ def rolling_window( key = [slice(None, None)] * self.ndim key[dimension] = slice(None, self.shape[dimension] - window + 1) new_cube = new_cube[tuple(key)] - - # take a view of the original data using the rolling_window function - # this will add an extra dimension to the data at dimension + 1 which - # represents the rolled window (i.e. will have a length of window) - rolling_window_data = iris.util.rolling_window( - self.core_data(), window=window, axis=dimension - ) + if not dataless: + # take a view of the original data using the rolling_window function + # this will add an extra dimension to the data at dimension + 1 which + # represents the rolled window (i.e. will have a length of window) + rolling_window_data = iris.util.rolling_window( + self.core_data(), window=window, axis=dimension + ) # now update all of the coordinates to reflect the aggregation for coord_ in self.coords(dimensions=dimension): @@ -5047,27 +5052,30 @@ def rolling_window( ) # and perform the data transformation, generating weights first if # needed - if isinstance( - aggregator, iris.analysis.WeightedAggregator - ) and aggregator.uses_weighting(**kwargs): - if "weights" in kwargs: - weights = kwargs["weights"] - if weights.ndim > 1 or weights.shape[0] != window: - raise ValueError( - "Weights for rolling window aggregation " - "must be a 1d array with the same length " - "as the window." + if not dataless: + if isinstance( + aggregator, iris.analysis.WeightedAggregator + ) and aggregator.uses_weighting(**kwargs): + if "weights" in kwargs: + weights = kwargs["weights"] + if weights.ndim > 1 or weights.shape[0] != window: + raise ValueError( + "Weights for rolling window aggregation " + "must be a 1d array with the same length " + "as the window." + ) + kwargs = dict(kwargs) + kwargs["weights"] = iris.util.broadcast_to_shape( + weights, rolling_window_data.shape, (dimension + 1,) ) - kwargs = dict(kwargs) - kwargs["weights"] = iris.util.broadcast_to_shape( - weights, rolling_window_data.shape, (dimension + 1,) - ) - if aggregator.lazy_func is not None and self.has_lazy_data(): - agg_method = aggregator.lazy_aggregate + if aggregator.lazy_func is not None and self.has_lazy_data(): + agg_method = aggregator.lazy_aggregate + else: + agg_method = aggregator.aggregate + data_result = agg_method(rolling_window_data, axis=dimension + 1, **kwargs) else: - agg_method = aggregator.aggregate - data_result = agg_method(rolling_window_data, axis=dimension + 1, **kwargs) + data_result = None result = aggregator.post_process(new_cube, data_result, [coord], **kwargs) return result diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py index 81b76998fe..ef59bd4463 100644 --- a/lib/iris/tests/unit/cube/test_Cube.py +++ b/lib/iris/tests/unit/cube/test_Cube.py @@ -1029,7 +1029,10 @@ def _setup(self): self.mock_agg.aggregate = mock.Mock(return_value=np.empty([4])) self.mock_agg.post_process = mock.Mock(side_effect=lambda x, y, z: x) - def test_string_coord(self): + @pytest.mark.parametrize("dataless", [True, False]) + def test_string_coord(self, dataless): + if dataless: + self.cube.data = None # Rolling window on a cube that contains a string coordinate. res_cube = self.cube.rolling_window("val", self.mock_agg, 3) val_coord = DimCoord( @@ -1082,12 +1085,18 @@ def test_lazy(self): ) _shared_utils.assert_masked_array_equal(expected_result, res_cube.data) - def test_ancillary_variables_and_cell_measures_kept(self): + @pytest.mark.parametrize("dataless", [True, False]) + def test_ancillary_variables_and_cell_measures_kept(self, dataless): + if dataless: + self.cube.data = None res_cube = self.multi_dim_cube.rolling_window("val", self.mock_agg, 3) assert res_cube.ancillary_variables() == [self.ancillary_variable] assert res_cube.cell_measures() == [self.cell_measure] - def test_ancillary_variables_and_cell_measures_removed(self): + @pytest.mark.parametrize("dataless", [True, False]) + def test_ancillary_variables_and_cell_measures_removed(self, dataless): + if dataless: + self.cube.data = None res_cube = self.multi_dim_cube.rolling_window("extra", self.mock_agg, 3) assert res_cube.ancillary_variables() == [] assert res_cube.cell_measures() == [] @@ -1104,16 +1113,24 @@ def test_weights_cube(self): _shared_utils.assert_array_equal(res_cube.data, [10, 13]) assert res_cube.units == "kg m2" - def test_weights_str(self): + @pytest.mark.parametrize("dataless", [True, False]) + def test_weights_str(self, dataless): + if dataless: + self.cube.data = None weights = "val" res_cube = self.cube.rolling_window("val", SUM, 6, weights=weights) - _shared_utils.assert_array_equal(res_cube.data, [55]) + if not dataless: + _shared_utils.assert_array_equal(res_cube.data, [55]) assert res_cube.units == "kg s" - def test_weights_dim_coord(self): + @pytest.mark.parametrize("dataless", [True, False]) + def test_weights_dim_coord(self, dataless): + if dataless: + self.cube.data = None weights = self.cube.coord("val") res_cube = self.cube.rolling_window("val", SUM, 6, weights=weights) - _shared_utils.assert_array_equal(res_cube.data, [55]) + if not dataless: + _shared_utils.assert_array_equal(res_cube.data, [55]) assert res_cube.units == "kg s" @@ -1210,12 +1227,12 @@ def test_all_permutations(self): @_shared_utils.skip_data class Test_slices_over: - @pytest.fixture(autouse=True, params=[True, False], ids=["dataless", "with data"]) + @pytest.fixture(autouse=True, params=[False, True], ids=["with data", "dataless"]) def _setup(self, request): - cube = stock.realistic_4d() - if request.param: - cube.data = None - self.cube = cube[:, :7, :10, :10] + dataless = request.param + self.cube = stock.realistic_4d()[:, :7, :10, :10] + if dataless: + self.cube.data = None # Define expected iterators for 1D and 2D test cases. self.exp_iter_1d = range(len(self.cube.coord("model_level_number").points)) self.exp_iter_2d = np.ndindex(6, 7, 1, 1) @@ -1344,11 +1361,16 @@ def test_nodimension(self): assert next(res) == self.cube -def create_cube(lon_min, lon_max, bounds=False): +def create_cube(lon_min, lon_max, bounds=False, dataless=False): n_lons = max(lon_min, lon_max) - min(lon_max, lon_min) data = np.arange(4 * 3 * n_lons, dtype="f4").reshape(4, 3, -1) - data = as_lazy_data(data) - cube = Cube(data, standard_name="x_wind", units="ms-1") + if dataless: + shape = data.shape + data = None + else: + shape = None + data = as_lazy_data(data) + cube = Cube(data=data, standard_name="x_wind", units="ms-1", shape=shape) cube.add_dim_coord( iris.coords.DimCoord([0, 20, 40, 80], long_name="level_height", units="m"), 0, @@ -1421,58 +1443,60 @@ def test_metadata_wrapped(self, request): # Explicitly check the handling of `circular` on the result. +@pytest.mark.parametrize("dataless", [True, False], ids=["dataless", "with data"]) class Test_intersection__Circular: - def test_regional(self): - cube = create_cube(0, 360) + def test_regional(self, dataless): + cube = create_cube(0, 360, dataless=dataless) result = cube.intersection(longitude=(170, 190)) assert not result.coord("longitude").circular - def test_regional_wrapped(self): - cube = create_cube(-180, 180) + def test_regional_wrapped(self, dataless): + cube = create_cube(-180, 180, dataless=dataless) result = cube.intersection(longitude=(170, 190)) assert not result.coord("longitude").circular - def test_global(self): - cube = create_cube(-180, 180) + def test_global(self, dataless): + cube = create_cube(-180, 180, dataless=dataless) result = cube.intersection(longitude=(-180, 180)) assert result.coord("longitude").circular - def test_global_wrapped(self): - cube = create_cube(-180, 180) + def test_global_wrapped(self, dataless): + cube = create_cube(-180, 180, dataless=dataless) result = cube.intersection(longitude=(10, 370)) assert result.coord("longitude").circular # Check the various error conditions. +@pytest.mark.parametrize("dataless", [True, False], ids=["dataless", "with data"]) class Test_intersection__Invalid: - def test_reversed_min_max(self): - cube = create_cube(0, 360) + def test_reversed_min_max(self, dataless): + cube = create_cube(0, 360, dataless=dataless) with pytest.raises(ValueError): cube.intersection(longitude=(30, 10)) - def test_dest_too_large(self): - cube = create_cube(0, 360) + def test_dest_too_large(self, dataless): + cube = create_cube(0, 360, dataless=dataless) with pytest.raises(ValueError): cube.intersection(longitude=(30, 500)) - def test_src_too_large(self): - cube = create_cube(0, 400) + def test_src_too_large(self, dataless): + cube = create_cube(0, 400, dataless=dataless) with pytest.raises(ValueError): cube.intersection(longitude=(10, 30)) - def test_missing_coord(self): - cube = create_cube(0, 360) + def test_missing_coord(self, dataless): + cube = create_cube(0, 360, dataless=dataless) with pytest.raises(iris.exceptions.CoordinateNotFoundError): cube.intersection(parrots=(10, 30)) - def test_multi_dim_coord(self): - cube = create_cube(0, 360) + def test_multi_dim_coord(self, dataless): + cube = create_cube(0, 360, dataless=dataless) with pytest.raises(iris.exceptions.CoordinateMultiDimError): cube.intersection(surface_altitude=(10, 30)) - def test_null_region(self): + def test_null_region(self, dataless): # 10 <= v < 10 - cube = create_cube(0, 360) + cube = create_cube(0, 360, dataless=dataless) with pytest.raises(IndexError): cube.intersection(longitude=(10, 10, False, False)) @@ -1521,9 +1545,10 @@ def test_lazy_data_wrapped(self): assert result.data[0, 0, -1] == 10 +@pytest.mark.parametrize("dataless", [True, False], ids=["dataless", "with data"]) class Test_intersection_Points: - def test_ignore_bounds(self): - cube = create_cube(0, 30, bounds=True) + def test_ignore_bounds(self, dataless): + cube = create_cube(0, 30, bounds=True, dataless=dataless) result = cube.intersection(longitude=(9.5, 12.5), ignore_bounds=True) _shared_utils.assert_array_equal( result.coord("longitude").points, np.arange(10, 13) @@ -1538,73 +1563,98 @@ def test_ignore_bounds(self): # Check what happens with a regional, points-only circular intersection # coordinate. +@pytest.mark.parametrize("dataless", [True, False], ids=["dataless", "with data"]) class Test_intersection__RegionalSrcModulus: - def test_request_subset(self): - cube = create_cube(40, 60) + def test_request_subset(self, dataless): + cube = create_cube(40, 60, dataless=dataless) result = cube.intersection(longitude=(45, 50)) _shared_utils.assert_array_equal( result.coord("longitude").points, np.arange(45, 51) ) - _shared_utils.assert_array_equal(result.data[0, 0], np.arange(5, 11)) + if dataless: + assert result.data is None + else: + _shared_utils.assert_array_equal(result.data[0, 0], np.arange(5, 11)) - def test_request_left(self): - cube = create_cube(40, 60) + def test_request_left(self, dataless): + cube = create_cube(40, 60, dataless=dataless) result = cube.intersection(longitude=(35, 45)) _shared_utils.assert_array_equal( result.coord("longitude").points, np.arange(40, 46) ) - _shared_utils.assert_array_equal(result.data[0, 0], np.arange(0, 6)) + if dataless: + assert result.data is None + else: + _shared_utils.assert_array_equal(result.data[0, 0], np.arange(0, 6)) - def test_request_right(self): - cube = create_cube(40, 60) + def test_request_right(self, dataless): + cube = create_cube(40, 60, dataless=dataless) result = cube.intersection(longitude=(55, 65)) _shared_utils.assert_array_equal( result.coord("longitude").points, np.arange(55, 60) ) - _shared_utils.assert_array_equal(result.data[0, 0], np.arange(15, 20)) + if dataless: + assert result.data is None + else: + _shared_utils.assert_array_equal(result.data[0, 0], np.arange(15, 20)) - def test_request_superset(self): - cube = create_cube(40, 60) + def test_request_superset(self, dataless): + cube = create_cube(40, 60, dataless=dataless) result = cube.intersection(longitude=(35, 65)) _shared_utils.assert_array_equal( result.coord("longitude").points, np.arange(40, 60) ) - _shared_utils.assert_array_equal(result.data[0, 0], np.arange(0, 20)) + if dataless: + assert result.data is None + else: + _shared_utils.assert_array_equal(result.data[0, 0], np.arange(0, 20)) - def test_request_subset_modulus(self): - cube = create_cube(40, 60) + def test_request_subset_modulus(self, dataless): + cube = create_cube(40, 60, dataless=dataless) result = cube.intersection(longitude=(45 + 360, 50 + 360)) _shared_utils.assert_array_equal( result.coord("longitude").points, np.arange(45 + 360, 51 + 360) ) - _shared_utils.assert_array_equal(result.data[0, 0], np.arange(5, 11)) + if dataless: + assert result.data is None + else: + _shared_utils.assert_array_equal(result.data[0, 0], np.arange(5, 11)) - def test_request_left_modulus(self): - cube = create_cube(40, 60) + def test_request_left_modulus(self, dataless): + cube = create_cube(40, 60, dataless=dataless) result = cube.intersection(longitude=(35 + 360, 45 + 360)) _shared_utils.assert_array_equal( result.coord("longitude").points, np.arange(40 + 360, 46 + 360) ) - _shared_utils.assert_array_equal(result.data[0, 0], np.arange(0, 6)) + if dataless: + assert result.data is None + else: + _shared_utils.assert_array_equal(result.data[0, 0], np.arange(0, 6)) - def test_request_right_modulus(self): - cube = create_cube(40, 60) + def test_request_right_modulus(self, dataless): + cube = create_cube(40, 60, dataless=dataless) result = cube.intersection(longitude=(55 + 360, 65 + 360)) _shared_utils.assert_array_equal( result.coord("longitude").points, np.arange(55 + 360, 60 + 360) ) - _shared_utils.assert_array_equal(result.data[0, 0], np.arange(15, 20)) + if dataless: + assert result.data is None + else: + _shared_utils.assert_array_equal(result.data[0, 0], np.arange(15, 20)) - def test_request_superset_modulus(self): - cube = create_cube(40, 60) + def test_request_superset_modulus(self, dataless): + cube = create_cube(40, 60, dataless=dataless) result = cube.intersection(longitude=(35 + 360, 65 + 360)) _shared_utils.assert_array_equal( result.coord("longitude").points, np.arange(40 + 360, 60 + 360) ) - _shared_utils.assert_array_equal(result.data[0, 0], np.arange(0, 20)) + if dataless: + assert result.data is None + else: + _shared_utils.assert_array_equal(result.data[0, 0], np.arange(0, 20)) - def test_tolerance_f4(self): - cube = create_cube(0, 5) + def test_tolerance_f4(self, dataless): + cube = create_cube(0, 5, dataless=dataless) cube.coord("longitude").points = np.array( [0.0, 3.74999905, 7.49999809, 11.24999714, 14.99999619], dtype="f4" ) @@ -1612,10 +1662,13 @@ def test_tolerance_f4(self): _shared_utils.assert_array_almost_equal( result.coord("longitude").points, np.array([0.0, 3.74999905]) ) - _shared_utils.assert_array_equal(result.data[0, 0], np.array([0, 1])) + if dataless: + assert result.data is None + else: + _shared_utils.assert_array_equal(result.data[0, 0], np.array([0, 1])) - def test_tolerance_f8(self): - cube = create_cube(0, 5) + def test_tolerance_f8(self, dataless): + cube = create_cube(0, 5, dataless=dataless) cube.coord("longitude").points = np.array( [0.0, 3.74999905, 7.49999809, 11.24999714, 14.99999619], dtype="f8" ) @@ -1623,173 +1676,231 @@ def test_tolerance_f8(self): _shared_utils.assert_array_almost_equal( result.coord("longitude").points, np.array([0.0, 3.74999905]) ) - _shared_utils.assert_array_equal(result.data[0, 0], np.array([0, 1])) + if dataless: + assert result.data is None + else: + _shared_utils.assert_array_equal(result.data[0, 0], np.array([0, 1])) # Check what happens with a global, points-only circular intersection # coordinate. +@pytest.mark.parametrize("dataless", [True, False], ids=["dataless", "with data"]) class Test_intersection__GlobalSrcModulus: - def test_global_wrapped_extreme_increasing_base_period(self): + def test_global_wrapped_extreme_increasing_base_period(self, dataless): # Ensure that we can correctly handle points defined at (base + period) - cube = create_cube(-180.0, 180.0) + cube = create_cube(-180.0, 180.0, dataless=dataless) lons = cube.coord("longitude") # Redefine longitude so that points at (base + period) lons.points = np.linspace(-180.0, 180, lons.points.size) result = cube.intersection(longitude=(lons.points.min(), lons.points.max())) - _shared_utils.assert_array_equal(result.data, cube.data) + if dataless: + assert result.data is None + else: + _shared_utils.assert_array_equal(result.data, cube.data) - def test_global_wrapped_extreme_decreasing_base_period(self): + def test_global_wrapped_extreme_decreasing_base_period(self, dataless): # Ensure that we can correctly handle points defined at (base + period) - cube = create_cube(180.0, -180.0) + cube = create_cube(180.0, -180.0, dataless=dataless) lons = cube.coord("longitude") # Redefine longitude so that points at (base + period) lons.points = np.linspace(180.0, -180.0, lons.points.size) result = cube.intersection(longitude=(lons.points.min(), lons.points.max())) - _shared_utils.assert_array_equal(result.data, cube.data) + if dataless: + assert result.data is None + else: + _shared_utils.assert_array_equal(result.data, cube.data) - def test_global(self): - cube = create_cube(0, 360) + def test_global(self, dataless): + cube = create_cube(0, 360, dataless=dataless) result = cube.intersection(longitude=(0, 360)) assert result.coord("longitude").points[0] == 0 assert result.coord("longitude").points[-1] == 359 - assert result.data[0, 0, 0] == 0 - assert result.data[0, 0, -1] == 359 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 0 + assert result.data[0, 0, -1] == 359 - def test_global_wrapped(self): - cube = create_cube(0, 360) + def test_global_wrapped(self, dataless): + cube = create_cube(0, 360, dataless=dataless) result = cube.intersection(longitude=(-180, 180)) assert result.coord("longitude").points[0] == -180 assert result.coord("longitude").points[-1] == 179 - assert result.data[0, 0, 0] == 180 - assert result.data[0, 0, -1] == 179 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 180 + assert result.data[0, 0, -1] == 179 - def test_aux_coord(self): - cube = create_cube(0, 360) + def test_aux_coord(self, dataless): + cube = create_cube(0, 360, dataless=dataless) cube.replace_coord(iris.coords.AuxCoord.from_coord(cube.coord("longitude"))) result = cube.intersection(longitude=(0, 360)) assert result.coord("longitude").points[0] == 0 assert result.coord("longitude").points[-1] == 359 - assert result.data[0, 0, 0] == 0 - assert result.data[0, 0, -1] == 359 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 0 + assert result.data[0, 0, -1] == 359 - def test_aux_coord_wrapped(self): - cube = create_cube(0, 360) + def test_aux_coord_wrapped(self, dataless): + cube = create_cube(0, 360, dataless=dataless) cube.replace_coord(iris.coords.AuxCoord.from_coord(cube.coord("longitude"))) result = cube.intersection(longitude=(-180, 180)) assert result.coord("longitude").points[0] == 0 assert result.coord("longitude").points[-1] == -1 - assert result.data[0, 0, 0] == 0 - assert result.data[0, 0, -1] == 359 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 0 + assert result.data[0, 0, -1] == 359 - def test_aux_coord_non_contiguous_wrapped(self): - cube = create_cube(0, 360) + def test_aux_coord_non_contiguous_wrapped(self, dataless): + cube = create_cube(0, 360, dataless=dataless) coord = iris.coords.AuxCoord.from_coord(cube.coord("longitude")) coord.points = (coord.points * 1.5) % 360 cube.replace_coord(coord) result = cube.intersection(longitude=(-90, 90)) assert result.coord("longitude").points[0] == 0 assert result.coord("longitude").points[-1] == 90 - assert result.data[0, 0, 0] == 0 - assert result.data[0, 0, -1] == 300 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 0 + assert result.data[0, 0, -1] == 300 - def test_decrementing(self): - cube = create_cube(360, 0) + def test_decrementing(self, dataless): + cube = create_cube(360, 0, dataless=dataless) result = cube.intersection(longitude=(40, 60)) assert result.coord("longitude").points[0] == 60 assert result.coord("longitude").points[-1] == 40 - assert result.data[0, 0, 0] == 300 - assert result.data[0, 0, -1] == 320 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 300 + assert result.data[0, 0, -1] == 320 - def test_decrementing_wrapped(self): - cube = create_cube(360, 0) + def test_decrementing_wrapped(self, dataless): + cube = create_cube(360, 0, dataless=dataless) result = cube.intersection(longitude=(-10, 10)) assert result.coord("longitude").points[0] == 10 assert result.coord("longitude").points[-1] == -10 - assert result.data[0, 0, 0] == 350 - assert result.data[0, 0, -1] == 10 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 350 + assert result.data[0, 0, -1] == 10 - def test_no_wrap_after_modulus(self): - cube = create_cube(0, 360) + def test_no_wrap_after_modulus(self, dataless): + cube = create_cube(0, 360, dataless=dataless) result = cube.intersection(longitude=(170 + 360, 190 + 360)) assert result.coord("longitude").points[0] == 170 + 360 assert result.coord("longitude").points[-1] == 190 + 360 - assert result.data[0, 0, 0] == 170 - assert result.data[0, 0, -1] == 190 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 170 + assert result.data[0, 0, -1] == 190 - def test_wrap_after_modulus(self): - cube = create_cube(-180, 180) + def test_wrap_after_modulus(self, dataless): + cube = create_cube(-180, 180, dataless=dataless) result = cube.intersection(longitude=(170 + 360, 190 + 360)) assert result.coord("longitude").points[0] == 170 + 360 assert result.coord("longitude").points[-1] == 190 + 360 - assert result.data[0, 0, 0] == 350 - assert result.data[0, 0, -1] == 10 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 350 + assert result.data[0, 0, -1] == 10 - def test_select_by_coord(self): - cube = create_cube(0, 360) + def test_select_by_coord(self, dataless): + cube = create_cube(0, 360, dataless=dataless) coord = iris.coords.DimCoord(0, "longitude", units="degrees") result = cube.intersection(iris.coords.CoordExtent(coord, 10, 30)) assert result.coord("longitude").points[0] == 10 assert result.coord("longitude").points[-1] == 30 - assert result.data[0, 0, 0] == 10 - assert result.data[0, 0, -1] == 30 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 10 + assert result.data[0, 0, -1] == 30 - def test_inclusive_exclusive(self): - cube = create_cube(0, 360) + def test_inclusive_exclusive(self, dataless): + cube = create_cube(0, 360, dataless=dataless) result = cube.intersection(longitude=(170, 190, True, False)) assert result.coord("longitude").points[0] == 170 assert result.coord("longitude").points[-1] == 189 - assert result.data[0, 0, 0] == 170 - assert result.data[0, 0, -1] == 189 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 170 + assert result.data[0, 0, -1] == 189 - def test_exclusive_inclusive(self): - cube = create_cube(0, 360) + def test_exclusive_inclusive(self, dataless): + cube = create_cube(0, 360, dataless=dataless) result = cube.intersection(longitude=(170, 190, False)) assert result.coord("longitude").points[0] == 171 assert result.coord("longitude").points[-1] == 190 - assert result.data[0, 0, 0] == 171 - assert result.data[0, 0, -1] == 190 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 171 + assert result.data[0, 0, -1] == 190 - def test_exclusive_exclusive(self): - cube = create_cube(0, 360) + def test_exclusive_exclusive(self, dataless): + cube = create_cube(0, 360, dataless=dataless) result = cube.intersection(longitude=(170, 190, False, False)) assert result.coord("longitude").points[0] == 171 assert result.coord("longitude").points[-1] == 189 - assert result.data[0, 0, 0] == 171 - assert result.data[0, 0, -1] == 189 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 171 + assert result.data[0, 0, -1] == 189 - def test_single_point(self): + def test_single_point(self, dataless): # 10 <= v <= 10 - cube = create_cube(0, 360) + cube = create_cube(0, 360, dataless=dataless) result = cube.intersection(longitude=(10, 10)) assert result.coord("longitude").points[0] == 10 assert result.coord("longitude").points[-1] == 10 - assert result.data[0, 0, 0] == 10 - assert result.data[0, 0, -1] == 10 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 10 + assert result.data[0, 0, -1] == 10 - def test_two_points(self): + def test_two_points(self, dataless): # -1.5 <= v <= 0.5 - cube = create_cube(0, 360) + cube = create_cube(0, 360, dataless=dataless) result = cube.intersection(longitude=(-1.5, 0.5)) assert result.coord("longitude").points[0] == -1 assert result.coord("longitude").points[-1] == 0 - assert result.data[0, 0, 0] == 359 - assert result.data[0, 0, -1] == 0 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 359 + assert result.data[0, 0, -1] == 0 - def test_wrap_radians(self): - cube = create_cube(0, 360) + def test_wrap_radians(self, dataless): + cube = create_cube(0, 360, dataless=dataless) cube.coord("longitude").convert_units("radians") result = cube.intersection(longitude=(-1, 0.5)) _shared_utils.assert_array_all_close( result.coord("longitude").points, np.arange(-57, 29) * np.pi / 180 ) - assert result.data[0, 0, 0] == 303 - assert result.data[0, 0, -1] == 28 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 303 + assert result.data[0, 0, -1] == 28 - def test_tolerance_bug(self): + def test_tolerance_bug(self, dataless): # Floating point changes introduced by wrapping mean # the resulting coordinate values are not equal to their # equivalents. This led to a bug that this test checks. - cube = create_cube(0, 400) + cube = create_cube(0, 400, dataless=dataless) cube.coord("longitude").points = np.linspace(-179.55, 179.55, 400) result = cube.intersection(longitude=(125, 145)) _shared_utils.assert_array_almost_equal( @@ -1797,8 +1908,8 @@ def test_tolerance_bug(self): cube.coord("longitude").points[339:361], ) - def test_tolerance_bug_wrapped(self): - cube = create_cube(0, 400) + def test_tolerance_bug_wrapped(self, dataless): + cube = create_cube(0, 400, dataless=dataless) cube.coord("longitude").points = np.linspace(-179.55, 179.55, 400) result = cube.intersection(longitude=(-190, -170)) # Expected result is the last 11 and first 11 points. @@ -1813,23 +1924,30 @@ def test_tolerance_bug_wrapped(self): # Check what happens with a global, points-and-bounds circular # intersection coordinate. +@pytest.mark.parametrize("dataless", [True, False], ids=["dataless", "with data"]) class Test_intersection__ModulusBounds: - def test_global_wrapped_extreme_increasing_base_period(self): + def test_global_wrapped_extreme_increasing_base_period(self, dataless): # Ensure that we can correctly handle bounds defined at (base + period) - cube = create_cube(-180.0, 180.0, bounds=True) + cube = create_cube(-180.0, 180.0, bounds=True, dataless=dataless) lons = cube.coord("longitude") result = cube.intersection(longitude=(lons.bounds.min(), lons.bounds.max())) - _shared_utils.assert_array_equal(result.data, cube.data) + if dataless: + assert result.data is None + else: + _shared_utils.assert_array_equal(result.data, cube.data) - def test_global_wrapped_extreme_decreasing_base_period(self): + def test_global_wrapped_extreme_decreasing_base_period(self, dataless): # Ensure that we can correctly handle bounds defined at (base + period) - cube = create_cube(180.0, -180.0, bounds=True) + cube = create_cube(180.0, -180.0, bounds=True, dataless=dataless) lons = cube.coord("longitude") result = cube.intersection(longitude=(lons.bounds.min(), lons.bounds.max())) - _shared_utils.assert_array_equal(result.data, cube.data) + if dataless: + assert result.data is None + else: + _shared_utils.assert_array_equal(result.data, cube.data) - def test_misaligned_points_inside(self): - cube = create_cube(0, 360, bounds=True) + def test_misaligned_points_inside(self, dataless): + cube = create_cube(0, 360, bounds=True, dataless=dataless) result = cube.intersection(longitude=(169.75, 190.25)) _shared_utils.assert_array_equal( result.coord("longitude").bounds[0], [169.5, 170.5] @@ -1837,11 +1955,14 @@ def test_misaligned_points_inside(self): _shared_utils.assert_array_equal( result.coord("longitude").bounds[-1], [189.5, 190.5] ) - assert result.data[0, 0, 0] == 170 - assert result.data[0, 0, -1] == 190 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 170 + assert result.data[0, 0, -1] == 190 - def test_misaligned_points_outside(self): - cube = create_cube(0, 360, bounds=True) + def test_misaligned_points_outside(self, dataless): + cube = create_cube(0, 360, bounds=True, dataless=dataless) result = cube.intersection(longitude=(170.25, 189.75)) _shared_utils.assert_array_equal( result.coord("longitude").bounds[0], [169.5, 170.5] @@ -1849,11 +1970,14 @@ def test_misaligned_points_outside(self): _shared_utils.assert_array_equal( result.coord("longitude").bounds[-1], [189.5, 190.5] ) - assert result.data[0, 0, 0] == 170 - assert result.data[0, 0, -1] == 190 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 170 + assert result.data[0, 0, -1] == 190 - def test_misaligned_bounds(self): - cube = create_cube(-180, 180, bounds=True) + def test_misaligned_bounds(self, dataless): + cube = create_cube(-180, 180, bounds=True, dataless=dataless) result = cube.intersection(longitude=(0, 360)) _shared_utils.assert_array_equal( result.coord("longitude").bounds[0], [-0.5, 0.5] @@ -1861,11 +1985,14 @@ def test_misaligned_bounds(self): _shared_utils.assert_array_equal( result.coord("longitude").bounds[-1], [358.5, 359.5] ) - assert result.data[0, 0, 0] == 180 - assert result.data[0, 0, -1] == 179 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 180 + assert result.data[0, 0, -1] == 179 - def test_misaligned_bounds_decreasing(self): - cube = create_cube(180, -180, bounds=True) + def test_misaligned_bounds_decreasing(self, dataless): + cube = create_cube(180, -180, bounds=True, dataless=dataless) result = cube.intersection(longitude=(0, 360)) _shared_utils.assert_array_equal( result.coord("longitude").bounds[0], [359.5, 358.5] @@ -1874,11 +2001,14 @@ def test_misaligned_bounds_decreasing(self): _shared_utils.assert_array_equal( result.coord("longitude").bounds[-1], [0.5, -0.5] ) - assert result.data[0, 0, 0] == 181 - assert result.data[0, 0, -1] == 180 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 181 + assert result.data[0, 0, -1] == 180 - def test_aligned_inclusive(self): - cube = create_cube(0, 360, bounds=True) + def test_aligned_inclusive(self, dataless): + cube = create_cube(0, 360, bounds=True, dataless=dataless) result = cube.intersection(longitude=(170.5, 189.5)) _shared_utils.assert_array_equal( result.coord("longitude").bounds[0], [169.5, 170.5] @@ -1886,11 +2016,14 @@ def test_aligned_inclusive(self): _shared_utils.assert_array_equal( result.coord("longitude").bounds[-1], [189.5, 190.5] ) - assert result.data[0, 0, 0] == 170 - assert result.data[0, 0, -1] == 190 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 170 + assert result.data[0, 0, -1] == 190 - def test_aligned_exclusive(self): - cube = create_cube(0, 360, bounds=True) + def test_aligned_exclusive(self, dataless): + cube = create_cube(0, 360, bounds=True, dataless=dataless) result = cube.intersection(longitude=(170.5, 189.5, False, False)) _shared_utils.assert_array_equal( result.coord("longitude").bounds[0], [170.5, 171.5] @@ -1898,21 +2031,27 @@ def test_aligned_exclusive(self): _shared_utils.assert_array_equal( result.coord("longitude").bounds[-1], [188.5, 189.5] ) - assert result.data[0, 0, 0] == 171 - assert result.data[0, 0, -1] == 189 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 171 + assert result.data[0, 0, -1] == 189 - def test_aligned_bounds_at_modulus(self): - cube = create_cube(-179.5, 180.5, bounds=True) + def test_aligned_bounds_at_modulus(self, dataless): + cube = create_cube(-179.5, 180.5, bounds=True, dataless=dataless) result = cube.intersection(longitude=(0, 360)) _shared_utils.assert_array_equal(result.coord("longitude").bounds[0], [0, 1]) _shared_utils.assert_array_equal( result.coord("longitude").bounds[-1], [359, 360] ) - assert result.data[0, 0, 0] == 180 - assert result.data[0, 0, -1] == 179 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 180 + assert result.data[0, 0, -1] == 179 - def test_negative_aligned_bounds_at_modulus(self): - cube = create_cube(0.5, 360.5, bounds=True) + def test_negative_aligned_bounds_at_modulus(self, dataless): + cube = create_cube(0.5, 360.5, bounds=True, dataless=dataless) result = cube.intersection(longitude=(-180, 180)) _shared_utils.assert_array_equal( result.coord("longitude").bounds[0], [-180, -179] @@ -1920,11 +2059,14 @@ def test_negative_aligned_bounds_at_modulus(self): _shared_utils.assert_array_equal( result.coord("longitude").bounds[-1], [179, 180] ) - assert result.data[0, 0, 0] == 180 - assert result.data[0, 0, -1] == 179 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 180 + assert result.data[0, 0, -1] == 179 - def test_negative_misaligned_points_inside(self): - cube = create_cube(0, 360, bounds=True) + def test_negative_misaligned_points_inside(self, dataless): + cube = create_cube(0, 360, bounds=True, dataless=dataless) result = cube.intersection(longitude=(-10.25, 10.25)) _shared_utils.assert_array_equal( result.coord("longitude").bounds[0], [-10.5, -9.5] @@ -1932,11 +2074,14 @@ def test_negative_misaligned_points_inside(self): _shared_utils.assert_array_equal( result.coord("longitude").bounds[-1], [9.5, 10.5] ) - assert result.data[0, 0, 0] == 350 - assert result.data[0, 0, -1] == 10 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 350 + assert result.data[0, 0, -1] == 10 - def test_negative_misaligned_points_outside(self): - cube = create_cube(0, 360, bounds=True) + def test_negative_misaligned_points_outside(self, dataless): + cube = create_cube(0, 360, bounds=True, dataless=dataless) result = cube.intersection(longitude=(-9.75, 9.75)) _shared_utils.assert_array_equal( result.coord("longitude").bounds[0], [-10.5, -9.5] @@ -1944,11 +2089,14 @@ def test_negative_misaligned_points_outside(self): _shared_utils.assert_array_equal( result.coord("longitude").bounds[-1], [9.5, 10.5] ) - assert result.data[0, 0, 0] == 350 - assert result.data[0, 0, -1] == 10 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 350 + assert result.data[0, 0, -1] == 10 - def test_negative_aligned_inclusive(self): - cube = create_cube(0, 360, bounds=True) + def test_negative_aligned_inclusive(self, dataless): + cube = create_cube(0, 360, bounds=True, dataless=dataless) result = cube.intersection(longitude=(-10.5, 10.5)) _shared_utils.assert_array_equal( result.coord("longitude").bounds[0], [-11.5, -10.5] @@ -1956,11 +2104,14 @@ def test_negative_aligned_inclusive(self): _shared_utils.assert_array_equal( result.coord("longitude").bounds[-1], [10.5, 11.5] ) - assert result.data[0, 0, 0] == 349 - assert result.data[0, 0, -1] == 11 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 349 + assert result.data[0, 0, -1] == 11 - def test_negative_aligned_exclusive(self): - cube = create_cube(0, 360, bounds=True) + def test_negative_aligned_exclusive(self, dataless): + cube = create_cube(0, 360, bounds=True, dataless=dataless) result = cube.intersection(longitude=(-10.5, 10.5, False, False)) _shared_utils.assert_array_equal( result.coord("longitude").bounds[0], [-10.5, -9.5] @@ -1968,11 +2119,14 @@ def test_negative_aligned_exclusive(self): _shared_utils.assert_array_equal( result.coord("longitude").bounds[-1], [9.5, 10.5] ) - assert result.data[0, 0, 0] == 350 - assert result.data[0, 0, -1] == 10 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 350 + assert result.data[0, 0, -1] == 10 - def test_decrementing(self): - cube = create_cube(360, 0, bounds=True) + def test_decrementing(self, dataless): + cube = create_cube(360, 0, bounds=True, dataless=dataless) result = cube.intersection(longitude=(40, 60)) _shared_utils.assert_array_equal( result.coord("longitude").bounds[0], [60.5, 59.5] @@ -1980,11 +2134,14 @@ def test_decrementing(self): _shared_utils.assert_array_equal( result.coord("longitude").bounds[-1], [40.5, 39.5] ) - assert result.data[0, 0, 0] == 300 - assert result.data[0, 0, -1] == 320 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 300 + assert result.data[0, 0, -1] == 320 - def test_decrementing_wrapped(self): - cube = create_cube(360, 0, bounds=True) + def test_decrementing_wrapped(self, dataless): + cube = create_cube(360, 0, bounds=True, dataless=dataless) result = cube.intersection(longitude=(-10, 10)) _shared_utils.assert_array_equal( result.coord("longitude").bounds[0], [10.5, 9.5] @@ -1992,13 +2149,16 @@ def test_decrementing_wrapped(self): _shared_utils.assert_array_equal( result.coord("longitude").bounds[-1], [-9.5, -10.5] ) - assert result.data[0, 0, 0] == 350 - assert result.data[0, 0, -1] == 10 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 350 + assert result.data[0, 0, -1] == 10 - def test_numerical_tolerance(self): + def test_numerical_tolerance(self, dataless): # test the tolerance on the coordinate value is not causing a # modulus wrapping - cube = create_cube(28.5, 68.5, bounds=True) + cube = create_cube(28.5, 68.5, bounds=True, dataless=dataless) result = cube.intersection(longitude=(27.74, 68.61)) result_lons = result.coord("longitude") _shared_utils.assert_array_almost_equal(result_lons.points[0], 28.5) @@ -2011,10 +2171,10 @@ def test_numerical_tolerance(self): result_lons.bounds[-1], np.array([67.0, 68.0], dtype=dtype) ) - def test_numerical_tolerance_wrapped(self): + def test_numerical_tolerance_wrapped(self, dataless): # test the tolerance on the coordinate value causes modulus wrapping # where appropriate - cube = create_cube(0.5, 3600.5, bounds=True) + cube = create_cube(0.5, 3600.5, bounds=True, dataless=dataless) lons = cube.coord("longitude") lons.points = lons.points / 10 lons.bounds = lons.bounds / 10 @@ -2030,9 +2190,9 @@ def test_numerical_tolerance_wrapped(self): result_lons.bounds[-1], np.array([60.0, 60.1], dtype=dtype) ) - def test_ignore_bounds_wrapped(self): + def test_ignore_bounds_wrapped(self, dataless): # Test `ignore_bounds` fully ignores bounds when wrapping - cube = create_cube(0, 360, bounds=True) + cube = create_cube(0, 360, bounds=True, dataless=dataless) result = cube.intersection(longitude=(10.25, 370.25), ignore_bounds=True) # Expect points 11..370 not bounds [9.5, 10.5] .. [368.5, 369.5] _shared_utils.assert_array_equal( @@ -2041,12 +2201,15 @@ def test_ignore_bounds_wrapped(self): _shared_utils.assert_array_equal( result.coord("longitude").bounds[-1], [369.5, 370.5] ) - assert result.data[0, 0, 0] == 11 - assert result.data[0, 0, -1] == 10 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 11 + assert result.data[0, 0, -1] == 10 - def test_within_cell(self): + def test_within_cell(self, dataless): # Test cell is included when it entirely contains the requested range - cube = create_cube(0, 10, bounds=True) + cube = create_cube(0, 10, bounds=True, dataless=dataless) result = cube.intersection(longitude=(0.7, 0.8)) _shared_utils.assert_array_equal( result.coord("longitude").bounds[0], [0.5, 1.5] @@ -2054,11 +2217,14 @@ def test_within_cell(self): _shared_utils.assert_array_equal( result.coord("longitude").bounds[-1], [0.5, 1.5] ) - assert result.data[0, 0, 0] == 1 - assert result.data[0, 0, -1] == 1 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 1 + assert result.data[0, 0, -1] == 1 - def test_threshold_half(self): - cube = create_cube(0, 10, bounds=True) + def test_threshold_half(self, dataless): + cube = create_cube(0, 10, bounds=True, dataless=dataless) result = cube.intersection(longitude=(1, 6.999), threshold=0.5) _shared_utils.assert_array_equal( result.coord("longitude").bounds[0], [0.5, 1.5] @@ -2066,11 +2232,14 @@ def test_threshold_half(self): _shared_utils.assert_array_equal( result.coord("longitude").bounds[-1], [5.5, 6.5] ) - assert result.data[0, 0, 0] == 1 - assert result.data[0, 0, -1] == 6 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 1 + assert result.data[0, 0, -1] == 6 - def test_threshold_full(self): - cube = create_cube(0, 10, bounds=True) + def test_threshold_full(self, dataless): + cube = create_cube(0, 10, bounds=True, dataless=dataless) result = cube.intersection(longitude=(0.5, 7.499), threshold=1) _shared_utils.assert_array_equal( result.coord("longitude").bounds[0], [0.5, 1.5] @@ -2078,13 +2247,16 @@ def test_threshold_full(self): _shared_utils.assert_array_equal( result.coord("longitude").bounds[-1], [5.5, 6.5] ) - assert result.data[0, 0, 0] == 1 - assert result.data[0, 0, -1] == 6 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 1 + assert result.data[0, 0, -1] == 6 - def test_threshold_wrapped(self): + def test_threshold_wrapped(self, dataless): # Test that a cell is wrapped to `maximum` if required to exceed # the threshold - cube = create_cube(-180, 180, bounds=True) + cube = create_cube(-180, 180, bounds=True, dataless=dataless) result = cube.intersection(longitude=(0.4, 360.4), threshold=0.2) _shared_utils.assert_array_equal( result.coord("longitude").bounds[0], [0.5, 1.5] @@ -2092,13 +2264,16 @@ def test_threshold_wrapped(self): _shared_utils.assert_array_equal( result.coord("longitude").bounds[-1], [359.5, 360.5] ) - assert result.data[0, 0, 0] == 181 - assert result.data[0, 0, -1] == 180 + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 181 + assert result.data[0, 0, -1] == 180 - def test_threshold_wrapped_gap(self): + def test_threshold_wrapped_gap(self, dataless): # Test that a cell is wrapped to `maximum` if required to exceed # the threshold (even with a gap in the range) - cube = create_cube(-180, 180, bounds=True) + cube = create_cube(-180, 180, bounds=True, dataless=dataless) result = cube.intersection(longitude=(0.4, 360.35), threshold=0.2) _shared_utils.assert_array_equal( result.coord("longitude").bounds[0], [0.5, 1.5] @@ -2106,13 +2281,21 @@ def test_threshold_wrapped_gap(self): _shared_utils.assert_array_equal( result.coord("longitude").bounds[-1], [359.5, 360.5] ) - assert result.data[0, 0, 0] == 181 - assert result.data[0, 0, -1] == 180 - - -def unrolled_cube(): - data = np.arange(5, dtype="f4") - cube = Cube(data) + if dataless: + assert result.data is None + else: + assert result.data[0, 0, 0] == 181 + assert result.data[0, 0, -1] == 180 + + +def unrolled_cube(dataless=False): + if dataless: + data = None + shape = (5,) + else: + data = np.arange(5, dtype="f4") + shape = None + cube = Cube(data=data, shape=shape) cube.add_aux_coord( iris.coords.AuxCoord([5.0, 10.0, 8.0, 5.0, 3.0], "longitude", units="degrees"), 0, @@ -2125,28 +2308,38 @@ def unrolled_cube(): # Check what happens with a "unrolled" scatter-point data with a circular # intersection coordinate. +@pytest.mark.parametrize("dataless", [True, False], ids=["dataless", "with data"]) class Test_intersection__ScatterModulus: - def test_subset(self): - cube = unrolled_cube() + def test_subset(self, dataless): + cube = unrolled_cube(dataless) result = cube.intersection(longitude=(5, 8)) _shared_utils.assert_array_equal(result.coord("longitude").points, [5, 8, 5]) - _shared_utils.assert_array_equal(result.data, [0, 2, 3]) + if dataless: + assert result.data is None + else: + _shared_utils.assert_array_equal(result.data, [0, 2, 3]) - def test_subset_wrapped(self): - cube = unrolled_cube() + def test_subset_wrapped(self, dataless): + cube = unrolled_cube(dataless) result = cube.intersection(longitude=(5 + 360, 8 + 360)) _shared_utils.assert_array_equal( result.coord("longitude").points, [365, 368, 365] ) - _shared_utils.assert_array_equal(result.data, [0, 2, 3]) + if dataless: + assert result.data is None + else: + _shared_utils.assert_array_equal(result.data, [0, 2, 3]) - def test_superset(self): - cube = unrolled_cube() + def test_superset(self, dataless): + cube = unrolled_cube(dataless) result = cube.intersection(longitude=(0, 15)) _shared_utils.assert_array_equal( result.coord("longitude").points, [5, 10, 8, 5, 3] ) - _shared_utils.assert_array_equal(result.data, np.arange(5)) + if dataless: + assert result.data is None + else: + _shared_utils.assert_array_equal(result.data, np.arange(5)) # Test the API of the cube interpolation method.