From 5ec3e42fc0216f8a996a7e9f053516bb488c3862 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Thu, 31 Oct 2024 06:03:08 -0600 Subject: [PATCH 1/2] Add CCPP register phase (#582) ## Overview This PR adds a new phase, *register*, that can be called by a host model and used by schemes to perform any set up that needs to happen BEFORE the grid is established. NOTE: this PR also *removes* the old `dynamic_constituent_routine` metadata implementation for runtime constituents. ## Description I have implemented it as an "optional" phase, by which I mean that it is not required that a host model call this phase (though I'm happy to be overruled!). As a result, the register phase does not change the CCPP "state" (but will produce an error if it is called after the `init` phase). More: ### Dynamic/run-time constituent handling: - If a scheme has run-time constituents, those shall be allocated, instantiated, and returned from the scheme's register phase. This metadata is required (the framework determines that there are runtime constituents from a scheme if there is a `ccpp_constituent_properties_t` variable required): ``` [ ] standard_name = dimensions = (:) type = ccpp_constituent_properties_t intent = out allocatable = true ``` - The standard name doesn't really matter but MUST be different from other runtime constituent standard names in the scheme; it may be easiest to standardize this to something like `dynamic_constituents_for_` - The framework will then compile all scheme constituents into module-level variables in the host cap called `_dynamic_constituents`, which are then used to pack and initialize the module level constituents object `_constituents_obj`. - If there are no dynamic constituents registered by any schemes within a suite, that suite's dynamic constituents array is allocated to 0. *Generated host cap code examples* 1. Multiple schemes have dynamic constituents: ``` subroutine test_host_ccpp_physics_register(suite_name, errmsg, errflg) use ccpp_cld_suite_cap, only: cld_suite_register character(len=*) :: suite_name character(len=512) :: errmsg integer :: errflg type(ccpp_constituent_properties_t),allocatable :: dyn_const(:) type(ccpp_constituent_properties_t),allocatable :: dyn_const_ice(:) integer :: num_dyn_consts integer :: const_index errflg = 0 errmsg = "" if (trim(suite_name) == 'cld_suite') then call cld_suite_register(errflg=errflg, errmsg=errmsg, dyn_const=dyn_const, & dyn_const_ice=dyn_const_ice) allocate(cld_suite_dynamic_constituents(0+size(dyn_const)+size(dyn_const_ice))) ! Pack the suite-level dynamic, run-time constituents array num_dyn_consts = 0 do const_index = 1, size(dyn_const) cld_suite_dynamic_constituents(num_dyn_consts + const_index) = dyn_const(const_index) end do num_dyn_consts = num_dyn_consts + size(dyn_const) deallocate(dyn_const) do const_index = 1, size(cld_suite_dynamic_constituents) call cld_suite_dynamic_constituents(const_index)%standard_name(stdname, & errcode=errflg, errmsg=errmsg) end do do const_index = 1, size(dyn_const_ice) cld_suite_dynamic_constituents(num_dyn_consts + const_index) = & dyn_const_ice(const_index) end do num_dyn_consts = num_dyn_consts + size(dyn_const_ice) deallocate(dyn_const_ice) else write(errmsg, '(3a)')"No suite named ", trim(suite_name), "found" errflg = 1 end if end subroutine test_host_ccpp_physics_register ``` 2. No schemes have dynamic constituents: ``` subroutine test_host_ccpp_physics_register(suite_name, errmsg, errflg) use ccpp_ddt_suite_cap, only: ddt_suite_register use ccpp_temp_suite_cap, only: temp_suite_register character(len=*) :: suite_name character(len=512) :: errmsg integer :: errflg errflg = 0 errmsg = "" if (trim(suite_name) == 'ddt_suite') then call ddt_suite_register(errflg=errflg, errmsg=errmsg) ! Suite does not return dynamic constituents; allocate to zero allocate(ddt_suite_dynamic_constituents(0)) else if (trim(suite_name) == 'temp_suite') then call temp_suite_register(errflg=errflg, errmsg=errmsg, config_var=config_var) ! Suite does not return dynamic constituents; allocate to zero allocate(temp_suite_dynamic_constituents(0)) else write(errmsg, '(3a)')"No suite named ", trim(suite_name), "found" errflg = 1 end if end subroutine test_host_ccpp_physics_register ``` ### Misc notes Since this phase is called before the grid is initialized, variables are not allocated at this time (that still happens in `init`) and no variables with horizontal and vertical dimensions can be passed in. ## UI Changes User interface changes?: Yes, but they're optional If a host model wishes to utilize schemes' register phases, they must add a call to `_ccpp_physics_register(suite_name, errmsg, errflg)` ## Testing test removed: removed unit tests for dyn_const_routines (old implementation of runtime constituent handling) - all pass unit tests: Removed old dynamic constituents testing - all pass system tests: Updated capgen and advection tests to include register phases (with and without dynamic constituents) - Also updated advection test CMakeLists to first run a version with dynamic constituents in the wrong phase and have an expected error - This is perhaps not the best way to test this, but it's what I came up with manual testing: Fixes: closes #572 --------- Co-authored-by: Courtney Peverley Co-authored-by: Courtney Peverley Co-authored-by: Courtney Peverley --- scripts/ccpp_capgen.py | 52 +-------- scripts/ccpp_datafile.py | 65 ----------- scripts/ccpp_state_machine.py | 5 +- scripts/ccpp_suite.py | 27 +++-- scripts/constituents.py | 62 +++-------- scripts/host_cap.py | 96 ++++++++++++++-- scripts/metadata_table.py | 11 +- scripts/parse_tools/parse_checkers.py | 2 +- scripts/suite_objects.py | 23 +++- src/ccpp_constituent_prop_mod.F90 | 3 + src/ccpp_constituent_prop_mod.meta | 11 ++ test/advection_test/CMakeLists.txt | 45 ++++++++ test/advection_test/cld_ice.F90 | 54 ++++----- test/advection_test/cld_ice.meta | 26 ++++- test/advection_test/cld_liq.F90 | 48 ++++---- test/advection_test/cld_liq.meta | 27 ++++- test/advection_test/cld_suite_error.xml | 9 ++ test/advection_test/cld_suite_files_error.txt | 3 + test/advection_test/dlc_liq.F90 | 41 +++++++ test/advection_test/dlc_liq.meta | 29 +++++ test/advection_test/run_test | 6 +- test/advection_test/test_host.F90 | 34 +++++- test/advection_test/test_reports.py | 8 +- test/capgen_test/run_test | 2 + test/capgen_test/temp_adjust.F90 | 22 ++++ test/capgen_test/temp_adjust.meta | 24 ++++ test/capgen_test/test_host.F90 | 10 ++ test/capgen_test/test_host_mod.F90 | 1 + test/capgen_test/test_host_mod.meta | 8 +- test/capgen_test/test_reports.py | 1 + test/unit_tests/sample_files/test_host.meta | 1 - .../duplicate_dyn_const.F90 | 96 ---------------- .../duplicate_dyn_const.meta | 104 ------------------ .../dyn_const_not_present.F90 | 75 ------------- .../dyn_const_not_present.meta | 104 ------------------ .../dyn_const_not_present_nested.F90 | 100 ----------------- .../dyn_const_not_present_nested.meta | 104 ------------------ .../sample_scheme_files/temp_adjust.F90 | 42 +++---- .../sample_scheme_files/temp_adjust.meta | 33 +++++- test/unit_tests/test_metadata_scheme_file.py | 43 +------- test/unit_tests/test_metadata_table.py | 3 - 41 files changed, 557 insertions(+), 903 deletions(-) create mode 100644 test/advection_test/cld_suite_error.xml create mode 100644 test/advection_test/cld_suite_files_error.txt create mode 100644 test/advection_test/dlc_liq.F90 create mode 100644 test/advection_test/dlc_liq.meta delete mode 100644 test/unit_tests/sample_scheme_files/duplicate_dyn_const.F90 delete mode 100644 test/unit_tests/sample_scheme_files/duplicate_dyn_const.meta delete mode 100644 test/unit_tests/sample_scheme_files/dyn_const_not_present.F90 delete mode 100644 test/unit_tests/sample_scheme_files/dyn_const_not_present.meta delete mode 100644 test/unit_tests/sample_scheme_files/dyn_const_not_present_nested.F90 delete mode 100644 test/unit_tests/sample_scheme_files/dyn_const_not_present_nested.meta diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index b16998f3..9eac0ecf 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -400,7 +400,7 @@ def compare_fheader_to_mheader(meta_header, fort_header, logger): ############################################################################### def check_fortran_against_metadata(meta_headers, fort_headers, mfilename, ffilename, logger, - dyn_routines=None, fortran_routines=None): + fortran_routines=None): ############################################################################### """Compare a set of metadata headers from against the code in the associated Fortran file, . @@ -452,17 +452,6 @@ def check_fortran_against_metadata(meta_headers, fort_headers, 's' if num_errors > 1 else '', mfilename, ffilename)) # end if - # Check that any dynamic constituent routines declared in the metadata are - # present in the Fortran - if dyn_routines: - for routine in dyn_routines: - if routine not in fortran_routines: - # throw an error - it's not in the Fortran - errmsg = f"Dynamic constituent routine {routine} not found in fortran {ffilename}" - raise CCPPError(errmsg) - # end if - # end for - # end if # No return, an exception is raised on error ############################################################################### @@ -561,15 +550,8 @@ def parse_scheme_files(scheme_filenames, run_env, skip_ddt_check=False): for sect in [x.sections() for x in ftables]: fheaders.extend(sect) # end for - dyn_routines = [] - for table in mtables: - if table.dyn_const_routine: - dyn_routines.append(table.dyn_const_routine) - # end if - # end for check_fortran_against_metadata(mheaders, fheaders, filename, fort_file, logger, - dyn_routines=dyn_routines, fortran_routines=additional_routines) # Check for duplicate tables, then add to dict for table in mtables: @@ -593,22 +575,6 @@ def parse_scheme_files(scheme_filenames, run_env, skip_ddt_check=False): # end if # end for # end for - # Check for duplicate dynamic constituent routine names - dyn_val_dict = {} - for table in table_dict: - routine_name = table_dict[table].dyn_const_routine - if routine_name: - if routine_name in dyn_val_dict: - # dynamic constituent routines must have unique names - scheme_name = dyn_val_dict[routine_name] - errmsg = f"ERROR: Dynamic constituent routine names must be unique. Cannot add " \ - f"{routine_name} for {table}. Routine already exists in {scheme_name}. " - raise CCPPError(errmsg) - else: - dyn_val_dict[routine_name] = table - # end if - # end if - # end for return header_dict.values(), table_dict @@ -674,24 +640,12 @@ def capgen(run_env, return_db=False): # First up, handle the host files host_model = parse_host_model_files(host_files, host_name, run_env) # Next, parse the scheme files - # We always need to parse the ccpp_constituent_prop_ptr_t DDT + # We always need to parse the constituent DDTs const_prop_mod = os.path.join(src_dir, "ccpp_constituent_prop_mod.meta") if const_prop_mod not in scheme_files: scheme_files= [const_prop_mod] + scheme_files # end if scheme_headers, scheme_tdict = parse_scheme_files(scheme_files, run_env) - # Pull out the dynamic constituent routines, if any - dyn_const_dict = {} - dyn_val_dict = {} - for table in scheme_tdict: - routine_name = scheme_tdict[table].dyn_const_routine - if routine_name is not None: - if routine_name not in dyn_val_dict: - dyn_const_dict[table] = routine_name - dyn_val_dict[routine_name] = table - # end if - # end if - # end for if run_env.verbose: ddts = host_model.ddt_lib.keys() if ddts: @@ -722,7 +676,7 @@ def capgen(run_env, return_db=False): # end if os.makedirs(outtemp_dir) # end if - ccpp_api = API(sdfs, host_model, scheme_headers, run_env, dyn_const_dict) + ccpp_api = API(sdfs, host_model, scheme_headers, run_env) cap_filenames = ccpp_api.write(outtemp_dir, run_env) if run_env.generate_host_cap: # Create a cap file diff --git a/scripts/ccpp_datafile.py b/scripts/ccpp_datafile.py index 20dee70d..6bb5e537 100755 --- a/scripts/ccpp_datafile.py +++ b/scripts/ccpp_datafile.py @@ -58,8 +58,6 @@ {"report" : "dependencies", "type" : bool, "help" : ("Return a list of scheme and host " "dependency module names")}, - {"report" : "dyn_const_routines", "type" : bool, - "help" : ("Return the constituent routines for a suite")}, {"report" : "suite_list", "type" : bool, "help" : "Return a list of configured suite names"}, {"report" : "required_variables", "type" : str, @@ -106,8 +104,6 @@ def __init__(self, action, value=True): # Test a valid action >>> DatatableReport('input_variables', False).action 'input_variables' - >>> DatatableReport('dyn_const_routines', True).value - True # Test an invalid action >>> DatatableReport('banana', True).value @@ -400,40 +396,6 @@ def _retrieve_dependencies(table): # end for return sorted(result) -############################################################################### -def _retrieve_dyn_const_routines(table): -############################################################################### - """Find and return a list of all scheme constituent routines. - # Test valid dynamic constituent routines - >>> table = ET.fromstring("" \ - "dyn_const_get" \ - "dyn_const_2" \ - "") - >>> _retrieve_dyn_const_routines(table) - ['dyn_const_2', 'dyn_const_get'] - - # Test no dynamic constituent routines - >>> table = ET.fromstring("" \ - "") - >>> _retrieve_dyn_const_routines(table) - [] - - # Test missing dynamic constituent routines tag - >>> table = ET.fromstring("") - >>> _retrieve_dyn_const_routines(table) - Traceback (most recent call last): - ... - ccpp_datafile.CCPPDatatableError: Could not find 'dyn_const_routines' element - - """ - routines = table.find("dyn_const_routines") - if routines is None: - raise CCPPDatatableError("Could not find 'dyn_const_routines' element") - # end if - routine_names = [routine.text for routine in routines if routine.text] - # end for - return sorted(routine_names) - ############################################################################### def _find_var_dictionary(table, dict_name=None, dict_type=None): ############################################################################### @@ -746,8 +708,6 @@ def datatable_report(datatable, action, sep, exclude_protected=False): result = _retrieve_module_list(table) elif action.action_is("dependencies"): result = _retrieve_dependencies(table) - elif action.action_is("dyn_const_routines"): - result = _retrieve_dyn_const_routines(table) elif action.action_is("suite_list"): result = _retrieve_suite_list(table) elif action.action_is("required_variables"): @@ -1093,20 +1053,6 @@ def _add_dependencies(parent, scheme_depends, host_depends): entry.text = sfile # end for -############################################################################### -def _add_dyn_const_routine(file_entry, routine, scheme): -############################################################################### - """Add a section to that lists all the constituent routines - for the suite - >>> file_entry = ET.fromstring("") - >>> _add_dyn_const_routine(file_entry, 'test_dyn_const', 'test_scheme') - >>> table_entry_pretty_print(file_entry, 0) - '\\n \\n \\n test_dyn_const\\n \\n\\n' - """ - entry = ET.SubElement(file_entry, "dyn_const_routine") - entry.text = routine - entry.set("parent", scheme) - ############################################################################### def _add_generated_files(parent, host_files, suite_files, ccpp_kinds, src_dir): ############################################################################### @@ -1233,17 +1179,6 @@ def generate_ccpp_datatable(run_env, host_model, api, scheme_headers, # end for # end for _add_dependencies(datatable, scheme_depends, host_depends) - # Add in all constituent routines - first_const_routine = True - for table in scheme_tdict: - if scheme_tdict[table].dyn_const_routine is not None: - if first_const_routine: - file_entry = ET.SubElement(datatable, "dyn_const_routines") - first_const_routine = False - # end if - _add_dyn_const_routine(file_entry, scheme_tdict[table].dyn_const_routine, table) - # end if - # end for # Write tree datatable_tree = PrettyElementTree(datatable) datatable_tree.write(run_env.datatable_file) diff --git a/scripts/ccpp_state_machine.py b/scripts/ccpp_state_machine.py index 0de2c7bd..832e0073 100644 --- a/scripts/ccpp_state_machine.py +++ b/scripts/ccpp_state_machine.py @@ -3,6 +3,7 @@ # CCPP framework imports from state_machine import StateMachine +_REG_ST = r"(?:register)" _INIT_ST = r"(?:init(?:ial(?:ize)?)?)" _FINAL_ST = r"(?:final(?:ize)?)" _RUN_ST = r"(?:run)" @@ -12,7 +13,9 @@ # Allowed CCPP transitions # pylint: disable=bad-whitespace RUN_PHASE_NAME = 'run' -CCPP_STATE_MACH = StateMachine((('initialize', 'uninitialized', +CCPP_STATE_MACH = StateMachine((('register', 'uninitialized', + 'uninitialized', _REG_ST), + ('initialize', 'uninitialized', 'initialized', _INIT_ST), ('timestep_initial', 'initialized', 'in_time_step', _TS_INIT_ST), diff --git a/scripts/ccpp_suite.py b/scripts/ccpp_suite.py index 67e89ede..b2d4c36e 100644 --- a/scripts/ccpp_suite.py +++ b/scripts/ccpp_suite.py @@ -70,6 +70,8 @@ class Suite(VarDictionary): ''' # Note that these group names need to match CCPP_STATE_MACH + __register_group_name = 'register' + __initial_group_name = 'initialize' __final_group_name = 'finalize' @@ -205,6 +207,8 @@ def parse(self, run_env): if run_env.logger and run_env.logger.isEnabledFor(logging.INFO): run_env.logger.info(lmsg.format(self.name)) # end if + gname = Suite.__register_group_name + self.__suite_reg_group = self.new_group_from_name(gname, run_env) gname = Suite.__initial_group_name self.__suite_init_group = self.new_group_from_name(gname, run_env) gname = Suite.__final_group_name @@ -214,11 +218,13 @@ def parse(self, run_env): gname = Suite.__timestep_final_group_name self.__timestep_final_group = self.new_group_from_name(gname, run_env) # Set up some groupings for later efficiency - self._beg_groups = [self.__suite_init_group.name, + self._beg_groups = [self.__suite_reg_group.name, + self.__suite_init_group.name, self.__timestep_init_group.name] self._end_groups = [self.__suite_final_group.name, self.__timestep_final_group.name] # Build hierarchical structure as in SDF + self.__groups.append(self.__suite_reg_group) self.__groups.append(self.__suite_init_group) self.__groups.append(self.__timestep_init_group) for suite_item in suite_xml: @@ -560,8 +566,13 @@ def write(self, output_dir, run_env): outfile.end_module_header() for group in self.__groups: if group.name in self._beg_groups: - group.write(outfile, self.__host_arg_list_noloop, - 1, const_mod, suite_vars=self, allocate=True) + if group.name == self.__suite_reg_group.name: + group.write(outfile, self.__host_arg_list_noloop, + 1, const_mod, suite_vars=self) + else: + group.write(outfile, self.__host_arg_list_noloop, + 1, const_mod, suite_vars=self, allocate=True) + # end if elif group.name in self._end_groups: group.write(outfile, self.__host_arg_list_noloop, 1, const_mod, suite_vars=self, deallocate=True) @@ -615,7 +626,7 @@ class API(VarDictionary): 'kind':'len=*', 'units':'', 'dimensions':'()'}, _API_SOURCE, _API_DUMMY_RUN_ENV) - def __init__(self, sdfs, host_model, scheme_headers, run_env, dyn_const_dict={}): + def __init__(self, sdfs, host_model, scheme_headers, run_env): """Initialize this API. is the list of Suite Definition Files to be parsed for data needed by the CCPP cap. @@ -624,14 +635,11 @@ def __init__(self, sdfs, host_model, scheme_headers, run_env, dyn_const_dict={}) is the list of parsed physics scheme metadata files. Every scheme referenced by an SDF in MUST be in this list, however, unused schemes are allowed. - is the dictionary (key = scheme name) of dynamic - constituent routine names is the CCPPFrameworkEnv object for this framework run. """ self.__module = 'ccpp_physics_api' self.__host = host_model self.__suites = list() - self.__dyn_const_dict = dyn_const_dict super().__init__(self.module, run_env, parent_dict=self.host_model) # Create a usable library out of scheme_headers # Structure is dictionary of dictionaries @@ -1187,11 +1195,6 @@ def suites(self): "Return the list of this API's suites" return self.__suites - @property - def dyn_const_dict(self): - """Return the dynamic constituent routine dictionary""" - return self.__dyn_const_dict - ############################################################################### if __name__ == "__main__": try: diff --git a/scripts/constituents.py b/scripts/constituents.py index 84f19b20..b1a08362 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -440,12 +440,11 @@ def write_constituent_use_statements(cap, suite_list, indent): @staticmethod def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcname, query_const_funcname, copy_in_funcname, copy_out_funcname, cleanup_funcname, - const_obj_name, dyn_const_name, const_names_name, const_indices_name, + const_obj_name, dyn_const_names, const_names_name, const_indices_name, const_array_func, advect_array_func, prop_array_func, - const_index_func, suite_list, dyn_const_dict, err_vars): + const_index_func, suite_list, err_vars): """Write out the host model routine which will instantiate constituent fields for all the constituents in . - is a dictionary (key=scheme name) of dynamic constituent routines is a list of the host model's error variables. Also write out the following routines: : Initialize constituent data @@ -479,13 +478,6 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.comment("Create constituent object for suites in ", 2) cap.blank_line() ConstituentVarDict.write_constituent_use_statements(cap, suite_list, 2) - # Conditionally include use statements for dynamic constituent routines - if len(dyn_const_dict) > 0: - cap.comment("Dynamic constituent routines", 2) - for scheme in dyn_const_dict: - cap.write(f"use {scheme}, only: {dyn_const_dict[scheme]}", 2) - # end for - # end if cap.blank_line() cap.comment("Dummy arguments", 2) cap.write(f"type({CONST_PROP_TYPE}), target, intent(in) :: " + \ @@ -497,19 +489,12 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna spc = ' '*37 cap.write(f"integer{spc} :: num_suite_consts", 2) cap.write(f"integer{spc} :: num_consts", 2) - cap.write(f"integer{spc} :: num_dyn_consts", 2) cap.write(f"integer{spc} :: index, index_start", 2) cap.write(f"integer{spc} :: field_ind", 2) cap.write(f"type({CONST_PROP_TYPE}), pointer :: const_prop => NULL()", 2) - # Declare dynamic constituent properties variables - for idx, scheme in enumerate(sorted(dyn_const_dict)): - cap.comment(f"dynamic constituent props variable for {scheme}", 2) - cap.write(f"type({CONST_PROP_TYPE}), allocatable :: dyn_const_prop_{idx}(:)", 2) - # end for cap.blank_line() cap.write(f"{herrcode} = 0", 2) cap.write("num_consts = size(host_constituents, 1)", 2) - cap.write("num_dyn_consts = 0", 2) for suite in suite_list: const_dict = suite.constituent_dictionary() funcname = const_dict.num_consts_funcname() @@ -522,32 +507,6 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.write("end if", 2) cap.write("num_consts = num_consts + num_suite_consts", 2) # end for - # Check for dynamic constituent routines - if len(dyn_const_dict) > 0: - cap.comment("Add in dynamic constituents", 2) - for idx, scheme in enumerate(sorted(dyn_const_dict)): - cap.write(f"call {dyn_const_dict[scheme]}(dyn_const_prop_{idx}, {obj_err_callstr})", 2) - cap.write(f"if ({herrcode} /= 0) then", 2) - cap.write("return", 3) - cap.write("end if", 2) - cap.write(f"num_dyn_consts = num_dyn_consts + size(dyn_const_prop_{idx})", 2) - # end for - cap.write("num_consts = num_consts + num_dyn_consts", 2) - cap.comment(f"Pack {dyn_const_name} array", 2) - cap.write(f"allocate({dyn_const_name}(num_dyn_consts), stat={herrcode})", 2) - cap.write(f"if ({herrcode} /= 0) then", 2) - cap.write(f"{herrmsg} = 'failed to allocate {dyn_const_name}'", 3) - cap.write("return", 3) - cap.write("end if", 2) - cap.write("index_start = 0", 2) - for idx, scheme in enumerate(sorted(dyn_const_dict)): - cap.write(f"do index = 1, size(dyn_const_prop_{idx}, 1)", 2) - cap.write(f"{dyn_const_name}(index + index_start) = dyn_const_prop_{idx}(index)", 3) - cap.write("end do", 2) - cap.write(f"index_start = index_start + size(dyn_const_prop_{idx}, 1)", 2) - cap.write(f"deallocate(dyn_const_prop_{idx})", 2) - # end for - # end if cap.comment("Initialize constituent data and field object", 2) stmt = f"call {const_obj_name}%initialize_table(num_consts)" cap.write(stmt, 2) @@ -564,10 +523,10 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.write("end do", 2) cap.blank_line() # Register dynamic constituents - if len(dyn_const_dict) > 0: - cap.comment("Add dynamic constituent properties", 2) - cap.write(f"do index = 1, size({dyn_const_name}, 1)", 2) - cap.write(f"const_prop => {dyn_const_name}(index)", 3) + cap.comment("Add dynamic constituent properties", 2) + for dyn_const in dyn_const_names: + cap.write(f"do index = 1, size({dyn_const}, 1)", 2) + cap.write(f"const_prop => {dyn_const}(index)", 3) stmt = f"call {const_obj_name}%new_field(const_prop, {obj_err_callstr})" cap.write(stmt, 3) cap.write("nullify(const_prop)", 3) @@ -575,7 +534,7 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.write("return", 4) cap.write("end if", 3) cap.write("end do", 2) - # end if + # end for # Register suite constituents for suite in suite_list: @@ -718,7 +677,12 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.write(f"{substmt}()", 1) cap.comment("Deallocate dynamic constituent array", 2) cap.blank_line() - cap.write(f"deallocate({dyn_const_name})", 2) + for dyn_const in dyn_const_names: + cap.write(f"if (allocated({dyn_const})) then", 2) + cap.write(f"deallocate({dyn_const})", 3) + cap.write("end if", 2) + cap.write(f"call {const_obj_name}%reset()", 2) + # end if cap.write(f"end {substmt}", 1) # Write constituents routine cap.blank_line() diff --git a/scripts/host_cap.py b/scripts/host_cap.py index 91d91493..3412e2cd 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -184,10 +184,10 @@ def constituent_model_object_name(host_model): return hvar.get_prop_value('local_name') ############################################################################### -def dynamic_constituent_array_name(host_model): +def suite_dynamic_constituent_array_name(host_model, suite): ############################################################################### """Return the name of the allocatable dynamic constituent properites array""" - hstr = f"{host_model.name}_dynamic_constituents" + hstr = f"{suite}_dynamic_constituents" return unique_local_name(hstr, host_model) ############################################################################### @@ -378,9 +378,12 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): # end if ddt_lib.collect_ddt_fields(const_dict, const_var, run_env, skip_duplicates=True) - # Declare the allocatable dynamic constituents array - dyn_const_name = dynamic_constituent_array_name(host_model) - cap.write(f"type({CONST_PROP_TYPE}), allocatable, target :: {dyn_const_name}(:)", 1) + # Declare the allocatable dynamic constituents array(s) + # One per suite + for suite in suite_list: + dyn_const_name = suite_dynamic_constituent_array_name(host_model, suite.name) + cap.write(f"type({CONST_PROP_TYPE}), allocatable, target :: {dyn_const_name}(:)", 1) + # end if # Declare variable for the constituent standard names array max_csname = max([len(x) for x in const_stdnames]) if const_stdnames else 0 num_const_fields = len(const_stdnames) @@ -441,7 +444,8 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): return const_dict ############################################################################### -def suite_part_call_list(host_model, const_dict, suite_part, subst_loop_vars): +def suite_part_call_list(host_model, const_dict, suite_part, subst_loop_vars, + dyn_const=False): ############################################################################### """Return the controlled call list for . is the constituent dictionary""" @@ -455,6 +459,12 @@ def suite_part_call_list(host_model, const_dict, suite_part, subst_loop_vars): for sp_var in spart_args: stdname = sp_var.get_prop_value('standard_name') sp_lname = sp_var.get_prop_value('local_name') + if sp_var.get_prop_value('type') == 'ccpp_constituent_properties_t': + if dyn_const: + hmvars.append(f"{sp_lname}={sp_lname}") + # end if + continue + # end if var_dicts = [host_model, const_dict] # Figure out which dictionary has the variable for vdict in var_dicts: @@ -547,6 +557,7 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): # Create a dict of local variables for stage host_local_vars = VarDictionary(f"{host_model.name}_{stage}", run_env) + has_dyn_consts = False # Create part call lists # Look for any loop-variable mismatch for suite in api.suites: @@ -555,6 +566,22 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): spart_args = spart.call_list.variable_list() for sp_var in spart_args: stdname = sp_var.get_prop_value('standard_name') + # Special handling for run-time constituents in register phase + if sp_var.get_prop_value('type') == 'ccpp_constituent_properties_t': + if spart.phase() == 'register': + prop_dict = {'standard_name' : sp_var.get_prop_value('standard_name'), + 'local_name' : sp_var.get_prop_value('local_name'), + 'dimensions' : '(:)', 'units' : 'none', + 'allocatable' : True, 'ddt_type' : 'ccpp_constituent_properties_t'} + newvar = Var(prop_dict, _API_SOURCE, run_env) + host_local_vars.add_variable(newvar, run_env) + has_dyn_consts = True + continue + else: + errmsg = f'ccpp_constituent_properties_t object "{stdname}" not allowed in "{spart.phase()}" phase' + raise CCPPError(errmsg) + # end if + # end if hvar = const_dict.find_variable(standard_name=stdname, any_scope=True) if hvar is None: @@ -564,6 +591,20 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): # End for (loop over part variables) # End for (loop of suite parts) # End for (loop over suites) + if has_dyn_consts: + prop_dict = {'standard_name' : 'unused_count', + 'local_name' : 'num_dyn_consts', + 'dimensions' : '()', 'units' : 'count', + 'type' : 'integer'} + newvar = Var(prop_dict, _API_SOURCE, run_env) + host_local_vars.add_variable(newvar, run_env) + prop_dict = {'standard_name' : 'unused_index', + 'local_name' : 'const_index', + 'dimensions' : '()', 'units' : 'none', + 'type': 'integer'} + newvar = Var(prop_dict, _API_SOURCE, run_env) + host_local_vars.add_variable(newvar, run_env) + # end if run_stage = stage == 'run' # All interfaces need the suite name apivars = [_SUITE_NAME_VAR] @@ -616,7 +657,8 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): var.write_def(cap, 2, host_model) # End for for var in host_local_vars.variable_list(): - var.write_def(cap, 2, host_model) + var.write_def(cap, 2, host_model, + allocatable=var.get_prop_value('allocatable')) # End for cap.write('', 0) # Write out the body clauses @@ -648,6 +690,41 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): cap.write(emsg, 4) cap.write("{errflg} = 1".format(errflg=errflg_name), 4) cap.write("end if", 3) + elif stage == 'register': + spart = suite.phase_group(stage) + dyn_const_array = suite_dynamic_constituent_array_name(host_model, suite.name) + call_str = suite_part_call_list(host_model, const_dict, spart, False, + dyn_const=True) + cap.write(f"call {suite.name}_{stage}({call_str})", 3) + # Allocate the suite's dynamic constituents array + size_string = "0+" + for var in host_local_vars.variable_list(): + vtype = var.get_prop_value('type') + if vtype == 'ccpp_constituent_properties_t': + local_name = var.get_prop_value('local_name') + size_string += f"size({local_name})+" + # end if + # end for + if not has_dyn_consts: + cap.comment("Suite does not return dynamic constituents; allocate to zero", 3) + # end if + cap.write(f"allocate({dyn_const_array}({size_string[:-1]}))", 3) + if has_dyn_consts: + cap.comment("Pack the suite-level dynamic, run-time constituents array", 3) + cap.write("num_dyn_consts = 0", 3) + for var in host_local_vars.variable_list(): + vtype = var.get_prop_value('type') + if vtype != 'ccpp_constituent_properties_t': + continue + # end if + local_name = var.get_prop_value('local_name') + cap.write(f"do const_index = 1, size({local_name})", 3) + cap.write(f"{dyn_const_array}(num_dyn_consts + const_index) = {local_name}(const_index)", 4) + cap.write("end do", 3) + cap.write(f"num_dyn_consts = num_dyn_consts + size({local_name})", 3) + cap.write(f"deallocate({local_name})", 3) + # end for + else: spart = suite.phase_group(stage) call_str = suite_part_call_list(host_model, const_dict, @@ -675,13 +752,13 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): cap.write("", 0) const_names_name = constituent_model_const_stdnames(host_model) const_indices_name = constituent_model_const_indices(host_model) - dyn_const_name = dynamic_constituent_array_name(host_model) + dyn_const_names = [suite_dynamic_constituent_array_name(host_model, suite.name) for suite in api.suites] ConstituentVarDict.write_host_routines(cap, host_model, reg_name, init_name, numconsts_name, queryconsts_name, copyin_name, copyout_name, cleanup_name, const_obj_name, - dyn_const_name, + dyn_const_names, const_names_name, const_indices_name, const_array_func, @@ -689,7 +766,6 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): prop_array_func, const_index_func, api.suites, - api.dyn_const_dict, err_vars) # End with return cap_filename diff --git a/scripts/metadata_table.py b/scripts/metadata_table.py index c05fa5b4..942f8d81 100755 --- a/scripts/metadata_table.py +++ b/scripts/metadata_table.py @@ -272,7 +272,7 @@ class MetadataTable(): __table_start = re.compile(r"(?i)\s*\[\s*ccpp-table-properties\s*\]") def __init__(self, run_env, table_name_in=None, table_type_in=None, - dependencies=None, relative_path=None, dyn_const_routine=None, + dependencies=None, relative_path=None, known_ddts=None, var_dict=None, module=None, parse_object=None, skip_ddt_check=False): """Initialize a MetadataTable, either with a name, , and @@ -286,7 +286,6 @@ def __init__(self, run_env, table_name_in=None, table_type_in=None, self.__pobj = parse_object self.__dependencies = dependencies self.__relative_path = relative_path - self.__dyn_const_routine = dyn_const_routine self.__sections = [] self.__run_env = run_env if parse_object is None: @@ -408,8 +407,6 @@ def __init_from_file(self, known_ddts, run_env, skip_ddt_check=False): # end if elif key == 'relative_path': self.__relative_path = value - elif key == 'dynamic_constituent_routine': - self.__dyn_const_routine = value else: tok_type = "metadata table start property" self.__pobj.add_syntax_err(tok_type, token=value) @@ -491,12 +488,6 @@ def relative_path(self): """Return the relative path for the table's dependencies""" return self.__relative_path - @property - def dyn_const_routine(self): - """Return the name of the routine that will dynamically return - an array of constituent properties""" - return self.__dyn_const_routine - @property def run_env(self): """Return this table's CCPPFrameworkEnv object""" diff --git a/scripts/parse_tools/parse_checkers.py b/scripts/parse_tools/parse_checkers.py index 1a0d1565..469bf1d0 100755 --- a/scripts/parse_tools/parse_checkers.py +++ b/scripts/parse_tools/parse_checkers.py @@ -229,7 +229,7 @@ def check_cf_standard_name(test_val, prop_dict, error): FORTRAN_DP_RE = re.compile(r"(?i)double\s*precision") FORTRAN_TYPE_RE = re.compile(r"(?i)type\s*\(\s*("+FORTRAN_ID+r")\s*\)") -_REGISTERED_FORTRAN_DDT_NAMES = list() +_REGISTERED_FORTRAN_DDT_NAMES = [] ######################################################################## diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 1056b8dd..3fdbe8e6 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -450,6 +450,13 @@ def add_call_list_variable(self, newvar, exists_ok=False, adjust_intent=True) # We need to make sure that this variable's dimensions are available for vardim in newvar.get_dim_stdnames(include_constants=False): + # Unnamed dimensions are ok for allocatable variables + if vardim == '' and newvar.get_prop_value('allocatable'): + continue + elif vardim == '': + emsg = f"{self.name}: Cannot have unnamed/empty string dimension" + raise ParseInternalError(emsg) + # end if dvar = self.find_variable(standard_name=vardim, any_scope=True) if dvar is None: @@ -838,12 +845,26 @@ def match_variable(self, var, run_env): else: vmatch = None # end if - + found_var = False missing_vert = None new_vdims = list() var_vdim = var.has_vertical_dimension(dims=vdims) compat_obj = None + dict_var = None + if var.get_prop_value('type') == 'ccpp_constituent_properties_t': + if self.phase() == 'register': + found_var = True + new_vdims = [':'] + return found_var, dict_var, var_vdim, new_vdims, missing_vert, compat_obj + else: + errmsg = "Variables of type ccpp_constituent_properties_t only allowed in register phase: " + sname = var.get_prop_value('standard_name') + errmsg += f"'{sname}' found in {self.phase()} phase" + raise CCPPError(errmsg) + # end if + # end if + # Does this variable exist in the calling tree? dict_var = self.find_variable(source_var=var, any_scope=True) if dict_var is None: diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index 3370bff4..c4086099 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -26,6 +26,9 @@ module ccpp_constituent_prop_mod integer, parameter :: int_unassigned = -HUGE(1) real(kind_phys), parameter :: kphys_unassigned = HUGE(1.0_kind_phys) +!! \section arg_table_ccpp_constituent_properties_t +!! \htmlinclude ccpp_constituent_properties_t.html +!! type, public, extends(ccpp_hashable_char_t) :: ccpp_constituent_properties_t ! A ccpp_constituent_properties_t object holds relevant metadata ! for a constituent species and provides interfaces to access that data. diff --git a/src/ccpp_constituent_prop_mod.meta b/src/ccpp_constituent_prop_mod.meta index 99cf3145..d6d9a4fc 100644 --- a/src/ccpp_constituent_prop_mod.meta +++ b/src/ccpp_constituent_prop_mod.meta @@ -1,4 +1,15 @@ ######################################################################## + +[ccpp-table-properties] + name = ccpp_constituent_properties_t + type = ddt + +[ccpp-arg-table] + name = ccpp_constituent_properties_t + type = ddt + +######################################################################## + [ccpp-table-properties] name = ccpp_constituent_prop_ptr_t type = ddt diff --git a/test/advection_test/CMakeLists.txt b/test/advection_test/CMakeLists.txt index 4aacdc18..c3f45190 100644 --- a/test/advection_test/CMakeLists.txt +++ b/test/advection_test/CMakeLists.txt @@ -20,8 +20,10 @@ get_filename_component(CCPP_ROOT "${TEST_ROOT}" DIRECTORY) # #------------------------------------------------------------------------------ LIST(APPEND SCHEME_FILES "cld_suite_files.txt") +LIST(APPEND SCHEME_FILES_ERROR "cld_suite_files_error.txt") LIST(APPEND HOST_FILES "test_host_data" "test_host_mod") LIST(APPEND SUITE_FILES "cld_suite.xml") +LIST(APPEND SUITE_FILES_ERROR "cld_suite_error.xml") # HOST is the name of the executable we will build. # We assume there are files ${HOST}.meta and ${HOST}.F90 in CMAKE_SOURCE_DIR SET(HOST "${CMAKE_PROJECT_NAME}") @@ -114,6 +116,13 @@ FOREACH(FILE ${SCHEME_FILES}) ENDFOREACH(FILE) string(REPLACE ";" "," SCHEME_METADATA "${SCHEME_FILES}") +# Create metadata and source file lists +FOREACH(FILE ${SCHEME_FILES_ERROR}) + FILE(STRINGS ${FILE} FILENAMES) + LIST(APPEND SCHEME_FILENAMES_ERROR ${FILENAMES}) +ENDFOREACH(FILE) +string(REPLACE ";" "," SCHEME_METADATA_ERROR "${SCHEME_FILES_ERROR}") + FOREACH(FILE ${SCHEME_FILENAMES}) # target_sources prefers absolute pathnames string(REPLACE ".meta" ".F90" TEMP "${FILE}") @@ -132,6 +141,42 @@ string(REPLACE ";" ".meta," HOST_METADATA "${HOST_FILES}") set(HOST_METADATA "${HOST_METADATA}.meta,${HOST}.meta") string(REPLACE ";" "," SUITE_XML "${SUITE_FILES}") +string(REPLACE ";" "," SUITE_XML_ERROR "${SUITE_FILES_ERROR}") + +# Run ccpp_capgen that we expect to fail +set(CAPGEN_CMD "${CCPP_FRAMEWORK}/ccpp_capgen.py") +list(APPEND CAPGEN_CMD "--host-files") +list(APPEND CAPGEN_CMD "${HOST_METADATA}") +list(APPEND CAPGEN_CMD "--scheme-files") +list(APPEND CAPGEN_CMD "${SCHEME_METADATA_ERROR}") +list(APPEND CAPGEN_CMD "--suites") +list(APPEND CAPGEN_CMD "${SUITE_XML_ERROR}") +list(APPEND CAPGEN_CMD "--host-name") +list(APPEND CAPGEN_CMD "test_host") +list(APPEND CAPGEN_CMD "--output-root") +list(APPEND CAPGEN_CMD "${CCPP_CAP_FILES}") +while (VERBOSITY GREATER 0) + list(APPEND CAPGEN_CMD "--verbose") + MATH(EXPR VERBOSITY "${VERBOSITY} - 1") +endwhile () +list(APPEND CAPGEN_CMD "--debug") +string(REPLACE ";" " " CAPGEN_STRING "${CAPGEN_CMD}") +MESSAGE(STATUS "Running: ${CAPGEN_STRING}") +EXECUTE_PROCESS(COMMAND ${CAPGEN_CMD} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE CAPGEN_OUT ERROR_VARIABLE CAPGEN_OUT RESULT_VARIABLE RES) +MESSAGE(STATUS "${CAPGEN_OUT}") +if (RES EQUAL 0) + MESSAGE(STATUS "CCPP cap generation completed") +else() + # Example: Validate the error message + string(FIND "${CAPGEN_OUT}" "Variables of type ccpp_constituent_properties_t only allowed in register phase" ERROR_INDEX) + + if (ERROR_INDEX GREATER -1) + MESSAGE(STATUS "Capgen build produces expected error message.") + else() + MESSAGE(FATAL_ERROR "CCPP cap generation did not generate expected error. Expected 'Variables of type ccpp_cosntituent_properties_t only allowed in register phase. Got: " ${CAPGEN_OUT}"") + endif() +endif(RES EQUAL 0) # Run ccpp_capgen set(CAPGEN_CMD "${CCPP_FRAMEWORK}/ccpp_capgen.py") diff --git a/test/advection_test/cld_ice.F90 b/test/advection_test/cld_ice.F90 index 0a1e13ee..ee53529d 100644 --- a/test/advection_test/cld_ice.F90 +++ b/test/advection_test/cld_ice.F90 @@ -8,15 +8,42 @@ MODULE cld_ice IMPLICIT NONE PRIVATE + PUBLIC :: cld_ice_register PUBLIC :: cld_ice_init PUBLIC :: cld_ice_run PUBLIC :: cld_ice_final - PUBLIC :: cld_ice_dynamic_constituents real(kind_phys), private :: tcld = HUGE(1.0_kind_phys) CONTAINS + !> \section arg_table_cld_ice_register Argument Table + !! \htmlinclude arg_table_cld_ice_register.html + !! + subroutine cld_ice_register(dyn_const_ice, errmsg, errcode) + use ccpp_constituent_prop_mod, only: ccpp_constituent_properties_t + type(ccpp_constituent_properties_t), allocatable, intent(out) :: dyn_const_ice(:) + integer, intent(out) :: errcode + character(len=512), intent(out) :: errmsg + + errmsg = '' + errcode = 0 + allocate(dyn_const_ice(2), stat=errcode) + if (errcode /= 0) then + errmsg = 'Error allocating dyn_const in cld_ice_dynamic_constituents' + return + end if + call dyn_const_ice(1)%instantiate(std_name='dyn_const1', long_name='dyn const1', & + units='kg kg-1', default_value=0._kind_phys, & + vertical_dim='vertical_layer_dimension', advected=.true., & + min_value=1000._kind_phys, errcode=errcode, errmsg=errmsg) + call dyn_const_ice(2)%instantiate(std_name='dyn_const2_wrt_moist_air', long_name='dyn const2', & + units='kg kg-1', default_value=0._kind_phys, & + vertical_dim='vertical_layer_dimension', advected=.true., & + errcode=errcode, errmsg=errmsg) + + end subroutine cld_ice_register + !> \section arg_table_cld_ice_run Argument Table !! \htmlinclude arg_table_cld_ice_run.html !! @@ -93,31 +120,6 @@ subroutine cld_ice_final(errmsg, errflg) end subroutine cld_ice_final - subroutine cld_ice_dynamic_constituents(dyn_const, errcode, errmsg) - use ccpp_constituent_prop_mod, only: ccpp_constituent_properties_t - type(ccpp_constituent_properties_t), allocatable, intent(out) :: dyn_const(:) - integer, intent(out) :: errcode - character(len=512), intent(out) :: errmsg - - errmsg = '' - errcode = 0 - allocate(dyn_const(2), stat=errcode) - if (errcode /= 0) then - errmsg = 'Error allocating dyn_const in cld_ice_dynamic_constituents' - end if - call dyn_const(1)%instantiate(std_name='dyn_const1', long_name='dyn const1', & - units='kg kg-1', default_value=0._kind_phys, & - vertical_dim='vertical_layer_dimension', advected=.true., & - min_value=1000._kind_phys, errcode=errcode, errmsg=errmsg) - if (errcode /= 0) then - return - end if - call dyn_const(2)%instantiate(std_name='dyn_const2_wrt_moist_air', long_name='dyn const2', & - units='kg kg-1', default_value=0._kind_phys, & - vertical_dim='vertical_layer_dimension', advected=.true., & - errcode=errcode, errmsg=errmsg) - - end subroutine cld_ice_dynamic_constituents !! @} !! @} diff --git a/test/advection_test/cld_ice.meta b/test/advection_test/cld_ice.meta index c27c3c6d..e57d0b08 100644 --- a/test/advection_test/cld_ice.meta +++ b/test/advection_test/cld_ice.meta @@ -2,7 +2,31 @@ [ccpp-table-properties] name = cld_ice type = scheme - dynamic_constituent_routine = cld_ice_dynamic_constituents +[ccpp-arg-table] + name = cld_ice_register + type = scheme +[ dyn_const_ice ] + standard_name = dynamic_constituents_for_cld_ice + units = none + dimensions = (:) + allocatable = True + type = ccpp_constituent_properties_t + intent = out +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errcode ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out [ccpp-arg-table] name = cld_ice_run type = scheme diff --git a/test/advection_test/cld_liq.F90 b/test/advection_test/cld_liq.F90 index 63148c52..ec19cb17 100644 --- a/test/advection_test/cld_liq.F90 +++ b/test/advection_test/cld_liq.F90 @@ -4,16 +4,42 @@ MODULE cld_liq USE ccpp_kinds, ONLY: kind_phys + use ccpp_constituent_prop_mod, only: ccpp_constituent_properties_t IMPLICIT NONE PRIVATE + PUBLIC :: cld_liq_register PUBLIC :: cld_liq_init PUBLIC :: cld_liq_run - PUBLIC :: cld_liq_dynamic_constituents CONTAINS + !> \section arg_table_cld_liq_register Argument Table + !! \htmlinclude arg_table_cld_liq_register.html + !! + subroutine cld_liq_register(dyn_const, errmsg, errflg) + type(ccpp_constituent_properties_t), allocatable, intent(out) :: dyn_const(:) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + character(len=256) :: stdname + + errmsg = '' + errflg = 0 + allocate(dyn_const(1), stat=errflg) + if (errflg /= 0) then + errmsg = 'Error allocating dyn_const in cld_liq_register' + return + end if + call dyn_const(1)%instantiate(std_name="dyn_const3", long_name='dyn const3', & + units='kg kg-1', default_value=1._kind_phys, & + vertical_dim='vertical_layer_dimension', advected=.true., & + errcode=errflg, errmsg=errmsg) + call dyn_const(1)%standard_name(stdname, errcode=errflg, errmsg=errmsg) + + end subroutine cld_liq_register + !> \section arg_table_cld_liq_run Argument Table !! \htmlinclude arg_table_cld_liq_run.html !! @@ -75,24 +101,4 @@ subroutine cld_liq_init(tfreeze, cld_liq_array, tcld, errmsg, errflg) end subroutine cld_liq_init - subroutine cld_liq_dynamic_constituents(dyn_const, errcode, errmsg) - use ccpp_constituent_prop_mod, only: ccpp_constituent_properties_t - type(ccpp_constituent_properties_t), allocatable, intent(out) :: dyn_const(:) - integer, intent(out) :: errcode - character(len=512), intent(out) :: errmsg - - errmsg = '' - errcode = 0 - allocate(dyn_const(1), stat=errcode) - if (errcode /= 0) then - errmsg = 'Error allocating dyn_const in cld_liq_dynamic_constituents' - end if - call dyn_const(1)%instantiate(std_name="dyn_const3", long_name='dyn const3', & - units='kg kg-1', default_value=1._kind_phys, & - vertical_dim='vertical_layer_dimension', advected=.true., & - errcode=errcode, errmsg=errmsg) - - end subroutine cld_liq_dynamic_constituents - - END MODULE cld_liq diff --git a/test/advection_test/cld_liq.meta b/test/advection_test/cld_liq.meta index 1b9373f9..da04ccf5 100644 --- a/test/advection_test/cld_liq.meta +++ b/test/advection_test/cld_liq.meta @@ -2,7 +2,30 @@ [ccpp-table-properties] name = cld_liq type = scheme - dynamic_constituent_routine = cld_liq_dynamic_constituents +[ccpp-arg-table] + name = cld_liq_register + type = scheme +[ dyn_const ] + standard_name = dynamic_constituents_for_cld_liq + dimensions = (:) + type = ccpp_constituent_properties_t + intent = out + allocatable = true +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out [ccpp-arg-table] name = cld_liq_run type = scheme @@ -45,7 +68,7 @@ state_variable = true type = real kind = kind_phys - units = Pa + units = hPa dimensions = (horizontal_loop_extent) intent = in [ cld_liq_array ] diff --git a/test/advection_test/cld_suite_error.xml b/test/advection_test/cld_suite_error.xml new file mode 100644 index 00000000..80acac91 --- /dev/null +++ b/test/advection_test/cld_suite_error.xml @@ -0,0 +1,9 @@ + + + + + dlc_liq + cld_liq + cld_ice + + diff --git a/test/advection_test/cld_suite_files_error.txt b/test/advection_test/cld_suite_files_error.txt new file mode 100644 index 00000000..63ff75b0 --- /dev/null +++ b/test/advection_test/cld_suite_files_error.txt @@ -0,0 +1,3 @@ +cld_liq.meta +cld_ice.meta +dlc_liq.meta diff --git a/test/advection_test/dlc_liq.F90 b/test/advection_test/dlc_liq.F90 new file mode 100644 index 00000000..93230f81 --- /dev/null +++ b/test/advection_test/dlc_liq.F90 @@ -0,0 +1,41 @@ +! Test parameterization with a runtime constituents +! properties object outside of the register phase + +MODULE dlc_liq + + USE ccpp_kinds, ONLY: kind_phys + use ccpp_constituent_prop_mod, only: ccpp_constituent_properties_t + + IMPLICIT NONE + PRIVATE + + PUBLIC :: dlc_liq_init + +CONTAINS + + !> \section arg_table_dlc_liq_init Argument Table + !! \htmlinclude arg_table_dlc_liq_init.html + !! + subroutine dlc_liq_init(dyn_const, errmsg, errflg) + type(ccpp_constituent_properties_t), allocatable, intent(out) :: dyn_const(:) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + character(len=256) :: stdname + + errmsg = '' + errflg = 0 + allocate(dyn_const(1), stat=errflg) + if (errflg /= 0) then + errmsg = 'Error allocating dyn_const in dlc_liq_init' + return + end if + call dyn_const(1)%instantiate(std_name="dyn_const3", long_name='dyn const3', & + units='kg kg-1', default_value=1._kind_phys, & + vertical_dim='vertical_layer_dimension', advected=.true., & + errcode=errflg, errmsg=errmsg) + call dyn_const(1)%standard_name(stdname, errcode=errflg, errmsg=errmsg) + + end subroutine dlc_liq_init + +END MODULE dlc_liq diff --git a/test/advection_test/dlc_liq.meta b/test/advection_test/dlc_liq.meta new file mode 100644 index 00000000..fedb6243 --- /dev/null +++ b/test/advection_test/dlc_liq.meta @@ -0,0 +1,29 @@ +# dlc_liq is a scheme that has a ccpp_constituent_properties_t variable +# outside of the register phase +[ccpp-table-properties] + name = dlc_liq + type = scheme +[ccpp-arg-table] + name = dlc_liq_init + type = scheme +[ dyn_const ] + standard_name = dynamic_constituents_for_dlc_liq + dimensions = (:) + type = ccpp_constituent_properties_t + intent = out + allocatable = true +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out diff --git a/test/advection_test/run_test b/test/advection_test/run_test index 1d9a6d44..143c7d7f 100755 --- a/test/advection_test/run_test +++ b/test/advection_test/run_test @@ -127,11 +127,12 @@ ccpp_files="${utility_files},${host_files},${suite_files}" process_list="" module_list="cld_ice,cld_liq" dependencies="" -dyn_const_routines="cld_ice_dynamic_constituents,cld_liq_dynamic_constituents" suite_list="cld_suite" required_vars="ccpp_error_code,ccpp_error_message" required_vars="${required_vars},cloud_ice_dry_mixing_ratio" required_vars="${required_vars},cloud_liquid_dry_mixing_ratio" +required_vars="${required_vars},dynamic_constituents_for_cld_ice" +required_vars="${required_vars},dynamic_constituents_for_cld_liq" required_vars="${required_vars},horizontal_dimension" required_vars="${required_vars},horizontal_loop_begin" required_vars="${required_vars},horizontal_loop_end" @@ -153,6 +154,8 @@ input_vars="${input_vars},water_vapor_specific_humidity" output_vars="ccpp_error_code,ccpp_error_message" output_vars="${output_vars},cloud_ice_dry_mixing_ratio" output_vars="${output_vars},cloud_liquid_dry_mixing_ratio" +output_vars="${output_vars},dynamic_constituents_for_cld_ice" +output_vars="${output_vars},dynamic_constituents_for_cld_liq" output_vars="${output_vars},temperature" output_vars="${output_vars},water_vapor_specific_humidity" @@ -220,7 +223,6 @@ echo -e "\nChecking lists from command line" check_datatable ${report_prog} ${datafile} "--process-list" "${process_list}" check_datatable ${report_prog} ${datafile} "--module-list" ${module_list} check_datatable ${report_prog} ${datafile} "--dependencies" "${dependencies}" -check_datatable ${report_prog} ${datafile} "--dyn-const-routines" "${dyn_const_routines}" check_datatable ${report_prog} ${datafile} "--suite-list" ${suite_list} \ --sep ";" echo -e "\nChecking variables from command line" diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index fe1f9e0b..5146097b 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -231,6 +231,7 @@ subroutine test_host(retval, test_suites) use test_host_ccpp_cap, only: test_host_ccpp_initialize_constituents use test_host_ccpp_cap, only: test_host_ccpp_number_constituents use test_host_ccpp_cap, only: test_host_constituents_array + use test_host_ccpp_cap, only: test_host_ccpp_physics_register use test_host_ccpp_cap, only: test_host_ccpp_physics_initialize use test_host_ccpp_cap, only: test_host_ccpp_physics_timestep_initial use test_host_ccpp_cap, only: test_host_ccpp_physics_run @@ -321,6 +322,18 @@ subroutine test_host(retval, test_suites) errflg_final = -1 ! Notify test script that a failure occurred end if + ! Use the suite information to call the register phase + do sind = 1, num_suites + if (errflg == 0) then + call test_host_ccpp_physics_register( & + test_suites(sind)%suite_name, errmsg, errflg) + if (errflg /= 0) then + write(6, '(4a)') 'ERROR in register of ', & + trim(test_suites(sind)%suite_name), ': ', trim(errmsg) + exit + end if + end if + end do ! Register the constituents to find out what needs advecting ! DO A COUPLE OF TESTS FIRST @@ -358,8 +371,21 @@ subroutine test_host(retval, test_suites) end if ! Now try again but with a compatible constituent - should be ignored when ! the constituents object is created + ! Use the suite information to call the register phase + errflg = 0 call test_host_ccpp_deallocate_dynamic_constituents() deallocate(host_constituents) + do sind = 1, num_suites + if (errflg == 0) then + call test_host_ccpp_physics_register( & + test_suites(sind)%suite_name, errmsg, errflg) + if (errflg /= 0) then + write(6, '(4a)') 'ERROR in register of ', & + trim(test_suites(sind)%suite_name), ': ', trim(errmsg) + exit + end if + end if + end do allocate(host_constituents(2)) call host_constituents(1)%instantiate(std_name="specific_humidity", & long_name="Specific humidity", units="kg kg-1", & @@ -1009,8 +1035,8 @@ program test character(len=cs), target :: test_parts1(1) character(len=cm), target :: test_invars1(7) - character(len=cm), target :: test_outvars1(6) - character(len=cm), target :: test_reqvars1(9) + character(len=cm), target :: test_outvars1(8) + character(len=cm), target :: test_reqvars1(11) type(suite_info) :: test_suites(1) logical :: run_okay @@ -1030,6 +1056,8 @@ program test 'temperature ', & 'water_vapor_specific_humidity ', & 'cloud_liquid_dry_mixing_ratio ', & + 'dynamic_constituents_for_cld_liq ', & + 'dynamic_constituents_for_cld_ice ', & 'cloud_ice_dry_mixing_ratio ' /) test_reqvars1 = (/ & 'surface_air_pressure ', & @@ -1037,6 +1065,8 @@ program test 'time_step_for_physics ', & 'cloud_liquid_dry_mixing_ratio ', & 'cloud_ice_dry_mixing_ratio ', & + 'dynamic_constituents_for_cld_liq ', & + 'dynamic_constituents_for_cld_ice ', & 'water_temperature_at_freezing ', & 'water_vapor_specific_humidity ', & 'ccpp_error_message ', & diff --git a/test/advection_test/test_reports.py b/test/advection_test/test_reports.py index 1a34462a..2061b796 100644 --- a/test/advection_test/test_reports.py +++ b/test/advection_test/test_reports.py @@ -74,6 +74,8 @@ def usage(errmsg=None): "water_vapor_specific_humidity", "cloud_ice_dry_mixing_ratio", "cloud_liquid_dry_mixing_ratio", + "dynamic_constituents_for_cld_ice", + "dynamic_constituents_for_cld_liq", # Added by --debug option "horizontal_dimension", "vertical_layer_dimension"] @@ -89,7 +91,9 @@ def usage(errmsg=None): _OUTPUT_VARS_CLD = ["ccpp_error_code", "ccpp_error_message", "water_vapor_specific_humidity", "temperature", "cloud_ice_dry_mixing_ratio", - "cloud_liquid_dry_mixing_ratio"] + "cloud_liquid_dry_mixing_ratio", + "dynamic_constituents_for_cld_ice", + "dynamic_constituents_for_cld_liq"] def fields_string(field_type, field_list, sep): """Create an error string for field(s), . @@ -155,8 +159,6 @@ def check_datatable(database, report_type, check_list, sep=','): _MODULE_LIST) NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("suite_list"), _SUITE_LIST) -NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("dyn_const_routines"), - _DYN_CONST_ROUTINES) print("\nChecking variables for CLD suite from python") NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("required_variables", value="cld_suite"), diff --git a/test/capgen_test/run_test b/test/capgen_test/run_test index 3d21e2c2..0f826c69 100755 --- a/test/capgen_test/run_test +++ b/test/capgen_test/run_test @@ -147,6 +147,7 @@ output_vars_ddt="ccpp_error_code,ccpp_error_message" output_vars_ddt="${output_vars_ddt},model_times,number_of_model_times" required_vars_temp="ccpp_error_code,ccpp_error_message" required_vars_temp="${required_vars_temp},coefficients_for_interpolation" +required_vars_temp="${required_vars_temp},configuration_variable" required_vars_temp="${required_vars_temp},horizontal_dimension" required_vars_temp="${required_vars_temp},horizontal_loop_begin" required_vars_temp="${required_vars_temp},horizontal_loop_end" @@ -161,6 +162,7 @@ required_vars_temp="${required_vars_temp},vertical_interface_dimension" required_vars_temp="${required_vars_temp},vertical_layer_dimension" required_vars_temp="${required_vars_temp},water_vapor_specific_humidity" input_vars_temp="coefficients_for_interpolation" +input_vars_temp="${input_vars_temp},configuration_variable" input_vars_temp="${input_vars_temp},horizontal_dimension" input_vars_temp="${input_vars_temp},horizontal_loop_begin" input_vars_temp="${input_vars_temp},horizontal_loop_end" diff --git a/test/capgen_test/temp_adjust.F90 b/test/capgen_test/temp_adjust.F90 index 3c6b24f0..e9733786 100644 --- a/test/capgen_test/temp_adjust.F90 +++ b/test/capgen_test/temp_adjust.F90 @@ -8,12 +8,29 @@ MODULE temp_adjust IMPLICIT NONE PRIVATE + PUBLIC :: temp_adjust_register PUBLIC :: temp_adjust_init PUBLIC :: temp_adjust_run PUBLIC :: temp_adjust_finalize + logical :: module_level_config = .false. + CONTAINS + !> \section arg_table_temp_adjust_register Argument Table + !! \htmlinclude arg_table_temp_adjust_register.hml + !! + subroutine temp_adjust_register(config_var, errmsg, errflg) + logical, intent(in) :: config_var + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + module_level_config = config_var + errflg = 0 + errmsg = '' + + end subroutine temp_adjust_register + !> \section arg_table_temp_adjust_run Argument Table !! \htmlinclude arg_table_temp_adjust_run.html !! @@ -40,6 +57,11 @@ subroutine temp_adjust_run(foo, timestep, temp_prev, temp_layer, qv, ps, & errmsg = '' errflg = 0 + if (.not. module_level_config) then + ! do nothing + return + end if + do col_index = 1, foo temp_layer(col_index) = temp_layer(col_index) + temp_prev(col_index) if (present(qv)) qv(col_index) = qv(col_index) + 1.0_kind_phys diff --git a/test/capgen_test/temp_adjust.meta b/test/capgen_test/temp_adjust.meta index 420e9112..e3006508 100644 --- a/test/capgen_test/temp_adjust.meta +++ b/test/capgen_test/temp_adjust.meta @@ -3,6 +3,30 @@ type = scheme dependencies = qux.F90 relative_path = adjust +[ccpp-arg-table] + name = temp_adjust_register + type = scheme +[ config_var ] + standard_name = configuration_variable + type = logical + units = none + dimensions = () + intent = in +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out [ccpp-arg-table] name = temp_adjust_run type = scheme diff --git a/test/capgen_test/test_host.F90 b/test/capgen_test/test_host.F90 index ec352ae6..1958db99 100644 --- a/test/capgen_test/test_host.F90 +++ b/test/capgen_test/test_host.F90 @@ -187,6 +187,7 @@ end function check_suite subroutine test_host(retval, test_suites) use test_host_mod, only: ncols, num_time_steps + use test_host_ccpp_cap, only: test_host_ccpp_physics_register use test_host_ccpp_cap, only: test_host_ccpp_physics_initialize use test_host_ccpp_cap, only: test_host_ccpp_physics_timestep_initial use test_host_ccpp_cap, only: test_host_ccpp_physics_run @@ -237,6 +238,15 @@ subroutine test_host(retval, test_suites) return end if + ! Use the suite information to call the register phase + do sind = 1, num_suites + call test_host_ccpp_physics_register(test_suites(sind)%suite_name, & + errmsg, errflg) + if (errflg /= 0) then + write(6, '(4a)') 'ERROR in register of ', & + trim(test_suites(sind)%suite_name), ': ', trim(errmsg) + end if + end do ! Use the suite information to setup the run do sind = 1, num_suites call test_host_ccpp_physics_initialize(test_suites(sind)%suite_name, & diff --git a/test/capgen_test/test_host_mod.F90 b/test/capgen_test/test_host_mod.F90 index 43be333a..2ce1fbd6 100644 --- a/test/capgen_test/test_host_mod.F90 +++ b/test/capgen_test/test_host_mod.F90 @@ -15,6 +15,7 @@ module test_host_mod integer, parameter :: pcnst = 2 integer, parameter :: DiagDimStart = 2 integer, parameter :: index_qv = 1 + logical, parameter :: config_var = .true. real(kind_phys), allocatable :: temp_midpoints(:,:) real(kind_phys) :: temp_interfaces(ncols, pverP) real(kind_phys) :: coeffs(ncols) diff --git a/test/capgen_test/test_host_mod.meta b/test/capgen_test/test_host_mod.meta index a450ee67..ae0abd05 100644 --- a/test/capgen_test/test_host_mod.meta +++ b/test/capgen_test/test_host_mod.meta @@ -10,6 +10,12 @@ type = integer protected = True dimensions = () +[ config_var ] + standard_name = configuration_variable + units = none + type = logical + protected = True + dimensions = () [ ncols] standard_name = horizontal_dimension units = count @@ -95,4 +101,4 @@ long_name = coefficients for interpolation units = none dimensions = (horizontal_dimension) - type = real | kind = kind_phys \ No newline at end of file + type = real | kind = kind_phys diff --git a/test/capgen_test/test_reports.py b/test/capgen_test/test_reports.py index c9fc452d..8061102a 100644 --- a/test/capgen_test/test_reports.py +++ b/test/capgen_test/test_reports.py @@ -80,6 +80,7 @@ def usage(errmsg=None): _PROT_VARS_TEMP = ["horizontal_loop_begin", "horizontal_loop_end", "horizontal_dimension", "vertical_layer_dimension", "number_of_tracers", + "configuration_variable", # Added for --debug "index_of_water_vapor_specific_humidity", "vertical_interface_dimension"] diff --git a/test/unit_tests/sample_files/test_host.meta b/test/unit_tests/sample_files/test_host.meta index b3bbc001..f618f871 100644 --- a/test/unit_tests/sample_files/test_host.meta +++ b/test/unit_tests/sample_files/test_host.meta @@ -2,7 +2,6 @@ name = test_host type = host dependencies = - dynamic_constituent_routine = dyn_consts ######################################################################## [ccpp-arg-table] diff --git a/test/unit_tests/sample_scheme_files/duplicate_dyn_const.F90 b/test/unit_tests/sample_scheme_files/duplicate_dyn_const.F90 deleted file mode 100644 index 2eb43a89..00000000 --- a/test/unit_tests/sample_scheme_files/duplicate_dyn_const.F90 +++ /dev/null @@ -1,96 +0,0 @@ -! Test parameterization with a dynamic constituents routine with the same name as another parameterization's -! - -MODULE duplicate_dyn_const - - USE ccpp_kinds, ONLY: kind_phys - - IMPLICIT NONE - PRIVATE - - PUBLIC :: duplicate_dyn_const_init - PUBLIC :: duplicate_dyn_const_run - PUBLIC :: duplicate_dyn_const_finalize - PUBLIC :: dyn_consts - -CONTAINS - - !> \section arg_table_duplicate_dyn_const_run Argument Table - !! \htmlinclude arg_table_duplicate_dyn_const_run.html - !! - subroutine duplicate_dyn_const_run(foo, timestep, temp_prev, temp_layer, qv, ps, & - errmsg, errflg) - - integer, intent(in) :: foo - real(kind_phys), intent(in) :: timestep - real(kind_phys), intent(inout) :: qv(:) - real(kind_phys), intent(inout) :: ps(:) - REAL(kind_phys), intent(in) :: temp_prev(:) - REAL(kind_phys), intent(inout) :: temp_layer(foo) - character(len=512), intent(out) :: errmsg - integer, intent(out) :: errflg - !---------------------------------------------------------------- - - integer :: col_index - - errmsg = '' - errflg = 0 - - do col_index = 1, foo - temp_layer(col_index) = temp_layer(col_index) + temp_prev(col_index) - qv(col_index) = qv(col_index) + 1.0_kind_phys - end do - - END SUBROUTINE duplicate_dyn_const_run - - !> \section arg_table_duplicate_dyn_const_init Argument Table - !! \htmlinclude arg_table_duplicate_dyn_const_init.html - !! - subroutine duplicate_dyn_const_init (errmsg, errflg) - - character(len=512), intent(out) :: errmsg - integer, intent(out) :: errflg - - ! This routine currently does nothing - - errmsg = '' - errflg = 0 - - end subroutine duplicate_dyn_const_init - - !> \section arg_table_duplicate_dyn_const_finalize Argument Table - !! \htmlinclude arg_table_duplicate_dyn_const_finalize.html - !! - subroutine duplicate_dyn_const_finalize (errmsg, errflg) - - character(len=512), intent(out) :: errmsg - integer, intent(out) :: errflg - - ! This routine currently does nothing - - errmsg = '' - errflg = 0 - - end subroutine duplicate_dyn_const_finalize - - subroutine dyn_consts(dyn_const, errcode, errmsg) - use ccpp_constituent_prop_mod, only: ccpp_constituent_properties_t - type(ccpp_constituent_properties_t), allocatable, intent(out) :: dyn_const(:) - integer, intent(out) :: errcode - character(len=512), intent(out) :: errmsg - - errmsg = '' - errcode = 0 - allocate(dyn_const(1), stat=errcode) - if (errcode /= 0) then - errmsg = 'Error allocating dyn_const in dyn_consts' - end if - call dyn_const(1)%instantiate(std_name="dyn_const1", long_name='dyn const1', & - units='kg kg-1', default_value=1._kind_phys, & - vertical_dim='vertical_layer_dimension', advected=.true., & - errcode=errcode, errmsg=errmsg) - - end subroutine dyn_consts - - -END MODULE duplicate_dyn_const diff --git a/test/unit_tests/sample_scheme_files/duplicate_dyn_const.meta b/test/unit_tests/sample_scheme_files/duplicate_dyn_const.meta deleted file mode 100644 index f369f8b2..00000000 --- a/test/unit_tests/sample_scheme_files/duplicate_dyn_const.meta +++ /dev/null @@ -1,104 +0,0 @@ -[ccpp-table-properties] - name = duplicate_dyn_const - type = scheme - dynamic_constituent_routine = dyn_consts - -######################################################################## -[ccpp-arg-table] - name = duplicate_dyn_const_run - type = scheme -[ foo ] - standard_name = horizontal_loop_extent - type = integer - units = count - dimensions = () - intent = in -[ timestep ] - standard_name = time_step_for_physics - long_name = time step - units = s - dimensions = () - type = real - kind = kind_phys - intent = in -[ temp_prev ] - standard_name = potential_temperature_at_previous_timestep - units = K - dimensions = (horizontal_loop_extent) - type = real - kind = kind_phys - intent = in -[ temp_layer ] - standard_name = potential_temperature - units = K - dimensions = (horizontal_loop_extent) - type = real - kind = kind_phys - intent = inout -[ qv ] - standard_name = water_vapor_specific_humidity - units = kg kg-1 - dimensions = (horizontal_loop_extent) - type = real - kind = kind_phys - advected = true - intent = inout -[ ps ] - standard_name = surface_air_pressure - state_variable = true - type = real - kind = kind_phys - units = Pa - dimensions = (horizontal_loop_extent) - intent = inout -[ errmsg ] - standard_name = ccpp_error_message - long_name = Error message for error handling in CCPP - units = none - dimensions = () - type = character - kind = len=512 - intent = out -[ errflg ] - standard_name = ccpp_error_code - long_name = Error flag for error handling in CCPP - units = 1 - dimensions = () - type = integer - intent = out -[ccpp-arg-table] - name = duplicate_dyn_const_init - type = scheme -[ errmsg ] - standard_name = ccpp_error_message - long_name = Error message for error handling in CCPP - units = none - dimensions = () - type = character - kind = len=512 - intent = out -[ errflg ] - standard_name = ccpp_error_code - long_name = Error flag for error handling in CCPP - units = 1 - dimensions = () - type = integer - intent = out -[ccpp-arg-table] - name = duplicate_dyn_const_finalize - type = scheme -[ errmsg ] - standard_name = ccpp_error_message - long_name = Error message for error handling in CCPP - units = none - dimensions = () - type = character - kind = len=512 - intent = out -[ errflg ] - standard_name = ccpp_error_code - long_name = Error flag for error handling in CCPP - units = 1 - dimensions = () - type = integer - intent = out diff --git a/test/unit_tests/sample_scheme_files/dyn_const_not_present.F90 b/test/unit_tests/sample_scheme_files/dyn_const_not_present.F90 deleted file mode 100644 index 615f0467..00000000 --- a/test/unit_tests/sample_scheme_files/dyn_const_not_present.F90 +++ /dev/null @@ -1,75 +0,0 @@ -! Test parameterization that is missing the specified dynamic constituents routine -! - -MODULE temp_adjust - - USE ccpp_kinds, ONLY: kind_phys - - IMPLICIT NONE - PRIVATE - - PUBLIC :: temp_adjust_init - PUBLIC :: temp_adjust_run - PUBLIC :: temp_adjust_finalize - -CONTAINS - - !> \section arg_table_temp_adjust_run Argument Table - !! \htmlinclude arg_table_temp_adjust_run.html - !! - subroutine temp_adjust_run(foo, timestep, temp_prev, temp_layer, qv, ps, & - errmsg, errflg) - - integer, intent(in) :: foo - real(kind_phys), intent(in) :: timestep - real(kind_phys), intent(inout) :: qv(:) - real(kind_phys), intent(inout) :: ps(:) - REAL(kind_phys), intent(in) :: temp_prev(:) - REAL(kind_phys), intent(inout) :: temp_layer(foo) - character(len=512), intent(out) :: errmsg - integer, intent(out) :: errflg - !---------------------------------------------------------------- - - integer :: col_index - - errmsg = '' - errflg = 0 - - do col_index = 1, foo - temp_layer(col_index) = temp_layer(col_index) + temp_prev(col_index) - qv(col_index) = qv(col_index) + 1.0_kind_phys - end do - - END SUBROUTINE temp_adjust_run - - !> \section arg_table_temp_adjust_init Argument Table - !! \htmlinclude arg_table_temp_adjust_init.html - !! - subroutine temp_adjust_init (errmsg, errflg) - - character(len=512), intent(out) :: errmsg - integer, intent(out) :: errflg - - ! This routine currently does nothing - - errmsg = '' - errflg = 0 - - end subroutine temp_adjust_init - - !> \section arg_table_temp_adjust_finalize Argument Table - !! \htmlinclude arg_table_temp_adjust_finalize.html - !! - subroutine temp_adjust_finalize (errmsg, errflg) - - character(len=512), intent(out) :: errmsg - integer, intent(out) :: errflg - - ! This routine currently does nothing - - errmsg = '' - errflg = 0 - - end subroutine temp_adjust_finalize - -END MODULE temp_adjust diff --git a/test/unit_tests/sample_scheme_files/dyn_const_not_present.meta b/test/unit_tests/sample_scheme_files/dyn_const_not_present.meta deleted file mode 100644 index 05638ded..00000000 --- a/test/unit_tests/sample_scheme_files/dyn_const_not_present.meta +++ /dev/null @@ -1,104 +0,0 @@ -[ccpp-table-properties] - name = temp_adjust - type = scheme - dynamic_constituent_routine = dyn_consts - -######################################################################## -[ccpp-arg-table] - name = temp_adjust_run - type = scheme -[ foo ] - standard_name = horizontal_loop_extent - type = integer - units = count - dimensions = () - intent = in -[ timestep ] - standard_name = time_step_for_physics - long_name = time step - units = s - dimensions = () - type = real - kind = kind_phys - intent = in -[ temp_prev ] - standard_name = potential_temperature_at_previous_timestep - units = K - dimensions = (horizontal_loop_extent) - type = real - kind = kind_phys - intent = in -[ temp_layer ] - standard_name = potential_temperature - units = K - dimensions = (horizontal_loop_extent) - type = real - kind = kind_phys - intent = inout -[ qv ] - standard_name = water_vapor_specific_humidity - units = kg kg-1 - dimensions = (horizontal_loop_extent) - type = real - kind = kind_phys - advected = true - intent = inout -[ ps ] - standard_name = surface_air_pressure - state_variable = true - type = real - kind = kind_phys - units = Pa - dimensions = (horizontal_loop_extent) - intent = inout -[ errmsg ] - standard_name = ccpp_error_message - long_name = Error message for error handling in CCPP - units = none - dimensions = () - type = character - kind = len=512 - intent = out -[ errflg ] - standard_name = ccpp_error_code - long_name = Error flag for error handling in CCPP - units = 1 - dimensions = () - type = integer - intent = out -[ccpp-arg-table] - name = temp_adjust_init - type = scheme -[ errmsg ] - standard_name = ccpp_error_message - long_name = Error message for error handling in CCPP - units = none - dimensions = () - type = character - kind = len=512 - intent = out -[ errflg ] - standard_name = ccpp_error_code - long_name = Error flag for error handling in CCPP - units = 1 - dimensions = () - type = integer - intent = out -[ccpp-arg-table] - name = temp_adjust_finalize - type = scheme -[ errmsg ] - standard_name = ccpp_error_message - long_name = Error message for error handling in CCPP - units = none - dimensions = () - type = character - kind = len=512 - intent = out -[ errflg ] - standard_name = ccpp_error_code - long_name = Error flag for error handling in CCPP - units = 1 - dimensions = () - type = integer - intent = out diff --git a/test/unit_tests/sample_scheme_files/dyn_const_not_present_nested.F90 b/test/unit_tests/sample_scheme_files/dyn_const_not_present_nested.F90 deleted file mode 100644 index 259d4d66..00000000 --- a/test/unit_tests/sample_scheme_files/dyn_const_not_present_nested.F90 +++ /dev/null @@ -1,100 +0,0 @@ -! Test parameterization with no vertical level -! - -MODULE temp_adjust - - USE ccpp_kinds, ONLY: kind_phys - - IMPLICIT NONE - PRIVATE - - PUBLIC :: temp_adjust_init - PUBLIC :: temp_adjust_run - PUBLIC :: temp_adjust_finalize - PUBLIC :: dyn_consts - -CONTAINS - - !> \section arg_table_temp_adjust_run Argument Table - !! \htmlinclude arg_table_temp_adjust_run.html - !! - subroutine temp_adjust_run(foo, timestep, temp_prev, temp_layer, qv, ps, & - errmsg, errflg) - - integer, intent(in) :: foo - real(kind_phys), intent(in) :: timestep - real(kind_phys), intent(inout) :: qv(:) - real(kind_phys), intent(inout) :: ps(:) - REAL(kind_phys), intent(in) :: temp_prev(:) - REAL(kind_phys), intent(inout) :: temp_layer(foo) - character(len=512), intent(out) :: errmsg - integer, intent(out) :: errflg - !---------------------------------------------------------------- - - integer :: col_index - - errmsg = '' - errflg = 0 - - do col_index = 1, foo - temp_layer(col_index) = temp_layer(col_index) + temp_prev(col_index) - qv(col_index) = qv(col_index) + 1.0_kind_phys - end do - - END SUBROUTINE temp_adjust_run - - !> \section arg_table_temp_adjust_init Argument Table - !! \htmlinclude arg_table_temp_adjust_init.html - !! - subroutine temp_adjust_init (errmsg, errflg) - - character(len=512), intent(out) :: errmsg - integer, intent(out) :: errflg - - ! This routine currently does nothing - - errmsg = '' - errflg = 0 - - end subroutine temp_adjust_init - - !> \section arg_table_temp_adjust_finalize Argument Table - !! \htmlinclude arg_table_temp_adjust_finalize.html - !! - subroutine temp_adjust_finalize (errmsg, errflg) - - character(len=512), intent(out) :: errmsg - integer, intent(out) :: errflg - - ! This routine currently does nothing - - errmsg = '' - errflg = 0 - - end subroutine temp_adjust_finalize - - subroutine some_subroutine() - contains - subroutine dyn_consts(dyn_const, errcode, errmsg) - use ccpp_constituent_prop_mod, only: ccpp_constituent_properties_t - type(ccpp_constituent_properties_t), allocatable, intent(out) :: dyn_const(:) - integer, intent(out) :: errcode - character(len=512), intent(out) :: errmsg - - errmsg = '' - errcode = 0 - allocate(dyn_const(1), stat=errcode) - if (errcode /= 0) then - errmsg = 'Error allocating dyn_const in dyn_consts' - end if - call dyn_const(1)%instantiate(std_name="dyn_const1", long_name='dyn const1', & - units='kg kg-1', default_value=1._kind_phys, & - vertical_dim='vertical_layer_dimension', advected=.true., & - errcode=errcode, errmsg=errmsg) - - end subroutine dyn_consts - - end subroutine some_subroutine - - -END MODULE temp_adjust diff --git a/test/unit_tests/sample_scheme_files/dyn_const_not_present_nested.meta b/test/unit_tests/sample_scheme_files/dyn_const_not_present_nested.meta deleted file mode 100644 index 05638ded..00000000 --- a/test/unit_tests/sample_scheme_files/dyn_const_not_present_nested.meta +++ /dev/null @@ -1,104 +0,0 @@ -[ccpp-table-properties] - name = temp_adjust - type = scheme - dynamic_constituent_routine = dyn_consts - -######################################################################## -[ccpp-arg-table] - name = temp_adjust_run - type = scheme -[ foo ] - standard_name = horizontal_loop_extent - type = integer - units = count - dimensions = () - intent = in -[ timestep ] - standard_name = time_step_for_physics - long_name = time step - units = s - dimensions = () - type = real - kind = kind_phys - intent = in -[ temp_prev ] - standard_name = potential_temperature_at_previous_timestep - units = K - dimensions = (horizontal_loop_extent) - type = real - kind = kind_phys - intent = in -[ temp_layer ] - standard_name = potential_temperature - units = K - dimensions = (horizontal_loop_extent) - type = real - kind = kind_phys - intent = inout -[ qv ] - standard_name = water_vapor_specific_humidity - units = kg kg-1 - dimensions = (horizontal_loop_extent) - type = real - kind = kind_phys - advected = true - intent = inout -[ ps ] - standard_name = surface_air_pressure - state_variable = true - type = real - kind = kind_phys - units = Pa - dimensions = (horizontal_loop_extent) - intent = inout -[ errmsg ] - standard_name = ccpp_error_message - long_name = Error message for error handling in CCPP - units = none - dimensions = () - type = character - kind = len=512 - intent = out -[ errflg ] - standard_name = ccpp_error_code - long_name = Error flag for error handling in CCPP - units = 1 - dimensions = () - type = integer - intent = out -[ccpp-arg-table] - name = temp_adjust_init - type = scheme -[ errmsg ] - standard_name = ccpp_error_message - long_name = Error message for error handling in CCPP - units = none - dimensions = () - type = character - kind = len=512 - intent = out -[ errflg ] - standard_name = ccpp_error_code - long_name = Error flag for error handling in CCPP - units = 1 - dimensions = () - type = integer - intent = out -[ccpp-arg-table] - name = temp_adjust_finalize - type = scheme -[ errmsg ] - standard_name = ccpp_error_message - long_name = Error message for error handling in CCPP - units = none - dimensions = () - type = character - kind = len=512 - intent = out -[ errflg ] - standard_name = ccpp_error_code - long_name = Error flag for error handling in CCPP - units = 1 - dimensions = () - type = integer - intent = out diff --git a/test/unit_tests/sample_scheme_files/temp_adjust.F90 b/test/unit_tests/sample_scheme_files/temp_adjust.F90 index 4b78642d..4db1f2a8 100644 --- a/test/unit_tests/sample_scheme_files/temp_adjust.F90 +++ b/test/unit_tests/sample_scheme_files/temp_adjust.F90 @@ -11,10 +11,30 @@ MODULE temp_adjust PUBLIC :: temp_adjust_init PUBLIC :: temp_adjust_run PUBLIC :: temp_adjust_finalize - PUBLIC :: dyn_consts CONTAINS + !> \section arg_table_temp_adjust_register Argument Table + !! \htmlinclude arg_table_temp_adjust_register.html + !! + subroutine temp_adjust_register(config_var, dyn_const, errmsg, errflg) + logical, intent(in) :: config_var + type(ccpp_constituent_properties_t), allocatable, intent(out) :: dyn_const + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + if (.not. config_var) then + return + end if + + allocate(dyn_const(1)) + call dyn_const(1)%instantiate(std_name="dyn_const", long_name='dyn const', & + units='kg kg-1', default_value=1._kind_phys, & + vertical_dim='vertical_layer_dimension', advected=.true., & + errcode=errflg, errmsg=errmsg) + + end subroutine temp_adjust_register + !> \section arg_table_temp_adjust_run Argument Table !! \htmlinclude arg_table_temp_adjust_run.html !! @@ -73,24 +93,4 @@ subroutine temp_adjust_finalize (errmsg, errflg) end subroutine temp_adjust_finalize - subroutine dyn_consts (dyn_const, errcode, errmsg) - use ccpp_constituent_prop_mod, only: ccpp_constituent_properties_t - type(ccpp_constituent_properties_t), allocatable, intent(out) :: dyn_const(:) - integer, intent(out) :: errcode - character(len=512), intent(out) :: errmsg - - errmsg = '' - errcode = 0 - allocate(dyn_const(1), stat=errcode) - if (errcode /= 0) then - errmsg = 'Error allocating dyn_const in dyn_consts' - end if - call dyn_const(1)%instantiate(std_name="dyn_const1", long_name='dyn const1', & - units='kg kg-1', default_value=1._kind_phys, & - vertical_dim='vertical_layer_dimension', advected=.true., & - errcode=errcode, errmsg=errmsg) - - end subroutine dyn_consts - - END MODULE temp_adjust diff --git a/test/unit_tests/sample_scheme_files/temp_adjust.meta b/test/unit_tests/sample_scheme_files/temp_adjust.meta index 063409d6..4b96e316 100644 --- a/test/unit_tests/sample_scheme_files/temp_adjust.meta +++ b/test/unit_tests/sample_scheme_files/temp_adjust.meta @@ -1,9 +1,40 @@ [ccpp-table-properties] name = temp_adjust type = scheme - dynamic_constituent_routine = dyn_consts ######################################################################## +[ccpp-arg-table] + name = temp_adjust_register + type = scheme +[ config_var ] + standard_name = configuration_variable + type = logical + units = none + dimensions = () + intent = in +[ dyn_const ] + standard_name = dynamic_constituents_for_temp_adjust + type = ccpp_constituent_properties_t + units = none + dimensions = () + intent = out + allocatable = True +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out +######################################################################## [ccpp-arg-table] name = temp_adjust_run type = scheme diff --git a/test/unit_tests/test_metadata_scheme_file.py b/test/unit_tests/test_metadata_scheme_file.py index fb0ce2f4..feab8acb 100644 --- a/test/unit_tests/test_metadata_scheme_file.py +++ b/test/unit_tests/test_metadata_scheme_file.py @@ -35,10 +35,6 @@ - Correctly interpret Fortran with preprocessor logic which affects the subroutine statement and/or the dummy argument statements resulting in incorrect Fortran - - Correctly detect a missing dynamic constituent routine - in Fortran - - Correctly raise an error if there are two dynamic - constituent routines with the same name Assumptions: @@ -98,13 +94,15 @@ def test_good_scheme_file(self): "temp_adjust.meta")] # Exercise scheme_headers, table_dict = parse_scheme_files(scheme_files, - self._run_env) + self._run_env, + skip_ddt_check=True) # Verify size of returned list equals number of scheme headers # in the test file and that header (subroutine) names are - # 'temp_adjust_[init,run,finalize]' - self.assertEqual(len(scheme_headers), 3) + # 'temp_adjust_[register,init,run,finalize]' + self.assertEqual(len(scheme_headers), 4) # Verify header titles titles = [elem.title for elem in scheme_headers] + self.assertTrue('temp_adjust_register' in titles) self.assertTrue('temp_adjust_init' in titles) self.assertTrue('temp_adjust_run' in titles) self.assertTrue('temp_adjust_finalize' in titles) @@ -345,37 +343,6 @@ def test_scheme_ddt_only(self): scheme_headers, table_dict = parse_scheme_files(scheme_files, self._run_env_ccpp) - def test_not_present_dynamic_constituents_routine(self): - scheme_files = [os.path.join(self._sample_files_dir, - "dyn_const_not_present.meta")] - - with self.assertRaises(CCPPError) as context: - _, _ = parse_scheme_files(scheme_files, self._run_env_ccpp) - - emsg = "Dynamic constituent routine dyn_consts not found in fortran" - self.assertTrue(emsg in str(context.exception)) - - def test_not_present_nested_dynamic_constituents_routine(self): - scheme_files = [os.path.join(self._sample_files_dir, - "dyn_const_not_present_nested.meta")] - - with self.assertRaises(CCPPError) as context: - _, _ = parse_scheme_files(scheme_files, self._run_env_ccpp) - - emsg = "Dynamic constituent routine dyn_consts not found in fortran" - self.assertTrue(emsg in str(context.exception)) - - def test_duplicate_dynamic_constituents_routine_name(self): - scheme_files = [os.path.join(self._sample_files_dir, - "temp_adjust.meta"), - os.path.join(self._sample_files_dir, - "duplicate_dyn_const.meta")] - - with self.assertRaises(CCPPError) as context: - _, _ = parse_scheme_files(scheme_files, self._run_env_ccpp) - emsg = "ERROR: Dynamic constituent routine names must be unique." - self.assertTrue(emsg in str(context.exception)) - if __name__ == "__main__": unittest.main() diff --git a/test/unit_tests/test_metadata_table.py b/test/unit_tests/test_metadata_table.py index 08248150..94ed1e67 100755 --- a/test/unit_tests/test_metadata_table.py +++ b/test/unit_tests/test_metadata_table.py @@ -48,7 +48,6 @@ def test_good_host_file(self): #Verify that: # no dependencies is returned as '' # rel_path is returned as None - # dynamic_constituent_routine is returned as 'dyn_consts' # size of returned list equals number of headers in the test file # ccpp-table-properties name is 'test_host' dependencies = result[0].dependencies @@ -57,8 +56,6 @@ def test_good_host_file(self): self.assertEqual(len(dependencies), 0) self.assertIsNone(rel_path) self.assertEqual(len(result), 1) - dyn_const_routine = result[0].dyn_const_routine - self.assertEqual(dyn_const_routine, 'dyn_consts') titles = [elem.table_name for elem in result] self.assertIn('test_host', titles, msg="Header name 'test_host' is expected but not found") From 76dbf36ddccbe1cf04fa9de04b38338c26b648aa Mon Sep 17 00:00:00 2001 From: mwaxmonsky <137746677+mwaxmonsky@users.noreply.github.com> Date: Thu, 31 Oct 2024 09:10:46 -0400 Subject: [PATCH 2/2] Add missing xmllint dependency for capgen tests (#602) Add missing xmllint dependency for capgen tests Adds the `libxml2-utils` dependency to allow full capgen tests to run without printing an error message about a missing dependency User interface changes?: No Fixes: closes #601 (by adding missing dependency) Testing: test removed: None unit tests: CI run system tests: N/A manual testing: N/A Co-authored-by: Dom Heinzeller --- .github/workflows/capgen_unit_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index 363d19e3..820553af 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: update repos and install dependencies - run: sudo apt-get update && sudo apt-get install -y build-essential ${{matrix.fortran-compiler}} cmake python3 git + run: sudo apt-get update && sudo apt-get install -y build-essential ${{matrix.fortran-compiler}} cmake python3 git libxml2-utils - name: Run unit tests run: cd test && ./run_fortran_tests.sh