From 35a25804c43f422ca6113c24912d3f8267b93774 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Tue, 24 May 2022 09:51:14 -0600 Subject: [PATCH 01/14] rename helper function to start with underscore to note that it does not need to be a public method --- .../pytests/compare_gridded/test_compare_gridded.py | 4 ++-- metplus/wrappers/command_builder.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal_tests/pytests/compare_gridded/test_compare_gridded.py b/internal_tests/pytests/compare_gridded/test_compare_gridded.py index 4e92821b34..0bc93453fe 100644 --- a/internal_tests/pytests/compare_gridded/test_compare_gridded.py +++ b/internal_tests/pytests/compare_gridded/test_compare_gridded.py @@ -274,12 +274,12 @@ def test_handle_window_once(metplus_config, win, app_win, file_win, app_file_win 'FCST_FILE_WINDOW_BEGIN', 'FILE_WINDOW_BEGIN', ] - fcst_file_window_begin = cgw.handle_window_once(input_list, 0) + fcst_file_window_begin = cgw._handle_window_once(input_list, 0) input_list = ['OBS_APP_NAME_WINDOW_BEGIN', 'OBS_WINDOW_BEGIN', ] - obs_window_begin = cgw.handle_window_once(input_list, -5400) + obs_window_begin = cgw._handle_window_once(input_list, -5400) assert(obs_window_begin == win_value and fcst_file_window_begin == file_win_value) diff --git a/metplus/wrappers/command_builder.py b/metplus/wrappers/command_builder.py index 05b66f89bb..1950a6f40c 100755 --- a/metplus/wrappers/command_builder.py +++ b/metplus/wrappers/command_builder.py @@ -302,7 +302,7 @@ def print_all_envs(self, print_copyable=True, print_each_item=True): return msg - def handle_window_once(self, input_list, default_val=0): + def _handle_window_once(self, input_list, default_val=0): """! Check and set window dictionary variables like OBS_WINDOW_BEG or FCST_FILE_WINDOW_END @@ -336,7 +336,7 @@ def handle_obs_window_legacy(self, c_dict): f'OBS_WINDOW_{edge}', ] output_key = f'OBS_WINDOW_{edge}' - value = self.handle_window_once(input_list, default_val) + value = self._handle_window_once(input_list, default_val) c_dict[output_key] = value def handle_file_window_variables(self, c_dict, dtypes=['FCST', 'OBS']): @@ -358,7 +358,7 @@ def handle_file_window_variables(self, c_dict, dtypes=['FCST', 'OBS']): f'FILE_WINDOW_{edge}', ] output_key = f'{dtype}_FILE_WINDOW_{edge}' - value = self.handle_window_once(input_list, 0) + value = self._handle_window_once(input_list, 0) c_dict[output_key] = value def set_met_config_obs_window(self, c_dict): From 856eb77a7931bb58417d9512969d6ede3552ad13 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Tue, 24 May 2022 13:02:40 -0600 Subject: [PATCH 02/14] update default value for function to avoid using mutable default --- metplus/wrappers/command_builder.py | 16 ++++++++++------ metplus/wrappers/series_analysis_wrapper.py | 3 +-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/metplus/wrappers/command_builder.py b/metplus/wrappers/command_builder.py index 1950a6f40c..4e0e3877e1 100755 --- a/metplus/wrappers/command_builder.py +++ b/metplus/wrappers/command_builder.py @@ -339,25 +339,29 @@ def handle_obs_window_legacy(self, c_dict): value = self._handle_window_once(input_list, default_val) c_dict[output_key] = value - def handle_file_window_variables(self, c_dict, dtypes=['FCST', 'OBS']): + def handle_file_window_variables(self, c_dict, data_types=None): """! Handle all window config variables like [FCST/OBS]__WINDOW_[BEGIN/END] and [FCST/OBS]__FILE_WINDOW_[BEGIN/END] - Args: + @param c_dict dictionary to set items in + @param data_types tuple of data types to check, i.e. FCST, OBS """ edges = ['BEGIN', 'END'] app = self.app_name.upper() - for dtype in dtypes: + if not data_types: + data_types = ['FCST', 'OBS'] + + for data_type in data_types: for edge in edges: input_list = [ - f'{dtype}_{app}_FILE_WINDOW_{edge}', + f'{data_type}_{app}_FILE_WINDOW_{edge}', f'{app}_FILE_WINDOW_{edge}', - f'{dtype}_FILE_WINDOW_{edge}', + f'{data_type}_FILE_WINDOW_{edge}', f'FILE_WINDOW_{edge}', ] - output_key = f'{dtype}_FILE_WINDOW_{edge}' + output_key = f'{data_type}_FILE_WINDOW_{edge}' value = self._handle_window_once(input_list, 0) c_dict[output_key] = value diff --git a/metplus/wrappers/series_analysis_wrapper.py b/metplus/wrappers/series_analysis_wrapper.py index 37b3a24938..491ca1d877 100755 --- a/metplus/wrappers/series_analysis_wrapper.py +++ b/metplus/wrappers/series_analysis_wrapper.py @@ -281,8 +281,7 @@ def create_c_dict(self): ) # set *_WINDOW_* variables for FCST and OBS - self.handle_file_window_variables(c_dict, - dtypes=['FCST', 'OBS']) + self.handle_file_window_variables(c_dict) # if fcst input list or obs input list are not set else: self.log_error('Cannot set ' From e9165dfb0705c88da83295f6774552bb1ed80bcc Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Thu, 2 Jun 2022 14:52:57 -0600 Subject: [PATCH 03/14] fix error messages for reporting invalid forecast lead variables -- previously %s was not substituted with the values --- metplus/util/met_util.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/metplus/util/met_util.py b/metplus/util/met_util.py index b167ffad8a..abe99536f7 100644 --- a/metplus/util/met_util.py +++ b/metplus/util/met_util.py @@ -514,22 +514,19 @@ def are_lead_configs_ok(lead_seq, init_seq, lead_groups, if lead_groups is None: return False - error_message = ('%s and %s are both listed in the configuration. ' + error_message = ('are both listed in the configuration. ' 'Only one may be used at a time.') if lead_seq: if init_seq: - config.logger.error(error_message.format('LEAD_SEQ', - 'INIT_SEQ')) + config.logger.error(f'LEAD_SEQ and INIT_SEQ {error_message}') return False if lead_groups: - config.logger.error(error_message.format('LEAD_SEQ', - 'LEAD_SEQ_')) + config.logger.error(f'LEAD_SEQ and LEAD_SEQ_ {error_message}') return False if init_seq and lead_groups: - config.logger.error(error_message.format('INIT_SEQ', - 'LEAD_SEQ_')) + config.logger.error(f'INIT_SEQ and LEAD_SEQ_ {error_message}') return False if init_seq: From 8825f6137146395da77eeaf44f7e21f1f25b3077 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Thu, 2 Jun 2022 15:01:22 -0600 Subject: [PATCH 04/14] renamed argument in calls to match change to function definition --- metplus/wrappers/ioda2nc_wrapper.py | 2 +- metplus/wrappers/pb2nc_wrapper.py | 2 +- metplus/wrappers/regrid_data_plane_wrapper.py | 2 +- metplus/wrappers/series_analysis_wrapper.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/metplus/wrappers/ioda2nc_wrapper.py b/metplus/wrappers/ioda2nc_wrapper.py index 4ff71addea..4e267e729a 100755 --- a/metplus/wrappers/ioda2nc_wrapper.py +++ b/metplus/wrappers/ioda2nc_wrapper.py @@ -63,7 +63,7 @@ def create_c_dict(self): self.log_error("IODA2NC_INPUT_TEMPLATE required to run") # handle input file window variables - self.handle_file_window_variables(c_dict, dtypes=['OBS']) + self.handle_file_window_variables(c_dict, data_types=['OBS']) c_dict['OUTPUT_DIR'] = self.config.getdir('IODA2NC_OUTPUT_DIR', '') c_dict['OUTPUT_TEMPLATE'] = ( diff --git a/metplus/wrappers/pb2nc_wrapper.py b/metplus/wrappers/pb2nc_wrapper.py index 922d7a7b6d..82519417c4 100755 --- a/metplus/wrappers/pb2nc_wrapper.py +++ b/metplus/wrappers/pb2nc_wrapper.py @@ -141,7 +141,7 @@ def create_c_dict(self): 'PB2NC_TIME_SUMMARY_TYPES'], extra_args={'allow_empty': True}) - self.handle_file_window_variables(c_dict, dtypes=['OBS']) + self.handle_file_window_variables(c_dict, data_types=['OBS']) c_dict['VALID_BEGIN_TEMPLATE'] = \ self.config.getraw('config', 'PB2NC_VALID_BEGIN', '') diff --git a/metplus/wrappers/regrid_data_plane_wrapper.py b/metplus/wrappers/regrid_data_plane_wrapper.py index b673a9d128..1cf8de3142 100755 --- a/metplus/wrappers/regrid_data_plane_wrapper.py +++ b/metplus/wrappers/regrid_data_plane_wrapper.py @@ -136,7 +136,7 @@ def create_c_dict(self): met_tool=self.app_name ) - self.handle_file_window_variables(c_dict, dtypes=window_types) + self.handle_file_window_variables(c_dict, data_types=window_types) c_dict['VERIFICATION_GRID'] = \ self.config.getraw('config', 'REGRID_DATA_PLANE_VERIF_GRID', '') diff --git a/metplus/wrappers/series_analysis_wrapper.py b/metplus/wrappers/series_analysis_wrapper.py index 491ca1d877..5435142fee 100755 --- a/metplus/wrappers/series_analysis_wrapper.py +++ b/metplus/wrappers/series_analysis_wrapper.py @@ -254,7 +254,7 @@ def create_c_dict(self): else: # set *_WINDOW_* variables for BOTH # used in CommandBuilder.find_data function) - self.handle_file_window_variables(c_dict, dtypes=['BOTH']) + self.handle_file_window_variables(c_dict, data_types=['BOTH']) prob_thresh = self.config.getraw( 'config', From 568ca91b00000d999a4997ba8127cc261e91ebb4 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Fri, 3 Jun 2022 11:53:58 -0600 Subject: [PATCH 05/14] clean up formatting and imports --- metplus/wrappers/compare_gridded_wrapper.py | 82 +++++++++++---------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/metplus/wrappers/compare_gridded_wrapper.py b/metplus/wrappers/compare_gridded_wrapper.py index 0a95831670..d9abefd8e6 100755 --- a/metplus/wrappers/compare_gridded_wrapper.py +++ b/metplus/wrappers/compare_gridded_wrapper.py @@ -12,9 +12,9 @@ import os -from ..util import met_util as util from ..util import do_string_sub, ti_calculate from ..util import parse_var_list +from ..util import get_lead_sequence, skip_time, sub_var_list from . import CommandBuilder '''!@namespace CompareGriddedWrapper @@ -32,14 +32,14 @@ class CompareGriddedWrapper(CommandBuilder): """ def __init__(self, config, instance=None): - # set app_name if not set by child class to allow tests to run on this wrapper + # set app_name if not set by child class for unit tests if not hasattr(self, 'app_name'): self.app_name = 'compare_gridded' super().__init__(config, instance=instance) - # check to make sure all necessary probabilistic settings are set correctly - # this relies on the subclass to finish creating the c_dict, so it has to - # be checked after that happens + # make sure all necessary probabilistic settings are set correctly + # this relies on the subclass to finish creating the c_dict, + # so it has to be checked after that happens self.check_probabilistic_settings() def create_c_dict(self): @@ -101,11 +101,12 @@ def create_c_dict(self): return c_dict def set_environment_variables(self, time_info): - """!Set environment variables that will be read set when running this tool. - Wrappers could override it to set wrapper-specific values, then call super - version to handle user configs and printing - Args: - @param time_info dictionary containing timing info from current run""" + """! Set environment variables that will be set when running this tool. + Wrappers can override this function to set wrapper-specific values, + then call this (super) version to handle user configs and printing + + @param time_info dictionary containing timing info from current run + """ self.get_output_prefix(time_info) @@ -118,8 +119,10 @@ def set_environment_variables(self, time_info): super().set_environment_variables(time_info) def check_probabilistic_settings(self): - """!If dataset is probabilistic, check if *_PROB_IN_GRIB_PDS or INPUT_DATATYPE - are set. If not enough information is set, report an error and set isOK to False""" + """! If dataset is probabilistic, check if *_PROB_IN_GRIB_PDS or + INPUT_DATATYPE are set. If not enough information is set, + report an error and set isOK to False + """ for dtype in ['FCST', 'OBS']: if self.c_dict[f'{dtype}_IS_PROB']: # if the data type is NetCDF, then we know how to @@ -130,34 +133,37 @@ def check_probabilistic_settings(self): # if the data is grib, the user must specify if the data is in # the GRIB PDS or not if self.c_dict[f'{dtype}_PROB_IN_GRIB_PDS'] == '': - self.log_error(f"If {dtype}_IS_PROB is True, you must set {dtype}_PROB_IN" - "_GRIB_PDS unless the forecast datatype is set to NetCDF") + self.log_error(f"If {dtype}_IS_PROB is True, you must set " + f"{dtype}_PROB_IN_GRIB_PDS unless the " + "forecast datatype is set to NetCDF") self.isOK = False def run_at_time(self, input_dict): """! Runs the MET application for a given run time. This function loops - over the list of forecast leads and runs the application for each. - Args: - @param input_dict dictionary containing time information + over the list of forecast leads and runs the application for each. + + @param input_dict dictionary containing time information """ # loop of forecast leads and process each - lead_seq = util.get_lead_sequence(self.config, input_dict) + lead_seq = get_lead_sequence(self.config, input_dict) for lead in lead_seq: input_dict['lead'] = lead # set current lead time config and environment variables time_info = ti_calculate(input_dict) - self.logger.info("Processing forecast lead {}".format(time_info['lead_string'])) + self.logger.info("Processing forecast lead " + f"{time_info['lead_string']}") - if util.skip_time(time_info, self.c_dict.get('SKIP_TIMES', {})): + if skip_time(time_info, self.c_dict.get('SKIP_TIMES', {})): self.logger.debug('Skipping run time') continue for custom_string in self.c_dict['CUSTOM_LOOP_LIST']: if custom_string: - self.logger.info(f"Processing custom string: {custom_string}") + self.logger.info("Processing custom string: " + f"{custom_string}") time_info['custom'] = custom_string @@ -165,12 +171,12 @@ def run_at_time(self, input_dict): self.run_at_time_once(time_info) def run_at_time_once(self, time_info): - """! Build MET command for a given init/valid time and forecast lead combination - Args: - @param time_info dictionary containing timing information + """! Build MET command for a given init/valid time and + forecast lead combination. + + @param time_info dictionary containing timing information """ - var_list = util.sub_var_list(self.c_dict['VAR_LIST_TEMP'], - time_info) + var_list = sub_var_list(self.c_dict['VAR_LIST_TEMP'], time_info) if not var_list and not self.c_dict.get('VAR_LIST_OPTIONAL', False): self.log_error('No input fields were specified. You must set ' @@ -185,7 +191,8 @@ def run_at_time_once(self, time_info): self.c_dict['CURRENT_VAR_INFO'] = var_info self.run_at_time_one_field(time_info, var_info) else: - # loop over all variables and all them to the field list, then call the app once + # loop over all variables and all them to the field list, + # then call the app once if var_list: self.c_dict['CURRENT_VAR_INFO'] = var_list[0] @@ -244,13 +251,12 @@ def run_at_time_one_field(self, time_info, var_info): self.process_fields(time_info) def run_at_time_all_fields(self, time_info): - """! Build MET command for all of the field/level combinations for a given - init/valid time and forecast lead combination - Args: - @param time_info dictionary containing timing information + """! Build MET command for all of the field/level combinations for a + given init/valid time and forecast lead combination + + @param time_info dictionary containing timing information """ - var_list = util.sub_var_list(self.c_dict['VAR_LIST_TEMP'], - time_info) + var_list = sub_var_list(self.c_dict['VAR_LIST_TEMP'], time_info) # get model from first var to compare model_path = self.find_model(time_info, @@ -302,12 +308,8 @@ def run_at_time_all_fields(self, time_info): def process_fields(self, time_info): """! Set and print environment variables, then build/run MET command - Args: - @param time_info dictionary with time information - @param fcst_field field information formatted for MET config file - @param obs_field field information formatted for MET config file - @param ens_field field information formatted for MET config file - only used for ensemble_stat + + @param time_info dictionary with time information """ # set config file since command is reset after each run self.param = do_string_sub(self.c_dict['CONFIG_FILE'], @@ -341,7 +343,7 @@ def create_and_set_output_dir(self, time_info): out_dir = self.c_dict['OUTPUT_DIR'] # use output template if it is set - # if output template is not set, do not add any extra directories to path + # if not set, do not add any extra directories to path out_template_name = '{}_OUTPUT_TEMPLATE'.format(self.app_name.upper()) if self.config.has_option('config', out_template_name): From 15de571908730da926a2901c352f66203edc0c84 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Fri, 3 Jun 2022 12:36:29 -0600 Subject: [PATCH 06/14] added utility to handle reading settings related to MET config fields, i.e. *_IS_PROB --- metplus/util/__init__.py | 1 + metplus/util/field_util.py | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 metplus/util/field_util.py diff --git a/metplus/util/__init__.py b/metplus/util/__init__.py index 368f1caae0..b4e143d39b 100644 --- a/metplus/util/__init__.py +++ b/metplus/util/__init__.py @@ -8,3 +8,4 @@ from .string_template_substitution import * from .met_config import * from .time_looping import * +from .field_util import * diff --git a/metplus/util/field_util.py b/metplus/util/field_util.py new file mode 100644 index 0000000000..8e3abdac33 --- /dev/null +++ b/metplus/util/field_util.py @@ -0,0 +1,38 @@ +""" +Program Name: field_util.py +Contact(s): George McCabe +Description: METplus utility to handle MET config dictionaries with field info +""" + + +def field_read_prob_info(config, c_dict, data_types, app_name): + """! Read probabilistic variables for each field data type from the config + object and sets values in the c_dict as appropriate. + + @param config METplusConfig object to read + @param c_dict dictionary to set values + @param data_types list of field types to check, i.e. FCST, OBS + @param app_name name of tool used to read wrapper-specific configs + """ + for data_type in data_types: + # check both wrapper-specific variable and generic variable + config_names = [ + f'{data_type}_{app_name.upper()}_IS_PROB', + f'{data_type}_IS_PROB', + ] + name = config.get_mp_config_name(config_names) + + is_prob = config.getbool('config', name) if name else False + c_dict[f'{data_type}_IS_PROB'] = is_prob + + # if field type is probabilistic, check if prob info is in GRIB PDS + if not is_prob: + continue + + config_names = [ + f'{data_type}_{app_name.upper()}_PROB_IN_GRIB_PDS', + f'{data_type}_PROB_IN_GRIB_PDS', + ] + name = config.get_mp_config_name(config_names) + prob_in_pds = config.getbool('config', name) if name else False + c_dict[f'{data_type}_PROB_IN_GRIB_PDS'] = prob_in_pds From 7401603d795d1f9999689d05f719278763711f67 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Fri, 3 Jun 2022 12:38:57 -0600 Subject: [PATCH 07/14] Per #1586, call utility function to read probabilistic settings for FCST and OBS fields. Removed check to ensure *_PROB_IN_GRIB_PDS config var is set if reading GRIB data. Instead make the value default to False --- metplus/wrappers/compare_gridded_wrapper.py | 41 +++------------------ metplus/wrappers/mtd_wrapper.py | 11 ------ metplus/wrappers/series_analysis_wrapper.py | 22 +++++------ 3 files changed, 15 insertions(+), 59 deletions(-) diff --git a/metplus/wrappers/compare_gridded_wrapper.py b/metplus/wrappers/compare_gridded_wrapper.py index d9abefd8e6..d089e22b5c 100755 --- a/metplus/wrappers/compare_gridded_wrapper.py +++ b/metplus/wrappers/compare_gridded_wrapper.py @@ -15,6 +15,7 @@ from ..util import do_string_sub, ti_calculate from ..util import parse_var_list from ..util import get_lead_sequence, skip_time, sub_var_list +from ..util import field_read_prob_info from . import CommandBuilder '''!@namespace CompareGriddedWrapper @@ -37,10 +38,6 @@ def __init__(self, config, instance=None): self.app_name = 'compare_gridded' super().__init__(config, instance=instance) - # make sure all necessary probabilistic settings are set correctly - # this relies on the subclass to finish creating the c_dict, - # so it has to be checked after that happens - self.check_probabilistic_settings() def create_c_dict(self): """!Create dictionary from config items to be used in the wrapper @@ -66,17 +63,11 @@ def create_c_dict(self): # to a value that contains /path/to c_dict['INPUT_BASE'] = self.config.getdir_nocheck('INPUT_BASE', '') - c_dict['FCST_IS_PROB'] = self.config.getbool('config', 'FCST_IS_PROB', False) - # if forecast is PROB, get variable to check if prob is in GRIB PDS - # it can be unset if the INPUT_DATATYPE is NetCDF, so check that after - # the entire c_dict is created - if c_dict['FCST_IS_PROB']: - c_dict['FCST_PROB_IN_GRIB_PDS'] = self.config.getbool('config', 'FCST_PROB_IN_GRIB_PDS', '') - - c_dict['OBS_IS_PROB'] = self.config.getbool('config', 'OBS_IS_PROB', False) - # see comment for FCST_IS_PROB - if c_dict['OBS_IS_PROB']: - c_dict['OBS_PROB_IN_GRIB_PDS'] = self.config.getbool('config', 'OBS_PROB_IN_GRIB_PDS', '') + # read probabilistic variables for FCST and OBS fields + field_read_prob_info(config=self.config, + c_dict=c_dict, + data_types=('FCST', 'OBS'), + app_name=self.app_name) c_dict['FCST_PROB_THRESH'] = None c_dict['OBS_PROB_THRESH'] = None @@ -118,26 +109,6 @@ def set_environment_variables(self, time_info): super().set_environment_variables(time_info) - def check_probabilistic_settings(self): - """! If dataset is probabilistic, check if *_PROB_IN_GRIB_PDS or - INPUT_DATATYPE are set. If not enough information is set, - report an error and set isOK to False - """ - for dtype in ['FCST', 'OBS']: - if self.c_dict[f'{dtype}_IS_PROB']: - # if the data type is NetCDF, then we know how to - # format the probabilistic fields - if self.c_dict[f'{dtype}_INPUT_DATATYPE'] != 'GRIB': - continue - - # if the data is grib, the user must specify if the data is in - # the GRIB PDS or not - if self.c_dict[f'{dtype}_PROB_IN_GRIB_PDS'] == '': - self.log_error(f"If {dtype}_IS_PROB is True, you must set " - f"{dtype}_PROB_IN_GRIB_PDS unless the " - "forecast datatype is set to NetCDF") - self.isOK = False - def run_at_time(self, input_dict): """! Runs the MET application for a given run time. This function loops over the list of forecast leads and runs the application for each. diff --git a/metplus/wrappers/mtd_wrapper.py b/metplus/wrappers/mtd_wrapper.py index e970e13c9f..fdc9b97761 100755 --- a/metplus/wrappers/mtd_wrapper.py +++ b/metplus/wrappers/mtd_wrapper.py @@ -111,17 +111,6 @@ def create_c_dict(self): if c_dict['FCST_FILE_LIST'] or c_dict['OBS_FILE_LIST']: c_dict['EXPLICIT_FILE_LIST'] = True - c_dict['FCST_IS_PROB'] = ( - self.config.getbool('config', - 'FCST_IS_PROB', - False) - ) - c_dict['OBS_IS_PROB'] = ( - self.config.getbool('config', - 'OBS_IS_PROB', - False) - ) - # if single run for OBS, read OBS values into FCST keys read_type = 'FCST' if c_dict['SINGLE_RUN'] and c_dict.get('SINGLE_DATA_SRC') == 'OBS': diff --git a/metplus/wrappers/series_analysis_wrapper.py b/metplus/wrappers/series_analysis_wrapper.py index 5435142fee..76f684a469 100755 --- a/metplus/wrappers/series_analysis_wrapper.py +++ b/metplus/wrappers/series_analysis_wrapper.py @@ -22,14 +22,14 @@ WRAPPER_CANNOT_RUN = True EXCEPTION_ERR = err_msg -from ..util import getlist -from ..util import met_util as util +from ..util import getlist, get_storms from ..util import do_string_sub, parse_template, get_tags from ..util import get_lead_sequence, get_lead_sequence_groups from ..util import ti_get_hours_from_lead, ti_get_seconds_from_lead from ..util import ti_get_lead_string from ..util import parse_var_list from ..util import add_to_time_input +from ..util import field_read_prob_info from .plot_data_plane_wrapper import PlotDataPlaneWrapper from . import RuntimeFreqWrapper @@ -178,6 +178,12 @@ def create_c_dict(self): 'SERIES_ANALYSIS_IS_PAIRED', False) + # read probabilistic variables for FCST and OBS fields + field_read_prob_info(config=self.config, + c_dict=c_dict, + data_types=('FCST', 'OBS'), + app_name=self.app_name) + # get input dir, template, and datatype for FCST, OBS, and BOTH for data_type in ('FCST', 'OBS', 'BOTH'): @@ -233,16 +239,6 @@ def create_c_dict(self): extra_args={'remove_quotes': True} ) - c_dict[f'{data_type}_IS_PROB'] = ( - self.config.getbool('config', f'{data_type}_IS_PROB', False) - ) - if c_dict[f'{data_type}_IS_PROB']: - c_dict[f'{data_type}_PROB_IN_GRIB_PDS'] = ( - self.config.getbool('config', - f'{data_type}_PROB_IN_GRIB_PDS', - False) - ) - c_dict['USING_BOTH'] = (c_dict['BOTH_INPUT_TEMPLATE'] or c_dict['BOTH_INPUT_FILE_LIST']) @@ -543,7 +539,7 @@ def get_storm_list(self, time_info): # Now that we have the filter filename for the init time, let's # extract all the storm ids in this filter file. - storm_list = util.get_storms(filter_file, id_only=True) + storm_list = get_storms(filter_file, id_only=True) if not storm_list: # No storms for this init time, check next init time in list self.logger.debug("No storms found for current runtime") From 1cafce91b82bd3d3af2a059438824381c6b6d200 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Fri, 3 Jun 2022 12:40:23 -0600 Subject: [PATCH 08/14] minor change to trigger testing - ci-run-all-diff --- metplus/util/field_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metplus/util/field_util.py b/metplus/util/field_util.py index 8e3abdac33..33f9cb8d3f 100644 --- a/metplus/util/field_util.py +++ b/metplus/util/field_util.py @@ -1,6 +1,6 @@ """ Program Name: field_util.py -Contact(s): George McCabe +Contact(s): George McCabe Description: METplus utility to handle MET config dictionaries with field info """ From de72fcf2e15e5b4e7f6b37988cbd268561a5a1d6 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Fri, 3 Jun 2022 12:49:41 -0600 Subject: [PATCH 09/14] removed function that does not appear to be used anymore and ci-run-all-diff --- metplus/wrappers/command_builder.py | 45 ----------------------------- 1 file changed, 45 deletions(-) diff --git a/metplus/wrappers/command_builder.py b/metplus/wrappers/command_builder.py index 4e0e3877e1..c0286c8852 100755 --- a/metplus/wrappers/command_builder.py +++ b/metplus/wrappers/command_builder.py @@ -430,51 +430,6 @@ def print_env_item(self, item): """ return f"{item}={self.env[item]}" - def handle_fcst_and_obs_field(self, gen_name, fcst_name, obs_name, default=None, sec='config'): - """!Handles config variables that have fcst/obs versions or a generic - variable to handle both, i.e. FCST_NAME, OBS_NAME, and NAME. - If FCST_NAME and OBS_NAME both exist, they are used. If both are don't - exist, NAME is used. - """ - has_gen = self.config.has_option(sec, gen_name) - has_fcst = self.config.has_option(sec, fcst_name) - has_obs = self.config.has_option(sec, obs_name) - - # use fcst and obs if both are set - if has_fcst and has_obs: - fcst_conf = self.config.getstr(sec, fcst_name) - obs_conf = self.config.getstr(sec, obs_name) - if has_gen: - self.logger.warning('Ignoring conf {} and using {} and {}' - .format(gen_name, fcst_name, obs_name)) - return fcst_conf, obs_conf - - # if one but not the other is set, error and exit - if has_fcst and not has_obs: - self.log_error('Cannot use {} without {}'.format(fcst_name, obs_name)) - return None, None - - if has_obs and not has_fcst: - self.log_error('Cannot use {} without {}'.format(obs_name, fcst_name)) - return None, None - - # if generic conf is set, use for both - if has_gen: - gen_conf = self.config.getstr(sec, gen_name) - return gen_conf, gen_conf - - # if none of the options are set, use default value for both if specified - if default is None: - msg = 'Must set both {} and {} in the config files'.format(fcst_name, - obs_name) - msg += ' or set {} instead'.format(gen_name) - self.log_error(msg) - - return None, None - - self.logger.warning('Using default values for {}'.format(gen_name)) - return default, default - def find_model(self, time_info, var_info=None, mandatory=True, return_list=False): """! Finds the model file to compare From 8da20bd04f26f7a1a5b9699bd94b78a6e9b26078 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Fri, 3 Jun 2022 13:24:51 -0600 Subject: [PATCH 10/14] moved CommandBuilder functions that involve fields from MET config files into metplus.util.field_util, ci-run-all-diff --- metplus/util/field_util.py | 109 +++++++++++++++++++++++++ metplus/wrappers/command_builder.py | 121 ++++++---------------------- 2 files changed, 135 insertions(+), 95 deletions(-) diff --git a/metplus/util/field_util.py b/metplus/util/field_util.py index 33f9cb8d3f..9f621f6afa 100644 --- a/metplus/util/field_util.py +++ b/metplus/util/field_util.py @@ -4,6 +4,7 @@ Description: METplus utility to handle MET config dictionaries with field info """ +from . import get_threshold_via_regex, is_python_script, remove_quotes def field_read_prob_info(config, c_dict, data_types, app_name): """! Read probabilistic variables for each field data type from the config @@ -36,3 +37,111 @@ def field_read_prob_info(config, c_dict, data_types, app_name): name = config.get_mp_config_name(config_names) prob_in_pds = config.getbool('config', name) if name else False c_dict[f'{data_type}_PROB_IN_GRIB_PDS'] = prob_in_pds + + +def get_field_info(c_dict, data_type='', v_name='', v_level='', v_thresh=None, + v_extra='', add_curly_braces=True): + """! Format field information into format expected by MET config file + + @param c_dict config dictionary to read values + @param v_level level of data to extract + @param v_thresh threshold value to use in comparison + @param v_name name of field to process + @param v_extra additional field information to add if available + @param data_type type of data to find i.e. FCST or OBS + @param add_curly_braces if True, add curly braces around each + field info string. If False, add single quotes around each + field info string (defaults to True) + @rtype string + @return Returns a list of formatted field information or a string + containing an error message if something went wrong + """ + # if thresholds are set + if v_thresh: + # if neither fcst or obs are probabilistic, + # pass in all thresholds as a comma-separated list for 1 field info + if (not c_dict.get('FCST_IS_PROB', False) and + not c_dict.get('OBS_IS_PROB', False)): + thresholds = [','.join(v_thresh)] + else: + thresholds = v_thresh + # if no thresholds are specified, fail if prob field is in grib PDS + elif (c_dict.get(f'{data_type}_IS_PROB', False) and + c_dict.get(f'{data_type}_PROB_IN_GRIB_PDS', False) and + not is_python_script(v_name)): + return 'No threshold was specified for probabilistic GRIB data' + else: + thresholds = [None] + + # list to hold field information + fields = [] + + for thresh in thresholds: + if (c_dict.get(f'{data_type}_PROB_IN_GRIB_PDS', False) and + not is_python_script(v_name)): + field = _handle_grib_pds_field_info(v_name, v_level, thresh) + else: + # add field name + field = f'name="{v_name}";' + + if v_level: + field += f' level="{remove_quotes(v_level)}";' + + if c_dict.get(f'{data_type}_IS_PROB', False): + field += " prob=TRUE;" + + # handle cat_thresh + if c_dict.get(f'{data_type}_IS_PROB', False): + # add probabilistic cat thresh if different from default ==0.1 + cat_thresh = c_dict.get(f'{data_type}_PROB_THRESH') + else: + cat_thresh = thresh + + if cat_thresh: + field += f" cat_thresh=[ {cat_thresh} ];" + + # handle extra options if set + if v_extra: + extra = v_extra.strip() + # if trailing semi-colon is not found, add it + if not extra.endswith(';'): + extra = f"{extra};" + field += f' {extra}' + + # add curly braces around field info if requested + # otherwise add single quotes around field info + field = f'{{ {field} }}' if add_curly_braces else f"'{field}'" + + # add field info string to list of fields + fields.append(field) + + # return list of strings in field dictionary format + return fields + + +def _handle_grib_pds_field_info(v_name, v_level, thresh): + """! Format field string to read probabilistic data from the PDS of a GRIB + file. Thresholds are formatted using thresh_lo and thresh_hi syntax. + + @param v_name name of field to read + @param v_level level of field to read + @param thresh threshold value to format if set + @returns formatted field string + """ + + field = f'name="PROB"; level="{v_level}"; prob={{ name="{v_name}";' + + if thresh: + thresh_tuple_list = get_threshold_via_regex(thresh) + for comparison, number in thresh_tuple_list: + # skip adding thresh_lo or thresh_hi if comparison is NA + if comparison == 'NA': + continue + + if comparison in ["gt", "ge", ">", ">=", "==", "eq"]: + field = f"{field} thresh_lo={number};" + if comparison in ["lt", "le", "<", "<=", "==", "eq"]: + field = f"{field} thresh_hi={number};" + + # add closing curly brace for prob= + return f'{field} }}' diff --git a/metplus/wrappers/command_builder.py b/metplus/wrappers/command_builder.py index c0286c8852..c45206013b 100755 --- a/metplus/wrappers/command_builder.py +++ b/metplus/wrappers/command_builder.py @@ -28,6 +28,7 @@ from ..util import get_custom_string_list from ..util import get_wrapped_met_config_file, add_met_config_item, format_met_config from ..util import remove_quotes +from ..util import get_field_info from ..util.met_config import add_met_config_dict # pylint:disable=pointless-string-statement @@ -1131,106 +1132,36 @@ def check_for_python_embedding(self, input_type, var_info): def get_field_info(self, d_type='', v_name='', v_level='', v_thresh=None, v_extra='', add_curly_braces=True): - """! Format field information into format expected by MET config file - Args: - @param v_level level of data to extract - @param v_thresh threshold value to use in comparison - @param v_name name of field to process - @param v_extra additional field information to add if available - @param d_type type of data to find i.e. FCST or OBS - @param add_curly_braces if True, add curly braces around each - field info string. If False, add single quotes around each - field info string (defaults to True) - @rtype string - @return Returns formatted field information + """! Format field information into format expected by MET config file. + Calls utility function found in metplus.util.field_util. + + @param v_level level of data to extract + @param v_thresh threshold value to use in comparison + @param v_name name of field to process + @param v_extra additional field information to add if available + @param d_type type of data to find i.e. FCST or OBS + @param add_curly_braces if True, add curly braces around each + field info string. If False, add single quotes around each + field info string (defaults to True) + @rtype string + @return Returns formatted field information or None on error """ - # if thresholds are set - if v_thresh: - # if neither fcst or obs are probabilistic, - # pass in all thresholds as a comma-separated list for 1 field info - if (not self.c_dict.get('FCST_IS_PROB', False) and - not self.c_dict.get('OBS_IS_PROB', False)): - thresholds = [','.join(v_thresh)] - else: - thresholds = v_thresh - # if no thresholds are specified, fail if prob field is in grib PDS - elif (self.c_dict.get(d_type + '_IS_PROB', False) and - self.c_dict.get(d_type + '_PROB_IN_GRIB_PDS', False) and - not util.is_python_script(v_name)): - self.log_error('No threshold was specified for probabilistic ' - 'forecast GRIB data') + fields = get_field_info(c_dict=self.c_dict, + data_type=d_type, + v_name=v_name, + v_level=v_level, + v_thresh=v_thresh, + v_extra=v_extra, + add_curly_braces=add_curly_braces) + + # if value returned is a string, it is an error message, + # so log error and return None + if isinstance(fields, str): + self.log_error(fields) return None - else: - thresholds = [None] - - # list to hold field information - fields = [] - for thresh in thresholds: - if (self.c_dict.get(d_type + '_PROB_IN_GRIB_PDS', False) and - not util.is_python_script(v_name)): - field = self._handle_grib_pds_field_info(v_name, v_level, - thresh) - else: - # add field name - field = f'name="{v_name}";' - - if v_level: - field += f' level="{remove_quotes(v_level)}";' - - if self.c_dict.get(d_type + '_IS_PROB', False): - field += " prob=TRUE;" - - # handle cat_thresh - if self.c_dict.get(d_type + '_IS_PROB', False): - # add probabilistic cat thresh if different from default ==0.1 - cat_thresh = self.c_dict.get(d_type + '_PROB_THRESH') - else: - cat_thresh = thresh - - if cat_thresh: - field += f" cat_thresh=[ {cat_thresh} ];" - - # handle extra options if set - if v_extra: - extra = v_extra.strip() - # if trailing semi-colon is not found, add it - if not extra.endswith(';'): - extra = f"{extra};" - field += f' {extra}' - - # add curly braces around field info - if add_curly_braces: - field = f'{{ {field} }}' - # otherwise add single quotes around field info - else: - field = f"'{field}'" - - # add field info string to list of fields - fields.append(field) - - # return list of field dictionary items return fields - def _handle_grib_pds_field_info(self, v_name, v_level, thresh): - - field = f'name="PROB"; level="{v_level}"; prob={{ name="{v_name}";' - - if thresh: - thresh_tuple_list = util.get_threshold_via_regex(thresh) - for comparison, number in thresh_tuple_list: - # skip adding thresh_lo or thresh_hi if comparison is NA - if comparison == 'NA': - continue - - if comparison in ["gt", "ge", ">", ">=", "==", "eq"]: - field = f"{field} thresh_lo={number};" - if comparison in ["lt", "le", "<", "<=", "==", "eq"]: - field = f"{field} thresh_hi={number};" - - # add closing curly brace for prob= - return f'{field} }}' - def get_command(self): """! Builds the command to run the MET application @rtype string From 84567ed6a3960556f8afa104a1a00890105cf474 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Mon, 6 Jun 2022 12:11:15 -0600 Subject: [PATCH 11/14] Per #1586, added unit tests to ensure IS_PROB probabilistic variables are read properly --- .../grid_stat/test_grid_stat_wrapper.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/internal_tests/pytests/grid_stat/test_grid_stat_wrapper.py b/internal_tests/pytests/grid_stat/test_grid_stat_wrapper.py index ebfc84a737..5125a6e059 100644 --- a/internal_tests/pytests/grid_stat/test_grid_stat_wrapper.py +++ b/internal_tests/pytests/grid_stat/test_grid_stat_wrapper.py @@ -51,6 +51,46 @@ def set_minimum_config_settings(config): config.set('config', 'OBS_VAR1_NAME', obs_name) config.set('config', 'OBS_VAR1_LEVELS', obs_level) +@pytest.mark.parametrize( + 'config_overrides, expected_values', [ + # 0 generic FCST is prob + ({'FCST_IS_PROB': True}, + {'FCST_IS_PROB': True, 'OBS_IS_PROB': False}), + # 1 generic OBS is prob + ({'OBS_IS_PROB': True}, + {'FCST_IS_PROB': False, 'OBS_IS_PROB': True}), + # 2 generic FCST and OBS is prob + ({'FCST_IS_PROB': True, 'OBS_IS_PROB': True}, + {'FCST_IS_PROB': True, 'OBS_IS_PROB': True}), + # 3 generic FCST true, wrapper FCST false + ({'FCST_IS_PROB': True, 'FCST_GRID_STAT_IS_PROB': False}, + {'FCST_IS_PROB': False, 'OBS_IS_PROB': False}), + # 4 generic OBS true, wrapper OBS false + ({'OBS_IS_PROB': True, 'OBS_GRID_STAT_IS_PROB': False}, + {'FCST_IS_PROB': False, 'OBS_IS_PROB': False}), + # 5 generic FCST unset, wrapper FCST true + ({'FCST_GRID_STAT_IS_PROB': True}, + {'FCST_IS_PROB': True, 'OBS_IS_PROB': False}), + # 6 generic OBS unset, wrapper OBS true + ({'OBS_GRID_STAT_IS_PROB': True}, + {'FCST_IS_PROB': False, 'OBS_IS_PROB': True}), + ] +) +def test_grid_stat_is_prob(metplus_config, config_overrides, expected_values): + + config = metplus_config() + + set_minimum_config_settings(config) + + # set config variable overrides + for key, value in config_overrides.items(): + config.set('config', key, value) + + wrapper = GridStatWrapper(config) + assert wrapper.isOK + for key, expected_value in expected_values.items(): + assert expected_value == wrapper.c_dict[key] + @pytest.mark.parametrize( 'config_overrides, env_var_values', [ # 0 no climo settings From 2ece10277627090bfe33580abb42d1f0a9c39cdc Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Mon, 6 Jun 2022 12:40:50 -0600 Subject: [PATCH 12/14] Per #1586, added documentation for newly supported config variables --- docs/Users_Guide/glossary.rst | 96 ++++++++++++++++++++++++++++++----- docs/Users_Guide/wrappers.rst | 14 ++++- 2 files changed, 94 insertions(+), 16 deletions(-) diff --git a/docs/Users_Guide/glossary.rst b/docs/Users_Guide/glossary.rst index dc44c7823e..dcae47e5e5 100644 --- a/docs/Users_Guide/glossary.rst +++ b/docs/Users_Guide/glossary.rst @@ -1250,14 +1250,92 @@ METplus Configuration Glossary .. warning:: **DEPRECATED:** FCST_IS_PROB - Specify whether the forecast data are probabilistic or not. Acceptable values: true/false + Boolean to specify whether the forecast data are probabilistic or not. - | *Used by:* EnsembleStat, GridStat, MODE, MTD, PointStat + | *Used by:* EnsembleStat, GridStat, MODE, MTD, PointStat, SeriesAnalysis FCST_PROB_IN_GRIB_PDS - Specify whether the probabilistic forecast data is stored in the GRIB Product Definition Section or not.Acceptable values: true/false. Only used when FCST_IS_PROB is True. This does not need to be set if the FCST__INPUT_DATATYPE is set to NetCDF. + Boolean to specify whether the probabilistic forecast data is stored in + the GRIB Product Definition Section or not. + Only used when :term:`FCST_IS_PROB` is True. - | *Used by:* EnsembleStat, GridStat, MODE, MTD, PointStat + | *Used by:* EnsembleStat, GridStat, MODE, MTD, PointStat, SeriesAnalysis + + OBS_IS_PROB + Specify whether the observation data are probabilistic or not. + Used when setting OBS_* variables to process probabilistic forecast data. + See :term:`FCST_IS_PROB` + + | *Used by:* EnsembleStat, GridStat, MODE, MTD, PointStat, SeriesAnalysis + + OBS_PROB_IN_GRIB_PDS + Boolean to specify whether the probabilistic forecast data is stored in + the GRIB Product Definition Section or not. + Used when setting OBS_* variables to process probabilistic forecast data. + Only used when :term:`OBS_IS_PROB` is True. + See :term:`FCST_PROB_IN_GRIB_PDS` and :term:`FCST_IS_PROB`. + + | *Used by:* EnsembleStat, GridStat, MODE, MTD, PointStat, SeriesAnalysis + + FCST_GRID_STAT_IS_PROB + Wrapper-specific version of :term:`FCST_IS_PROB`. + + | *Used by:* GridStat + + FCST_GRID_STAT_PROB_IN_GRIB_PDS + Wrapper-specific version of :term:`FCST_PROB_IN_GRIB_PDS`. + + | *Used by:* GridStat + + FCST_ENSEMBLE_STAT_IS_PROB + Wrapper-specific version of :term:`FCST_IS_PROB`. + + | *Used by:* EnsembleStat + + FCST_ENSEMBLE_STAT_PROB_IN_GRIB_PDS + Wrapper-specific version of :term:`FCST_PROB_IN_GRIB_PDS`. + + | *Used by:* EnsembleStat + + FCST_MODE_IS_PROB + Wrapper-specific version of :term:`FCST_IS_PROB`. + + | *Used by:* MODE + + FCST_MODE_PROB_IN_GRIB_PDS + Wrapper-specific version of :term:`FCST_PROB_IN_GRIB_PDS`. + + | *Used by:* MODE + + FCST_MTD_IS_PROB + Wrapper-specific version of :term:`FCST_IS_PROB`. + + | *Used by:* MTD + + FCST_MTD_PROB_IN_GRIB_PDS + Wrapper-specific version of :term:`FCST_PROB_IN_GRIB_PDS`. + + | *Used by:* MTD + + FCST_POINT_STAT_IS_PROB + Wrapper-specific version of :term:`FCST_IS_PROB`. + + | *Used by:* PointStat + + FCST_POINT_STAT_PROB_IN_GRIB_PDS + Wrapper-specific version of :term:`FCST_PROB_IN_GRIB_PDS`. + + | *Used by:* PointStat + + FCST_SERIES_ANALYSIS_IS_PROB + Wrapper-specific version of :term:`FCST_IS_PROB`. + + | *Used by:* SeriesAnalysis + + FCST_SERIES_ANALYSIS_PROB_IN_GRIB_PDS + Wrapper-specific version of :term:`FCST_PROB_IN_GRIB_PDS`. + + | *Used by:* SeriesAnalysis FCST_LEAD .. warning:: **DEPRECATED:** Please use :term:`FCST_LEAD_LIST` instead. @@ -2796,16 +2874,6 @@ METplus Configuration Glossary OBS_IS_DAILY_FILE .. warning:: **DEPRECATED:** - OBS_IS_PROB - Used when setting OBS_* variables to process forecast data for comparisons with mtd. Specify whether the observation data are probabilistic or not. See :term:`FCST_IS_PROB` .Acceptable values: true/false - - | *Used by:* EnsembleStat, GridStat, MODE, MTD, PointStat - - OBS_PROB_IN_GRIB_PDS - Specify whether the probabilistic observation data is stored in the GRIB Product Definition Section or not.Acceptable values: true/false. Only used when :term:`OBS_IS_PROB` is True. This does not need to be set if the OBS__INPUT_DATATYPE is set to NetCDF. - - | *Used by:* EnsembleStat, GridStat, MODE, MTD, PointStat - OBS_LEVEL .. warning:: **DEPRECATED:** Please use :term:`OBS_PCP_COMBINE_INPUT_LEVEL` instead. diff --git a/docs/Users_Guide/wrappers.rst b/docs/Users_Guide/wrappers.rst index 9ae9e7312e..0915490602 100644 --- a/docs/Users_Guide/wrappers.rst +++ b/docs/Users_Guide/wrappers.rst @@ -296,6 +296,8 @@ METplus Configuration | :term:`ENSEMBLE_STAT_PROB_CAT_THRESH` | :term:`ENSEMBLE_STAT_PROB_PCT_THRESH` | :term:`ENSEMBLE_STAT_ECLV_POINTS` +| :term:`FCST_ENSEMBLE_STAT_IS_PROB` +| :term:`FCST_ENSEMBLE_STAT_PROB_IN_GRIB_PDS` | :term:`ENSEMBLE_STAT_VERIFICATION_MASK_TEMPLATE` (optional) | :term:`ENS_VAR_NAME` (optional) | :term:`ENS_VAR_LEVELS` (optional) @@ -2948,6 +2950,8 @@ METplus Configuration | :term:`GRID_STAT_FOURIER_WAVE_1D_END` | :term:`GRID_STAT_CENSOR_THRESH` | :term:`GRID_STAT_CENSOR_VAL` +| :term:`FCST_GRID_STAT_IS_PROB` +| :term:`FCST_GRID_STAT_PROB_IN_GRIB_PDS` | :term:`GRID_STAT_MASK_GRID` (optional) | :term:`GRID_STAT_MASK_POLY` (optional) | :term:`GRID_STAT_MET_CONFIG_OVERRIDES` @@ -4114,6 +4118,8 @@ METplus Configuration | :term:`MODE_INTEREST_FUNCTION_CONVEX_HULL_DIST` | :term:`MODE_PS_PLOT_FLAG` | :term:`MODE_CT_STATS_FLAG` +| :term:`FCST_MODE_IS_PROB` +| :term:`FCST_MODE_PROB_IN_GRIB_PDS` | :term:`FCST_MODE_VAR_NAME` (optional) | :term:`FCST_MODE_VAR_LEVELS` (optional) | :term:`FCST_MODE_VAR_THRESH` (optional) @@ -4716,6 +4722,8 @@ METplus Configuration | :term:`MTD_REGRID_VLD_THRESH` | :term:`MTD_REGRID_SHAPE` | :term:`MTD_MET_CONFIG_OVERRIDES` +| :term:`FCST_MTD_IS_PROB` +| :term:`FCST_MTD_PROB_IN_GRIB_PDS` | :term:`FCST_MTD_VAR_NAME` (optional) | :term:`FCST_MTD_VAR_LEVELS` (optional) | :term:`FCST_MTD_VAR_THRESH` (optional) @@ -5209,8 +5217,6 @@ METplus Configuration | :term:`OBS_PCP_COMBINE_INPUT_TEMPLATE` | :term:`OBS_PCP_COMBINE_OUTPUT_TEMPLATE` | :term:`LOG_PCP_COMBINE_VERBOSITY` -| :term:`FCST_IS_PROB` -| :term:`OBS_IS_PROB` | :term:`FCST_PCP_COMBINE_INPUT_ACCUMS` | :term:`FCST_PCP_COMBINE_INPUT_NAMES` | :term:`FCST_PCP_COMBINE_INPUT_LEVELS` @@ -5457,6 +5463,8 @@ Configuration | :term:`POINT_STAT_HIRA_SHAPE` | :term:`POINT_STAT_HIRA_PROB_CAT_THRESH` | :term:`POINT_STAT_MESSAGE_TYPE_GROUP_MAP` +| :term:`FCST_POINT_STAT_IS_PROB` +| :term:`FCST_POINT_STAT_PROB_IN_GRIB_PDS` | :term:`FCST_POINT_STAT_WINDOW_BEGIN` (optional) | :term:`FCST_POINT_STAT_WINDOW_END` (optional) | :term:`OBS_POINT_STAT_WINDOW_BEGIN` (optional) @@ -6104,6 +6112,8 @@ METplus Configuration | :term:`SERIES_ANALYSIS_OUTPUT_STATS_PRC` | :term:`FCST_SERIES_ANALYSIS_CAT_THRESH` | :term:`OBS_SERIES_ANALYSIS_CAT_THRESH` +| :term:`FCST_SERIES_ANALYSIS_IS_PROB` +| :term:`FCST_SERIES_ANALYSIS_PROB_IN_GRIB_PDS` | .. warning:: **DEPRECATED:** From 5a3e0edcf432cb4906ad0bab6bf5b8ee7aa99802 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Wed, 8 Jun 2022 13:50:20 -0600 Subject: [PATCH 13/14] per comment in PR review from @j-opatz, added unit test for missing condition --- internal_tests/pytests/grid_stat/test_grid_stat_wrapper.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal_tests/pytests/grid_stat/test_grid_stat_wrapper.py b/internal_tests/pytests/grid_stat/test_grid_stat_wrapper.py index 5125a6e059..f211de7071 100644 --- a/internal_tests/pytests/grid_stat/test_grid_stat_wrapper.py +++ b/internal_tests/pytests/grid_stat/test_grid_stat_wrapper.py @@ -74,6 +74,9 @@ def set_minimum_config_settings(config): # 6 generic OBS unset, wrapper OBS true ({'OBS_GRID_STAT_IS_PROB': True}, {'FCST_IS_PROB': False, 'OBS_IS_PROB': True}), + # 7 generic FCST false, wrapper FCST true + ({'FCST_IS_PROB': False, 'FCST_GRID_STAT_IS_PROB': True}, + {'FCST_IS_PROB': True, 'OBS_IS_PROB': False}), ] ) def test_grid_stat_is_prob(metplus_config, config_overrides, expected_values): From bce462d7965d84d9899b9cceb2f74330cf001e1d Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Wed, 8 Jun 2022 13:52:38 -0600 Subject: [PATCH 14/14] added another unit test to check if generic is set to true but wrapper-specific is set to false --- internal_tests/pytests/grid_stat/test_grid_stat_wrapper.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal_tests/pytests/grid_stat/test_grid_stat_wrapper.py b/internal_tests/pytests/grid_stat/test_grid_stat_wrapper.py index f211de7071..34385c23c2 100644 --- a/internal_tests/pytests/grid_stat/test_grid_stat_wrapper.py +++ b/internal_tests/pytests/grid_stat/test_grid_stat_wrapper.py @@ -77,6 +77,9 @@ def set_minimum_config_settings(config): # 7 generic FCST false, wrapper FCST true ({'FCST_IS_PROB': False, 'FCST_GRID_STAT_IS_PROB': True}, {'FCST_IS_PROB': True, 'OBS_IS_PROB': False}), + # 8 generic FCST true, wrapper FCST false + ({'FCST_IS_PROB': True, 'FCST_GRID_STAT_IS_PROB': False}, + {'FCST_IS_PROB': False, 'OBS_IS_PROB': False}), ] ) def test_grid_stat_is_prob(metplus_config, config_overrides, expected_values):