def process(self, cube: Cube) -> Cube: """Expected value calculation and metadata updates. Args: cube: Probabilistic data with a realization, threshold or percentile representation. Returns: Expected value of probability distribution. Same shape as input cube but with realization/threshold/percentile coordinate removed. """ if is_probability(cube): # TODO: replace this with direct calculation of the integral over # probability thresholds. The current approach works and has the # correct interface, but some accuracy will be lost in the conversion # and memory usage is very high. # # 19 percentiles corresponds to 5, 10, 15...95%. cube = ConvertProbabilitiesToPercentiles().process( cube, no_of_percentiles=19) if is_percentile(cube): cube = RebadgePercentilesAsRealizations().process(cube) mean_cube = collapse_realizations(cube) mean_cube.add_cell_method(CellMethod("mean", coords="realization")) return mean_cube
def test_basic(): """Test that a collapsed cube is returned with no realization coord""" data = np.full((3, 3, 3), fill_value=281.0, dtype=np.float32) cube = set_up_variable_cube(data, realizations=[0, 1, 2]) result = collapse_realizations(cube) assert "realization" not in [ coord.name() for coord in result.dim_coords + result.aux_coords ] assert (result.data == np.full((3, 3), fill_value=281.0, dtype=np.float32)).all()
def process(self, cube: Cube) -> Cube: """Expected value calculation and metadata updates. Args: cube: Probabilistic data with a realization, threshold or percentile representation. Returns: Expected value of probability distribution. Same shape as input cube but with realization/threshold/percentile coordinate removed. """ if is_probability(cube): ev_cube = self.integrate_over_thresholds(cube) elif is_percentile(cube): ev_cube = collapse_realizations( RebadgePercentilesAsRealizations().process(cube) ) else: ev_cube = collapse_realizations(cube) ev_cube.add_cell_method(CellMethod("mean", coords="realization")) return ev_cube
def process(self, cubes: CubeList) -> Cube: """ Create a shower condition probability from cloud fraction and convective ratio fields. This plugin thresholds the two input diagnostics, creates a hybrid probability field from the resulting binary fields, and then collapses the realizations to give a non-binary probability field that represents the likelihood of conditions being showery. Args: cubes: A cubelist containing a cube of cloud fraction and one of convective ratio. Returns: Probability of any precipitation, if present, being classified as showery """ cloud, convection = self._extract_inputs(cubes) # Threshold cubes cloud_thresholded = BasicThreshold( self.cloud_threshold, comparison_operator="<=").process(cloud) convection_thresholded = BasicThreshold( self.convection_threshold).process(convection) # Fill any missing data in the convective ratio field with zeroes. if np.ma.is_masked(convection_thresholded.data): convection_thresholded.data = convection_thresholded.data.filled(0) # Create a combined field taking the maximum of each input shower_probability = np.maximum( cloud_thresholded.data, convection_thresholded.data).astype(FLOAT_DTYPE) result = self._create_shower_condition_cube(shower_probability, convection_thresholded) try: shower_conditions = collapse_realizations(result) except CoordinateNotFoundError: shower_conditions = result return iris.util.squeeze(shower_conditions)
def process( *cubes: cli.inputcube, cycletime: str = None, ): """Runs equal-weighted blending for a specific scenario. Calculates an equal-weighted blend of input cube data across the realization and forecast_reference_time coordinates. Args: cubes (iris.cube.CubeList): Cubelist of cubes to be blended. cycletime (str): The forecast reference time to be used after blending has been applied, in the format YYYYMMDDTHHMMZ. If not provided, the blended file takes the latest available forecast reference time from the input datasets. Returns: iris.cube.Cube: Merged and blended Cube. """ from iris.cube import CubeList from improver.blending.calculate_weights_and_blend import WeightAndBlend from improver.utilities.cube_manipulation import collapse_realizations cubelist = CubeList() for cube in cubes: cubelist.append(collapse_realizations(cube)) plugin = WeightAndBlend( "forecast_reference_time", "linear", y0val=0.5, ynval=0.5, ) cube = plugin( cubelist, cycletime=cycletime, ) return cube
def process( neighbour_cube: cli.inputcube, cube: cli.inputcube, lapse_rate: cli.inputcube = None, *, apply_lapse_rate_correction=False, land_constraint=False, similar_altitude=False, extract_percentiles: cli.comma_separated_list = None, ignore_ecc_bounds=False, new_title: str = None, suppress_warnings=False, realization_collapse=False, ): """Module to run spot data extraction. Extract diagnostic data from gridded fields for spot data sites. It is possible to apply a temperature lapse rate adjustment to temperature data that helps to account for differences between the spot site's real altitude and that of the grid point from which the temperature data is extracted. Args: neighbour_cube (iris.cube.Cube): Cube of spot-data neighbours and the spot site information. cube (iris.cube.Cube): Cube containing the diagnostic data to be extracted. lapse_rate (iris.cube.Cube): Optional cube containing temperature lapse rates. If this cube is provided and a screen temperature cube is being processed, the lapse rates will be used to adjust the temperature to better represent each spot's site-altitude. apply_lapse_rate_correction (bool): Use to apply a lapse-rate correction to screen temperature data so that the data are a better match the altitude of the spot site for which they have been extracted. land_constraint (bool): Use to select the nearest-with-land-constraint neighbour-selection method from the neighbour_cube. This means that the grid points should be land points except for sites where none were found within the search radius when the neighbour cube was created. May be used with similar_altitude. similar_altitude (bool): Use to select the nearest-with-height-constraint neighbour-selection method from the neighbour_cube. These are grid points that were found to be the closest in altitude to the spot site within the search radius defined when the neighbour cube was created. May be used with land_constraint. extract_percentiles (list or int): If set to a percentile value or a list of percentile values, data corresponding to those percentiles will be returned. For example "25, 50, 75" will result in the 25th, 50th and 75th percentiles being returned from a cube of probabilities, percentiles or realizations. Deterministic input data will raise a warning message. Note that for percentiles inputs, the desired percentile(s) must exist in the input cube. ignore_ecc_bounds (bool): Demotes exceptions where calculated percentiles are outside the ECC bounds range to warnings. new_title (str): New title for the spot-extracted data. If None, this attribute is removed from the output cube since it has no prescribed standard and may therefore contain grid information that is no longer correct after spot-extraction. suppress_warnings (bool): Suppress warning output. This option should only be used if it is known that warnings will be generated but they are not required. realization_collapse (bool): Triggers equal-weighting blending of the realization coord if required. Use this if a threshold coord is also present on the input cube. Returns: iris.cube.Cube: Cube of spot data. Raises: ValueError: If the percentile diagnostic cube does not contain the requested percentile value. ValueError: If the lapse rate cube was provided but the diagnostic being processed is not air temperature. ValueError: If the lapse rate cube provided does not have the name "air_temperature_lapse_rate" ValueError: If the lapse rate cube does not contain a single valued height coordinate. Warns: warning: If diagnostic cube is not a known probabilistic type. warning: If a lapse rate cube was provided, but the height of the temperature does not match that of the data used. warning: If a lapse rate cube was not provided, but the option to apply the lapse rate correction was enabled. """ import warnings import iris import numpy as np from iris.exceptions import CoordinateNotFoundError from improver.ensemble_copula_coupling.ensemble_copula_coupling import ( ConvertProbabilitiesToPercentiles, ) from improver.metadata.probabilistic import find_percentile_coordinate from improver.percentile import PercentileConverter from improver.spotdata.apply_lapse_rate import SpotLapseRateAdjust from improver.spotdata.neighbour_finding import NeighbourSelection from improver.spotdata.spot_extraction import SpotExtraction from improver.utilities.cube_extraction import extract_subcube from improver.utilities.cube_manipulation import collapse_realizations if realization_collapse: cube = collapse_realizations(cube) neighbour_selection_method = NeighbourSelection( land_constraint=land_constraint, minimum_dz=similar_altitude ).neighbour_finding_method_name() result = SpotExtraction(neighbour_selection_method=neighbour_selection_method)( neighbour_cube, cube, new_title=new_title ) # If a probability or percentile diagnostic cube is provided, extract # the given percentile if available. This is done after the spot-extraction # to minimise processing time; usually there are far fewer spot sites than # grid points. if extract_percentiles: extract_percentiles = [np.float32(x) for x in extract_percentiles] try: perc_coordinate = find_percentile_coordinate(result) except CoordinateNotFoundError: if "probability_of_" in result.name(): result = ConvertProbabilitiesToPercentiles( ecc_bounds_warning=ignore_ecc_bounds )(result, percentiles=extract_percentiles) result = iris.util.squeeze(result) elif result.coords("realization", dim_coords=True): fast_percentile_method = not np.ma.isMaskedArray(result.data) result = PercentileConverter( "realization", percentiles=extract_percentiles, fast_percentile_method=fast_percentile_method, )(result) else: msg = ( "Diagnostic cube is not a known probabilistic type. " "The {} percentile could not be extracted. Extracting " "data from the cube including any leading " "dimensions.".format(extract_percentiles) ) if not suppress_warnings: warnings.warn(msg) else: constraint = ["{}={}".format(perc_coordinate.name(), extract_percentiles)] perc_result = extract_subcube(result, constraint) if perc_result is not None: result = perc_result else: msg = ( "The percentile diagnostic cube does not contain the " "requested percentile value. Requested {}, available " "{}".format(extract_percentiles, perc_coordinate.points) ) raise ValueError(msg) # Check whether a lapse rate cube has been provided and we are dealing with # temperature data and the lapse-rate option is enabled. if apply_lapse_rate_correction and lapse_rate: if not result.name() == "air_temperature": msg = ( "A lapse rate cube was provided, but the diagnostic being " "processed is not air temperature and cannot be adjusted." ) raise ValueError(msg) if not lapse_rate.name() == "air_temperature_lapse_rate": msg = ( "A cube has been provided as a lapse rate cube but does " "not have the expected name air_temperature_lapse_rate: " "{}".format(lapse_rate.name()) ) raise ValueError(msg) try: lapse_rate_height_coord = lapse_rate.coord("height") except (ValueError, CoordinateNotFoundError): msg = ( "Lapse rate cube does not contain a single valued height " "coordinate. This is required to ensure it is applied to " "equivalent temperature data." ) raise ValueError(msg) # Check the height of the temperature data matches that used to # calculate the lapse rates. If so, adjust temperatures using the lapse # rate values. if cube.coord("height") == lapse_rate_height_coord: plugin = SpotLapseRateAdjust( neighbour_selection_method=neighbour_selection_method ) result = plugin(result, neighbour_cube, lapse_rate) elif not suppress_warnings: warnings.warn( "A lapse rate cube was provided, but the height of the " "temperature data does not match that of the data used " "to calculate the lapse rates. As such the temperatures " "were not adjusted with the lapse rates." ) elif apply_lapse_rate_correction and not lapse_rate: if not suppress_warnings: warnings.warn( "A lapse rate cube was not provided, but the option to " "apply the lapse rate correction was enabled. No lapse rate " "correction could be applied." ) # Remove the internal model_grid_hash attribute if present. result.attributes.pop("model_grid_hash", None) return result
def process(self, input_cube): """ Calculates a field of texture to use in differentiating solid and more scattered features. Args: input_cube (iris.cube.Cube): Input data in cube format containing the field for which the texture is to be assessed. Returns: iris.cube.Cube: A cube containing either the mean across realization of the thresholded ratios to give the field texture, if a realization coordinate is present, or the thresholded ratios directly, if no realization coordinate is present. """ values = np.unique(input_cube.data) non_binary = np.where((values != 0) & (values != 1), True, False) if non_binary.any(): raise ValueError( "Incorrect input. Cube should hold binary data only") # Create new cube name for _calculate_ratio method. cube_name = find_threshold_coordinate(input_cube).name() # Extract threshold from input data to work with, taking into account floating # point comparisons. cube = input_cube.extract( iris.Constraint( coord_values={ cube_name: lambda cell: np.isclose(cell.point, self. diagnostic_threshold) })) try: cube.remove_coord(cube_name) except AttributeError: msg = "Threshold {} is not present on coordinate with values {} {}" raise ValueError( msg.format( self.diagnostic_threshold, input_cube.coord(cube_name).points, input_cube.coord(cube_name).units, )) ratios = iris.cube.CubeList() try: cslices = cube.slices_over("realization") except CoordinateNotFoundError: cslices = [cube] for cslice in cslices: ratios.append( self._calculate_ratio(cslice, cube_name, self.nbhood_radius)) ratios = ratios.merge_cube() thresholded = BasicThreshold(self.textural_threshold).process(ratios) # Squeeze scalar threshold coordinate. try: field_texture = iris.util.squeeze( collapse_realizations(thresholded)) except CoordinateNotFoundError: field_texture = iris.util.squeeze(thresholded) return field_texture