def test_unmodified_cell_methods(self): """Test that plugin leaves cell methods that are diagnostic name agnostic unchanged.""" cell_methods = list(self.cube.cell_methods) additional_cell_method_1 = CellMethod("sum", coords="longitude") additional_cell_method_2 = CellMethod("sum", coords="latitude", comments="Kittens are great") cell_methods.extend( [additional_cell_method_1, additional_cell_method_2]) self.cube.cell_methods = cell_methods cubelist = iris.cube.CubeList([self.cube, self.multiplier]) new_cube_name = "new_cube_name" expected = [ CellMethod("sum", coords="time", comments=f"of {new_cube_name}"), additional_cell_method_1, additional_cell_method_2, ] result = CubeMultiplier()(cubelist, new_cube_name, broadcast_to_threshold=True) self.assertArrayEqual(result.cell_methods, expected)
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, ) result = CubeMultiplier()( [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_exception_for_single_entry_cubelist(self): """Test that the plugin raises an exception if a cubelist containing only one cube is passed in.""" plugin = CubeMultiplier() 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 process( *cubes: cli.inputcube, operation="+", new_name=None, use_midpoint=False, check_metadata=False, broadcast_to_threshold=False, ): r"""Combine input cubes. Combine the input cubes into a single cube using the requested operation. The first cube in the input list provides the template for output metadata. Args: cubes (iris.cube.CubeList or list of iris.cube.Cube): An iris CubeList to be combined. operation (str): An operation to use in combining input cubes. One of: +, -, \*, add, subtract, multiply, min, max, mean new_name (str): New name for the resulting dataset. use_midpoint (bool): If False (not set), uses the upper bound as the new coordinate point for expanded coordinates (eg time for accumulations / max in period). If True, uses the mid-point. check_metadata (bool): If True, warn on metadata mismatch between inputs. broadcast_to_threshold (bool): If True, broadcast input cubes to the threshold coord prior to combining - a threshold coord must already exist on the first input cube. Returns: result (iris.cube.Cube): Returns a cube with the combined data. """ from improver.cube_combiner import CubeCombiner, CubeMultiplier from iris.cube import CubeList if not cubes: raise TypeError("A cube is needed to be combined.") if new_name is None: new_name = cubes[0].name() if operation == "*" or operation == "multiply": result = CubeMultiplier()( CubeList(cubes), new_name, broadcast_to_threshold=broadcast_to_threshold, ) else: result = CubeCombiner(operation)( CubeList(cubes), new_name, use_midpoint=use_midpoint, ) return result
def test_error_broadcast_coord_is_auxcoord(self): """Test that plugin throws an error if asked to broadcast to a threshold coord that already exists on later cubes""" 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): CubeMultiplier()(cubelist, "new_cube_name", broadcast_to_threshold=True)
def test_error_broadcast_coord_is_auxcoord(self): """Test that plugin throws an error if asked to broadcast to a threshold coord that already exists on later cubes""" self.multiplier.add_aux_coord(self.threshold_aux) cubelist = iris.cube.CubeList([self.cube.copy(), self.multiplier]) msg = "Cannot broadcast to coord threshold as it already exists as an AuxCoord" with self.assertRaisesRegex(TypeError, msg): CubeMultiplier(broadcast_to_threshold=True)(cubelist, "new_cube_name")
def test_error_broadcast_coord_not_found(self): """Test that plugin throws an error if asked to broadcast to a threshold coord that is not present on the first cube""" cubelist = iris.cube.CubeList([self.multiplier, self.cube.copy()]) msg = ( r"Cannot find coord threshold in " r"<iris 'Cube' of " r"probability_of_lwe_thickness_of_precipitation_amount_above_threshold / \(1\) " r"\(realization: 3; latitude: 2; longitude: 2\)> to broadcast to") with self.assertRaisesRegex(CoordinateNotFoundError, msg): CubeMultiplier(broadcast_to_threshold=True)(cubelist, "new_cube_name")
def test_vicinity_names(self): """Test plugin names the cube and threshold coordinate correctly for a vicinity diagnostic""" input = "lwe_thickness_of_precipitation_amount_in_vicinity" output = "thickness_of_rainfall_amount_in_vicinity" self.cube.rename(f"probability_of_{input}_above_threshold") cubelist = iris.cube.CubeList([self.cube.copy(), self.multiplier]) result = CubeMultiplier(broadcast_to_threshold=True)(cubelist, output) self.assertEqual(result.name(), f"probability_of_{output}_above_threshold") self.assertEqual( result.coord(var_name="threshold").name(), "thickness_of_rainfall_amount")
def test_update_cell_methods(self): """Test that plugin updates cell methods where required when a new diagnostic name is provided.""" cubelist = iris.cube.CubeList([self.cube, self.multiplier]) new_cube_name = "new_cube_name" expected = CellMethod("sum", coords="time", comments=f"of {new_cube_name}") result = CubeMultiplier(broadcast_to_threshold=True)(cubelist, new_cube_name) self.assertEqual(result.cell_methods[0], expected)
def test_error_broadcast_coord_not_found(self): """Test that plugin throws an error if asked to broadcast to a threshold coord that is not present on the first cube""" 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): CubeMultiplier()(cubelist, "new_cube_name", broadcast_to_threshold=True)
def test_vicinity_names(self): """Test plugin names the cube and threshold coordinate correctly for a vicinity diagnostic""" input = "lwe_thickness_of_precipitation_amount_in_vicinity" output = "thickness_of_rainfall_amount_in_vicinity" self.cube4.rename(f"probability_of_{input}_above_threshold") 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 = CubeMultiplier()(cubelist, output, broadcast_to_threshold=True) self.assertEqual(result.name(), f"probability_of_{output}_above_threshold") self.assertEqual( result.coord(var_name="threshold").name(), "thickness_of_rainfall_amount" )
def test_broadcast_coord(self): """Test that plugin broadcasts to threshold coord without changing inputs. Using the broadcast_to_coords argument including a value of "threshold" will result in the returned cube maintaining the probabilistic elements of the name of the first input cube.""" cubelist = iris.cube.CubeList([self.cube.copy(), self.multiplier]) input_copy = deepcopy(cubelist) result = CubeMultiplier(broadcast_to_threshold=True)(cubelist, "new_cube_name") self.assertIsInstance(result, Cube) self.assertEqual(result.name(), "probability_of_new_cube_name_above_threshold") self.assertEqual( result.coord(var_name="threshold").name(), "new_cube_name") self.assertArrayAlmostEqual(result.data, self.cube.data) self.assertCubeListEqual(input_copy, cubelist)
def test_broadcast_coord(self): """Test that plugin broadcasts to threshold coord without changing inputs. Using the broadcast_to_coords argument including a value of "threshold" will result in the returned cube maintaining the probabilistic elements of the name of the first input cube.""" 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 = CubeMultiplier()( cubelist, "new_cube_name", broadcast_to_threshold=True ) self.assertIsInstance(result, Cube) self.assertEqual(result.name(), "probability_of_new_cube_name_above_threshold") self.assertEqual(result.coord(var_name="threshold").name(), "new_cube_name") self.assertArrayAlmostEqual(result.data, self.cube4.data) self.assertCubeListEqual(input_copy, cubelist)