def test_in_vicinity(self): """Test correct name is returned from an "in vicinity" probability. Name "cloud_height" is used in this test to illustrate why suffix cannot be removed with "rstrip".""" diagnostic = 'cloud_height' result = extract_diagnostic_name( 'probability_of_{}_in_vicinity_above_threshold'.format(diagnostic)) self.assertEqual(result, diagnostic)
def set_up_probability_threshold_cube(data, phenomenon_standard_name, phenomenon_units, forecast_thresholds=np.array([8, 10, 12]), timesteps=1, y_dimension_length=3, x_dimension_length=3, spp__relative_to_threshold='above'): """ Create a cube containing multiple probability_above/below_threshold values for the coordinate. """ cube_long_name = ("probability_of_{}_{}_threshold".format( phenomenon_standard_name, spp__relative_to_threshold)) cube = Cube(data, long_name=cube_long_name, units=1) threshold_coord_name = extract_diagnostic_name(cube_long_name) try: cube.add_dim_coord( DimCoord(forecast_thresholds, threshold_coord_name, units=phenomenon_units, var_name="threshold"), 0) except ValueError: cube.add_dim_coord( DimCoord(forecast_thresholds, long_name=threshold_coord_name, units=phenomenon_units, var_name="threshold"), 0) time_origin = "hours since 1970-01-01 00:00:00" calendar = "gregorian" tunit = Unit(time_origin, calendar) cube.add_dim_coord( DimCoord(np.linspace(412227.0, 412327.0, timesteps, dtype=np.float32), "time", units=tunit), 1) cube.add_dim_coord( DimCoord(np.linspace(-45.0, 45.0, y_dimension_length, dtype=np.float32), 'latitude', units='degrees'), 2) cube.add_dim_coord( DimCoord(np.linspace(120, 180, x_dimension_length, dtype=np.float32), 'longitude', units='degrees'), 3) cube.coord( var_name="threshold").attributes["spp__relative_to_threshold"] = ( spp__relative_to_threshold) return cube
def _update_metadata(self, output_cube, original_units): """ Update output cube name and threshold coordinate Args: output_cube (iris.cube.Cube): Cube containing new "between_thresholds" probabilities original_units (str): Required threshold-type coordinate units """ output_cube.rename( 'probability_of_{}_between_thresholds'.format( extract_diagnostic_name(self.cube.name()))) new_thresh_coord = output_cube.coord(self.thresh_coord.name()) new_thresh_coord.convert_units(original_units) new_thresh_coord.attributes['spp__relative_to_threshold'] = ( 'between_thresholds')
def check_input_cubes(self, cubes): """ Check that the input cubes contain all the diagnostics and thresholds required by the decision tree. Sets self.coord_named_threshold to "True" if threshold-type coordinates have the name "threshold" (as opposed to the standard name of the diagnostic), for backward compatibility. Args: cubes (iris.cube.CubeList): A CubeList containing the input diagnostic cubes. Raises: IOError: Raises an IOError if any of the required input data is missing. The error includes details of which fields are missing. """ missing_data = [] for query in self.queries.values(): diagnostics = expand_nested_lists(query, 'diagnostic_fields') thresholds = expand_nested_lists(query, 'diagnostic_thresholds') conditions = expand_nested_lists(query, 'diagnostic_conditions') for diagnostic, threshold, condition in zip( diagnostics, thresholds, conditions): # First we check the diagnostic name and units, performing # a conversion is required and possible. test_condition = (iris.Constraint(name=diagnostic)) matched_cube = cubes.extract(test_condition) if not matched_cube: missing_data.append([diagnostic, threshold, condition]) continue else: cube_threshold_units = (find_threshold_coordinate( matched_cube[0]).units) threshold.convert_units(cube_threshold_units) # Then we check if the required threshold is present in the # cube, and that the thresholding is relative to it correctly. threshold = threshold.points.item() threshold_name = find_threshold_coordinate( matched_cube[0]).name() # Check cube and threshold coordinate names match according to # expected convention. If not, add to exception dictionary. if extract_diagnostic_name(diagnostic) != threshold_name: self.threshold_coord_names[diagnostic] = (threshold_name) # Set flag to check for old threshold coordinate names if (threshold_name == "threshold" and not self.coord_named_threshold): self.coord_named_threshold = True test_condition = (iris.Constraint( coord_values={ threshold_name: lambda cell: (threshold * (1. - self.float_tolerance) < cell < threshold * (1. + self.float_tolerance)) }, cube_func=lambda cube: (find_threshold_coordinate(cube).attributes[ 'spp__relative_to_threshold'] == condition))) matched_threshold = matched_cube.extract(test_condition) if not matched_threshold: missing_data.append([diagnostic, threshold, condition]) if missing_data: msg = ('Weather Symbols input cubes are missing' ' the following required' ' input fields:\n') dyn_msg = ('name: {}, threshold: {}, ' 'spp__relative_to_threshold: {}\n') for item in missing_data: msg = msg + dyn_msg.format(*item) raise IOError(msg) return
def construct_extract_constraint(self, diagnostics, thresholds, coord_named_threshold): """ Construct an iris constraint. Args: diagnostics (str or list of str): The names of the diagnostics to be extracted from the CubeList. thresholds (iris.AuxCoord or list of iris.AuxCoord): All thresholds within the given diagnostic cubes that are needed, including units. Note these are NOT coords from the original cubes, just constructs to associate units with values. coord_named_threshold (bool): If true, use old naming convention for threshold coordinates (coord.long_name=threshold). Otherwise extract threshold coordinate name from diagnostic name Returns: (str or list of str): String, or list of strings, encoding iris cube constraints. """ def _constraint_string(diagnostic, threshold_name, threshold_val): """ Return iris constraint as a string for deferred creation of the lambda functions. Args: diagnostic (str): Name of diagnostic threshold_name (str): Name of threshold coordinate on input cubes threshold_val (float): Value of threshold coordinate required Returns: (str) """ return ("iris.Constraint(name='{diagnostic}', {threshold_name}=" "lambda cell: {threshold_val} * {float_min} < cell < " "{threshold_val} * {float_max})".format( diagnostic=diagnostic, threshold_name=threshold_name, threshold_val=threshold_val, float_min=(1. - WeatherSymbols().float_tolerance), float_max=(1. + WeatherSymbols().float_tolerance))) # if input is list, loop over and return a list of strings if isinstance(diagnostics, list): constraints = [] for diagnostic, threshold in zip(diagnostics, thresholds): if coord_named_threshold: threshold_coord_name = "threshold" elif diagnostic in self.threshold_coord_names: threshold_coord_name = ( self.threshold_coord_names[diagnostic]) else: threshold_coord_name = extract_diagnostic_name(diagnostic) threshold_val = threshold.points.item() constraints.append( _constraint_string(diagnostic, threshold_coord_name, threshold_val)) return constraints # otherwise, return a string if coord_named_threshold: threshold_coord_name = "threshold" elif diagnostics in self.threshold_coord_names: threshold_coord_name = self.threshold_coord_names[diagnostics] else: threshold_coord_name = extract_diagnostic_name(diagnostics) threshold_val = thresholds.points.item() constraint = _constraint_string(diagnostics, threshold_coord_name, threshold_val) return constraint
def test_error_not_probability(self): """Test exception if input is not a probability cube name""" with self.assertRaises(ValueError): extract_diagnostic_name('lwe_precipitation_rate')
def test_between_thresholds(self): """Test correct name is returned from a probability between thresholds """ result = extract_diagnostic_name( 'probability_of_visibility_in_air_between_thresholds') self.assertEqual(result, 'visibility_in_air')
def test_below_threshold(self): """Test correct name is returned from a probability below threshold""" result = extract_diagnostic_name( 'probability_of_air_temperature_below_threshold') self.assertEqual(result, 'air_temperature')
def test_basic(self): """Test correct name is returned from a standard (above threshold) probability field""" result = extract_diagnostic_name( 'probability_of_air_temperature_above_threshold') self.assertEqual(result, 'air_temperature')
def test_in_vicinity(self): """Test correct name is returned from an "in vicinity" probability""" diagnostic = 'lwe_precipitation_rate_in_vicinity' result = extract_diagnostic_name( 'probability_of_{}_above_threshold'.format(diagnostic)) self.assertEqual(result, diagnostic)