def test_diagnostic_len_match(self): """Test diagnostic fields, thresholds and conditions are same len.""" tree = wxcode_decision_tree_global() for node in tree: query = tree[node] diag_len = len(expand_nested_lists(query, 'diagnostic_fields')) thres_len = len(expand_nested_lists(query, 'diagnostic_thresholds')) cond_len = len(expand_nested_lists(query, 'diagnostic_conditions')) self.assertEqual(diag_len, thres_len) self.assertEqual(diag_len, cond_len)
def test_list_of_lists(self): """Returns a expanded list if given a list of lists.""" result = expand_nested_lists(self.dictionary, "list_of_lists") for val in result: self.assertEqual(val, "a")
def test_simple_list(self): """Testexpand_nested_lists returns a expanded list if given a list.""" result = expand_nested_lists(self.dictionary, "list") for val in result: self.assertEqual(val, "a")
def test_basic(self): """Test that the expand_nested_lists returns a list.""" result = expand_nested_lists(self.dictionary, "list") self.assertIsInstance(result, list)
def check_input_cubes(self, cubes: CubeList) -> Optional[Dict[str, Any]]: """ 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: A CubeList containing the input diagnostic cubes. Returns: A dictionary of (keyword) nodes names where the diagnostic data is missing and (values) node associated with diagnostic_missing_action. Raises: IOError: Raises an IOError if any of the required input data is missing. The error includes details of which fields are missing. """ optional_node_data_missing = {} missing_data = [] for key, query in self.queries.items(): diagnostics = get_parameter_names( 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: if "diagnostic_missing_action" in query: optional_node_data_missing.update( {key: query[query["diagnostic_missing_action"]]} ) else: missing_data.append([diagnostic, threshold, condition]) continue 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() # Set flag to check for old threshold coordinate names if threshold_name == "threshold" and not self.coord_named_threshold: self.coord_named_threshold = True # Check threshold == 0.0 if abs(threshold) < self.float_abs_tolerance: coord_constraint = { threshold_name: lambda cell: np.isclose( cell.point, 0, rtol=0, atol=self.float_abs_tolerance ) } else: coord_constraint = { threshold_name: lambda cell: np.isclose( cell.point, threshold, rtol=self.float_tolerance, atol=0 ) } # Checks whether the spp__relative_to_threshold attribute is above # or below a threshold and and compares to the diagnostic_condition. test_condition = iris.Constraint( coord_values=coord_constraint, cube_func=lambda cube: ( probability_is_above_or_below(cube) == 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) if not optional_node_data_missing: optional_node_data_missing = None return optional_node_data_missing
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. Returns: dict or None: A dictionary of (keyword) nodes names where the diagnostic data is missing and (values) node associated with diagnostic_missing_action. Raises: IOError: Raises an IOError if any of the required input data is missing. The error includes details of which fields are missing. """ optional_node_data_missing = {} missing_data = [] for key, query in self.queries.items(): 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: if 'diagnostic_missing_action' in query: optional_node_data_missing.update( {key: query[query['diagnostic_missing_action']]}) else: 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 # Check threshold == 0.0 if abs(threshold) < self.float_abs_tolerance: coord_constraint = { threshold_name: lambda cell: (-self.float_abs_tolerance < cell < self. float_abs_tolerance) } else: coord_constraint = { threshold_name: lambda cell: (threshold * (1. - self.float_tolerance) < cell < threshold * (1. + self.float_tolerance)) } test_condition = (iris.Constraint( coord_values=coord_constraint, 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) if not optional_node_data_missing: optional_node_data_missing = None return optional_node_data_missing