def test_set_record_run_attr_existing_attribute(model_cube, existing, expected): """Test the case in which the cubes already have record_run_attr entries and these must be combined to create a new shared attribute. There are three tests here: - cubes with unique model_run attributes that are combined - cubes with distinct but overlapping model_run attributes from which the elements are combined without duplicates - cubes with identical model_run attributes that are combined such that only a single entry is returned. The test cubes are only vehicles for the attributes in this test, such that the attributes imposed do not necessarily match the other cube metadata.""" record_run_attr = "mosg__model_run" model_id_attr = "mosg__model_configuration" cubes = [model_cube[0], model_cube[1]] for run_attr, cube in zip(existing, cubes): cube.attributes.update({record_run_attr: run_attr}) set_record_run_attr(cubes, record_run_attr, model_id_attr) for cube in cubes: assert cube.attributes[record_run_attr] == expected
def process( self, cubes_in: Union[List[Cube], Cube, CubeList], cycletime: Optional[str] = None, ) -> Cube: """ Prepares merged input cube for cycle and grid blending. Makes updates to metadata (attributes and time-type coordinates) ONLY in so far as these are needed to ensure inputs can be merged into a single cube. Args: cubes_in: Cubes to be merged. cycletime: The cycletime in a YYYYMMDDTHHMMZ format e.g. 20171122T0100Z. Can be used in rationalise_blend_time_coordinates. Returns: Merged cube. Raises: ValueError: If self.blend_coord is not present on all cubes (unless blending over models) """ cubelist = ([cubes_in.copy()] if isinstance(cubes_in, iris.cube.Cube) else [cube.copy() for cube in cubes_in]) if self.record_run_attr is not None and self.model_id_attr is not None: set_record_run_attr(cubelist, self.record_run_attr, self.model_id_attr) if "model" in self.blend_coord: cubelist = [self._remove_blend_time(cube) for cube in cubelist] cubelist = [ self._remove_deprecation_warnings(cube) for cube in cubelist ] if (self.weighting_coord is not None and "forecast_period" in self.weighting_coord): # if blending models using weights by forecast period, unify # forecast periods (assuming validity times are identical); # method returns a new cubelist with copies of input cubes cubelist = rebadge_forecasts_as_latest_cycle( cubelist, cycletime=cycletime) # check all input cubes have the blend coordinate for cube in cubelist: if "model" not in self.blend_coord and not cube.coords( self.blend_coord): raise ValueError("{} coordinate is not present on all input " "cubes".format(self.blend_coord)) # create model ID and model configuration coordinates if blending # different models if "model" in self.blend_coord and self.model_id_attr is not None: self._create_model_coordinates(cubelist) # merge resulting cubelist result = MergeCubes()(cubelist, check_time_bounds_ranges=True) return result
def test_set_record_run_attr_exception_model_id(model_cube): """Test an exception is raised if no model_id_attr is set on the input cubes and no existing record_run_attr was present.""" record_run_attr = "mosg__model_run" model_id_attr = "mosg__model_configuration" cubes = [model_cube[0], model_cube[1]] with pytest.raises(Exception, match="Failure to record run information"): set_record_run_attr(cubes, record_run_attr, model_id_attr)
def test_set_record_run_attr_exception_model_id_unset(model_cube): """Test an exception is raised if the model_id_attr argument provided is none and the input cubes do not all have an existing record_run_attr.""" record_run_attr = "mosg__model_run" model_id_attr = None cubes = [model_cube[0], model_cube[1]] with pytest.raises(Exception, match="Not all input cubes contain an existing"): set_record_run_attr(cubes, record_run_attr, model_id_attr)
def process(self, cubes: CubeList) -> Cube: """Calculate the modal weather code, with handling for edge cases. Args: cubes: A list of weather code cubes at different times. A modal code will be calculated over the time coordinate to return the most comon code, which is taken to be the best representation of the whole period. Returns: A single weather code cube with time bounds that span those of the input weather code cubes. """ # Set the record_run attribute on all cubes. This will survive the # merge and be present on the output. if self.record_run_attr: set_record_run_attr(cubes, self.record_run_attr, self.model_id_attr) cube = MergeCubes()(cubes) self._unify_day_and_night(cube) # Handle case in which a single time is provided. if len(cube.coord("time").points) == 1: result = cube result.add_cell_method(self.mode_cell_method) else: result = cube.collapsed("time", self.aggregator_instance) self._set_blended_times(result) if self.model_id_attr: # Update contributing models contributing_models = set() for source_cube in cubes: for model in source_cube.attributes[self.model_id_attr].split( " "): contributing_models.update([model]) result.attributes[self.model_id_attr] = " ".join( sorted(list(contributing_models))) # Handle any unset points where it was hard to determine a suitable mode if (result.data == UNSET_CODE_INDICATOR).any(): self._group_codes(result, cube) return result
def test_set_record_run_attr_mixed_inputs(model_cube): """Test use case where the record_run_attr is constructed from one cube with an existing record_run_attr, and one where other information on the cube is used.""" record_run_attr = "mosg__model_run" model_id_attr = "mosg__model_configuration" cubes = [model_cube[0], model_cube[1]] cubes[0].attributes.update({record_run_attr: "uk_det:20171110T0000Z:"}) cubes[1].attributes = { model_id_attr: cubes[1].coord("model_configuration").points[0] } expected = "uk_det:20171110T0000Z:\nuk_ens:20171110T0100Z:" set_record_run_attr(cubes, record_run_attr, model_id_attr) for cube in cubes: assert cube.attributes[record_run_attr] == expected
def test_set_record_run_attr_basic(model_cube, indices, expected): """Test use case where the record_run_attr is constructed from other information on the cubes. There are two tests here: - cubes with unique attributes that are combined - cubes with identical attributes attributes that are combined such that only a single entry is returned.""" record_run_attr = "mosg__model_run" model_id_attr = "mosg__model_configuration" cubes = [model_cube[index] for index in indices] for cube in cubes: cube.attributes = { model_id_attr: cube.coord("model_configuration").points[0] } set_record_run_attr(cubes, record_run_attr, model_id_attr) for cube in cubes: assert cube.attributes[record_run_attr] == expected
def create_symbol_cube(self, cubes: Union[List[Cube], CubeList]) -> Cube: """ Create an empty weather symbol cube Args: cubes: List of input cubes used to generate weather symbols Returns: A cube with suitable metadata to describe the weather symbols that will fill it and data initiated with the value -1 to allow any unset points to be readily identified. """ threshold_coord = find_threshold_coordinate(self.template_cube) template_cube = next(self.template_cube.slices_over([threshold_coord ])).copy() # remove coordinates and bounds that do not apply to weather symbols template_cube.remove_coord(threshold_coord) mandatory_attributes = generate_mandatory_attributes(cubes) optional_attributes = weather_code_attributes() if self.model_id_attr: optional_attributes.update( update_model_id_attr_attribute(cubes, self.model_id_attr)) if self.record_run_attr: set_record_run_attr(cubes, self.record_run_attr, self.model_id_attr) optional_attributes.update({ self.record_run_attr: cubes[0].attributes[self.record_run_attr] }) symbols = create_new_diagnostic_cube( "weather_code", "1", template_cube, mandatory_attributes, optional_attributes=optional_attributes, data=np.ma.masked_all_like(template_cube.data).astype(np.int32), ) return symbols