def test_exception_mismatched_dimensions(self): """Test an error is raised if dimension coordinates do not match""" self.cube2.coord("latitude").rename("projection_y_coordinate") plugin = CubeCombiner("+") msg = "Cannot combine cubes with different dimensions" with self.assertRaisesRegex(ValueError, msg): plugin.process([self.cube1, self.cube2], "new_cube_name")
def test_min(self): """Test combine finds the min of the cubes correctly.""" operation = 'min' plugin = CubeCombiner(operation) result = plugin.combine(self.cube1, self.cube2) expected_data = np.full((1, 2, 2), 0.5, dtype=np.float32) self.assertArrayAlmostEqual(result.data, expected_data)
def test_fails_with_multi_point_coord(self): """Test that if an error is raised if a coordinate with more than one point is given""" emsg = 'the expand bounds function should only be used on a' with self.assertRaisesRegex(ValueError, emsg): CubeCombiner.expand_bounds(self.cubelist[0], self.cubelist, 'latitude', 'mid')
def test_multiply_preserves_bounds(self): """Test specific case for precipitation type, where multiplying a precipitation accumulation by a point-time probability of snow retains the bounds on the original accumulation.""" validity_time = datetime(2015, 11, 19, 0) time_bounds = [datetime(2015, 11, 18, 23), datetime(2015, 11, 19, 0)] forecast_reference_time = datetime(2015, 11, 18, 22) precip_accum = set_up_variable_cube( np.full((2, 3, 3), 1.5, dtype=np.float32), name="lwe_thickness_of_precipitation_amount", units="mm", time=validity_time, time_bounds=time_bounds, frt=forecast_reference_time, ) snow_prob = set_up_variable_cube( np.full(precip_accum.shape, 0.2, dtype=np.float32), name="probability_of_snow", units="1", time=validity_time, frt=forecast_reference_time, ) plugin = CubeCombiner("multiply") result = plugin.process([precip_accum, snow_prob], "lwe_thickness_of_snowfall_amount") self.assertArrayAlmostEqual(result.data, np.full((2, 3, 3), 0.3)) self.assertArrayEqual(result.coord("time"), precip_accum.coord("time"))
def test_mean(self): """Test that the function adds the cubes correctly for mean.""" operation = 'mean' plugin = CubeCombiner(operation) result = plugin.combine(self.cube1, self.cube2) expected_data = np.full((1, 2, 2), 1.1, dtype=np.float32) self.assertArrayAlmostEqual(result.data, expected_data)
def test_exception_mismatched_dimensions(self): """Test an error is raised if dimension coordinates do not match""" self.cube2.coord("lwe_thickness_of_precipitation_amount").rename("snow_depth") plugin = CubeCombiner("+") msg = "Cannot combine cubes with different dimensions" with self.assertRaisesRegex(ValueError, msg): plugin.process([self.cube1, self.cube2], "new_cube_name")
def test_mean(self): """Test that the plugin calculates the mean correctly. """ plugin = CubeCombiner("mean") cubelist = iris.cube.CubeList([self.cube1, self.cube2]) result = plugin.process(cubelist, "new_cube_name") expected_data = np.full((2, 2), 0.55, dtype=np.float32) self.assertEqual(result.name(), "new_cube_name") self.assertArrayAlmostEqual(result.data, expected_data)
def test_mean_multi_cube(self): """Test that the plugin calculates the mean for three cubes. """ plugin = CubeCombiner('mean') cubelist = iris.cube.CubeList([self.cube1, self.cube2, self.cube3]) result = plugin.process(cubelist, 'new_cube_name') expected_data = np.full((1, 2, 2), 0.4, dtype=np.float32) self.assertEqual(result.name(), 'new_cube_name') self.assertArrayAlmostEqual(result.data, expected_data)
def test_exception_for_cube_passed_in(self): """Test that the plugin raises an exception if something other than a cubelist is passed in.""" plugin = CubeCombiner('-') msg = "Expecting data to be an instance of" with self.assertRaisesRegex(TypeError, msg): plugin.process(self.cube1, 'new_cube_name')
def test_exception_for_single_entry_cubelist(self): """Test that the plugin raises an exception if a cubelist containing only one cube is passed in.""" plugin = CubeCombiner("-") msg = "Expecting 2 or more cubes in cube_list" cubelist = iris.cube.CubeList([self.cube1]) with self.assertRaisesRegex(ValueError, msg): plugin.process(cubelist, "new_cube_name")
def test_basic(self): """Test that the function returns a Cube. """ operation = '*' plugin = CubeCombiner(operation) result = plugin.combine(self.cube1, self.cube2) self.assertIsInstance(result, Cube) expected_data = np.full((1, 2, 2), 0.3, dtype=np.float32) self.assertArrayAlmostEqual(result.data, expected_data)
def test_error_broadcast_coord_is_auxcoord(self): """Test that plugin throws an error if the broadcast coord already exists""" plugin = CubeCombiner("*") cube = self.cube4[:, 0, ...].copy() cube.data = np.ones_like(cube.data) cubelist = iris.cube.CubeList([self.cube4.copy(), cube]) msg = "Cannot broadcast to coord threshold as it already exists as an AuxCoord" with self.assertRaisesRegex(TypeError, msg): plugin.process(cubelist, "new_cube_name", broadcast_to_coords=["threshold"])
def test_mixed_dtypes_overflow(self): """Test the plugin with a dtype combination that results in float64 data.""" plugin = CubeCombiner("add") cubelist = iris.cube.CubeList( [self.cube1, self.cube2.copy(np.ones_like(self.cube2.data, dtype=np.int32))] ) msg = "Operation .* results in float64 data" with self.assertRaisesRegex(TypeError, msg): plugin.process(cubelist, "new_cube_name")
def test_basic(self): """Test that the plugin returns a Cube. """ plugin = CubeCombiner('+') cubelist = iris.cube.CubeList([self.cube1, self.cube2]) result = plugin.process(cubelist, 'new_cube_name') self.assertIsInstance(result, Cube) self.assertEqual(result.name(), 'new_cube_name') expected_data = np.full((1, 2, 2), 1.1, dtype=np.float32) self.assertArrayAlmostEqual(result.data, expected_data)
def test_min(self): """Test combine finds the min of the cubes correctly.""" operation = 'min' plugin = CubeCombiner(operation) result = plugin.combine(self.cube1, self.cube3, operation) expected_data = np.zeros((1, 2, 2, 2)) expected_data[0, 0, :, :] = 0.1 expected_data[0, 1, :, :] = 0.6 self.assertArrayAlmostEqual(result.data, expected_data)
def test_minus(self): """Test combine minus the cubes correctly. """ operation = '-' plugin = CubeCombiner(operation) result = plugin.combine(self.cube1, self.cube2, operation) expected_data = np.zeros((1, 2, 2, 2)) expected_data[0, 0, :, :] = 0.4 expected_data[0, 1, :, :] = 0.2 self.assertArrayAlmostEqual(result.data, expected_data)
def test_bounds_expansion_midpoint(self): """Test option to use the midpoint between the bounds as the time coordinate point, rather than the (default) maximum.""" plugin = CubeCombiner("add") cubelist = iris.cube.CubeList([self.cube1, self.cube2]) result = plugin.process(cubelist, "new_cube_name", use_midpoint=True) self.assertEqual(result.name(), "new_cube_name") self.assertEqual(result.coord("time").points[0], 1447891200) self.assertArrayEqual(result.coord("time").bounds, [[1447887600, 1447894800]])
def test_mean(self): """Test that the function adds the cubes correctly for mean.""" operation = '+' plugin = CubeCombiner(operation) result = plugin.combine(self.cube1, self.cube3, operation) expected_data = np.zeros((1, 2, 2, 2)) expected_data[0, 0, :, :] = 0.6 expected_data[0, 1, :, :] = 1.4 self.assertArrayAlmostEqual(result.data, expected_data)
def test_mean(self): """Test that the plugin calculates the mean correctly. """ plugin = CubeCombiner('mean') cubelist = iris.cube.CubeList([self.cube1, self.cube2]) result = plugin.process(cubelist, 'new_cube_name') expected_data = np.zeros((1, 2, 2, 2)) expected_data[:, 0, :, :] = 0.3 expected_data[:, 1, :, :] = 0.5 self.assertEqual(result.name(), 'new_cube_name') self.assertArrayAlmostEqual(result.data, expected_data)
def test_basic(self): """Test that the plugin returns a Cube. """ plugin = CubeCombiner('+') cubelist = iris.cube.CubeList([self.cube1, self.cube1]) result = plugin.process(cubelist, 'new_cube_name') self.assertIsInstance(result, Cube) self.assertEqual(result.name(), 'new_cube_name') expected_data = np.zeros((1, 2, 2, 2)) expected_data[:, 0, :, :] = 1.0 expected_data[:, 1, :, :] = 1.2 self.assertArrayAlmostEqual(result.data, expected_data)
def test_with_mask(self): """Test that the plugin preserves the mask if any of the inputs are masked""" expected_data = np.full((1, 2, 2), 1.2, dtype=np.float32) mask = [[[False, True], [False, False]]] self.cube1.data = np.ma.MaskedArray(self.cube1.data, mask=mask) plugin = CubeCombiner("add") result = plugin.process([self.cube1, self.cube2, self.cube3], "new_cube_name") self.assertIsInstance(result.data, np.ma.MaskedArray) self.assertArrayAlmostEqual(result.data.data, expected_data) self.assertArrayEqual(result.data.mask, mask)
def test_basic(self): """Test that the plugin returns a Cube and doesn't modify the inputs.""" plugin = CubeCombiner("+") cubelist = iris.cube.CubeList([self.cube1, self.cube2]) input_copy = deepcopy(cubelist) result = plugin.process(cubelist, "new_cube_name") self.assertIsInstance(result, Cube) self.assertEqual(result.name(), "new_cube_name") expected_data = np.full((2, 2), 1.1, dtype=np.float32) self.assertArrayAlmostEqual(result.data, expected_data) self.assertCubeListEqual(input_copy, cubelist)
def test_bounds_expansion(self): """Test that the plugin calculates the sum of the input cubes correctly and expands the time coordinate bounds on the resulting output.""" plugin = CubeCombiner("add") cubelist = iris.cube.CubeList([self.cube1, self.cube2]) result = plugin.process(cubelist, "new_cube_name") expected_data = np.full((1, 2, 2), 1.1, dtype=np.float32) self.assertEqual(result.name(), "new_cube_name") self.assertArrayAlmostEqual(result.data, expected_data) self.assertEqual(result.coord("time").points[0], 1447894800) self.assertArrayEqual(result.coord("time").bounds, [[1447887600, 1447894800]])
def test_basic(self): """Test that the function returns a Cube. """ operation = '*' plugin = CubeCombiner(operation) cube1 = self.cube1 cube2 = cube1.copy() result = plugin.combine(cube1, cube2, operation) self.assertIsInstance(result, Cube) expected_data = np.zeros((1, 2, 2, 2)) expected_data[0, 0, :, :] = 0.25 expected_data[0, 1, :, :] = 0.36 self.assertArrayAlmostEqual(result.data, expected_data)
def test_unmatched_coords_ignored(self): """Test coordinates that are not present on all cubes are ignored, regardless of input order""" expected_coord_set = {"time", "forecast_period"} height = iris.coords.AuxCoord([1.5], "height", units="m") self.cube1.add_aux_coord(height) result = CubeCombiner("+")._get_expanded_coord_names( [self.cube1, self.cube2, self.cube3]) self.assertSetEqual(set(result), expected_coord_set) result = CubeCombiner("+")._get_expanded_coord_names( [self.cube3, self.cube2, self.cube1]) self.assertSetEqual(set(result), expected_coord_set)
def test_revised_attributes(self): """Test that the plugin passes through the relevant dictionary to modify an attribute and that these modifications are present in the returned cube.""" plugin = CubeCombiner('mean') revised_attributes = {'attribute_to_update': 'second_value'} cubelist = iris.cube.CubeList([self.cube1, self.cube2]) result = plugin.process(cubelist, 'new_cube_name', revised_attributes=revised_attributes) self.assertEqual(result.attributes['attribute_to_update'], 'second_value')
def test_mixed_dtypes(self): """Test that the plugin calculates the sum correctly and doesn't mangle dtypes.""" plugin = CubeCombiner("add") cubelist = iris.cube.CubeList( [self.cube1, self.cube2.copy(np.ones_like(self.cube2.data, dtype=np.int8))] ) result = plugin.process(cubelist, "new_cube_name") expected_data = np.full((2, 2), 1.5, dtype=np.float32) self.assertEqual(result.name(), "new_cube_name") self.assertArrayAlmostEqual(result.data, expected_data) self.assertTrue(cubelist[0].dtype == np.float32) self.assertTrue(cubelist[1].dtype == np.int8) self.assertTrue(result.dtype == np.float32)
def test_error_broadcast_coord_not_found(self): """Test that plugin throws an error if the broadcast coord is not present anywhere""" plugin = CubeCombiner("*") cube = self.cube4[:, 0, ...].copy() cube.data = np.ones_like(cube.data) cubelist = iris.cube.CubeList([self.cube4.copy(), cube]) msg = ( "Cannot find coord kittens in " "<iris 'Cube' of probability_of_lwe_thickness_of_precipitation_amount_above_threshold / \(1\) " "\(realization: 3; lwe_thickness_of_precipitation_amount: 2; latitude: 2; longitude: 2\)> " "to broadcast to." ) with self.assertRaisesRegex(CoordinateNotFoundError, msg): plugin.process(cubelist, "new_cube_name", broadcast_to_coords=["kittens"])
def test_error_broadcast_coord_wrong_order(self): """Test that plugin throws an error if the broadcast coord is not on the first cube""" plugin = CubeCombiner("*") cube = self.cube4[:, 0, ...].copy() cube.data = np.ones_like(cube.data) cube.remove_coord("lwe_thickness_of_precipitation_amount") cubelist = iris.cube.CubeList([cube, self.cube4.copy()]) msg = ( "Cannot find coord threshold in " "<iris 'Cube' of probability_of_lwe_thickness_of_precipitation_amount_above_threshold / \(1\) " "\(realization: 3; latitude: 2; longitude: 2\)> to broadcast to" ) with self.assertRaisesRegex(CoordinateNotFoundError, msg): plugin.process(cubelist, "new_cube_name", broadcast_to_coords=["threshold"])
def test_broadcast_coord(self): """Test that plugin broadcasts to a coord and doesn't change the inputs.""" plugin = CubeCombiner("*") cube = self.cube4[:, 0, ...].copy() cube.data = np.ones_like(cube.data) cube.remove_coord("lwe_thickness_of_precipitation_amount") cubelist = iris.cube.CubeList([self.cube4.copy(), cube]) input_copy = deepcopy(cubelist) result = plugin.process(cubelist, "new_cube_name", broadcast_to_coords=["threshold"]) self.assertIsInstance(result, Cube) self.assertEqual(result.name(), "new_cube_name") self.assertArrayAlmostEqual(result.data, self.cube4.data) self.assertCubeListEqual(input_copy, cubelist)