diff --git a/docs/examples/comparison_workflows.md b/docs/examples/comparison_workflows.md index 0ec9b2a4e..49fbc20d5 100644 --- a/docs/examples/comparison_workflows.md +++ b/docs/examples/comparison_workflows.md @@ -21,3 +21,34 @@ comparison_experiment_paths: These experiments should have matching assimilation window parameters. By default in this suite, start and end cycle points are not specified, in which case Swell will parse the two experiments to find the matching cycle times between the two. Alternatively, start and end cycle points can be set manually. The experiment can then be created using `swell create compare_variational_marine -o override.yaml` or `swell create compare_variational_atmosphere -o override`, depending on the type of experiments being compared. Launching the experiment will run tasks analyzing the jedi log and generating plots using Eva for increments. Comparison of the log analysis will be placed under the comparison suite's directory in a file named `jedi_log_comparison.txt`, while the eva plots will be located under the cycle directory for each cycle. + +## Comparing IODA observations +The task `CompareIodaObservations` compares IODA files between experiments by checking for equality in standard IODA fields for simulated variables, including: + +``` +EffectiveError0 +EffectiveError1 +EffectiveQC0 +EffectiveQC1 +ObsBias0 +ObsBias1 +ObsValue +PreQC +hofx +hofx0, +hofx1 +oman +ombg +``` + +Which variables are compared depends on the parameter `ioda_fields_for_comparison`. By default, `hofx` will be compared for hofx experiments, and `hofx0` and `hofx1` for variational and fgat suites. If a mismatch in data field length or average is detected, this task will fail. + +The `CleanCycle` task may clear observation files depending on the setting for `clean_patterns` in `experiment.yaml`. To use this task, ensure that `clean_patterns` is set correctly in the comparison experiments. + +## Comparing Increment files +`CompareIncrement` compares increment values between experiments. By default, `geos_atmosphere` suites will compare `Salt, Temp, and ave_ssh`, `geos_atmosphere` will compare `ps, ts, ua, va, t, q`, and `geos_cf` suites compare `NO2`. Data fields will be checked to ensure they share the same size, and an average will be taken to check for equality + + +The `CleanCycle` task may clear observation files depending on the setting for `clean_patterns` in ` +experiment.yaml`. To use this task, ensure that `clean_patterns` is set correctly in the comparison +experiments. diff --git a/src/swell/suites/compare/flow.cylc b/src/swell/suites/compare/flow.cylc index 8e604e0b1..5891ca569 100644 --- a/src/swell/suites/compare/flow.cylc +++ b/src/swell/suites/compare/flow.cylc @@ -41,9 +41,12 @@ {% for path in comparison_experiment_paths %} JediOopsLogParser-{{model_component}}-{{ loop.index0 }} {% endfor %} - JediLogComparison-{{model_component}}[^]:fail? => EvaComparisonIncrement-{{model_component}} => PublishComparisons => comparison_fail - JediLogComparison-{{model_component}}[^]:fail? => EvaComparisonJediLog-{{model_component}} => PublishComparisons => comparison_fail - JediLogComparison-{{model_component}}[^]:fail? => EvaComparisonObservations-{{model_component}} => PublishComparisons => comparison_fail + CompareIodaObservations-{{model_component}}? & CompareIncrement-{{model_component}}? & JediLogComparison-{{model_component}}[^]? => Pass + + CompareIodaObservations-{{model_component}}:fail? => EvaComparisonObservations-{{model_component}}? => PublishComparisons? + CompareIncrement-{{model_component}}:fail? => EvaComparisonIncrement-{{model_component}} => PublishComparisons? + JediLogComparison-{{model_component}}[^]:fail? => EvaComparisonJediLog-{{model_component}} => PublishComparisons? + PublishComparisons? => comparison_fail? {% endif %} {% endfor %} """ @@ -69,12 +72,30 @@ script = "exit 1" {% for model_component in model_components %} + [[Pass]] + script = """ + cylc set $CYLC_WORKFLOW_ID//$datetime/EvaComparisonIncrement-{{model_component}} --out skip + cylc set $CYLC_WORKFLOW_ID//$datetime/EvaComparisonJediLog-{{model_component}} --out skip + cylc set $CYLC_WORKFLOW_ID//$datetime/EvaComparisonObservations-{{model_component}} --out skip + cylc set $CYLC_WORKFLOW_ID//$datetime/PublishComparisons --out skip + cylc set $CYLC_WORKFLOW_ID//$datetime/comparison_fail --out skip + """ + + [[CompareIncrement-{{model_component}}]] + script = "swell task CompareIncrement $config -d $datetime -m {{model_component}}" + [[EvaComparisonIncrement-{{model_component}}]] script = "swell task EvaComparisonIncrement $config -d $datetime -m {{model_component}}" [[EvaComparisonJediLog-{{model_component}}]] script = "swell task EvaComparisonJediLog $config -d $datetime -m {{model_component}}" + [[PublishComparisons]] + script = "swell task PublishComparisons $config -d $datetime -m {{model_component}}" + + [[CompareIodaObservations-{{model_component}}]] + script = "swell task CompareIodaObservations $config -d $datetime -m {{model_component}}" + [[EvaComparisonObservations-{{model_component}}]] script = "swell task EvaComparisonObservations $config -d $datetime -m {{model_component}}" platform = {{platform}} @@ -84,9 +105,6 @@ --{{key}} = {{value}} {%- endfor %} - [[PublishComparisons]] - script = "swell task PublishComparisons $config -d $datetime -m {{model_component}}" - {% if comparison_experiment_paths is mapping %} {% for path in comparison_experiment_paths.values() %} [[JediOopsLogParser-{{model_component}}-{{ loop.index0 }}]] diff --git a/src/swell/suites/compare/suite_config.py b/src/swell/suites/compare/suite_config.py index 7847c366a..0689fa63a 100644 --- a/src/swell/suites/compare/suite_config.py +++ b/src/swell/suites/compare/suite_config.py @@ -30,6 +30,9 @@ class SuiteConfig(QuestionContainer, Enum): qd.cycle_times(default_value=[None], widget_type=WidgetType.STRING_CHECK_LIST), qd.model_components(), qd.runahead_limit(), + qd.ioda_fields_for_comparison(['hofx0/{variable}', + 'hofx1/{variable}']), + qd.observations([]), ] ) @@ -41,6 +44,21 @@ class SuiteConfig(QuestionContainer, Enum): compare, qd.comparison_log_type('variational'), qd.model_components(['geos_marine']), + qd.observations([ + "adt_cryosat2n", + "adt_jason3", + "adt_saral", + "adt_sentinel3a", + "adt_sentinel3b", + "insitu_profile_argo", + "sst_ostia", + "sss_smos", + "sss_smapv5", + "sst_abi_g16_l3c", + "sst_gmi_l3u", + "sst_viirs_n20_l3u", + "temp_profile_xbt" + ]) ] ) @@ -52,6 +70,42 @@ class SuiteConfig(QuestionContainer, Enum): compare, qd.comparison_log_type('variational'), qd.model_components(['geos_atmosphere']), + qd.observations([ + "aircraft_temperature", + "aircraft_wind", + "airs_aqua", + "amsr2_gcom-w1", + "amsua_aqua", + "amsua_metop-b", + "amsua_metop-c", + "amsua_n15", + "amsua_n18", + "amsua_n19", + "atms_n20", + "atms_npp", + "avhrr3_metop-b", + "avhrr3_n18", + "avhrr3_n19", + "cris-fsr_n20", + "cris-fsr_npp", + "gmi_gpm", + "gps", + "iasi_metop-b", + "iasi_metop-c", + "mhs_metop-b", + "mhs_metop-c", + "mhs_n19", + "mls55_aura", + "omi_aura", + "ompsnm_npp", + "pibal", + "satwind", + "scatwind", + "sfcship", + "sfc", + "sondes", + "ssmis_f17" + ]) ] ) @@ -63,6 +117,10 @@ class SuiteConfig(QuestionContainer, Enum): compare, qd.comparison_log_type('variational'), qd.model_components(['geos_cf']), + qd.observations([ + "tempo_no2_tropo", + "tropomi_s5p_no2_tropo", + ]) ] ) @@ -74,6 +132,95 @@ class SuiteConfig(QuestionContainer, Enum): compare, qd.comparison_log_type('fgat'), qd.model_components(['geos_marine']), + qd.observations([ + "adt_cryosat2n", + "adt_jason3", + "adt_jason3n", + "adt_saral", + "adt_sentinel3a", + "adt_sentinel3b", + "adt_sentinel6a", + "adt_swot_nadir", + "insitu_profile_argo", + "insitu_profile_ctd", + "insitu_profile_pirata", + "insitu_profile_rama", + "insitu_profile_tao", + "icec_amsr2_north", + "icec_amsr2_south", + "icec_nsidc_nh", + "icec_nsidc_sh", + "sst_ostia", + "sss_smos", + "sss_smapv5", + "sst_abi_g16_l3c", + "sst_avhrrf_mb_l3u", + "sst_avhrrf_mc_l3u", + "sst_viirs_n20_l3u", + "sst_viirs_npp_l3u", + "temp_profile_xbt" + ]) + ] + ) + + # -------------------------------------------------------------------------------------------------- + + compare_hofx = QuestionList( + list_name="compare_hofx", + questions=[ + compare, + qd.comparison_log_type('hofx'), + qd.model_components(['geos_atmosphere']), + qd.ioda_fields_for_comparison(['hofx/{variable}']), + qd.observations([ + "aircraft_temperature", + "aircraft_wind", + "airs_aqua", + "amsr2_gcom-w1", + "amsua_aqua", + "amsua_metop-b", + "amsua_metop-c", + "amsua_n15", + "amsua_n18", + "amsua_n19", + "atms_n20", + "atms_npp", + "avhrr3_metop-b", + "avhrr3_n18", + "avhrr3_n19", + "cris-fsr_n20", + "cris-fsr_npp", + "gmi_gpm", + "gps", + "iasi_metop-b", + "iasi_metop-c", + "mhs_metop-b", + "mhs_metop-c", + "mhs_n19", + "mls55_aura", + "omi_aura", + "ompsnm_npp", + "pibal", + "satwind", + "scatwind", + "sfcship", + "sfc", + "sondes", + "ssmis_f17" + ]), + qd.mock_experiment(True) + ] + ) + + # -------------------------------------------------------------------------------------------------- + + compare_hofx_cf = QuestionList( + list_name="compare_hofx_cf", + questions=[ + compare, + qd.comparison_log_type('hofx'), + qd.ioda_fields_for_comparison(['hofx/{variable}']), + qd.model_components(['geos_cf']), ] ) diff --git a/src/swell/suites/hofx_cf/suite_config.py b/src/swell/suites/hofx_cf/suite_config.py index 9fa8ad6d1..6760047cb 100644 --- a/src/swell/suites/hofx_cf/suite_config.py +++ b/src/swell/suites/hofx_cf/suite_config.py @@ -30,16 +30,21 @@ class SuiteConfig(QuestionContainer, Enum): qd.final_cycle_point("2023-08-05T18:00:00Z"), qd.jedi_build_method("use_existing"), qd.model_components(['geos_cf']), - qd.check_for_obs(False) # don't check empty for empty obs + qd.check_for_obs(False), # don't check empty for empty obs + qd.clean_patterns([]) ], geos_cf=[ ] ) + # -------------------------------------------------------------------------------------------------- + hofx_cf_tier1 = QuestionList( list_name="hofx_cf_tier1", questions=[ hofx_cf ] ) + +# -------------------------------------------------------------------------------------------------- diff --git a/src/swell/tasks/compare_increment.py b/src/swell/tasks/compare_increment.py new file mode 100644 index 000000000..31de2431e --- /dev/null +++ b/src/swell/tasks/compare_increment.py @@ -0,0 +1,155 @@ +# (C) Copyright 2021- United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. + +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + + +# -------------------------------------------------------------------------------------------------- + +import os +import glob +import numpy as np +from ruamel.yaml import YAML +from netCDF4 import Dataset +from pathlib import Path +from swell.tasks.base.task_base import taskBase +from swell.utilities.comparisons import comparison_tags + +# -------------------------------------------------------------------------------------------------- + +increment_variables = {} + +increment_variables['geos_marine'] = ['Temp', 'Salt', 'ave_ssh'] +increment_variables['geos_atmosphere'] = ['ps', 'ts', 'ua', 'va', 't', 'q'] +increment_variables['geos_cf'] = ['NO2'] + + +class CompareIncrement(taskBase): + ''' + Compares increment results between experiments. Compares length of output results and + average along a certain length. + ''' + + def var_mean(self, incr_file: str, variables: list[str], cutoff: int | None = None) -> dict: + '''Read increment file, returns dictionary of length and mean for each variable. + + Arguments: + incr_file: Filepath to increment file + variables: Increment variables to compare + cutoff: int array index to cut off mean of array + + Returns: + Dictionary of increment variable with length and mean + ''' + var_means = {} + + with Dataset(incr_file, 'r') as ds: + for var in variables: + ds_var = ds[var] + var_means[var] = {} + var_means[var]['length'] = len(ds_var) + var_means[var]['mean'] = np.mean(ds_var[:cutoff]) + + return var_means + + # -------------------------------------------------------------------------------------------------- + + def execute(self) -> None: + + comparison_experiment_paths = self.config.comparison_experiment_paths() + + experiment_tag_paths = comparison_tags(comparison_experiment_paths, self.logger) + + tag_1 = list(experiment_tag_paths.keys())[0] + tag_2 = list(experiment_tag_paths.keys())[1] + + path_1 = list(experiment_tag_paths.values())[0] + path_2 = list(experiment_tag_paths.values())[1] + + yaml = YAML() + + increment_files = [] + + for experiment_yaml in comparison_experiment_paths: + + with open(experiment_yaml, 'r') as f: + experiment_dict = yaml.load(f) + + window_length = experiment_dict['models'][self.get_model()]['window_length'] + window_type = experiment_dict['models'][self.get_model()]['window_type'] + experiment_id = experiment_dict['experiment_id'] + + local_bkg_dir, local_bkg_dto = self.da_window_params.local_background_time( + window_length, window_type, dto=True) + + iter_no = 1 + incr_file = f'{experiment_id}.increment-iter{iter_no}.*.nc4' + if self.suite_name() == 'localensembleda': + incr_file = f'geos.mean-inc.*.nc4' + if window_type == '4D' and 'atmos' in self.suite_name(): + incr_file = f'{experiment_id}.increment-iter{iter_no}.*.nc4' + + # Soca case + if self.get_model() == 'geos_marine': + incr_file = f'ocn.{experiment_id}.incr.*.nc' + + increment_file_glob = os.path.join(os.path.dirname(experiment_yaml), '..', 'run', + self.__datetime__.string_directory(), + self.get_model(), incr_file) + + increment_file_path = glob.glob(increment_file_glob)[0] + + increment_files.append(increment_file_path) + + model_vars = increment_variables[self.get_model()] + + var_means_1 = self.var_mean(increment_files[0], model_vars, int(1e4)) + var_means_2 = self.var_mean(increment_files[1], model_vars, int(1e4)) + + output_str = '' + passed = True + + self.logger.info(f'Comparing increment files:\n{increment_files[0]}\n{increment_files[1]}') + self.logger.info(f'Comparing variables: {model_vars}') + + for var in model_vars: + output_str += f'{var} Comparison Results\n' + output_str += f'{tag_1}: {path_1}\n' + output_str += f'{tag_2}: {path_2}\n' + output_str += '\n' + + len_1 = var_means_1[var]['length'] + len_2 = var_means_2[var]['length'] + + mean_1 = var_means_1[var]['mean'] + mean_2 = var_means_2[var]['mean'] + + output_str += f'{var}\n' + if len_1 != len_2 or mean_1 != mean_2: + tag_length = max(len(tag_1), len(tag_2)) + 2 + len_length = max(len(str(len_1)), len(str(len_2))) + 2 + mean_length = max(len(str(mean_1)), len(str(mean_2))) + 2 + output_str += (f'{"":<{tag_length}} {"Length":<{len_length}} ' + f'{"Mean":<{mean_length}}\n') + output_str += (f'{tag_1:<{tag_length}} {len_1:<{len_length}} ' + f'{mean_1:<{mean_length}}\n') + output_str += (f'{tag_2:<{tag_length}} {len_2:<{len_length}} ' + f'{mean_2:<{mean_length}}\n\n') + passed = False + else: + output_str += f'Passed\n\n' + + # Fail suite if not passed + if not passed: + output_file = Path(self.cycle_dir()) / f'increment_comparison.txt' + + # Output to file + with open(output_file, 'w') as f: + f.write(output_str) + raise Exception(f'Mismatch in increment field length or average, ' + f'check {output_file}') + else: + self.logger.info('Comparison passed.') + +# -------------------------------------------------------------------------------------------------- diff --git a/src/swell/tasks/compare_ioda_observations.py b/src/swell/tasks/compare_ioda_observations.py new file mode 100644 index 000000000..ceaf553ef --- /dev/null +++ b/src/swell/tasks/compare_ioda_observations.py @@ -0,0 +1,146 @@ +# (C) Copyright 2021- United States Government as represented by the Administrator of the +# National Aeronautics and Space Administration. All Rights Reserved. + +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + + +# -------------------------------------------------------------------------------------------------- + +import os +import numpy as np +from ruamel.yaml import YAML +from netCDF4 import Dataset +from pathlib import Path +from swell.tasks.base.task_base import taskBase +from swell.utilities.comparisons import comparison_tags + +# -------------------------------------------------------------------------------------------------- + + +class CompareIodaObservations(taskBase): + + def ioda_means(self, exp_path: str, observation: str, field: str, cutoff: int | None = None): + ''' Reads observation files for ioda variables, returning total length of + the array and mean of all data points. + + Arguments: + exp_path: experiment.yaml location for experiment to compare + observation: ioda name of observation + field: name of data field + cutoff: int array index to cut off mean of array + + Returns: + Dictionary of simulated variable with length and mean + ''' + + exp_path = Path(exp_path) + + window_length = self.config.window_length() + window_begin = self.da_window_params.window_begin(window_length) + + obs_path = (Path(os.path.dirname(exp_path)) / '..' / 'run' / + self.__datetime__.string_directory() / self.get_model()) + + # Get list of obs files + obs_files = list(obs_path.glob(f'*.{observation}.{window_begin}.nc4')) + + # If empty obs return black dictionary + if len(obs_files) > 0: + obs_file = obs_files[0] + else: + return {} + + # Read obs config yaml to get names of simulated variables + yaml = YAML(typ='safe') + with open(obs_path / 'obs.yaml', 'r') as f: + obs_config = yaml.load(f) + + for ob in obs_config: + if ob['observation_name'] == observation: + simulated_variables = ob['obs space']['simulated variables'] + + field_means = {} + with Dataset(obs_file, 'r') as ds: + for sim_var in simulated_variables: + var_name = field.format(variable=sim_var) + obs_var = ds[var_name] + field_means[var_name] = {} + field_means[var_name]['length'] = len(obs_var) + field_means[var_name]['mean'] = np.mean(obs_var[0:cutoff]) + + return field_means + + # -------------------------------------------------------------------------------------------------- + + def execute(self) -> None: + + ''' + Reads observation files for ioda variables, compare total length of array and mean + of data points to evaluate diff. Output is sent to + /ioda__comparison.txt + ''' + + comparison_experiment_paths = self.config.comparison_experiment_paths() + observations = self.config.observations() + ioda_fields = self.config.ioda_fields_for_comparison() + + experiment_tag_paths = comparison_tags(comparison_experiment_paths, self.logger) + + tag_1 = list(experiment_tag_paths.keys())[0] + tag_2 = list(experiment_tag_paths.keys())[1] + + path_1 = list(experiment_tag_paths.values())[0] + path_2 = list(experiment_tag_paths.values())[1] + + for observation in observations: + + output_str = f'{observation} Comparison Results\n' + output_str += f'{tag_1}: {path_1}\n' + output_str += f'{tag_2}: {path_2}\n' + output_str += '\n' + passed = True + + for field in ioda_fields: + + field_means_1 = self.ioda_means(path_1, observation, field, cutoff=int(1e4)) + field_means_2 = self.ioda_means(path_2, observation, field, cutoff=int(1e4)) + + if len(field_means_1) != len(field_means_2): + raise Exception(f'Length of {field} fields does not ' + 'match between experiments.') + + output_str += f'{observation}\n' + for sim_var in field_means_1.keys(): + len_1 = field_means_1[sim_var]['length'] + len_2 = field_means_2[sim_var]['length'] + + mean_1 = field_means_1[sim_var]['mean'] + mean_2 = field_means_2[sim_var]['mean'] + + output_str += f'{sim_var}\n' + if len_1 != len_2 or mean_1 != mean_2: + tag_length = max(len(tag_1), len(tag_2)) + 2 + len_length = max(len(str(len_1)), len(str(len_2))) + 2 + mean_length = max(len(str(mean_1)), len(str(mean_2))) + 2 + output_str += (f'{"":<{tag_length}} {"Length":<{len_length}} ' + f'{"Mean":<{mean_length}}\n') + output_str += (f'{tag_1:<{tag_length}} {len_1:<{len_length}} ' + f'{mean_1:<{mean_length}}\n') + output_str += (f'{tag_2:<{tag_length}} {len_2:<{len_length}} ' + f'{mean_2:<{mean_length}}\n\n') + passed = False + else: + output_str += f'Passed\n\n' + + # Fail suite if not passed + if not passed: + output_file = Path(self.cycle_dir()) / f'ioda_{observation}_comparison.txt' + + # Output to file + with open(output_file, 'w') as f: + f.write(output_str) + raise Exception(f'Mismatch in IODA field length or average, ' + f'check {output_file}') + +# -------------------------------------------------------------------------------------------------- diff --git a/src/swell/tasks/eva_comparison_observations.py b/src/swell/tasks/eva_comparison_observations.py index 59a318cbe..c6f4d7f9e 100644 --- a/src/swell/tasks/eva_comparison_observations.py +++ b/src/swell/tasks/eva_comparison_observations.py @@ -11,6 +11,7 @@ from multiprocessing import Pool import os import yaml +from pathlib import Path from eva.eva_driver import eva @@ -62,7 +63,6 @@ def execute(self) -> None: experiment_config_1 = yaml.safe_load(f) experiment_id_1 = experiment_config_1['experiment_id'] comparison_suite = experiment_config_1['suite_to_run'] - observations = experiment_config_1['models'][model]['observations'] # Second file parameters with open(experiment_path_2, 'r') as f: @@ -120,16 +120,21 @@ def execute(self) -> None: 445, 552, 573, 906, 1121, 1194, 1427, 1585], } - # Loop over observations and create dictionaries - # ---------------------------------------------- - eva_dicts = [] # Empty list of dictionaries - # Set the observing system records path self.jedi_rendering.set_obs_records_path(self.config.observing_system_records_path(None)) - for observation in observations: + eva_dicts = [] + + for observation in self.config.observations(): + + obs_comparison_file = Path(self.cycle_dir()) / f'ioda_{observation}_comparison.txt' + if not obs_comparison_file.exists(): + continue + if self.get_model() == 'geos_atmosphere': obs_long_name = ioda_name_to_long_name(observation, self.logger) + obs_long_name = obs_long_name.replace('(', '') + obs_long_name = obs_long_name.replace(')', '') else: obs_long_name = observation @@ -144,7 +149,7 @@ def execute(self) -> None: observation_dict_2 = value.copy() if observation_dict_1 is None or observation_dict_2 is None: - continue + return # Check if IODA observation input and output have non-zero location dimensions use_obs_1 = check_obs(self.jedi_rendering.observing_system_records_path, observation, @@ -156,7 +161,7 @@ def execute(self) -> None: use_obs = use_obs_1 and use_obs_2 if not use_obs: - continue + return # Split the full path into path and filename obs_path_file_1 = observation_dict_1['obs space']['obsdataout']['engine']['obsfile'] @@ -261,8 +266,8 @@ def execute(self) -> None: with open(conf_output, 'w') as outfile: yaml.dump(eva_dict, outfile, default_flow_style=False) - # Add eva dictionary to list - # -------------------------- + # Call Eva + # -------- eva_dicts.append(eva_dict) # Call eva in parallel diff --git a/src/swell/tasks/jedi_log_comparison.py b/src/swell/tasks/jedi_log_comparison.py index ebb081d6f..68085f364 100644 --- a/src/swell/tasks/jedi_log_comparison.py +++ b/src/swell/tasks/jedi_log_comparison.py @@ -27,6 +27,11 @@ class JediLogComparison(taskBase): def execute(self): + log_type = self.config.comparison_log_type() + + if log_type == 'hofx': + return + experiment_paths = self.config.comparison_experiment_paths() experiment_tag_paths = comparison_tags(experiment_paths, self.logger) @@ -59,8 +64,6 @@ def execute(self): # Boolean for whether fields fall within tolerances passed = True - log_type = self.config.comparison_log_type() - for exp_tag, experiment_path in experiment_tag_paths.items(): # Paths to cycle dirs diff --git a/src/swell/tasks/jedi_oops_log_parser.py b/src/swell/tasks/jedi_oops_log_parser.py index 543f77dda..11b9c8a22 100644 --- a/src/swell/tasks/jedi_oops_log_parser.py +++ b/src/swell/tasks/jedi_oops_log_parser.py @@ -48,9 +48,7 @@ def execute(self) -> None: output_file = os.path.join(self.cycle_dir(), 'jedi_log_analysis.txt') - for parser_option in self.config.parser_options(['fgrep_residual_norm']): - if parser_option == 'fgrep_residual_norm': - self.fgrep_residual_norm(output_file) - + if self.config.comparison_log_type() == 'variational': + self.fgrep_residual_norm(output_file) # -------------------------------------------------------------------------------------------------- diff --git a/src/swell/tasks/task_questions.py b/src/swell/tasks/task_questions.py index 640509295..debdc0dc1 100644 --- a/src/swell/tasks/task_questions.py +++ b/src/swell/tasks/task_questions.py @@ -196,6 +196,17 @@ class TaskQuestions(QuestionContainer, Enum): # -------------------------------------------------------------------------------------------------- + CompareIodaObservations = QuestionList( + list_name="CompareIodaObservations", + questions=[ + qd.window_length(), + qd.ioda_fields_for_comparison(), + qd.observations() + ] + ) + + # -------------------------------------------------------------------------------------------------- + ConvertObsToIoda = QuestionList( list_name="ConvertObsToIoda", questions=[ @@ -232,6 +243,7 @@ class TaskQuestions(QuestionContainer, Enum): list_name="EvaComparisonObservations", questions=[ qd.comparison_log_type(), + qd.observations() ] ) diff --git a/src/swell/utilities/question_defaults.py b/src/swell/utilities/question_defaults.py index ed72fab1f..78906fe52 100644 --- a/src/swell/utilities/question_defaults.py +++ b/src/swell/utilities/question_defaults.py @@ -151,17 +151,6 @@ class model_components(SuiteQuestion): # -------------------------------------------------------------------------------------------------- - @dataclass - class parser_options(SuiteQuestion): - default_value: list = mutable_field(['fgrep_residual_norm']) - question_name: str = "parser_options" - ask_question: bool = True - options: list = mutable_field(['fgrep_residual_norm']) - prompt: str = "List the test types to run on the JEDI oops log." - widget_type: WType = WType.STRING_DROP_LIST - - # -------------------------------------------------------------------------------------------------- - @dataclass class r2d2_experiment_id(SuiteQuestion): default_value: str = "defer_to_code" @@ -983,6 +972,32 @@ class horizontal_resolution(TaskQuestion): # ------------------------------------------------------------------------------------------------ + @dataclass + class ioda_fields_for_comparison(TaskQuestion): + default_value: list[str] = mutable_field(['hofx']) + question_name: str = 'ioda_fields_for_comparison' + ask_question: bool = False + options: list[str] = mutable_field([ + 'EffectiveError0/{variable}', + 'EffectiveError1/{variable}', + 'EffectiveQC0/{variable}', + 'EffectiveQC1/{variable}', + 'ObsBias0/{variable}', + 'ObsBias1/{variable}', + 'ObsValue/{variable}', + 'PreQC/{variable}', + 'hofx/{variable}' + 'hofx0/{variable}', + 'hofx1/{variable}', + 'oman/{variable}', + 'ombg/{variable}' + ]) + models: List[str] = mutable_field(['all_models']) + prompt: str = "List of IODA fields to run comparisons on for two experiments." + widget_type: WType = WType.STRING_CHECK_LIST + + # ------------------------------------------------------------------------------------------------ + @dataclass class dry_run(TaskQuestion): default_value: bool = True