def test_fails_coord_not_in_cube(self): """Test it raises CoordinateNotFoundError if coord not in the cube.""" coord = "notset" plugin = WeightedBlendAcrossWholeDimension(coord, 'weighted_mean') msg = ('Expected to find exactly 1 coordinate, but found none.') with self.assertRaisesRegexp(CoordinateNotFoundError, msg): plugin.process(self.cube)
def test_fails_coord_not_in_cube(self): """Test it raises CoordinateNotFoundError if coord not in the cube.""" coord = "notset" plugin = WeightedBlendAcrossWholeDimension(coord, 'weighted_mean') msg = ('Coordinate to be collapsed not found in cube.') with self.assertRaisesRegex(CoordinateNotFoundError, msg): plugin.process(self.cube)
def test_fails_input_not_a_cube(self): """Test it raises a Type Error if not supplied with a cube.""" coord = "time" plugin = WeightedBlendAcrossWholeDimension(coord, 'weighted_mean') notacube = 0.0 msg = ('The first argument must be an instance of ' + 'iris.cube.Cube') with self.assertRaisesRegexp(TypeError, msg): plugin.process(notacube)
def test_fails_coord_not_in_weights_cube(self): """Test it raises CoordinateNotFoundError if the blending coord is not found in the weights cube.""" coord = "forecast_reference_time" self.weights1d.remove_coord("forecast_reference_time") plugin = WeightedBlendAcrossWholeDimension(coord) msg = ('Coordinate to be collapsed not found in weights cube.') with self.assertRaisesRegex(CoordinateNotFoundError, msg): plugin.process(self.cube, self.weights1d)
def test_forecast_reference_time_exception(self): """Test that a ValueError is raised if the coordinate to be blended is forecast_reference_time and the points on the time coordinate are not equal.""" coord = "forecast_reference_time" plugin = WeightedBlendAcrossWholeDimension(coord, 'weighted_mean') msg = ('For blending using the forecast_reference_time') with self.assertRaisesRegexp(ValueError, msg): plugin.process(self.cube)
def test_fails_more_than_one_perc_coord(self): """Test it raises a Value Error if more than one percentile coord.""" coord = "time" plugin = WeightedBlendAcrossWholeDimension(coord, 'weighted_mean') new_cube = percentile_cube() new_cube.add_aux_coord( AuxCoord([10.0], long_name="percentile_over_dummy")) msg = ('There should only be one percentile coord ' 'on the cube.') with self.assertRaisesRegexp(ValueError, msg): plugin.process(new_cube)
def test_fails_weights_shape(self): """Test it raises a Value Error if weights shape does not match coord shape.""" coord = "time" plugin = WeightedBlendAcrossWholeDimension(coord, 'weighted_mean') weights = [0.1, 0.2, 0.7] msg = ('The weights array must match the shape ' + 'of the coordinate in the input cube') with self.assertRaisesRegexp(ValueError, msg): plugin.process(self.cube, weights)
def test_fails_perc_coord_not_dim(self): """Test it raises a Value Error if not percentile coord not a dim.""" coord = "time" plugin = WeightedBlendAcrossWholeDimension(coord, 'weighted_mean') new_cube = self.cube.copy() new_cube.add_aux_coord( AuxCoord([10.0], long_name="percentile_over_time")) msg = ('The percentile coord must be a dimension ' 'of the cube.') with self.assertRaisesRegexp(ValueError, msg): plugin.process(new_cube)
def test_scalar_coord(self): """Test plugin throws an error if trying to blending across a scalar coordinate.""" coord = "dummy_scalar_coord" new_scalar_coord = AuxCoord(1, long_name=coord, units='no_unit') self.cube.add_aux_coord(new_scalar_coord) plugin = WeightedBlendAcrossWholeDimension(coord) weights = ([1.0]) msg = 'has no associated dimension' with self.assertRaisesRegex(ValueError, msg): plugin.process(self.cube, weights)
def test_fails_only_one_percentile_value(self): """Test it raises a Value Error if there is only one percentile.""" coord = "time" plugin = WeightedBlendAcrossWholeDimension(coord, 'weighted_mean') new_cube = Cube([[0.0]]) new_cube.add_dim_coord( DimCoord([10.0], long_name="percentile_over_time"), 0) new_cube.add_dim_coord(DimCoord([10.0], long_name="time"), 1) msg = ('Percentile coordinate does not have enough points' ' in order to blend. Must have at least 2 percentiles.') with self.assertRaisesRegexp(ValueError, msg): plugin.process(new_cube)
def test_specific_cycletime(self): """Test that the plugin setup with a specific cycletime returns a cube in which the forecast reference time has been changed to match the given cycletime. The forecast period should also have been adjusted to be given relative to this time. For this we need a single time in our cube and so to blend over something else. In this case we create a "model_id" coordinate as if we are model blending.""" coord_name = "model_id" cube1 = self.cube[0].copy() model_crd1 = iris.coords.DimCoord([0], long_name=coord_name, units=1) cube1.add_aux_coord(model_crd1) cube2 = self.cube[0].copy() model_crd2 = iris.coords.DimCoord([1], long_name=coord_name, units=1) cube2.add_aux_coord(model_crd2) cubes = iris.cube.CubeList([cube1, cube2]) cube = merge_cubes(cubes) plugin = WeightedBlendAcrossWholeDimension(coord_name) expected_frt = 1447837200 expected_forecast_period = 61200 result = plugin.process(cube, cycletime='20151118T0900Z') self.assertEqual( result.coord('forecast_reference_time').points, expected_frt) self.assertEqual( result.coord('forecast_period').points, expected_forecast_period) self.assertEqual( result.coord('time').points, cube.coord('time').points)
def test_coord_adjust_set(self): """Test it works with coord adjust set.""" coord = "time" coord_adjust = example_coord_adjust plugin = WeightedBlendAcrossWholeDimension(coord, 'weighted_mean', coord_adjust) result = plugin.process(self.cube) self.assertAlmostEquals(result.coord(coord).points, [402193.5])
def test_weights_equal_array(self): """Test it works with weights set to array (0.8, 0.2).""" coord = "time" plugin = WeightedBlendAcrossWholeDimension(coord, 'weighted_mean') weights = np.array([0.8, 0.2]) result = plugin.process(self.cube, weights) expected_result_array = np.ones((2, 2)) * 1.2 self.assertArrayAlmostEqual(result.data, expected_result_array)
def test_weighted_max_weights_none(self): """Test it works for weighted max with weights set to None.""" coord = "time" plugin = WeightedBlendAcrossWholeDimension(coord, 'weighted_maximum') weights = None result = plugin.process(self.cube, weights) expected_result_array = np.ones((2, 2)) self.assertArrayAlmostEqual(result.data, expected_result_array)
def tests_threshold_splicing_works_weighted_max(self): """Test weighted_max works with a threshold dimension.""" coord = "time" plugin = WeightedBlendAcrossWholeDimension(coord, 'weighted_maximum') weights = np.array([0.8, 0.2]) result = plugin.process(self.cube_threshold, weights) expected_result_array = np.ones((2, 2, 2)) * 0.4 self.assertArrayAlmostEqual(result.data, expected_result_array)
def test_percentiles_weights_none(self): """Test it works for percentiles with weights set to None.""" coord = "time" plugin = WeightedBlendAcrossWholeDimension(coord, 'weighted_mean') weights = None perc_cube = percentile_cube() result = plugin.process(perc_cube, weights) expected_result_array = np.reshape(BLENDED_PERCENTILE_DATA1, (6, 2, 2)) self.assertArrayAlmostEqual(result.data, expected_result_array)
def test_percentiles_different_coordinate_orders(self): """Test the result of the percentile aggregation is the same regardless of the coordinate order in the input cube. Most importantly, the result should be the same regardless of on which side of the collapsing coordinate the percentile coordinate falls.""" coord = "time" plugin = WeightedBlendAcrossWholeDimension(coord, 'weighted_mean') weights = None percentile_leading = percentile_cube() time_leading = percentile_cube() time_leading.transpose([1, 0, 2, 3]) result_percentile_leading = plugin.process(percentile_leading, weights) result_time_leading = plugin.process(time_leading, weights) expected_result_array = np.reshape(BLENDED_PERCENTILE_DATA1, (6, 2, 2)) self.assertArrayAlmostEqual(result_percentile_leading.data, expected_result_array) self.assertArrayAlmostEqual(result_time_leading.data, expected_result_array)
def test_weighted_max_non_equal_weights_array(self): """Test it works for weighted_max with weights [0.2, 0.8] given as a array.""" coord = "time" plugin = WeightedBlendAcrossWholeDimension(coord, 'weighted_maximum') weights = np.array([0.2, 0.8]) result = plugin.process(self.cube, weights) expected_result_array = np.ones((2, 2)) * 1.6 self.assertArrayAlmostEqual(result.data, expected_result_array)
def test_scalar_coord(self): """Test plugin throws an error if trying to blending across a scalar coordinate.""" coord = "dummy_scalar_coord" plugin = WeightedBlendAcrossWholeDimension(coord, 'weighted_mean') weights = ([1.0]) msg = 'has no associated dimension' with self.assertRaisesRegex(ValueError, msg): _ = plugin.process(self.cube_with_scalar, weights)
def tests_threshold_splicing_works_with_threshold(self): """Test splicing works when the blending is over threshold.""" coord = "threshold" plugin = WeightedBlendAcrossWholeDimension(coord, 'weighted_mean') weights = np.array([0.8, 0.2]) self.cube_threshold.data[0, :, :, :] = 0.5 self.cube_threshold.data[1, :, :, :] = 0.8 result = plugin.process(self.cube_threshold, weights) expected_result_array = np.ones((2, 2, 2)) * 0.56 self.assertArrayAlmostEqual(result.data, expected_result_array)
def test_alternative_title(self): """Test that the plugin returns an iris.cube.Cube with metadata that matches the input cube where appropriate. In this case the title is removed from the input cube, resulting in a default title being applied to the result cube.""" coord = "forecast_reference_time" plugin = WeightedBlendAcrossWholeDimension(coord, 'weighted_mean') self.cube.attributes.pop('title') expected = "IMPROVER Model Forecast" result = plugin.process(self.cube) self.assertEqual(result.attributes['title'], expected)
def test_source_realizations_attribute_added(self): """Test that when a realization coordinate is collapsed, a new source_realizations attribute is added to record the contributing realizations.""" coord = "realization" self.cube.coord('time').rename(coord) self.cube.coord(coord).points = [1, 4] plugin = WeightedBlendAcrossWholeDimension(coord, 'weighted_mean') weights = None result = plugin.process(self.cube, weights) expected = [1, 4] self.assertArrayEqual(result.attributes['source_realizations'], expected)
def test_cycletime_not_updated(self): """Test changes to forecast period and forecast reference time are not made when not blending over cycle or model.""" cube = set_up_variable_cube(278 * np.ones((3, 5, 5), dtype=np.float32), time=datetime(2019, 10, 11, 1), frt=datetime(2019, 10, 10, 21)) expected_frt = cube.coord("forecast_reference_time").points[0] expected_fp = cube.coord("forecast_period").points[0] plugin = WeightedBlendAcrossWholeDimension("realization") result = plugin.process(cube, cycletime='20191011T0000Z') self.assertEqual( result.coord("forecast_reference_time").points[0], expected_frt) self.assertEqual( result.coord("forecast_period").points[0], expected_fp)
def test_scalar_coord(self, warning_list=None): """Test it works on scalar coordinate and check that a warning has been raised if the dimension that you want to blend on is a scalar coordinate. """ coord = "dummy_scalar_coord" plugin = WeightedBlendAcrossWholeDimension(coord, 'weighted_mean') weights = np.array([1.0]) result = plugin.process(self.cube_with_scalar, weights) self.assertTrue( any(item.category == UserWarning for item in warning_list)) warning_msg = "Trying to blend across a scalar coordinate" self.assertTrue(any(warning_msg in str(item) for item in warning_list)) self.assertArrayAlmostEqual(result.data, self.cube.data)
def test_attributes_dict(self): """Test updates to attributes on output cube""" attributes_dict = {"source": "IMPROVER", "history": "cycle blended"} for key in self.cube.attributes: if "mosg__" in key: attributes_dict[key] = "remove" expected_attributes = { "source": "IMPROVER", "history": "cycle blended", "title": self.cube.attributes["title"], "institution": MANDATORY_ATTRIBUTE_DEFAULTS["institution"] } coord = "forecast_reference_time" plugin = WeightedBlendAcrossWholeDimension(coord) result = plugin.process(self.cube, attributes_dict=attributes_dict) self.assertDictEqual(result.attributes, expected_attributes)
def process(self, cube): """ Apply the weighted blend for each point in the given coordinate. Args: cube : iris.cube.Cube Cube to blend. Returns: cube: iris.cube.Cube The processed cube, with the same coordinates as the input cube. The points in one coordinate will be blended with the adjacent points based on a triangular weighting function of the specified width. """ # We need to correct all the coordinates associated with the dimension # we are collapsing over, so find the relevant coordinates now. dimension_to_collapse = cube.coord_dims(self.coord) coords_to_correct = cube.coords(dimensions=dimension_to_collapse) coords_to_correct = [coord.name() for coord in coords_to_correct] # We will also need to correct the bounds on these coordinates, # as bounds will be added when the blending happens, so add bounds if # it doesn't have some already. for coord in coords_to_correct: if not cube.coord(coord).has_bounds(): cube.coord(coord).guess_bounds() # Set up a plugin to calculate the triangular weights. WeightsPlugin = ChooseDefaultWeightsTriangular( self.width, units=self.parameter_units) # Set up the blending function, based on whether weighted blending or # maximum probabilities are needed. BlendingPlugin = WeightedBlendAcrossWholeDimension(self.coord, self.mode) result = iris.cube.CubeList([]) # Loop over each point in the coordinate we are blending over, and # calculate a new weighted average for it. for cube_slice in cube.slices_over(self.coord): point = cube_slice.coord(self.coord).points[0] weights = WeightsPlugin.process(cube, self.coord, point) blended_cube = BlendingPlugin.process(cube, weights) self.correct_collapsed_coordinates(cube_slice, blended_cube, coords_to_correct) result.append(blended_cube) result = concatenate_cubes(result) return result
def test_basic(self): """Test that the plugin returns an iris.cube.Cube with metadata that matches the input cube where appropriate.""" coord = "forecast_reference_time" plugin = WeightedBlendAcrossWholeDimension(coord) result = plugin.process(self.cube) expected_frt = int( self.cube.coord('forecast_reference_time').points[-1]) expected_forecast_period = int( self.cube.coord('forecast_period').points[-1]) self.assertIsInstance(result, Cube) self.assertEqual(result.attributes, self.expected_attributes) self.assertEqual( result.coord('forecast_reference_time').points, expected_frt) self.assertEqual( result.coord('forecast_period').points, expected_forecast_period)
def test_remove_unnecessary_scalar_coordinates(self): """Test model_id and model_configuration coordinates are both removed after model blending""" cube_model = set_up_variable_cube(282 * np.zeros( (2, 2), dtype=np.float32)) cube_model = add_coordinate(cube_model, [0, 1], "model_id") cube_model.add_aux_coord(AuxCoord(["uk_ens", "uk_det"], long_name="model_configuration"), data_dims=0) weights_model = Cube(np.array([0.5, 0.5]), long_name='weights', dim_coords_and_dims=[ (cube_model.coord("model_id"), 0) ]) plugin = WeightedBlendAcrossWholeDimension("model_id") result = plugin.process(cube_model, weights_model) for coord_name in ["model_id", "model_configuration"]: self.assertNotIn(coord_name, [coord.name() for coord in result.coords()])
def tests_threshold_cube_with_weights_weighted_maximum(self): """Test weighted_maximum works collapsing a cube with a threshold dimension when the blending is over a different coordinate.""" coord = "forecast_reference_time" plugin = WeightedBlendAcrossWholeDimension(coord, 'weighted_maximum') result = plugin.process(self.cube_threshold, self.weights1d) expected_result_array = np.ones((2, 2, 2)) * 0.12 expected_result_array[1, :, :] = 0.24 expected_frt = int( self.cube.coord('forecast_reference_time').points[-1]) expected_forecast_period = int( self.cube.coord('forecast_period').points[-1]) self.assertArrayAlmostEqual(result.data, expected_result_array) self.assertEqual(result.attributes, self.attributes) self.assertEqual(result.coord('time').points, expected_frt) self.assertEqual( result.coord('forecast_period').points, expected_forecast_period)
def test_threshold_cube_with_weights_weighted_mean(self): """Test weighted_mean method works collapsing a cube with a threshold dimension when the blending is over a different coordinate. Note that this test is in process to include the slicing.""" coord = "forecast_reference_time" plugin = WeightedBlendAcrossWholeDimension(coord) result = plugin.process(self.cube_threshold, self.weights1d) expected_result_array = np.ones((2, 2, 2)) * 0.3 expected_result_array[1, :, :] = 0.5 expected_frt = int( self.cube.coord('forecast_reference_time').points[-1]) expected_forecast_period = int( self.cube.coord('forecast_period').points[-1]) self.assertArrayAlmostEqual(result.data, expected_result_array) self.assertEqual(result.attributes, self.expected_attributes) self.assertEqual(result.coord('time').points, expected_frt) self.assertEqual( result.coord('forecast_period').points, expected_forecast_period)