def check_diagnostic_lists_consistency(query): """ Checks if specific input lists have same nested list structure. e.g. ['item'] != [['item']] Args: query (dict): of weather-symbols decision-making information Raises: ValueError: if diagnostic query lists have different nested list structure. """ diagnostic_keys = [ "diagnostic_fields", "diagnostic_conditions", "diagnostic_thresholds", ] values = [ get_parameter_names(query[key]) if key == "diagnostic_fields" else query[key] for key in diagnostic_keys ] if not check_nested_list_consistency(values): msg = f"Inconsistent list structure: \n" for key in diagnostic_keys: msg += f"{key} = {query[key]}; \n" raise ValueError(msg)
def test_probability_len_match(tree_name): """Test probability_thresholds list is right shape.""" tree = TREES[tree_name] for _, query in tree.items(): check_list = query["probability_thresholds"] assert all(isinstance(x, (int, float)) for x in check_list) assert len(check_list) == len(get_parameter_names(query["diagnostic_fields"]))
def test_basic(self): """Test that the get_parameter_names method does what it says.""" condition = [ "parameter_name_one", "*", "4.0", "+", "parameter_name_two" ] expected = ["parameter_name_one", "parameter_name_two"] result = get_parameter_names(condition) self.assertEqual(result, expected)
def test_nested(self): """Test getting parameter names from nested lists.""" condition = [ ["parameter_name_one", "*", "4.0", "+", "parameter_name_two"], ["parameter_name_three", "parameter_name_four"], ] expected = [ ["parameter_name_one", "parameter_name_two"], ["parameter_name_three", "parameter_name_four"], ] result = get_parameter_names(condition) self.assertEqual(result, expected)
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