def __init__(self, wxtree='high_resolution'): """ Define a decision tree for determining weather symbols based upon the input diagnostics. Use this decision tree to allocate a weather symbol to each point. Key Args: wxtree (str): Used to choose weather symbol decision tree. Default is 'high_resolution' 'global' will load the global weather symbol decision tree. float_tolerance defines the tolerance when matching thresholds to allow for the difficulty of float comparisons. float_abs_tolerance defines the tolerance for when the threshold is zero. It has to be sufficiently small that a valid rainfall rate or snowfall rate could not trigger it. """ self.wxtree = wxtree if wxtree == 'global': self.queries = wxcode_decision_tree_global() self.start_node = START_NODE_GLOBAL else: self.queries = wxcode_decision_tree() self.start_node = START_NODE self.float_tolerance = 0.01 self.float_abs_tolerance = 1e-12 # flag to indicate whether to expect "threshold" as a coordinate name # (defaults to False, checked on reading input cubes) self.coord_named_threshold = False # dictionary to contain names of threshold coordinates that do not # match expected convention self.threshold_coord_names = {}
def test_keywords(self): """Test that the only permissible keywords are used.""" tree = wxcode_decision_tree() all_key_words = REQUIRED_KEY_WORDS + OPTIONAL_KEY_WORDS for node in tree: for entry in tree[node]: self.assertEqual(entry in all_key_words, True)
def __init__(self, wxtree='high_resolution'): """ Define a decision tree for determining weather symbols based upon the input diagnostics. Use this decision tree to allocate a weather symbol to each point. Key Args: wxtree (str): Choose weather symbol decision tree. Default is 'high_resolution' 'global' will load the global weather symbol decision tree. float_tolerance defines the tolerance when matching thresholds to allow for the difficulty of float comparisons. """ self.wxtree = wxtree if wxtree == 'global': self.queries = wxcode_decision_tree_global() else: self.queries = wxcode_decision_tree() self.float_tolerance = 0.01 # flag to indicate whether to expect "threshold" as a coordinate name # (defaults to False, checked on reading input cubes) self.coord_named_threshold = False # dictionary to contain names of threshold coordinates that do not # match expected convention self.threshold_coord_names = {}
def test_keywords_diagnostic_missing(self): """Test only set keywords are used in diagnostic_missing_action.""" tree = wxcode_decision_tree() all_key_words = KEYWORDS_DIAGNOSTIC_MISSING_ACTION for items in tree.values(): if 'diagnostic_missing_action' in items: entry = items['diagnostic_missing_action'] self.assertEqual(entry in all_key_words, True)
def test_condition_combination(self): """Test only permissible values are used in condition_combination.""" tree = wxcode_decision_tree() for node in tree: combination = tree[node]['condition_combination'] num_diagnostics = len(tree[node]['diagnostic_fields']) if num_diagnostics == 2: self.assertEqual(combination in CONDITION_COMBINATIONS, True) else: self.assertEqual(combination, '')
def test_node_points_to_valid_value(self): """Test that succeed and fail point to valid values or nodes.""" tree = wxcode_decision_tree() for node in tree: succeed = tree[node]['succeed'] if isinstance(succeed, str): self.assertEqual(succeed in tree.keys(), True) fail = tree[node]['fail'] if isinstance(fail, str): self.assertEqual(fail in tree, True)
def test_diagnostic_len_match(self): """Test diagnostic fields, thresholds and conditions are same len.""" tree = wxcode_decision_tree() 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_diagnostic_condition(self): """Test only permissible values are used in diagnostic_conditions.""" tree = wxcode_decision_tree() for node in tree: diagnostic = tree[node]['diagnostic_conditions'] tests_diagnostic = diagnostic if isinstance(diagnostic[0], list): tests_diagnostic = [item for sublist in diagnostic for item in sublist] for value in tests_diagnostic: self.assertEqual(value in DIAGNOSTIC_CONDITIONS, True)
def __init__(self): """ Define a decision tree for determining weather symbols based upon the input diagnostics. Use this decision tree to allocate a weather symbol to each point. float_tolerance defines the tolerance when matching thresholds to allow for the difficulty of float comparisons. """ self.queries = wxcode_decision_tree() self.float_tolerance = 0.01
def interrogate_decision_tree(wxtree): """ Obtain a list of necessary inputs from the decision tree as it is currently defined. Return a list of the diagnostic names, the thresholds needed, and whether they are thresholded above or below these values. This output is used to create the CLI help, informing the user of the necessary inputs. Returns: list of str: Returns a list of strings, an entry for each diagnostic required, including threshold details. """ # Get current weather symbol decision tree and populate a list of # required inputs for printing. if wxtree == 'high_resolution': queries = wxcode_decision_tree() elif wxtree == 'global': queries = wxcode_decision_tree_global() # Diagnostic names and threshold values. requirements = {} # How the data has been thresholded relative to these thresholds. relative = {} for query in queries.values(): diagnostics = expand_nested_lists(query, 'diagnostic_fields') for index, diagnostic in enumerate(diagnostics): if diagnostic not in requirements: requirements[diagnostic] = [] relative[diagnostic] = [] requirements[diagnostic].extend( expand_nested_lists(query, 'diagnostic_thresholds')[index]) relative[diagnostic].append( expand_nested_lists(query, 'diagnostic_conditions')[index]) # Create a list of formatted strings that will be printed as part of the # CLI help. output = [] for requirement in requirements: entries = np.array([entry for entry in requirements[requirement]]) relations = np.array([entry for entry in relative[requirement]]) _, thresholds = np.unique(np.array( [item.points.item() for item in entries]), return_index=True) output.append('{}; thresholds: {}'.format( requirement, ', '.join([ '{} ({})'.format(str(threshold.points.item()), str(threshold.units)) for threshold, relation in zip(entries[thresholds], relations[thresholds]) ]))) return output
def interrogate_decision_tree(wxtree): """ Obtain a list of necessary inputs from the decision tree as it is currently defined. Return a formatted string that contains the diagnostic names, the thresholds needed, and whether they are thresholded above or below these values. This output is used to create the CLI help, informing the user of the necessary inputs. Args: wxtree (str): The weather symbol tree that is to be interrogated. Returns: list of str: Returns a formatted string descring the diagnostics required, including threshold details. """ # Get current weather symbol decision tree and populate a list of # required inputs for printing. if wxtree == "high_resolution": queries = wxcode_decision_tree() elif wxtree == "global": queries = wxcode_decision_tree_global() else: raise ValueError("Unknown decision tree name provided.") # Diagnostic names and threshold values. requirements = {} for query in queries.values(): diagnostics = get_parameter_names( expand_nested_lists(query, "diagnostic_fields") ) thresholds = expand_nested_lists(query, "diagnostic_thresholds") for diagnostic, threshold in zip(diagnostics, thresholds): requirements.setdefault(diagnostic, set()).add(threshold) # Create a list of formatted strings that will be printed as part of the # CLI help. output = [] for requirement, uniq_thresh in sorted(requirements.items()): (units,) = set(u for (_, u) in uniq_thresh) # enforces same units thresh_str = ", ".join(map(str, sorted(v for (v, _) in uniq_thresh))) output.append("{} ({}): {}".format(requirement, units, thresh_str)) n_files = len(output) formatted_string = "{}\n" * n_files formatted_output = formatted_string.format(*output) return formatted_output
def __init__(self, wxtree: str = "high_resolution", model_id_attr: Optional[str] = None) -> None: """ Define a decision tree for determining weather symbols based upon the input diagnostics. Use this decision tree to allocate a weather symbol to each point. Args: wxtree: Used to choose weather symbol decision tree. Default is "high_resolution" "global" will load the global weather symbol decision tree. model_id_attr: Name of attribute recording source models that should be inherited by the output cube. The source models are expected as a space-separated string. float_tolerance defines the tolerance when matching thresholds to allow for the difficulty of float comparisons. float_abs_tolerance defines the tolerance for when the threshold is zero. It has to be sufficiently small that a valid rainfall rate or snowfall rate could not trigger it. """ def make_thresholds_with_units(items): if isinstance(items, list): return [make_thresholds_with_units(item) for item in items] values, units = items return iris.coords.AuxCoord(values, units=units) self.wxtree = wxtree self.model_id_attr = model_id_attr if wxtree == "global": self.queries = wxcode_decision_tree_global() self.start_node = START_NODE_GLOBAL else: self.queries = wxcode_decision_tree() self.start_node = START_NODE for query in self.queries.values(): query["diagnostic_thresholds"] = make_thresholds_with_units( query["diagnostic_thresholds"]) self.float_tolerance = 0.01 self.float_abs_tolerance = 1e-12 # flag to indicate whether to expect "threshold" as a coordinate name # (defaults to False, checked on reading input cubes) self.coord_named_threshold = False
def __init__(self, wxtree="high_resolution"): """ Define a decision tree for determining weather symbols based upon the input diagnostics. Use this decision tree to allocate a weather symbol to each point. Key Args: wxtree (str): Used to choose weather symbol decision tree. Default is "high_resolution" "global" will load the global weather symbol decision tree. float_tolerance defines the tolerance when matching thresholds to allow for the difficulty of float comparisons. float_abs_tolerance defines the tolerance for when the threshold is zero. It has to be sufficiently small that a valid rainfall rate or snowfall rate could not trigger it. """ def make_thresholds_with_units(items): if isinstance(items, list): return [make_thresholds_with_units(item) for item in items] values, units = items return iris.coords.AuxCoord(values, units=units) self.wxtree = wxtree if wxtree == "global": self.queries = wxcode_decision_tree_global() self.start_node = START_NODE_GLOBAL else: self.queries = wxcode_decision_tree() self.start_node = START_NODE for query in self.queries.values(): query["diagnostic_thresholds"] = make_thresholds_with_units( query["diagnostic_thresholds"]) self.float_tolerance = 0.01 self.float_abs_tolerance = 1e-12 # flag to indicate whether to expect "threshold" as a coordinate name # (defaults to False, checked on reading input cubes) self.coord_named_threshold = False # dictionary to contain names of threshold coordinates that do not # match expected convention self.threshold_coord_names = {}
def __init__(self, wxtree='high_resolution'): """ Define a decision tree for determining weather symbols based upon the input diagnostics. Use this decision tree to allocate a weather symbol to each point. Key Args: wxtree (str): Choose weather symbol decision tree. Default is 'high_resolution' 'global' will load the global weather symbol decision tree. float_tolerance defines the tolerance when matching thresholds to allow for the difficulty of float comparisons. """ self.wxtree = wxtree if wxtree == 'global': self.queries = wxcode_decision_tree_global() else: self.queries = wxcode_decision_tree() self.float_tolerance = 0.01
def test_basic(self): """Test that the wxcode_decision_tree returns a dictionary.""" result = wxcode_decision_tree() self.assertIsInstance(result, dict)
def test_threshold_condition(self): """Test only permissible values are used in threshold_condition.""" tree = wxcode_decision_tree() for node in tree: threshold = tree[node]['threshold_condition'] self.assertEqual(threshold in THRESHOLD_CONDITIONS, True)
def test_start_node_in_tree(self): """Test that the start node is in the tree""" tree = wxcode_decision_tree() self.assertTrue(START_NODE in tree)
"""Unit tests for Weather Symbols Trees.""" import pytest from improver.wxcode.utilities import WX_DICT, get_parameter_names from improver.wxcode.wxcode_decision_tree import START_NODE, wxcode_decision_tree from improver.wxcode.wxcode_decision_tree_global import ( START_NODE_GLOBAL, wxcode_decision_tree_global, ) from . import check_diagnostic_lists_consistency TREE_NAMES = ["high_resolution", "global"] TREES = { "high_resolution": wxcode_decision_tree(), "global": wxcode_decision_tree_global(), } START_NODES = {"high_resolution": START_NODE, "global": START_NODE_GLOBAL} REQUIRED_KEY_WORDS = [ "succeed", "fail", "probability_thresholds", "threshold_condition", "condition_combination", "diagnostic_fields", "diagnostic_thresholds", "diagnostic_conditions", ]