diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml new file mode 100644 index 00000000..c57461cb --- /dev/null +++ b/.github/workflows/python.yaml @@ -0,0 +1,49 @@ +name: Python package + +on: + workflow_dispatch: + pull_request: + branches: [feature/capgen, main] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest + - name: Test with pytest + run: | + export PYTHONPATH=$(pwd)/scripts:$(pwd)/scripts/parse_tools + pytest -v test/ + + doctest: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest + - name: Doctest + run: | + export PYTHONPATH=$(pwd)/scripts:$(pwd)/scripts/parse_tools + pytest -v scripts/ --doctest-modules diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..59386b60 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +addopts = -ra --ignore=scripts/metadata2html.py --ignore-glob=test/**/test_reports.py + diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index 042f6d16..b16998f3 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -312,17 +312,29 @@ def compare_fheader_to_mheader(meta_header, fort_header, logger): # end if for mind, mvar in enumerate(mlist): lname = mvar.get_prop_value('local_name') + mname = mvar.get_prop_value('standard_name') arrayref = is_arrayspec(lname) fvar, find = find_var_in_list(lname, flist) # Check for consistency between optional variables in metadata and # optional variables in fortran. Error if optional attribute is # missing from fortran declaration. + # first check: if metadata says the variable is optional, does the fortran match? mopt = mvar.get_prop_value('optional') if find and mopt: fopt = fvar.get_prop_value('optional') if (not fopt): - errmsg = 'Missing optional attribute in fortran declaration for variable {}, in file {}' - errors_found = add_error(errors_found, errmsg.format(mname,title)) + errmsg = f'Missing "optional" attribute in fortran declaration for variable {mname}, ' \ + f'for {title}' + errors_found = add_error(errors_found, errmsg) + # end if + # end if + # now check: if fortran says the variable is optional, does the metadata match? + if fvar: + fopt = fvar.get_prop_value('optional') + if (fopt and not mopt): + errmsg = f'Missing "optional" metadata property for variable {mname}, ' \ + f'for {title}' + errors_found = add_error(errors_found, errmsg) # end if # end if if mind >= flen: @@ -387,7 +399,8 @@ def compare_fheader_to_mheader(meta_header, fort_header, logger): ############################################################################### def check_fortran_against_metadata(meta_headers, fort_headers, - mfilename, ffilename, logger): + mfilename, ffilename, logger, + dyn_routines=None, fortran_routines=None): ############################################################################### """Compare a set of metadata headers from against the code in the associated Fortran file, . @@ -439,6 +452,17 @@ 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 ############################################################################### @@ -470,7 +494,7 @@ def parse_host_model_files(host_filenames, host_name, run_env): # parse metadata file mtables = parse_metadata_file(filename, known_ddts, run_env) fort_file = find_associated_fortran_file(filename) - ftables = parse_fortran_file(fort_file, run_env) + ftables, _ = parse_fortran_file(fort_file, run_env) # Check Fortran against metadata (will raise an exception on error) mheaders = list() for sect in [x.sections() for x in mtables]: @@ -511,7 +535,7 @@ def parse_host_model_files(host_filenames, host_name, run_env): return host_model ############################################################################### -def parse_scheme_files(scheme_filenames, run_env): +def parse_scheme_files(scheme_filenames, run_env, skip_ddt_check=False): ############################################################################### """ Gather information from scheme files (e.g., init, run, and finalize @@ -524,9 +548,10 @@ def parse_scheme_files(scheme_filenames, run_env): for filename in scheme_filenames: logger.info('Reading CCPP schemes from {}'.format(filename)) # parse metadata file - mtables = parse_metadata_file(filename, known_ddts, run_env) + mtables = parse_metadata_file(filename, known_ddts, run_env, + skip_ddt_check=skip_ddt_check) fort_file = find_associated_fortran_file(filename) - ftables = parse_fortran_file(fort_file, run_env) + ftables, additional_routines = parse_fortran_file(fort_file, run_env) # Check Fortran against metadata (will raise an exception on error) mheaders = list() for sect in [x.sections() for x in mtables]: @@ -536,8 +561,16 @@ def parse_scheme_files(scheme_filenames, run_env): 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) + filename, fort_file, logger, + dyn_routines=dyn_routines, + fortran_routines=additional_routines) # Check for duplicate tables, then add to dict for table in mtables: if table.table_name in table_dict: @@ -560,6 +593,23 @@ def parse_scheme_files(scheme_filenames, run_env): # 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 ############################################################################### @@ -623,13 +673,25 @@ def capgen(run_env, return_db=False): # end if # 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 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 + scheme_files= [const_prop_mod] + scheme_files # end if - # Next, parse the scheme files 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: @@ -660,7 +722,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) + ccpp_api = API(sdfs, host_model, scheme_headers, run_env, dyn_const_dict) 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 194a46ea..20dee70d 100755 --- a/scripts/ccpp_datafile.py +++ b/scripts/ccpp_datafile.py @@ -22,14 +22,21 @@ import sys import xml.etree.ElementTree as ET # CCPP framework imports +from framework_env import CCPPFrameworkEnv from metadata_table import UNKNOWN_PROCESS_TYPE from metavar import Var from parse_tools import read_xml_file, PrettyElementTree +from parse_tools import ParseContext, ParseSource from suite_objects import VerticalLoop, Subcycle # Global data _INDENT_STR = " " +# Used for creating template variables +_MVAR_DUMMY_RUN_ENV = CCPPFrameworkEnv(None, ndict={'host_files':'', + 'scheme_files':'', + 'suites':''}) + ## datatable_report must have an action for each report type _VALID_REPORTS = [{"report" : "host_files", "type" : bool, "help" : @@ -51,6 +58,8 @@ {"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, @@ -93,7 +102,20 @@ class DatatableReport(object): __valid_actions = [x["report"] for x in _VALID_REPORTS] def __init__(self, action, value=True): - """Initialize this report as report-type, """ + """Initialize this report as report-type, + # 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 + Traceback (most recent call last): + ... + ValueError: Invalid action, 'banana' + + """ if action in DatatableReport.__valid_actions: self.__action = action self.__value = value @@ -103,7 +125,13 @@ def __init__(self, action, value=True): def action_is(self, action): """If matches this report type, return True. - Otherwise, return False""" + Otherwise, return False + >>> DatatableReport('suite_files', False).action_is('suite_files') + True + + >>> DatatableReport('suite_files', False).action_is('banana') + False + """ return action == self.__action @property @@ -205,7 +233,19 @@ def _read_datatable(datatable): def _find_table_section(table, elem_type): ############################################################################### """Look for and return an element type, , in . - Raise an exception if the element is not found.""" + Raise an exception if the element is not found. + # Test present section + >>> table = ET.fromstring("") + >>> _find_table_section(table, "ccpp_files").tag + 'ccpp_files' + + # Test missing section + >>> table = ET.fromstring("") + >>> _find_table_section(table, "ccpp_files").tag + Traceback (most recent call last): + ... + ccpp_datafile.CCPPDatatableError: Element type, 'ccpp_files', not found in table + """ found = table.find(elem_type) if found is None: emsg = "Element type, '{}', not found in table" @@ -217,7 +257,26 @@ def _find_table_section(table, elem_type): def _retrieve_ccpp_files(table, file_type=None): ############################################################################### """Find and retrieve a list of generated filenames from
. - If is not None, only return that file type.""" + If is not None, only return that file type. + # Test valid ccpp files + >>> table = ET.fromstring(""\ + "/path/to/file1"\ + "/path/to/file2"\ + "/path/to/file3"\ + "/path/to/file4"\ + "") + >>> _retrieve_ccpp_files(table) + ['/path/to/file1', '/path/to/file2', '/path/to/file3', '/path/to/file4'] + + # Test invalid file type + >>> table = ET.fromstring(""\ + "/path/to/file1"\ + "") + >>> _retrieve_ccpp_files(table) + Traceback (most recent call last): + ... + ccpp_datafile.CCPPDatatableError: Invalid file list entry type, 'banana' + """ ccpp_files = list() # Find the files section for section in _find_table_section(table, "ccpp_files"): @@ -237,7 +296,23 @@ def _retrieve_ccpp_files(table, file_type=None): ############################################################################### def _retrieve_process_list(table): ############################################################################### - """Find and return a list of all physics scheme processes in
.""" + """Find and return a list of all physics scheme processes in
. + # Test valid module + >>> table = ET.fromstring(""\ + ""\ + ""\ + "") + >>> _retrieve_process_list(table) + ['four=scheme2', 'three=scheme1'] + + # Test no schemes element + >>> table = ET.fromstring("") + >>> _retrieve_process_list(table) + Traceback (most recent call last): + ... + ccpp_datafile.CCPPDatatableError: Could not find 'schemes' element + """ + result = list() schemes = table.find("schemes") if schemes is None: @@ -250,12 +325,28 @@ def _retrieve_process_list(table): result.append("{}={}".format(proc, name)) # end if # end for - return result + return sorted(result) ############################################################################### def _retrieve_module_list(table): ############################################################################### - """Find and return a list of all scheme modules in
.""" + """Find and return a list of all scheme modules in
. + # Test valid module + >>> table = ET.fromstring(""\ + ""\ + ""\ + "") + >>> _retrieve_module_list(table) + ['partridge', 'turtle_dove'] + + # Test no schemes element + >>> table = ET.fromstring("") + >>> _retrieve_module_list(table) + Traceback (most recent call last): + ... + ccpp_datafile.CCPPDatatableError: Could not find 'schemes' element + + """ result = set() schemes = table.find("schemes") if schemes is None: @@ -274,7 +365,28 @@ def _retrieve_module_list(table): ############################################################################### def _retrieve_dependencies(table): ############################################################################### - """Find and return a list of all host and scheme dependencies.""" + """Find and return a list of all host and scheme dependencies. + # Test valid dependencies + >>> table = ET.fromstring("" \ + "bananaorange" \ + "") + >>> _retrieve_dependencies(table) + ['banana', 'orange'] + + # Test no dependencies + >>> table = ET.fromstring("" \ + "") + >>> _retrieve_dependencies(table) + [] + + # Test missing dependencies tag + >>> table = ET.fromstring("") + >>> _retrieve_dependencies(table) + Traceback (most recent call last): + ... + ccpp_datafile.CCPPDatatableError: Could not find 'dependencies' element + """ + result = set() depends = table.find("dependencies") if depends is None: @@ -288,11 +400,72 @@ 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): ############################################################################### """Find and return a var_dictionary named, in
. - If not found, return None""" + If not found, return None + # Test valid table with dict_name provided + >>> table = ET.fromstring(""\ + ""\ + ""\ + ""\ + "") + >>> _find_var_dictionary(table, dict_name='orange').get("name") + 'orange' + + # Test valid table with dict_type provided + >>> _find_var_dictionary(table, dict_type='host').get("name") + 'banana' + + # Test valid table with both dict_type and dict_name provided + >>> _find_var_dictionary(table, dict_type='host', dict_name='banana').get("name") + 'banana' + + # Test no table found (expect None) + >>> _find_var_dictionary(table, dict_name='apple') is None + True + + # Test error handling + >>> _find_var_dictionary(table) + Traceback (most recent call last): + ... + ValueError: At least one of or must contain a string + """ var_dicts = table.find("var_dictionaries") target_dict = None if (dict_name is None) and (dict_type is None): @@ -311,7 +484,19 @@ def _find_var_dictionary(table, dict_name=None, dict_type=None): ############################################################################### def _retrieve_suite_list(table): ############################################################################### - """Find and return a list of all suites found in
.""" + """Find and return a list of all suites found in
. + # Test suites are found + >>> table = ET.fromstring(""\ + ""\ + "") + >>> _retrieve_suite_list(table) + ['umbrella', 'galoshes'] + + # Test suites not found + >>> table = ET.fromstring("") + >>> _retrieve_suite_list(table) + [] + """ result = list() # First, find the API variable dictionary api_elem = table.find("api") @@ -328,7 +513,21 @@ def _retrieve_suite_list(table): ############################################################################### def _retrieve_suite_group_names(table, suite_name): ############################################################################### - """Find and return a list of the group names for this suite.""" + """Find and return a list of the group names for this suite. + # Test suites are found + >>> table = ET.fromstring(""\ + ""\ + ""\ + ""\ + "") + >>> _retrieve_suite_group_names(table, 'umbrella') + ['florence', 'delores', 'edna'] + + # Test non-present suite + >>> _retrieve_suite_group_names(table, 'poncho') + [] + """ + result = list() # First, find the API variable dictionary api_elem = table.find("api") @@ -354,6 +553,23 @@ def _is_variable_protected(table, var_name, var_dict): """Determine whether variable, , from is protected. So this by checking for the 'protected' attribute for in or any of 's ancestors (parent dictionaries). + # Test found variable + >>> table = ET.fromstring(""\ + ""\ + ""\ + ""\ + "") + >>> var_dict = _find_var_dictionary(table, dict_name="banana") + >>> _is_variable_protected(table, "hi", var_dict) + True + + >>> var_dict = _find_var_dictionary(table, dict_type="api") + >>> _is_variable_protected(table, "hello", var_dict) + False + + # Test non-present variable also returns False + >>> _is_variable_protected(table, "hiya", var_dict) + False """ protected = False while (not protected) and (var_dict is not None): @@ -377,13 +593,54 @@ def _is_variable_protected(table, var_name, var_dict): ############################################################################### def _retrieve_variable_list(table, suite_name, - intent_type=None, excl_prot=True): + intent_type=None, exclude_protected=True): ############################################################################### """Find and return a list of all the required variables in . If suite, , is not found in
, return an empty list. If is present, return only that variable type (input or output). - If is True, do not include protected variables""" + If is True, do not include protected variables + >>> table = ET.fromstring(""\ + ""\ + ""\ + ""\ + ""\ + ""\ + "") + + # Test group variable retrieval + >>> _retrieve_variable_list(table, 'fruit', exclude_protected=False) + ['var3', 'var4', 'var5'] + + >>> _retrieve_variable_list(table, 'fruit') + ['var3'] + + >>> _retrieve_variable_list(table, 'fruit', intent_type='input', exclude_protected=False) + ['var3', 'var5'] + + >>> _retrieve_variable_list(table, 'fruit', intent_type='output', exclude_protected=False) + ['var4', 'var5'] + + >>> _retrieve_variable_list(table, 'fruit', intent_type='input') + ['var3'] + + >>> _retrieve_variable_list(table, 'fruit', intent_type='output') + [] + + # Test host variable retrieval + >>> _retrieve_variable_list(table, 'fruit', intent_type='host', exclude_protected=False) + ['var1', 'var2'] + + >>> _retrieve_variable_list(table, 'fruit', intent_type='host') + ['var1'] + + # Test invalid intent type + >>> _retrieve_variable_list(table, 'fruit', intent_type='banana') + Traceback (most recent call last): + ... + ccpp_datafile.CCPPDatatableError: Invalid intent_type, 'banana' + + """ # Note that suites do not have call lists so we have to collect # all the variables from the suite's groups. var_set = set() @@ -400,14 +657,14 @@ def _retrieve_variable_list(table, suite_name, emsg = "Invalid intent_type, '{}'" raise CCPPDatatableError(emsg.format(intent_type)) # end if - if excl_prot or (intent_type == "host"): + if exclude_protected or (intent_type == "host"): host_dict = _find_var_dictionary(table, dict_type="host") if host_dict is not None: hvars = host_dict.find("variables") if hvars is not None: for var in hvars: vname = var.get("name") - if excl_prot: + if exclude_protected: exclude = _is_variable_protected(table, vname, host_dict) else: @@ -440,7 +697,7 @@ def _retrieve_variable_list(table, suite_name, for var in gvars: vname = var.get("name") vintent = var.get("intent") - if excl_prot: + if exclude_protected: exclude = vname in excl_vars if not exclude: exclude = _is_variable_protected(table, vname, @@ -460,7 +717,7 @@ def _retrieve_variable_list(table, suite_name, return sorted(var_set) ############################################################################### -def datatable_report(datatable, action, sep, excl_prot=False): +def datatable_report(datatable, action, sep, exclude_protected=False): ############################################################################### """Perform a lookup on and return the result. """ @@ -489,21 +746,23 @@ def datatable_report(datatable, action, sep, excl_prot=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"): result = _retrieve_variable_list(table, action.value, - excl_prot=excl_prot) + exclude_protected=exclude_protected) elif action.action_is("input_variables"): result = _retrieve_variable_list(table, action.value, intent_type="input", - excl_prot=excl_prot) + exclude_protected=exclude_protected) elif action.action_is("output_variables"): result = _retrieve_variable_list(table, action.value, intent_type="output", - excl_prot=excl_prot) + exclude_protected=exclude_protected) elif action.action_is("host_variables"): - result = _retrieve_variable_list(table, "host", excl_prot=excl_prot, + result = _retrieve_variable_list(table, "host", exclude_protected=exclude_protected, intent_type="host") else: result = '' @@ -528,6 +787,19 @@ def _format_line(line_in, indent, line_wrap, increase_indent=True): If is True, increase the indent level for new lines created by the process. A value of less one means do not wrap the line. + >>> line = "This is a very long string that should be wrapped hopefully" + >>> _format_line(line, 1, 50) + ' This is a very long string that should be\\n wrapped hopefully\\n' + + >>> _format_line(line, 1, 50, increase_indent=False) + ' This is a very long string that should be\\n wrapped hopefully\\n' + + >>> _format_line(line, 0, 50) + 'This is a very long string that should be wrapped\\n hopefully\\n' + + >>> line = 'short line' + >>> _format_line(line, 0, 2, increase_indent=False) + 'short\\nline\\n' """ in_squote = False in_dquote = False @@ -589,7 +861,14 @@ def _format_line(line_in, indent, line_wrap, increase_indent=True): ############################################################################### def table_entry_pretty_print(entry, indent, line_wrap=-1): ############################################################################### - """Create and return a pretty print string of the contents of """ + """Create and return a pretty print string of the contents of + >>> table = ET.fromstring("") + >>> table_entry_pretty_print(table, 0) + '\\n \\n\\n' + + >>> table_entry_pretty_print(table, 1, line_wrap=20) + ' \\n \\n \\n' + """ output = "" outline = "<{}".format(entry.tag) for name in entry.attrib: @@ -647,6 +926,16 @@ def _new_var_entry(parent, var, full_entry=True): ############################################################################### """Create a variable sub-element of with information from . If is False, only include standard name and intent. + >>> parent = ET.fromstring('') + >>> var = Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '(horizontal_loop_extent)', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'DDT', ParseContext()), _MVAR_DUMMY_RUN_ENV) + >>> _new_var_entry(parent, var) + >>> table_entry_pretty_print(parent, 0) + '\\n \\n \\n horizontal_loop_extent\\n \\n \\n ddt\\n \\n \\n vname\\n \\n \\n\\n' + + >>> parent = ET.fromstring('') + >>> _new_var_entry(parent, var, full_entry=False) + >>> table_entry_pretty_print(parent, 0) + '\\n \\n\\n' """ prop_list = ["intent", "local_name"] if full_entry: @@ -787,6 +1076,12 @@ def _add_dependencies(parent, scheme_depends, host_depends): ############################################################################### """Add a section to that lists all the dependencies required by schemes or the host model. + >>> parent = ET.fromstring("") + >>> scheme_depends = ['file1', 'file2'] + >>> host_depends = ['file3', 'file4'] + >>> _add_dependencies(parent, scheme_depends, host_depends) + >>> table_entry_pretty_print(parent, 0) + '\\n \\n \\n file3\\n \\n \\n file4\\n \\n \\n file1\\n \\n \\n file2\\n \\n \\n\\n' """ file_entry = ET.SubElement(parent, "dependencies") for hfile in host_depends: @@ -798,6 +1093,20 @@ 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): ############################################################################### @@ -924,6 +1233,17 @@ 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_fortran_to_metadata.py b/scripts/ccpp_fortran_to_metadata.py index 8fc1d682..4c301d1d 100755 --- a/scripts/ccpp_fortran_to_metadata.py +++ b/scripts/ccpp_fortran_to_metadata.py @@ -177,7 +177,7 @@ def parse_fortran_files(filenames, run_env, output_dir, sep, logger): for filename in filenames: logger.info('Looking for arg_tables from {}'.format(filename)) reset_standard_name_counter() - ftables = parse_fortran_file(filename, run_env) + ftables, _ = parse_fortran_file(filename, run_env) # Create metadata filename filepath = '.'.join(os.path.basename(filename).split('.')[0:-1]) fname = filepath + '.meta' diff --git a/scripts/ccpp_suite.py b/scripts/ccpp_suite.py index d19ca4e8..67e89ede 100644 --- a/scripts/ccpp_suite.py +++ b/scripts/ccpp_suite.py @@ -23,6 +23,7 @@ from parse_tools import init_log, set_log_to_null from suite_objects import CallList, Group, Scheme from metavar import CCPP_LOOP_VAR_STDNAMES +from var_props import is_horizontal_dimension # pylint: disable=too-many-lines @@ -308,6 +309,32 @@ def find_variable(self, standard_name=None, source_var=None, self.add_variable(var, self.__run_env) # Remove the variable from the group group.remove_variable(standard_name) + # Make sure the variable's dimensions are available + # at the init stage (for allocation) + for group in self.groups: + # only add dimension variables to init phase calling list + if group.name == self.__suite_init_group.name: + dims = var.get_dimensions() + # replace horizontal loop dimension if necessary + for idx, dim in enumerate(dims): + if is_horizontal_dimension(dim): + if 'horizontal_loop' in dim: + dims[idx] = 'ccpp_constant_one:horizontal_dimension' + # end if + # end if + # end for + subst_dict = {'dimensions': dims} + prop_dict = var.copy_prop_dict(subst_dict=subst_dict) + temp_var = Var(prop_dict, + ParseSource(var.get_prop_value('scheme'), + var.get_prop_value('local_name'), var.context), + self.__run_env) + # Add dimensions if they're not already there + group.add_variable_dimensions(temp_var, [], + adjust_intent=True, + to_dict=group.call_list) + # end if + # end for else: emsg = ("Group, {}, claimed it had created {} " "but variable was not found") @@ -588,7 +615,7 @@ class API(VarDictionary): 'kind':'len=*', 'units':'', 'dimensions':'()'}, _API_SOURCE, _API_DUMMY_RUN_ENV) - def __init__(self, sdfs, host_model, scheme_headers, run_env): + def __init__(self, sdfs, host_model, scheme_headers, run_env, dyn_const_dict={}): """Initialize this API. is the list of Suite Definition Files to be parsed for data needed by the CCPP cap. @@ -597,11 +624,14 @@ def __init__(self, sdfs, host_model, scheme_headers, run_env): 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 @@ -721,7 +751,7 @@ def write_var_set_loop(ofile, varlist_name, var_list, indent, ofile.write("allocate({}({}))".format(varlist_name, len(var_list)), indent) # end if - for ind, var in enumerate(var_list): + for ind, var in enumerate(sorted(var_list)): if start_var: ind_str = "{} + {}".format(start_var, ind + start_index) else: @@ -1157,6 +1187,11 @@ 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 6b0ac759..84f19b20 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -126,10 +126,10 @@ def __init_err_var(evar, outfile, indent): stdname = evar.get_prop_value('standard_name') if stdname == 'ccpp_error_message': lname = evar.get_prop_value('local_name') - outfile.write("{} = ''".format(lname), indent) + outfile.write(f"{lname} = ''", indent) elif stdname == 'ccpp_error_code': lname = evar.get_prop_value('local_name') - outfile.write("{} = 0".format(lname), indent) + outfile.write(f"{lname} = 0", indent) # end if (no else, just ignore) def declare_public_interfaces(self, outfile, indent): @@ -138,27 +138,25 @@ def declare_public_interfaces(self, outfile, indent): outfile.write("! Public interfaces for handling constituents", indent) outfile.write("! Return the number of constituents for this suite", indent) - outfile.write("public :: {}".format(self.num_consts_funcname()), indent) + outfile.write(f"public :: {self.num_consts_funcname()}", indent) outfile.write("! Return the name of a constituent", indent) - outfile.write("public :: {}".format(self.const_name_subname()), indent) + outfile.write(f"public :: {self.const_name_subname()}", indent) outfile.write("! Copy the data for a constituent", indent) - outfile.write("public :: {}".format(self.copy_const_subname()), indent) + outfile.write(f"public :: {self.copy_const_subname()}", indent) def declare_private_data(self, outfile, indent): """Declare private suite module variables and interfaces to with indent, .""" outfile.write("! Private constituent module data", indent) if self: - stmt = "type({}), private, allocatable :: {}(:)" - outfile.write(stmt.format(CONST_PROP_TYPE, - self.constituent_prop_array_name()), - indent) + stmt = f"type({CONST_PROP_TYPE}), private, allocatable :: {self.constituent_prop_array_name()}(:)" + outfile.write(stmt, indent) # end if - stmt = "logical, private :: {} = .false." - outfile.write(stmt.format(self.constituent_prop_init_name()), indent) + stmt = f"logical, private :: {self.constituent_prop_init_name()} = .false." + outfile.write(stmt, indent) outfile.write("! Private interface for constituents", indent) - stmt = "private :: {}" - outfile.write(stmt.format(self.constituent_prop_init_consts()), indent) + stmt = f"private :: {self.constituent_prop_init_consts()}" + outfile.write(stmt, indent) @classmethod def __errcode_names(cls, err_vars): @@ -175,8 +173,8 @@ def __errcode_names(cls, err_vars): elif stdname == 'ccpp_error_message': errmsg = evar.get_prop_value('local_name') else: - emsg = "Bad errcode variable, '{}'" - raise ParseInternalError(emsg.format(stdname)) + emsg = f"Bad errcode variable, '{stdname}'" + raise ParseInternalError(emsg) # end if # end for if (not errcode) or (not errmsg): @@ -192,8 +190,7 @@ def __errcode_callstr(errcode_name, errmsg_name, suite): """ err_vars = suite.find_error_variables(any_scope=True, clone_as_out=True) errcode, errmsg = ConstituentVarDict.__errcode_names(err_vars) - errvar_str = "{}={}, {}={}".format(errcode, errcode_name, - errmsg, errmsg_name) + errvar_str = f"{errcode}={errcode_name}, {errmsg}={errmsg_name}" return errvar_str def _write_init_check(self, outfile, indent, suite_name, @@ -204,20 +201,20 @@ def _write_init_check(self, outfile, indent, suite_name, outfile.write('', 0) if use_errcode: errcode, errmsg = self.__errcode_names(err_vars) - outfile.write("{} = 0".format(errcode), indent+1) - outfile.write("{} = ''".format(errmsg), indent+1) + outfile.write(f"{errcode} = 0", indent+1) + outfile.write(f"{errmsg} = ''", indent+1) else: raise ParseInternalError("Alternative to errcode not implemented") # end if outfile.write("! Make sure that our constituent array is initialized", indent+1) - stmt = "if (.not. {}) then" - outfile.write(stmt.format(self.constituent_prop_init_name()), indent+1) + stmt = f"if (.not. {self.constituent_prop_init_name()}) then" + outfile.write(stmt, indent+1) if use_errcode: - outfile.write("{} = 1".format(errcode), indent+2) - stmt = 'errmsg = "constituent properties not ' - stmt += 'initialized for suite, {}"' - outfile.write(stmt.format(suite_name), indent+2) + outfile.write(f"{errcode} = 1", indent+2) + stmt = f'{errmsg} = "constituent properties not ' + stmt += f'initialized for suite, {suite_name}"' + outfile.write(stmt, indent+2) outfile.write("end if", indent+1) # end if (no else until an alternative error mechanism supported) @@ -230,25 +227,22 @@ def _write_index_check(self, outfile, indent, suite_name, errcode, errmsg = self.__errcode_names(err_vars) if self: outfile.write("if (index < 1) then", indent+1) - outfile.write("{} = 1".format(errcode), indent+2) - stmt = "write({}, '(a,i0,a)') 'ERROR: index (',index,') " + outfile.write(f"{errcode} = 1", indent+2) + stmt = f"write({errmsg}, '(a,i0,a)') 'ERROR: index (',index,') " stmt += "too small, must be >= 1'" - outfile.write(stmt.format(errmsg), indent+2) - stmt = "else if (index > SIZE({})) then" - outfile.write(stmt.format(self.constituent_prop_array_name()), - indent+1) - outfile.write("{} = 1".format(errcode), indent+2) - stmt = "write({}, '(2(a,i0))') 'ERROR: index (',index,') " - stmt += "too large, must be <= ', SIZE({})" - outfile.write(stmt.format(errmsg, - self.constituent_prop_array_name()), - indent+2) + outfile.write(stmt, indent+2) + stmt = f"else if (index > SIZE({self.constituent_prop_array_name()})) then" + outfile.write(stmt, indent+1) + outfile.write(f"{errcode} = 1", indent+2) + stmt = f"write({errmsg}, '(2(a,i0))') 'ERROR: index (',index,') " + stmt += f"too large, must be <= ', SIZE({self.constituent_prop_array_name()})" + outfile.write(stmt, indent+2) outfile.write("end if", indent+1) else: - outfile.write("{} = 1".format(errcode), indent+1) - stmt = "write({}, '(a,i0,a)') 'ERROR: {}, " + outfile.write(f"{errcode} = 1", indent+1) + stmt = f"write({errmsg}, '(a,i0,a)') 'ERROR: {self.name}, " stmt += "has no constituents'" - outfile.write(stmt.format(errmsg, self.name), indent+1) + outfile.write(stmt, indent+1) # end if else: raise ParseInternalError("Alternative to errcode not implemented") @@ -266,17 +260,14 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): use_errcode = all([x.get_prop_value('standard_name') in errcode_snames for x in err_vars]) errvar_alist = ", ".join([x for x in errvar_names.values()]) - errvar_alist2 = ", {}".format(errvar_alist) if errvar_alist else "" + errvar_alist2 = f", {errvar_alist}" if errvar_alist else "" call_vnames = {'ccpp_error_code' : 'errcode', 'ccpp_error_message' : 'errmsg'} - errvar_call = ", ".join(["{}={}".format(call_vnames[x], errvar_names[x]) - for x in errcode_snames]) - errvar_call2 = ", {}".format(errvar_call) if errvar_call else "" - local_call = ", ".join(["{}={}".format(errvar_names[x], errvar_names[x]) - for x in errcode_snames]) + errvar_call = ", ".join([f"{call_vnames[x]}={errvar_names[x]}" for x in errcode_snames]) + errvar_call2 = f", {errvar_call}" if errvar_call else "" + local_call = ", ".join([f"{errvar_names[x]}={errvar_names[x]}" for x in errcode_snames]) # Allocate and define constituents - stmt = "subroutine {}({})".format(self.constituent_prop_init_consts(), - errvar_alist) + stmt = f"subroutine {self.constituent_prop_init_consts()}({errvar_alist})" outfile.write(stmt, indent) outfile.write("! Allocate and fill the constituent property array", indent + 1) @@ -288,9 +279,8 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): if self: outfile.write("! Local variables", indent+1) outfile.write("integer :: index", indent+1) - stmt = "allocate({}({}))" - outfile.write(stmt.format(self.constituent_prop_array_name(), - len(self)), indent+1) + stmt = f"allocate({self.constituent_prop_array_name()}({len(self)}))" + outfile.write(stmt, indent+1) outfile.write("index = 0", indent+1) # end if for evar in err_vars: @@ -317,22 +307,20 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): f'errmsg={errvar_names["ccpp_error_message"]}'] if default_value is not None and default_value != '': init_args.append(f'default_value={default_value}') - stmt = 'call {}(index)%instantiate({})' + stmt = f'call {self.constituent_prop_array_name()}(index)%instantiate({", ".join(init_args)})' outfile.write(f'if ({errvar_names["ccpp_error_code"]} == 0) then', indent+1) - outfile.write(stmt.format(self.constituent_prop_array_name(), - ", ".join(init_args)), indent+2) + outfile.write(stmt, indent+2) outfile.write("end if", indent+1) # end for - outfile.write("{} = .true.".format(self.constituent_prop_init_name()), - indent+1) - stmt = "end subroutine {}".format(self.constituent_prop_init_consts()) + outfile.write(f"{self.constituent_prop_init_name()} = .true.", indent+1) + stmt = f"end subroutine {self.constituent_prop_init_consts()}" outfile.write(stmt, indent) outfile.write("", 0) - outfile.write("\n! {}\n".format("="*72), 1) + border = "="*72 + outfile.write(f"\n! {border}\n", 1) # Return number of constituents fname = self.num_consts_funcname() - outfile.write("integer function {}({})".format(fname, errvar_alist), - indent) + outfile.write(f"integer function {fname}({errvar_alist})", indent) outfile.write("! Return the number of constituents for this suite", indent+1) outfile.write("! Dummy arguments", indent+1) @@ -344,18 +332,16 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): # end for outfile.write("! Make sure that our constituent array is initialized", indent+1) - stmt = "if (.not. {}) then" - outfile.write(stmt.format(self.constituent_prop_init_name()), indent+1) - outfile.write("call {}({})".format(self.constituent_prop_init_consts(), - local_call), indent+2) + stmt = f"if (.not. {self.constituent_prop_init_name()}) then" + outfile.write(stmt, indent+1) + outfile.write(f"call {self.constituent_prop_init_consts()}({local_call})", indent+2) outfile.write("end if", indent+1) - outfile.write("{} = {}".format(fname, len(self)), indent+1) - outfile.write("end function {}".format(fname), indent) - outfile.write("\n! {}\n".format("="*72), 1) + outfile.write(f"{fname} = {len(self)}", indent+1) + outfile.write(f"end function {fname}", indent) + outfile.write(f"\n! {border}\n", 1) # Return the name of a constituent given an index - stmt = "subroutine {}(index, name_out{})" - outfile.write(stmt.format(self.const_name_subname(), errvar_alist2), - indent) + stmt = f"subroutine {self.const_name_subname()}(index, name_out{errvar_alist2})" + outfile.write(stmt, indent) outfile.write("! Return the name of constituent, ", indent+1) outfile.write("! Dummy arguments", indent+1) outfile.write("integer, intent(in) :: index", indent+1) @@ -371,17 +357,15 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): init_args = ['std_name=name_out', f'errcode={errvar_names["ccpp_error_code"]}', f'errmsg={errvar_names["ccpp_error_message"]}'] - stmt = "call {}(index)%standard_name({})" - outfile.write(stmt.format(self.constituent_prop_array_name(), - ", ".join(init_args)), indent+1) + stmt = f"call {self.constituent_prop_array_name()}(index)%standard_name({', '.join(init_args)})" + outfile.write(stmt, indent+1) # end if - outfile.write("end subroutine {}".format(self.const_name_subname()), - indent) - outfile.write("\n! {}\n".format("="*72), 1) + outfile.write(f"end subroutine {self.const_name_subname()}", indent) + outfile.write(f"\n! {border}\n", 1) # Copy a consitituent's properties - stmt = "subroutine {}(index, cnst_out{})" fname = self.copy_const_subname() - outfile.write(stmt.format(fname, errvar_alist2), indent) + stmt = f"subroutine {fname}(index, cnst_out{errvar_alist2})" + outfile.write(stmt, indent) outfile.write("! Copy the data for a constituent", indent+1) outfile.write("! Dummy arguments", indent+1) outfile.write("integer, intent(in) :: index", indent+1) @@ -395,11 +379,10 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): self._write_index_check(outfile, indent, suite_name, err_vars, use_errcode) if self: - stmt = "cnst_out = {}(index)" - outfile.write(stmt.format(self.constituent_prop_array_name()), - indent+1) + stmt = f"cnst_out = {self.constituent_prop_array_name()}(index)" + outfile.write(stmt, indent+1) # end if - outfile.write("end subroutine {}".format(fname), indent) + outfile.write(f"end subroutine {fname}", indent) def constituent_module_name(self): """Return the name of host model constituent module""" @@ -414,22 +397,22 @@ def constituent_module_name(self): def num_consts_funcname(self): """Return the name of the function which returns the number of constituents for this suite.""" - return "{}_num_consts".format(self.name) + return f"{self.name}_num_consts" def const_name_subname(self): """Return the name of the routine that returns a constituent's - given an index""" - return "{}_const_name".format(self.name) + standard name given an index""" + return f"{self.name}_const_name" def copy_const_subname(self): """Return the name of the routine that returns a copy of a constituent's metadata given an index""" - return "{}_copy_const".format(self.name) + return f"{self.name}_copy_const" @staticmethod def constituent_index_name(standard_name): """Return the index name associated with """ - return "index_of_{}".format(standard_name) + return f"index_of_{standard_name}" @staticmethod def write_constituent_use_statements(cap, suite_list, indent): @@ -456,12 +439,13 @@ 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, - const_obj_name, const_names_name, const_indices_name, + query_const_funcname, copy_in_funcname, copy_out_funcname, cleanup_funcname, + const_obj_name, dyn_const_name, const_names_name, const_indices_name, const_array_func, advect_array_func, prop_array_func, - const_index_func, suite_list, err_vars): + const_index_func, suite_list, dyn_const_dict, 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 @@ -484,23 +468,26 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna raise ParseInternalError(emsg(host.name)) # end if herrcode, herrmsg = ConstituentVarDict.__errcode_names(err_vars) - err_dummy_str = "{errcode}, {errmsg}".format(errcode=herrcode, - errmsg=herrmsg) - obj_err_callstr = "errcode={errcode}, errmsg={errmsg}" - obj_err_callstr = obj_err_callstr.format(errcode=herrcode, - errmsg=herrmsg) + err_dummy_str = f"{herrcode}, {herrmsg}" + obj_err_callstr = f"errcode={herrcode}, errmsg={herrmsg}" # XXgoldyXX: ^ need to generalize host model error var type support # First up, the registration routine substmt = f"subroutine {reg_funcname}" - args = "suite_list, host_constituents " + args = "host_constituents " stmt = f"{substmt}({args}, {err_dummy_str})" cap.write(stmt, 1) 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("character(len=*), intent(in) :: suite_list(:)", 2) cap.write(f"type({CONST_PROP_TYPE}), target, intent(in) :: " + \ "host_constituents(:)", 2) for evar in err_vars: @@ -508,14 +495,21 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna # end for cap.comment("Local variables", 2) spc = ' '*37 - cap.write("integer{} :: num_suite_consts".format(spc), 2) - cap.write("integer{} :: num_consts".format(spc), 2) - cap.write("integer{} :: index".format(spc), 2) - cap.write("integer{} :: field_ind".format(spc), 2) - cap.write(f"type({CONST_PROP_TYPE}), pointer :: const_prop", 2) + 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("{} = 0".format(herrcode), 2) + 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() @@ -523,85 +517,119 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna errvar_str = ConstituentVarDict.__errcode_callstr(herrcode, herrmsg, suite) cap.write(f"num_suite_consts = {funcname}({errvar_str})", 2) + cap.write(f"if ({herrcode} /= 0) then", 2) + cap.write("return", 3) + cap.write("end if", 2) cap.write("num_consts = num_consts + num_suite_consts", 2) # end for - cap.write("if ({} == 0) then".format(herrcode), 2) - cap.comment("Initialize constituent data and field object", 3) - stmt = "call {}%initialize_table(num_consts)" - cap.write(stmt.format(const_obj_name), 3) + # 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) # Register host model constituents - cap.comment("Add host model constituent metadata", 3) - cap.write("do index = 1, size(host_constituents, 1)", 3) - cap.write(f"if ({herrcode} == 0) then", 4) - cap.write("const_prop => host_constituents(index)", 5) - stmt = "call {}%new_field(const_prop, {})" - cap.write(stmt.format(const_obj_name, obj_err_callstr), 5) - cap.write("end if", 4) - cap.write("nullify(const_prop)", 4) - cap.write("if ({} /= 0) then".format(herrcode), 4) - cap.write("exit", 5) - cap.write("end if", 4) - cap.write("end do", 3) - cap.write("end if", 2) - cap.blank_line() + cap.comment("Add host model constituent metadata", 2) + cap.write("do index = 1, size(host_constituents, 1)", 2) + cap.write("const_prop => host_constituents(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) + cap.write(f"if ({herrcode} /= 0) then", 3) + cap.write("return", 4) + cap.write("end if", 3) + 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) + stmt = f"call {const_obj_name}%new_field(const_prop, {obj_err_callstr})" + cap.write(stmt, 3) + cap.write("nullify(const_prop)", 3) + cap.write(f"if ({herrcode} /= 0) then", 3) + cap.write("return", 4) + cap.write("end if", 3) + cap.write("end do", 2) + # end if + # Register suite constituents for suite in suite_list: errvar_str = ConstituentVarDict.__errcode_callstr(herrcode, herrmsg, suite) - cap.write(f"if ({herrcode} == 0) then", 2) - cap.comment(f"Add {suite.name} constituent metadata", 3) + cap.comment(f"Add {suite.name} constituent metadata", 2) const_dict = suite.constituent_dictionary() funcname = const_dict.num_consts_funcname() - cap.write(f"num_suite_consts = {funcname}({errvar_str})", 3) + cap.write(f"num_suite_consts = {funcname}({errvar_str})", 2) + cap.write(f"if ({herrcode} /= 0) then", 2) + cap.write("return", 3) cap.write("end if", 2) funcname = const_dict.copy_const_subname() - cap.write(f"if ({herrcode} == 0) then", 2) - cap.write("do index = 1, num_suite_consts", 3) - cap.write(f"if ({herrcode} == 0) then", 4) - cap.write(f"allocate(const_prop, stat={herrcode})", 5) - cap.write("end if", 4) - cap.write(f"if ({herrcode} /= 0) then", 4) - cap.write(f'{herrmsg} = "ERROR allocating const_prop"', 5) - cap.write("exit", 5) - cap.write("end if", 4) - cap.write(f"if ({herrcode} == 0) then", 4) - stmt = "call {}(index, const_prop, {})" - cap.write(stmt.format(funcname, errvar_str), 5) - cap.write("end if", 4) - cap.write(f"if ({herrcode} == 0) then", 4) - stmt = "call {}%new_field(const_prop, {})" - cap.write(stmt.format(const_obj_name, obj_err_callstr), 5) - cap.write("end if", 4) - cap.write("nullify(const_prop)", 4) - cap.write(f"if ({herrcode} /= 0) then", 4) - cap.write("exit", 5) - cap.write("end if", 4) - cap.write("end do", 3) - cap.write("end if", 2) + cap.write("do index = 1, num_suite_consts", 2) + cap.write(f"allocate(const_prop, stat={herrcode})", 3) + cap.write(f"if ({herrcode} /= 0) then", 3) + cap.write(f'{herrmsg} = "ERROR allocating const_prop"', 4) + cap.write("return", 4) + cap.write("end if", 3) + stmt = f"call {funcname}(index, const_prop, {errvar_str})" + cap.write(stmt, 3) + cap.write(f"if ({herrcode} /= 0) then", 3) + cap.write("return", 4) + cap.write("end if", 3) + stmt = f"call {const_obj_name}%new_field(const_prop, {obj_err_callstr})" + cap.write(stmt, 3) + cap.write("nullify(const_prop)", 3) + cap.write(f"if ({herrcode} /= 0) then", 3) + cap.write("return", 4) + cap.write("end if", 3) + cap.write("end do", 2) cap.blank_line() # end for - cap.write(f"if ({herrcode} == 0) then", 2) - stmt = "call {}%lock_table({})" - cap.write(stmt.format(const_obj_name, obj_err_callstr), 3) - cap.write("end if", 2) - cap.write(f"if ({herrcode} == 0) then", 2) - cap.comment("Set the index for each active constituent", 3) - cap.write(f"do index = 1, SIZE({const_indices_name})", 3) - stmt = "call {}%const_index(field_ind, {}(index), {})" - cap.write(stmt.format(const_obj_name, const_names_name, - obj_err_callstr), 4) - cap.write("if (field_ind > 0) then", 4) - cap.write(f"{const_indices_name}(index) = field_ind", 5) - cap.write("else", 4) - cap.write(f"{herrcode} = 1", 5) - stmt = "{} = 'No field index for '//trim({}(index))" - cap.write(stmt.format(herrmsg, const_names_name), 5) - cap.write("end if", 4) - cap.write(f"if ({herrcode} /= 0) then", 4) - cap.write("exit", 5) - cap.write("end if", 4) - cap.write("end do", 3) + stmt = f"call {const_obj_name}%lock_table({obj_err_callstr})" + cap.write(stmt, 2) + cap.write(f"if ({herrcode} /= 0) then", 2) + cap.write("return", 3) cap.write("end if", 2) + cap.comment("Set the index for each active constituent", 2) + cap.write(f"do index = 1, SIZE({const_indices_name})", 2) + stmt = f"call {const_obj_name}%const_index(field_ind, {const_names_name}(index), {obj_err_callstr})" + cap.write(stmt, 3) + cap.write(f"if ({herrcode} /= 0) then", 3) + cap.write("return", 4) + cap.write("end if", 3) + cap.write("if (field_ind > 0) then", 3) + cap.write(f"{const_indices_name}(index) = field_ind", 4) + cap.write("else", 3) + cap.write(f"{herrcode} = 1", 4) + stmt = f"{herrmsg} = 'No field index for '//trim({const_names_name}(index))" + cap.write(stmt, 4) + cap.write("return", 4) + cap.write("end if", 3) + cap.write("end do", 2) cap.write(f"end {substmt}", 1) # Write constituent_init routine substmt = f"subroutine {init_funcname}" @@ -632,9 +660,9 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna evar.write_def(cap, 2, host, dummy=True, add_intent="out") # end for cap.blank_line() - call_str = "call {}%num_constituents(num_flds, advected=advected, {})" - cap.write(call_str.format(const_obj_name, obj_err_callstr), 2) - cap.write("end {}".format(substmt), 1) + call_str = f"call {const_obj_name}%num_constituents(num_flds, advected=advected, {obj_err_callstr})" + cap.write(call_str, 2) + cap.write(f"end {substmt}", 1) # Write query_consts routine substmt = f"subroutine {query_const_funcname}" cap.blank_line() @@ -657,9 +685,9 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.blank_line() cap.write(f"end {substmt}", 1) # Write copy_in routine - substmt = "subroutine {}".format(copy_in_funcname) + substmt = f"subroutine {copy_in_funcname}" cap.blank_line() - cap.write("{}(const_array, {})".format(substmt, err_dummy_str), 1) + cap.write(f"{substmt}(const_array, {err_dummy_str})", 1) cap.comment("Copy constituent field info into ", 2) cap.blank_line() cap.comment("Dummy arguments", 2) @@ -668,13 +696,12 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna evar.write_def(cap, 2, host, dummy=True, add_intent="out") # end for cap.blank_line() - cap.write("call {}%copy_in(const_array, {})".format(const_obj_name, - obj_err_callstr), 2) - cap.write("end {}".format(substmt), 1) + cap.write(f"call {const_obj_name}%copy_in(const_array, {obj_err_callstr})", 2) + cap.write(f"end {substmt}", 1) # Write copy_out routine - substmt = "subroutine {}".format(copy_out_funcname) + substmt = f"subroutine {copy_out_funcname}" cap.blank_line() - cap.write("{}(const_array, {})".format(substmt, err_dummy_str), 1) + cap.write(f"{substmt}(const_array, {err_dummy_str})", 1) cap.comment("Update constituent field info from ", 2) cap.blank_line() cap.comment("Dummy arguments", 2) @@ -683,10 +710,16 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna evar.write_def(cap, 2, host, dummy=True, add_intent="out") # end for cap.blank_line() - cap.write("call {}%copy_out(const_array, {})".format(const_obj_name, - obj_err_callstr), - 2) - cap.write("end {}".format(substmt), 1) + cap.write(f"call {const_obj_name}%copy_out(const_array, {obj_err_callstr})", 2) + cap.write(f"end {substmt}", 1) + # Write cleanup routine + substmt = f"subroutine {cleanup_funcname}" + cap.blank_line() + cap.write(f"{substmt}()", 1) + cap.comment("Deallocate dynamic constituent array", 2) + cap.blank_line() + cap.write(f"deallocate({dyn_const_name})", 2) + cap.write(f"end {substmt}", 1) # Write constituents routine cap.blank_line() cap.write(f"function {const_array_func}() result(const_ptr)", 1) @@ -742,7 +775,7 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.blank_line() cap.write(f"call {const_obj_name}%const_index(const_index, " + \ f"stdname, {obj_err_callstr})", 2) - cap.write("end {}".format(substmt), 1) + cap.write(f"end {substmt}", 1) @staticmethod def constitutent_source_type(): diff --git a/scripts/fortran_tools/offline_check_fortran_vs_metadata.py b/scripts/fortran_tools/offline_check_fortran_vs_metadata.py new file mode 100755 index 00000000..94570ec7 --- /dev/null +++ b/scripts/fortran_tools/offline_check_fortran_vs_metadata.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 + +""" +Recursively compare all fortran and metadata files in user-supplied directory, and report any problems +USAGE: + ./offline_check_fortran_vs_metadata.py --directory (--debug) +""" + + +import sys +import os +import glob +import logging +import argparse +import site +# Enable imports from parent directory +site.addsitedir(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +# CCPP framework imports +from framework_env import CCPPFrameworkEnv +from fortran_tools import parse_fortran_file +from metadata_table import parse_metadata_file +from ccpp_capgen import find_associated_fortran_file +from ccpp_capgen import parse_scheme_files +from parse_tools import init_log, set_log_level +from parse_tools import register_fortran_ddt_name +from parse_tools import CCPPError, ParseInternalError + +_LOGGER = init_log(os.path.basename(__file__)) +_DUMMY_RUN_ENV = CCPPFrameworkEnv(_LOGGER, ndict={'host_files':'', + 'scheme_files':'', + 'suites':''}) + +def find_files_to_compare(directory): + metadata_files = [] + for file in glob.glob(os.path.join(directory,'**','*.meta'), recursive=True): + metadata_files.append(file) + # end for + return metadata_files + +def compare_fortran_and_metadata(scheme_directory, run_env): + ## Check for files + metadata_files = find_files_to_compare(scheme_directory) + # Perform checks + parse_scheme_files(metadata_files, run_env, skip_ddt_check=True) + +def parse_command_line(arguments, description): + """Parse command-line arguments""" + parser = argparse.ArgumentParser(description=description, + formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument("--directory", type=str, required=True, + metavar='top-level directory to analyze - REQUIRED', + help="""Full path to scheme directory""") + parser.add_argument("--debug", action='store_true', default=False, + help="""turn on debug mode for additional verbosity""") + pargs = parser.parse_args(arguments) + return pargs + +def _main_func(): + """Parse command line, then parse indicated host, scheme, and suite files. + Finally, generate code to allow host model to run indicated CCPP suites.""" + pargs = parse_command_line(sys.argv[1:], __doc__) + logger = _LOGGER + if pargs.debug: + set_log_level(logger, logging.DEBUG) + else: + set_log_level(logger, logging.INFO) + # end if + compare_fortran_and_metadata(pargs.directory, _DUMMY_RUN_ENV) + print('All checks passed!') + +############################################################################### + +if __name__ == "__main__": + try: + _main_func() + sys.exit(0) + except ParseInternalError as pie: + _LOGGER.exception(pie) + sys.exit(-1) + except CCPPError as ccpp_err: + if _LOGGER.getEffectiveLevel() <= logging.DEBUG: + _LOGGER.exception(ccpp_err) + else: + _LOGGER.error(ccpp_err) + # end if + sys.exit(1) + finally: + logging.shutdown() + # end try + diff --git a/scripts/fortran_tools/parse_fortran_file.py b/scripts/fortran_tools/parse_fortran_file.py index a67816e4..9bc39efa 100644 --- a/scripts/fortran_tools/parse_fortran_file.py +++ b/scripts/fortran_tools/parse_fortran_file.py @@ -904,12 +904,18 @@ def parse_module(pobj, statements, run_env): statements = read_statements(pobj, statements) inmodule = pobj.in_region('MODULE', region_name=mod_name) active_table = None + additional_subroutines = [] + seen_contains = False + insub = False while inmodule and (statements is not None): while statements: statement = statements.pop(0) # End module pmatch = _ENDMODULE_RE.match(statement) asmatch = _ARG_TABLE_START_RE.match(statement) + smatch = _SUBROUTINE_RE.match(statement) + esmatch = _END_SUBROUTINE_RE.match(statement) + seen_contains = seen_contains or is_contains_statement(statement, insub) if asmatch is not None: active_table = asmatch.group(1) elif pmatch is not None: @@ -939,13 +945,21 @@ def parse_module(pobj, statements, run_env): active_table = None inmodule = pobj.in_region('MODULE', region_name=mod_name) break + elif smatch is not None and not seen_contains: + routine_name = smatch.group(1).strip() + additional_subroutines.append(routine_name) + insub = True + elif esmatch is not None and not seen_contains: + insub = False + elif esmatch is not None: + seen_contains = False # End if # End while if inmodule and (statements is not None) and (len(statements) == 0): statements = read_statements(pobj) # End if # End while - return statements, mtables + return statements, mtables, additional_subroutines ######################################################################## @@ -970,14 +984,14 @@ def parse_fortran_file(filename, run_env): elif _MODULE_RE.match(statement) is not None: # push statement back so parse_module can use it statements.insert(0, statement) - statements, ptables = parse_module(pobj, statements, run_env) + statements, ptables, additional_routines = parse_module(pobj, statements, run_env) mtables.extend(ptables) # End if if (statements is not None) and (len(statements) == 0): statements = read_statements(pobj) # End if # End while - return mtables + return mtables, additional_routines ######################################################################## diff --git a/scripts/host_cap.py b/scripts/host_cap.py index d2c4ed7e..91d91493 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -11,7 +11,7 @@ from ccpp_suite import API, API_SOURCE_NAME from ccpp_state_machine import CCPP_STATE_MACH from constituents import ConstituentVarDict, CONST_DDT_NAME, CONST_DDT_MOD -from constituents import CONST_OBJ_STDNAME +from constituents import CONST_OBJ_STDNAME, CONST_PROP_TYPE from ddt_library import DDTLibrary from file_utils import KINDS_MODULE from framework_env import CCPPFrameworkEnv @@ -101,6 +101,14 @@ def constituent_initialize_subname(host_model): Because this is a user interface API function, the name is fixed.""" return f"{host_model.name}_ccpp_initialize_constituents" +############################################################################### +def constituent_initialize_subname(host_model): +############################################################################### + """Return the name of the subroutine used to initialize the + constituents for this run. + Because this is a user interface API function, the name is fixed.""" + return f"{host_model.name}_ccpp_initialize_constituents" + ############################################################################### def constituent_num_consts_funcname(host_model): ############################################################################### @@ -117,6 +125,14 @@ def query_scheme_constituents_funcname(host_model): Because this is a user interface API function, the name is fixed.""" return f"{host_model.name}_ccpp_is_scheme_constituent" +############################################################################### +def query_scheme_constituents_funcname(host_model): +############################################################################### + """Return the name of the function to return True if the standard name + passed in matches an existing constituent + Because this is a user interface API function, the name is fixed.""" + return f"{host_model.name}_ccpp_is_scheme_constituent" + ############################################################################### def constituent_copyin_subname(host_model): ############################################################################### @@ -133,6 +149,14 @@ def constituent_copyout_subname(host_model): Because this is a user interface API function, the name is fixed.""" return f"{host_model.name}_ccpp_update_constituents" +############################################################################### +def constituent_cleanup_subname(host_model): +############################################################################### + """Return the name of the subroutine to deallocate dynamic constituent + arrays + Because this is a user interface API function, the name is fixed.""" + return f"{host_model.name}_ccpp_deallocate_dynamic_constituents" + ############################################################################### def unique_local_name(loc_name, host_model): ############################################################################### @@ -159,6 +183,13 @@ def constituent_model_object_name(host_model): # end if return hvar.get_prop_value('local_name') +############################################################################### +def dynamic_constituent_array_name(host_model): +############################################################################### + """Return the name of the allocatable dynamic constituent properites array""" + hstr = f"{host_model.name}_dynamic_constituents" + return unique_local_name(hstr, host_model) + ############################################################################### def constituent_model_const_stdnames(host_model): ############################################################################### @@ -204,6 +235,37 @@ def constituent_model_const_index(host_model): hstr = f"{host_model.name}_const_get_index" return unique_local_name(hstr, host_model) +############################################################################### +def constituent_model_consts(host_model): +############################################################################### + """Return the name of the function that will return a pointer to the + array of all constituents""" + hstr = f"{host_model.name}_constituents_array" + return unique_local_name(hstr, host_model) + +############################################################################### +def constituent_model_advected_consts(host_model): +############################################################################### + """Return the name of the function that will return a pointer to the + array of advected constituents""" + hstr = f"{host_model.name}_advected_constituents_array" + return unique_local_name(hstr, host_model) + +############################################################################### +def constituent_model_const_props(host_model): +############################################################################### + """Return the name of the array of constituent property object pointers""" + hstr = f"{host_model.name}_model_const_properties" + return unique_local_name(hstr, host_model) + +############################################################################### +def constituent_model_const_index(host_model): +############################################################################### + """Return the name of the interface that returns the array index of + a constituent array given its standard name""" + hstr = f"{host_model.name}_const_get_index" + return unique_local_name(hstr, host_model) + ############################################################################### def add_constituent_vars(cap, host_model, suite_list, run_env): ############################################################################### @@ -316,6 +378,9 @@ 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 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) @@ -432,12 +497,13 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): mlen = max([len(x.module) for x in api.suites]) maxmod = max(maxmod, mlen) maxmod = max(maxmod, len(CONST_DDT_MOD)) - for mod in modules: + for mod in sorted(modules): mspc = (maxmod - len(mod[0]))*' ' cap.write("use {}, {}only: {}".format(mod[0], mspc, mod[1]), 1) # End for mspc = ' '*(maxmod - len(CONST_DDT_MOD)) cap.write(f"use {CONST_DDT_MOD}, {mspc}only: {CONST_DDT_NAME}", 1) + cap.write(f"use {CONST_DDT_MOD}, {mspc}only: {CONST_PROP_TYPE}", 1) cap.write_preamble() max_suite_len = 0 for suite in api.suites: @@ -463,6 +529,8 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): cap.write(f"public :: {copyin_name}", 1) copyout_name = constituent_copyout_subname(host_model) cap.write(f"public :: {copyout_name}", 1) + cleanup_name = constituent_cleanup_subname(host_model) + cap.write(f"public :: {cleanup_name}", 1) const_array_func = constituent_model_consts(host_model) cap.write(f"public :: {const_array_func}", 1) advect_array_func = constituent_model_advected_consts(host_model) @@ -530,7 +598,7 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): for suite in api.suites: mspc = (max_suite_len - len(suite.module))*' ' spart_list = suite_part_list(suite, stage) - for spart in spart_list: + for _, spart in sorted(enumerate(spart_list)): stmt = "use {}, {}only: {}" cap.write(stmt.format(suite.module, mspc, spart.name), 2) # End for @@ -607,17 +675,22 @@ 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) ConstituentVarDict.write_host_routines(cap, host_model, reg_name, init_name, numconsts_name, queryconsts_name, - copyin_name, copyout_name, + copyin_name, copyout_name, + cleanup_name, const_obj_name, + dyn_const_name, const_names_name, const_indices_name, const_array_func, advect_array_func, prop_array_func, const_index_func, - api.suites, err_vars) + 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 946e9782..d8b68000 100755 --- a/scripts/metadata_table.py +++ b/scripts/metadata_table.py @@ -63,6 +63,7 @@ type = scheme relative_path = dependencies = + dynamic_constituent_routine = [ccpp-arg-table] name = @@ -179,7 +180,7 @@ def _parse_config_line(line, context): ######################################################################## -def parse_metadata_file(filename, known_ddts, run_env): +def parse_metadata_file(filename, known_ddts, run_env, skip_ddt_check=False): """Parse and return list of parsed metadata tables""" # Read all lines of the file at once meta_tables = [] @@ -196,7 +197,8 @@ def parse_metadata_file(filename, known_ddts, run_env): while curr_line is not None: if MetadataTable.table_start(curr_line): new_table = MetadataTable(run_env, parse_object=parse_obj, - known_ddts=known_ddts) + known_ddts=known_ddts, + skip_ddt_check=skip_ddt_check) ntitle = new_table.table_name if ntitle not in table_titles: meta_tables.append(new_table) @@ -270,8 +272,9 @@ 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, known_ddts=None, - var_dict=None, module=None, parse_object=None): + dependencies=None, relative_path=None, dyn_const_routine=None, + known_ddts=None, var_dict=None, module=None, parse_object=None, + skip_ddt_check=False): """Initialize a MetadataTable, either with a name, , and type, , or with information from a file (). if is None, and are @@ -283,6 +286,7 @@ 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: @@ -317,7 +321,8 @@ def __init__(self, run_env, table_name_in=None, table_type_in=None, sect = MetadataSection(self.table_name, self.table_type, run_env, title=stitle, type_in=self.table_type, module=module, - var_dict=var_dict, known_ddts=known_ddts) + var_dict=var_dict, known_ddts=known_ddts, + skip_ddt_check=skip_ddt_check) self.__sections.append(sect) # end if else: @@ -342,10 +347,10 @@ def __init__(self, run_env, table_name_in=None, table_type_in=None, known_ddts = [] # end if self.__start_context = ParseContext(context=self.__pobj) - self.__init_from_file(known_ddts, self.__run_env) + self.__init_from_file(known_ddts, self.__run_env, skip_ddt_check=skip_ddt_check) # end if - def __init_from_file(self, known_ddts, run_env): + def __init_from_file(self, known_ddts, run_env, skip_ddt_check=False): """ Read the table preamble, assume the caller already figured out the first line of the header using the header_start method.""" curr_line, _ = self.__pobj.next_line() @@ -395,6 +400,8 @@ def __init_from_file(self, known_ddts, run_env): # 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) @@ -407,7 +414,8 @@ def __init_from_file(self, known_ddts, run_env): skip_rest_of_section = False section = MetadataSection(self.table_name, self.table_type, run_env, parse_object=self.__pobj, - known_ddts=known_ddts) + known_ddts=known_ddts, + skip_ddt_check=skip_ddt_check) # Some table types only allow for one associated section if ((len(self.__sections) == 1) and (self.table_type in _SINGLETON_TABLE_TYPES)): @@ -475,6 +483,12 @@ 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""" @@ -623,7 +637,7 @@ class MetadataSection(ParseSource): def __init__(self, table_name, table_type, run_env, parse_object=None, title=None, type_in=None, module=None, process_type=None, - var_dict=None, known_ddts=None): + var_dict=None, known_ddts=None, skip_ddt_check=False): """Initialize a new MetadataSection object. If is not None, initialize from the current file and location in . @@ -693,7 +707,8 @@ def __init__(self, table_name, table_type, run_env, parse_object=None, known_ddts = [] # end if self.__start_context = ParseContext(context=self.__pobj) - self.__init_from_file(table_name, table_type, known_ddts, run_env) + self.__init_from_file(table_name, table_type, known_ddts, run_env, + skip_ddt_check=skip_ddt_check) # end if # Register this header if it is a DDT if self.header_type == 'ddt': @@ -724,7 +739,7 @@ def _default_module(self): # end if return def_mod - def __init_from_file(self, table_name, table_type, known_ddts, run_env): + def __init_from_file(self, table_name, table_type, known_ddts, run_env, skip_ddt_check=False): """ Read the section preamble, assume the caller already figured out the first line of the header using the header_start method.""" start_ctx = context_string(self.__pobj) @@ -809,7 +824,8 @@ def __init_from_file(self, table_name, table_type, known_ddts, run_env): valid_lines = True self.__variables = VarDictionary(self.title, run_env) while valid_lines: - newvar, curr_line = self.parse_variable(curr_line, known_ddts) + newvar, curr_line = self.parse_variable(curr_line, known_ddts, + skip_ddt_check=skip_ddt_check) valid_lines = newvar is not None if valid_lines: if run_env.verbose: @@ -828,7 +844,7 @@ def __init_from_file(self, table_name, table_type, known_ddts, run_env): # end if # end while - def parse_variable(self, curr_line, known_ddts): + def parse_variable(self, curr_line, known_ddts, skip_ddt_check=False): """Parse a new metadata variable beginning on . The header line has the format [ ]. """ @@ -872,7 +888,10 @@ def parse_variable(self, curr_line, known_ddts): pval_str = prop[1].strip() if ((pname == 'type') and (not check_fortran_intrinsic(pval_str, error=False))): - if pval_str in known_ddts: + if skip_ddt_check or pval_str in known_ddts: + if skip_ddt_check: + register_fortran_ddt_name(pval_str) + # end if pval = pval_str pname = 'ddt_type' else: diff --git a/scripts/metavar.py b/scripts/metavar.py index 2974f1db..e84cd2b7 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -1068,15 +1068,15 @@ def write_def(self, outfile, indent, wdict, allocatable=False, target=False, intent = None # end if if protected and allocatable: - errmsg = 'Cannot create allocatable variable from protected, {}' + errmsg = "Cannot create allocatable variable from protected, {}" raise CCPPError(errmsg.format(name)) # end if if dummy and (intent is None): if add_intent is not None: intent = add_intent else: - errmsg = " is missing for dummy argument, {}" - raise CCPPError(errmsg.format(name)) + errmsg = f" is missing for dummy argument, {name}" + raise CCPPError(errmsg) # end if # end if optional = self.get_prop_value('optional') @@ -1502,12 +1502,18 @@ class VarDictionary(OrderedDict): VarDictionary(foo) >>> VarDictionary('bar', _MVAR_DUMMY_RUN_ENV, variables={}) VarDictionary(bar) - >>> VarDictionary('baz', _MVAR_DUMMY_RUN_ENV, variables=Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV)) #doctest: +ELLIPSIS - VarDictionary(baz, [('hi_mom', )]) + >>> test_dict = VarDictionary('baz', _MVAR_DUMMY_RUN_ENV, variables=Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV)) + >>> print(test_dict.name) + baz + >>> print(test_dict.variable_list()) #doctest: +ELLIPSIS + [] >>> print("{}".format(VarDictionary('baz', _MVAR_DUMMY_RUN_ENV, variables=Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV)))) VarDictionary(baz, ['hi_mom']) - >>> VarDictionary('qux', _MVAR_DUMMY_RUN_ENV, variables=[Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV)]) #doctest: +ELLIPSIS - VarDictionary(qux, [('hi_mom', )]) + >>> test_dict = VarDictionary('qux', _MVAR_DUMMY_RUN_ENV, variables=[Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV)]) + >>> print(test_dict.name) + qux + >>> print(test_dict.variable_list()) #doctest: +ELLIPSIS + [] >>> VarDictionary('boo', _MVAR_DUMMY_RUN_ENV).add_variable(Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV), _MVAR_DUMMY_RUN_ENV) >>> VarDictionary('who', _MVAR_DUMMY_RUN_ENV, variables=[Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV)]).prop_list('local_name') diff --git a/scripts/parse_tools/parse_source.py b/scripts/parse_tools/parse_source.py index 38a72953..2ed0e5b8 100644 --- a/scripts/parse_tools/parse_source.py +++ b/scripts/parse_tools/parse_source.py @@ -11,7 +11,6 @@ import os.path import logging # CCPP framework imports -# pylint: enable=wrong-import-position class _StdNameCounter(): """Class to hold a global counter to avoid using global keyword""" diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index f611c5d2..1056b8dd 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -2384,7 +2384,7 @@ def write(self, outfile, host_arglist, indent, const_mod, # end if # Write out the scheme use statements scheme_use = 'use {},{} only: {}' - for scheme in self._local_schemes: + for scheme in sorted(self._local_schemes): smod = scheme[0] sname = scheme[1] slen = ' '*(modmax - len(smod)) @@ -2480,7 +2480,7 @@ def write(self, outfile, host_arglist, indent, const_mod, # Allocate local arrays outfile.write('\n! Allocate local arrays', indent+1) alloc_stmt = "allocate({}({}))" - for lname in allocatable_var_set: + for lname in sorted(allocatable_var_set): var = subpart_allocate_vars[lname][0] dims = var.get_dimensions() alloc_str = self.allocate_dim_str(dims, var.context) @@ -2523,7 +2523,7 @@ def write(self, outfile, host_arglist, indent, const_mod, if allocatable_var_set: outfile.write('\n! Deallocate local arrays', indent+1) # end if - for lname in allocatable_var_set: + for lname in sorted(allocatable_var_set): outfile.write('if (allocated({})) {} deallocate({})'.format(lname,' '*(20-len(lname)),lname), indent+1) # end for for lname in optional_var_set: diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index 41d8213f..3370bff4 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -36,6 +36,7 @@ module ccpp_constituent_prop_mod integer, private :: const_ind = int_unassigned logical, private :: advected = .false. logical, private :: thermo_active = .false. + logical, private :: water_species = .false. ! While the quantities below can be derived from the standard name, ! this implementation avoids string searching in parameterizations ! const_type distinguishes mass, volume, and number conc. mixing ratios @@ -44,8 +45,8 @@ module ccpp_constituent_prop_mod integer, private :: const_water = int_unassigned ! minimum_mr is the minimum allowed value (default zero) real(kind_phys), private :: min_val = 0.0_kind_phys - ! molar_mass is the molecular weight of the constituent (g mol-1) - real(kind_phys), private :: molar_mass = kphys_unassigned + ! molar_mass_val is the molar mass of the constituent (kg mol-1) + real(kind_phys), private :: molar_mass_val = kphys_unassigned ! default_value is the default value that the constituent array will be ! initialized to real(kind_phys), private :: const_default_value = kphys_unassigned @@ -56,6 +57,7 @@ module ccpp_constituent_prop_mod procedure :: is_instantiated => ccp_is_instantiated procedure :: standard_name => ccp_get_standard_name procedure :: long_name => ccp_get_long_name + procedure :: units => ccp_get_units procedure :: is_layer_var => ccp_is_layer_var procedure :: is_interface_var => ccp_is_interface_var procedure :: is_2d_var => ccp_is_2d_var @@ -63,6 +65,7 @@ module ccpp_constituent_prop_mod procedure :: const_index => ccp_const_index procedure :: is_advected => ccp_is_advected procedure :: is_thermo_active => ccp_is_thermo_active + procedure :: is_water_species => ccp_is_water_species procedure :: equivalent => ccp_is_equivalent procedure :: is_mass_mixing_ratio => ccp_is_mass_mixing_ratio procedure :: is_volume_mixing_ratio => ccp_is_volume_mixing_ratio @@ -71,9 +74,10 @@ module ccpp_constituent_prop_mod procedure :: is_moist => ccp_is_moist procedure :: is_wet => ccp_is_wet procedure :: minimum => ccp_min_val - procedure :: molec_weight => ccp_molec_weight + procedure :: molar_mass => ccp_molar_mass procedure :: default_value => ccp_default_value procedure :: has_default => ccp_has_default + procedure :: is_match => ccp_is_match ! Copy method (be sure to update this anytime fields are added) procedure :: copyConstituent generic :: assignment(=) => copyConstituent @@ -82,6 +86,9 @@ module ccpp_constituent_prop_mod procedure :: deallocate => ccp_deallocate procedure :: set_const_index => ccp_set_const_index procedure :: set_thermo_active => ccp_set_thermo_active + procedure :: set_water_species => ccp_set_water_species + procedure :: set_minimum => ccp_set_min_val + procedure :: set_molar_mass => ccp_set_molar_mass end type ccpp_constituent_properties_t !! \section arg_table_ccpp_constituent_prop_ptr_t @@ -93,6 +100,7 @@ module ccpp_constituent_prop_mod ! Informational methods procedure :: standard_name => ccpt_get_standard_name procedure :: long_name => ccpt_get_long_name + procedure :: units => ccpt_get_units procedure :: is_layer_var => ccpt_is_layer_var procedure :: is_interface_var => ccpt_is_interface_var procedure :: is_2d_var => ccpt_is_2d_var @@ -100,6 +108,7 @@ module ccpp_constituent_prop_mod procedure :: const_index => ccpt_const_index procedure :: is_advected => ccpt_is_advected procedure :: is_thermo_active => ccpt_is_thermo_active + procedure :: is_water_species => ccpt_is_water_species procedure :: is_mass_mixing_ratio => ccpt_is_mass_mixing_ratio procedure :: is_volume_mixing_ratio => ccpt_is_volume_mixing_ratio procedure :: is_number_concentration => ccpt_is_number_concentration @@ -107,7 +116,7 @@ module ccpp_constituent_prop_mod procedure :: is_moist => ccpt_is_moist procedure :: is_wet => ccpt_is_wet procedure :: minimum => ccpt_min_val - procedure :: molec_weight => ccpt_molec_weight + procedure :: molar_mass => ccpt_molar_mass procedure :: default_value => ccpt_default_value procedure :: has_default => ccpt_has_default ! ccpt_set: Set the internal pointer @@ -116,6 +125,9 @@ module ccpp_constituent_prop_mod procedure :: deallocate => ccpt_deallocate procedure :: set_const_index => ccpt_set_const_index procedure :: set_thermo_active => ccpt_set_thermo_active + procedure :: set_water_species => ccpt_set_water_species + procedure :: set_minimum => ccpt_set_min_val + procedure :: set_molar_mass => ccpt_set_molar_mass end type ccpp_constituent_prop_ptr_t !! \section arg_table_ccpp_model_constituents_t @@ -201,15 +213,18 @@ subroutine copyConstituent(outConst, inConst) class(ccpp_constituent_properties_t), intent(inout) :: outConst type(ccpp_constituent_properties_t), intent(in) :: inConst - outConst%var_std_name = inConst%var_std_name - outConst%var_long_name = inConst%var_long_name - outConst%vert_dim = inConst%vert_dim - outConst%const_ind = inConst%const_ind - outConst%advected = inConst%advected - outConst%const_type = inConst%const_type - outConst%const_water = inConst%const_water - outConst%min_val = inConst%min_val + outConst%var_std_name = inConst%var_std_name + outConst%var_long_name = inConst%var_long_name + outConst%vert_dim = inConst%vert_dim + outConst%const_ind = inConst%const_ind + outConst%advected = inConst%advected + outConst%const_type = inConst%const_type + outConst%const_water = inConst%const_water + outConst%min_val = inConst%min_val outConst%const_default_value = inConst%const_default_value + outConst%molar_mass_val = inConst%molar_mass_val + outConst%thermo_active = inConst%thermo_active + outConst%water_species = inConst%water_species end subroutine copyConstituent !####################################################################### @@ -353,7 +368,7 @@ end function ccp_is_instantiated !####################################################################### subroutine ccp_instantiate(this, std_name, long_name, units, vertical_dim, & - advected, default_value, errcode, errmsg) + advected, default_value, min_value, molar_mass, errcode, errmsg) ! Initialize all fields in ! Dummy arguments @@ -364,6 +379,8 @@ subroutine ccp_instantiate(this, std_name, long_name, units, vertical_dim, & character(len=*), intent(in) :: vertical_dim logical, optional, intent(in) :: advected real(kind_phys), optional, intent(in) :: default_value + real(kind_phys), optional, intent(in) :: min_value + real(kind_phys), optional, intent(in) :: molar_mass integer, intent(out) :: errcode character(len=*), intent(out) :: errmsg @@ -388,7 +405,14 @@ subroutine ccp_instantiate(this, std_name, long_name, units, vertical_dim, & if (present(default_value)) then this%const_default_value = default_value end if - ! Determine if this is a (moist) mixing ratio or volume mixing ratio + if (present(min_value)) then + this%min_val = min_value + end if + if (present(molar_mass)) then + this%molar_mass_val = molar_mass + end if + end if + if (errcode == 0) then if (index(this%var_std_name, "volume_mixing_ratio") > 0) then this%const_type = volume_mixing_ratio else if (index(this%var_std_name, "number_concentration") > 0) then @@ -396,6 +420,8 @@ subroutine ccp_instantiate(this, std_name, long_name, units, vertical_dim, & else this%const_type = mass_mixing_ratio end if + end if + if (errcode == 0) then ! Determine if this mixing ratio is dry, moist, or "wet". if (index(this%var_std_name, "wrt_moist_air") > 0) then this%const_water = moist_mixing_ratio @@ -477,6 +503,25 @@ end subroutine ccp_get_long_name !####################################################################### + subroutine ccp_get_units(this, units, errcode, errmsg) + ! Return this constituent's units + + ! Dummy arguments + class(ccpp_constituent_properties_t), intent(in) :: this + character(len=*), intent(out) :: units + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + + if (this%is_instantiated(errcode, errmsg)) then + units = this%var_units + else + units = '' + end if + + end subroutine ccp_get_units + + !####################################################################### + subroutine ccp_get_vertical_dimension(this, vert_dim, errcode, errmsg) ! Return the standard name of this constituent's vertical dimension @@ -603,6 +648,26 @@ end subroutine ccp_set_thermo_active !####################################################################### + subroutine ccp_set_water_species(this, water_flag, errcode, errmsg) + ! Set whether this constituent is a water species, which means + ! that this constituent represents a particular phase or type + ! of water in the atmosphere. + + ! Dummy arguments + class(ccpp_constituent_properties_t), intent(inout) :: this + logical, intent(in) :: water_flag + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + + !Set water species flag for this constituent: + if (this%is_instantiated(errcode, errmsg)) then + this%water_species = water_flag + end if + + end subroutine ccp_set_water_species + + !####################################################################### + subroutine ccp_is_thermo_active(this, val_out, errcode, errmsg) ! Dummy arguments @@ -622,6 +687,25 @@ end subroutine ccp_is_thermo_active !####################################################################### + subroutine ccp_is_water_species(this, val_out, errcode, errmsg) + + ! Dummy arguments + class(ccpp_constituent_properties_t), intent(in) :: this + logical, intent(out) :: val_out + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + + !If instantiated then check if constituent is + !a water species, otherwise return false: + if (this%is_instantiated(errcode, errmsg)) then + val_out = this%water_species + else + val_out = .false. + end if + end subroutine ccp_is_water_species + + !####################################################################### + subroutine ccp_is_advected(this, val_out, errcode, errmsg) ! Dummy arguments @@ -648,14 +732,17 @@ subroutine ccp_is_equivalent(this, oconst, equiv, errcode, errmsg) integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg - if (this%is_instantiated(errcode, errmsg) .and. & + if (this%is_instantiated(errcode, errmsg) .and. & oconst%is_instantiated(errcode, errmsg)) then equiv = (trim(this%var_std_name) == trim(oconst%var_std_name)) .and. & (trim(this%var_long_name) == trim(oconst%var_long_name)) .and. & (trim(this%vert_dim) == trim(oconst%vert_dim)) .and. & (this%advected .eqv. oconst%advected) .and. & (this%const_default_value == oconst%const_default_value) .and. & - (this%thermo_active .eqv. oconst%thermo_active) + (this%min_val == oconst%min_val) .and. & + (this%molar_mass_val == oconst%molar_mass_val) .and. & + (this%thermo_active .eqv. oconst%thermo_active) .and. & + (this%water_species .eqv. oconst%water_species) else equiv = .false. end if @@ -787,7 +874,27 @@ end subroutine ccp_min_val !######################################################################## - subroutine ccp_molec_weight(this, val_out, errcode, errmsg) + subroutine ccp_set_min_val(this, min_value, errcode, errmsg) + ! Set the minimum value of this particular constituent. + ! If this subroutine is never used then the minimum + ! value defaults to zero. + + ! Dummy arguments + class(ccpp_constituent_properties_t), intent(inout) :: this + real(kind_phys), intent(in) :: min_value + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + + !Set minimum allowed value for this constituent: + if (this%is_instantiated(errcode, errmsg)) then + this%min_val = min_value + end if + + end subroutine ccp_set_min_val + + !######################################################################## + + subroutine ccp_molar_mass(this, val_out, errcode, errmsg) ! Dummy arguments class(ccpp_constituent_properties_t), intent(in) :: this @@ -796,12 +903,28 @@ subroutine ccp_molec_weight(this, val_out, errcode, errmsg) character(len=*), intent(out) :: errmsg if (this%is_instantiated(errcode, errmsg)) then - val_out = this%molar_mass + val_out = this%molar_mass_val else val_out = kphys_unassigned end if - end subroutine ccp_molec_weight + end subroutine ccp_molar_mass + + !######################################################################## + + subroutine ccp_set_molar_mass(this, molar_mass, errcode, errmsg) + + ! Dummy arguments + class(ccpp_constituent_properties_t), intent(inout) :: this + real(kind_phys), intent(in) :: molar_mass + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg + + if (this%is_instantiated(errcode, errmsg)) then + this%molar_mass_val = molar_mass + end if + + end subroutine ccp_set_molar_mass !######################################################################## @@ -841,6 +964,55 @@ subroutine ccp_has_default(this, val_out, errcode, errmsg) end subroutine ccp_has_default + !######################################################################## + + logical function ccp_is_match(this, comp_props) result(is_match) + ! Return .true. iff the constituent's properties match the checked + ! attributes of another constituent properties object + ! Since this is a private function, error checking for locked status + ! is *not* performed. + + ! Dummy arguments + class(ccpp_constituent_properties_t), intent(in) :: this + type(ccpp_constituent_properties_t), intent(in) :: comp_props + ! Local variable + logical :: val, comp_val + character(len=stdname_len) :: char_val, char_comp_val + logical :: check + + ! By default, every constituent is a match + is_match = .true. + ! Check: advected, thermo_active, water_species, units + call this%is_advected(val) + call comp_props%is_advected(comp_val) + if (val .neqv. comp_val) then + is_match = .false. + return + end if + + call this%is_thermo_active(val) + call comp_props%is_thermo_active(comp_val) + if (val .neqv. comp_val) then + is_match = .false. + return + end if + + call this%is_water_species(val) + call comp_props%is_water_species(comp_val) + if (val .neqv. comp_val) then + is_match = .false. + return + end if + + call this%units(char_val) + call comp_props%units(char_comp_val) + if (trim(char_val) /= trim(char_comp_val)) then + is_match = .false. + return + end if + + end function ccp_is_match + !######################################################################## ! ! CCPP_MODEL_CONSTITUENTS_T (constituent field data) methods @@ -989,13 +1161,33 @@ subroutine ccp_model_const_add_metadata(this, field_data, errcode, errmsg) integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg ! Local variables - character(len=errmsg_len) :: error - character(len=*), parameter :: subname = 'ccp_model_const_add_metadata' + character(len=errmsg_len) :: error + character(len=*), parameter :: subname = 'ccp_model_const_add_metadata' + type(ccpp_constituent_properties_t), pointer :: cprop => NULL() + character(len=stdname_len) :: standard_name + logical :: match if (this%okay_to_add(errcode=errcode, errmsg=errmsg, & warn_func=subname)) then error = '' -!!XXgoldyXX: Add check on key to see if incompatible item already there. + ! Check to see if standard name is already in the table + call field_data%standard_name(standard_name, errcode, errmsg) + cprop => this%find_const(standard_name) + if (associated(cprop)) then + ! Standard name already in table, let's see if the existing constituent is the same + match = cprop%is_match(field_data) + if (match) then + ! Existing constituent is a match - no need to throw an error, just don't add + return + else + ! Existing constituent is not a match - this is an error + call append_errvars(1, "ERROR: Trying to add constituent " // & + trim(standard_name) // " but an incompatible" // & + " constituent with this name already exists", subname, & + errcode=errcode, errmsg=errmsg) + return + end if + end if call this%hash_table%add_hash_key(field_data, error) if (len_trim(error) > 0) then call append_errvars(1, trim(error), subname, errcode=errcode, errmsg=errmsg) @@ -1067,6 +1259,7 @@ function ccp_model_const_find_const(this, standard_name, errcode, errmsg) & character(len=*), parameter :: subname = 'ccp_model_const_find_const' nullify(cprop) + hval => this%hash_table%table_value(standard_name, errmsg=error) if (len_trim(error) > 0) then call append_errvars(1, trim(error), subname, & @@ -1251,6 +1444,7 @@ subroutine ccp_model_const_data_lock(this, ncols, num_layers, errcode, errmsg) ! Local variables integer :: astat, index, errcode_local real(kind=kind_phys) :: default_value + real(kind=kind_phys) :: minvalue character(len=*), parameter :: subname = 'ccp_model_const_data_lock' errcode_local = 0 @@ -1280,11 +1474,16 @@ subroutine ccp_model_const_data_lock(this, ncols, num_layers, errcode, errmsg) if (errcode_local == 0) then this%num_layers = num_layers do index = 1, this%hash_table%num_values() + !Set all constituents to their default values: call this%const_metadata(index)%default_value(default_value, & errcode, errmsg) this%vars_layer(:,:,index) = default_value + + ! Also set the minimum allowed value array + call this%const_metadata(index)%minimum(minvalue, errcode, & + errmsg) + this%vars_minvalue(index) = minvalue end do - this%vars_minvalue = 0.0_kind_phys end if if (present(errcode)) then if (errcode /= 0) then @@ -1342,7 +1541,7 @@ end subroutine ccp_model_const_reset !######################################################################## logical function ccp_model_const_is_match(this, index, advected, & - thermo_active) result(is_match) + thermo_active, water_species) result(is_match) ! Return .true. iff the constituent at matches a pattern ! Each (optional) property which is present represents something ! which is required as part of a match. @@ -1354,6 +1553,7 @@ logical function ccp_model_const_is_match(this, index, advected, & integer, intent(in) :: index logical, optional, intent(in) :: advected logical, optional, intent(in) :: thermo_active + logical, optional, intent(in) :: water_species ! Local variable logical :: check @@ -1373,13 +1573,20 @@ logical function ccp_model_const_is_match(this, index, advected, & end if end if + if (present(water_species)) then + call this%const_metadata(index)%is_water_species(check) + if (water_species .neqv. check) then + is_match = .false. + end if + end if + end function ccp_model_const_is_match !######################################################################## subroutine ccp_model_const_num_match(this, nmatch, advected, thermo_active, & - errcode, errmsg) + water_species, errcode, errmsg) ! Query number of constituents matching pattern ! Each (optional) property which is present represents something ! which is required as part of a match. @@ -1390,6 +1597,7 @@ subroutine ccp_model_const_num_match(this, nmatch, advected, thermo_active, & integer, intent(out) :: nmatch logical, optional, intent(in) :: advected logical, optional, intent(in) :: thermo_active + logical, optional, intent(in) :: water_species integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg ! Local variables @@ -1399,7 +1607,8 @@ subroutine ccp_model_const_num_match(this, nmatch, advected, thermo_active, & nmatch = 0 if (this%const_props_locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then do index = 1, SIZE(this%const_metadata) - if (this%is_match(index, advected=advected, thermo_active=thermo_active)) then + if (this%is_match(index, advected=advected, thermo_active=thermo_active, & + water_species=water_species)) then nmatch = nmatch + 1 end if end do @@ -1420,11 +1629,11 @@ subroutine ccp_model_const_index(this, index, standard_name, errcode, errmsg) integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg ! Local variables - type(ccpp_constituent_properties_t), pointer :: cprop + type(ccpp_constituent_properties_t), pointer :: cprop => NULL() character(len=*), parameter :: subname = "ccp_model_const_index" if (this%const_props_locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then - cprop => this%find_const(standard_name, errcode=errcode, errmsg=errmsg) + cprop => this%find_const(standard_name) if (associated(cprop)) then index = cprop%const_index() else @@ -1450,7 +1659,7 @@ subroutine ccp_model_const_metadata(this, standard_name, const_data, & integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg ! Local variables - type(ccpp_constituent_properties_t), pointer :: cprop + type(ccpp_constituent_properties_t), pointer :: cprop => NULL() character(len=*), parameter :: subname = "ccp_model_const_metadata" if (this%const_props_locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then @@ -1465,7 +1674,7 @@ end subroutine ccp_model_const_metadata !######################################################################## subroutine ccp_model_const_copy_in_3d(this, const_array, advected, & - thermo_active, errcode, errmsg) + thermo_active, water_species, errcode, errmsg) ! Gather constituent fields matching pattern ! Each (optional) property which is present represents something ! which is required as part of a match. @@ -1476,6 +1685,7 @@ subroutine ccp_model_const_copy_in_3d(this, const_array, advected, & real(kind_phys), intent(out) :: const_array(:,:,:) logical, optional, intent(in) :: advected logical, optional, intent(in) :: thermo_active + logical, optional, intent(in) :: water_species integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg ! Local variables @@ -1493,7 +1703,8 @@ subroutine ccp_model_const_copy_in_3d(this, const_array, advected, & num_levels = SIZE(const_array, 2) do index = 1, SIZE(this%const_metadata) if (this%is_match(index, advected=advected, & - thermo_active=thermo_active)) then + thermo_active=thermo_active, & + water_species=water_species)) then ! See if we have room for another constituent cindex = cindex + 1 if (cindex > max_cind) then @@ -1541,7 +1752,7 @@ end subroutine ccp_model_const_copy_in_3d !######################################################################## subroutine ccp_model_const_copy_out_3d(this, const_array, advected, & - thermo_active, errcode, errmsg) + thermo_active, water_species, errcode, errmsg) ! Update constituent fields matching pattern ! Each (optional) property which is present represents something ! which is required as part of a match. @@ -1552,6 +1763,7 @@ subroutine ccp_model_const_copy_out_3d(this, const_array, advected, & real(kind_phys), intent(in) :: const_array(:,:,:) logical, optional, intent(in) :: advected logical, optional, intent(in) :: thermo_active + logical, optional, intent(in) :: water_species integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg ! Local variables @@ -1569,7 +1781,8 @@ subroutine ccp_model_const_copy_out_3d(this, const_array, advected, & num_levels = SIZE(const_array, 2) do index = 1, SIZE(this%const_metadata) if (this%is_match(index, advected=advected, & - thermo_active=thermo_active)) then + thermo_active=thermo_active, & + water_species=water_species)) then ! See if we have room for another constituent cindex = cindex + 1 if (cindex > max_cind) then @@ -1735,6 +1948,29 @@ end subroutine ccpt_get_long_name !####################################################################### + subroutine ccpt_get_units(this, units, errcode, errmsg) + ! Return this constituent's units + + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(in) :: this + character(len=*), intent(out) :: units + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_get_units' + + if (associated(this%prop)) then + call this%prop%units(units, errcode, errmsg) + else + units = '' + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) + end if + + end subroutine ccpt_get_units + + !####################################################################### + subroutine ccpt_get_vertical_dimension(this, vert_dim, errcode, errmsg) ! Return the standard name of this constituent's vertical dimension @@ -1865,6 +2101,28 @@ end subroutine ccpt_is_thermo_active !####################################################################### + subroutine ccpt_is_water_species(this, val_out, errcode, errmsg) + + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(in) :: this + logical, intent(out) :: val_out + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_is_water_species' + + if (associated(this%prop)) then + call this%prop%is_water_species(val_out, errcode, errmsg) + else + val_out = .false. + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) + end if + + end subroutine ccpt_is_water_species + + !####################################################################### + subroutine ccpt_is_advected(this, val_out, errcode, errmsg) ! Dummy arguments @@ -2041,7 +2299,32 @@ end subroutine ccpt_min_val !######################################################################## - subroutine ccpt_molec_weight(this, val_out, errcode, errmsg) + subroutine ccpt_set_min_val(this, min_value, errcode, errmsg) + ! Set the minimum value of this particular constituent. + ! If this subroutine is never used then the minimum + ! value defaults to zero. + + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(inout) :: this + real(kind_phys), intent(in) :: min_value + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_set_min_val' + + !Set minimum value for this constituent: + if (associated(this%prop)) then + call this%prop%set_minimum(min_value, errcode, errmsg) + else + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) + end if + + end subroutine ccpt_set_min_val + + !######################################################################## + + subroutine ccpt_molar_mass(this, val_out, errcode, errmsg) ! Dummy arguments class(ccpp_constituent_prop_ptr_t), intent(in) :: this @@ -2049,17 +2332,38 @@ subroutine ccpt_molec_weight(this, val_out, errcode, errmsg) integer, intent(out) :: errcode character(len=*), intent(out) :: errmsg ! Local variable - character(len=*), parameter :: subname = 'ccpt_molec_weight' + character(len=*), parameter :: subname = 'ccpt_molar_mass' if (associated(this%prop)) then - call this%prop%molec_weight(val_out, errcode, errmsg) + call this%prop%molar_mass(val_out, errcode, errmsg) else val_out = kphys_unassigned call append_errvars(1, ": invalid constituent pointer", & subname, errcode=errcode, errmsg=errmsg) end if - end subroutine ccpt_molec_weight + end subroutine ccpt_molar_mass + + !######################################################################## + + subroutine ccpt_set_molar_mass(this, molar_mass, errcode, errmsg) + + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(inout) :: this + real(kind_phys), intent(in) :: molar_mass + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_set_molar_mass' + + if (associated(this%prop)) then + call this%prop%set_molar_mass(molar_mass, errcode, errmsg) + else + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) + end if + + end subroutine ccpt_set_molar_mass !######################################################################## @@ -2210,4 +2514,30 @@ subroutine ccpt_set_thermo_active(this, thermo_flag, errcode, errmsg) end subroutine ccpt_set_thermo_active + !####################################################################### + + subroutine ccpt_set_water_species(this, water_flag, errcode, errmsg) + ! Set whether this constituent is a water species, which means + ! that this constituent represents a particular phase or type + ! of water in the atmosphere. + + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(inout) :: this + logical, intent(in) :: water_flag + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_set_water_species' + + if (associated(this%prop)) then + if (this%prop%is_instantiated(errcode, errmsg)) then + this%prop%water_species = water_flag + end if + else + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) + end if + + end subroutine ccpt_set_water_species + end module ccpp_constituent_prop_mod diff --git a/test/advection_test/cld_ice.F90 b/test/advection_test/cld_ice.F90 index 9c1e769a..0a1e13ee 100644 --- a/test/advection_test/cld_ice.F90 +++ b/test/advection_test/cld_ice.F90 @@ -11,6 +11,7 @@ MODULE cld_ice 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) @@ -91,6 +92,32 @@ subroutine cld_ice_final(errmsg, errflg) errflg = 0 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 010fb419..c27c3c6d 100644 --- a/test/advection_test/cld_ice.meta +++ b/test/advection_test/cld_ice.meta @@ -2,6 +2,7 @@ [ccpp-table-properties] name = cld_ice type = scheme + dynamic_constituent_routine = cld_ice_dynamic_constituents [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 9411e0cb..63148c52 100644 --- a/test/advection_test/cld_liq.F90 +++ b/test/advection_test/cld_liq.F90 @@ -10,6 +10,7 @@ MODULE cld_liq PUBLIC :: cld_liq_init PUBLIC :: cld_liq_run + PUBLIC :: cld_liq_dynamic_constituents CONTAINS @@ -74,4 +75,24 @@ 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 4c87f4b7..1b9373f9 100644 --- a/test/advection_test/cld_liq.meta +++ b/test/advection_test/cld_liq.meta @@ -2,6 +2,7 @@ [ccpp-table-properties] name = cld_liq type = scheme + dynamic_constituent_routine = cld_liq_dynamic_constituents [ccpp-arg-table] name = cld_liq_run type = scheme diff --git a/test/advection_test/run_test b/test/advection_test/run_test index 9c75aa08..1d9a6d44 100755 --- a/test/advection_test/run_test +++ b/test/advection_test/run_test @@ -127,6 +127,7 @@ 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" @@ -219,6 +220,7 @@ 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 49a3c602..fe1f9e0b 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -23,7 +23,7 @@ module test_prog character(len=cm), pointer :: suite_required_vars(:) => NULL() end type suite_info - type(ccpp_constituent_properties_t), private, target :: host_constituents(1) + type(ccpp_constituent_properties_t), private, target, allocatable :: host_constituents(:) private :: check_list @@ -225,6 +225,7 @@ subroutine test_host(retval, test_suites) use test_host_mod, only: num_time_steps use test_host_mod, only: init_data, compare_data use test_host_mod, only: ncols, pver + use test_host_ccpp_cap, only: test_host_ccpp_deallocate_dynamic_constituents use test_host_ccpp_cap, only: test_host_ccpp_register_constituents use test_host_ccpp_cap, only: test_host_ccpp_is_scheme_constituent use test_host_ccpp_cap, only: test_host_ccpp_initialize_constituents @@ -246,6 +247,7 @@ subroutine test_host(retval, test_suites) integer :: col_start, col_end integer :: index, sind integer :: index_liq, index_ice + integer :: index_dyn1, index_dyn2, index_dyn3 integer :: time_step integer :: num_suites integer :: num_advected ! Num advected species @@ -255,10 +257,12 @@ subroutine test_host(retval, test_suites) character(len=128), allocatable :: suite_names(:) character(len=256) :: const_str character(len=512) :: errmsg + character(len=512) :: expected_error integer :: errflg integer :: errflg_final ! Used to notify testing script of test failure real(kind_phys), pointer :: const_ptr(:,:,:) real(kind_phys) :: default_value + real(kind_phys) :: check_value type(ccpp_constituent_prop_ptr_t), pointer :: const_props(:) character(len=*), parameter :: subname = 'test_host' @@ -304,7 +308,7 @@ subroutine test_host(retval, test_suites) ! specific_humidity should not be an existing constituent if (is_constituent) then write(6, *) "ERROR: specific humidity is already a constituent" - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if call test_host_ccpp_is_scheme_constituent('cloud_ice_dry_mixing_ratio', & is_constituent, errflg, errmsg) @@ -314,19 +318,63 @@ subroutine test_host(retval, test_suites) if (.not. is_constituent) then write(6, *) "ERROR: cloud_ice_dry_mixing ratio not found in ", & "host cap constituent list" - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if ! Register the constituents to find out what needs advecting + ! DO A COUPLE OF TESTS FIRST + + ! First confirm the correct error occurs if you try to add an + ! incompatible constituent with the same standard name + expected_error = 'ccp_model_const_add_metadata ERROR: Trying to add ' //& + 'constituent specific_humidity but an incompatible ' // & + 'constituent with this name already exists' + allocate(host_constituents(2)) call host_constituents(1)%instantiate(std_name="specific_humidity", & long_name="Specific humidity", units="kg kg-1", & vertical_dim="vertical_layer_dimension", advected=.true., & + min_value=1000._kind_phys, molar_mass=2000._kind_phys, & + errcode=errflg, errmsg=errmsg) + call host_constituents(2)%instantiate(std_name="specific_humidity", & + long_name="Specific humidity", units="kg kg", & + vertical_dim="vertical_layer_dimension", advected=.true., & + min_value=1000._kind_phys, molar_mass=2000._kind_phys, & errcode=errflg, errmsg=errmsg) call check_errflg(subname//'.initialize', errflg, errmsg, errflg_final) if (errflg == 0) then - call test_host_ccpp_register_constituents(suite_names(:), & - host_constituents, errmsg=errmsg, errflg=errflg) + call test_host_ccpp_register_constituents(host_constituents, & + errmsg=errmsg, errflg=errflg) + end if + ! Check the error + if (errflg == 0) then + write(6, '(2a)') 'ERROR register_constituents: expected this error: ', & + trim(expected_error) + else + if (trim(errmsg) /= trim(expected_error)) then + write(6, '(4a)') 'ERROR register_constituents: expected this error: ', & + trim(expected_error), ' Got: ', trim(errmsg) + end if + end if + ! Now try again but with a compatible constituent - should be ignored when + ! the constituents object is created + call test_host_ccpp_deallocate_dynamic_constituents() + deallocate(host_constituents) + allocate(host_constituents(2)) + call host_constituents(1)%instantiate(std_name="specific_humidity", & + long_name="Specific humidity", units="kg kg-1", & + vertical_dim="vertical_layer_dimension", advected=.true., & + min_value=1000._kind_phys, molar_mass=2000._kind_phys, & + errcode=errflg, errmsg=errmsg) + call host_constituents(2)%instantiate(std_name="specific_humidity", & + long_name="Specific humidity", units="kg kg-1", & + vertical_dim="vertical_layer_dimension", advected=.true., & + min_value=1000._kind_phys, molar_mass=2000._kind_phys, & + errcode=errflg, errmsg=errmsg) + call check_errflg(subname//'.initialize', errflg, errmsg, errflg_final) + if (errflg == 0) then + call test_host_ccpp_register_constituents(host_constituents, & + errmsg=errmsg, errflg=errflg) end if if (errflg /= 0) then write(6, '(2a)') 'ERROR register_constituents: ', trim(errmsg) @@ -339,7 +387,7 @@ subroutine test_host(retval, test_suites) errflg=errflg) call check_errflg(subname//".num_advected", errflg, errmsg, errflg_final) end if - if (num_advected /= 3) then + if (num_advected /= 6) then write(6, '(a,i0)') "ERROR: num advected constituents = ", num_advected retval = .false. return @@ -347,8 +395,8 @@ subroutine test_host(retval, test_suites) ! Initialize constituent data call test_host_ccpp_initialize_constituents(ncols, pver, errflg, errmsg) - !Stop tests here if initialization failed (as all other tests will likely - !fail as well: + ! Stop tests here if initialization failed (as all other tests will likely + ! fail as well: if (errflg /= 0) then retval = .false. return @@ -357,101 +405,167 @@ subroutine test_host(retval, test_suites) ! Initialize our 'data' const_ptr => test_host_constituents_array() - !Check if the specific humidity index can be found: + ! Check if the specific humidity index can be found: call test_host_const_get_index('specific_humidity', index, & errflg, errmsg) call check_errflg(subname//".index_specific_humidity", errflg, errmsg, & errflg_final) - !Check if the cloud liquid index can be found: + ! Check if the cloud liquid index can be found: call test_host_const_get_index('cloud_liquid_dry_mixing_ratio', & index_liq, errflg, errmsg) call check_errflg(subname//".index_cld_liq", errflg, errmsg, & errflg_final) - !Check if the cloud ice index can be found: + ! Check if the cloud ice index can be found: call test_host_const_get_index('cloud_ice_dry_mixing_ratio', & index_ice, errflg, errmsg) call check_errflg(subname//".index_cld_ice", errflg, errmsg, & errflg_final) - !Stop tests here if the index checks failed, as all other tests will - !likely fail as well: + ! Check if the dynamic constituents indices can be found + call test_host_const_get_index('dyn_const1', index_dyn1, errflg, errmsg) + call check_errflg(subname//".index_dyn_const1", errflg, errmsg, & + errflg_final) + call test_host_const_get_index('dyn_const2_wrt_moist_air', index_dyn2, errflg, errmsg) + call check_errflg(subname//".index_dyn_const2", errflg, errmsg, & + errflg_final) + call test_host_const_get_index('dyn_const3', index_dyn3, errflg, errmsg) + call check_errflg(subname//".index_dyn_const3", errflg, errmsg, & + errflg_final) + + + ! Stop tests here if the index checks failed, as all other tests will + ! likely fail as well: if (errflg_final /= 0) then retval = .false. return end if - call init_data(const_ptr, index, index_liq, index_ice) + call init_data(const_ptr, index, index_liq, index_ice, index_dyn3) ! Check some constituent properties - !++++++++++++++++++++++++++++++++++ + ! ++++++++++++++++++++++++++++++++++ const_props => test_host_model_const_properties() - !Standard name: + ! Standard name: call const_props(index)%standard_name(const_str, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to get standard_name for specific_humidity, index = ", & index, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if if (errflg == 0) then if (trim(const_str) /= 'specific_humidity') then write(6, *) "ERROR: standard name, '", trim(const_str), & "' should be 'specific_humidity'" - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured + end if + else + ! Reset error flag to continue testing other properties: + errflg = 0 + end if + ! Check standard name for a dynamic constituent + call const_props(index_dyn2)%standard_name(const_str, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to get standard_name for dyn_const2, index = ", & + index_dyn2, trim(errmsg) + errflg_final = -1 ! Notify test script that a failure occured + end if + if (errflg == 0) then + if (trim(const_str) /= 'dyn_const2_wrt_moist_air') then + write(6, *) "ERROR: standard name, '", trim(const_str), & + "' should be 'dyn_const2_wrt_moist_air'" + errflg_final = -1 ! Notify test script that a failure occured end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if - !Long name: + + ! Long name: call const_props(index_liq)%long_name(const_str, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to get long_name for cld_liq index = ", & index_liq, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if if (errflg == 0) then if (trim(const_str) /= 'Cloud liquid dry mixing ratio') then write(6, *) "ERROR: long name, '", trim(const_str), & "' should be 'Cloud liquid dry mixing ratio'" - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if - - !Mass mixing ratio: + ! Check long name for a dynamic constituent + call const_props(index_dyn1)%long_name(const_str, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to get long_name for dyn_const1 index = ", & + index_dyn1, trim(errmsg) + errflg_final = -1 ! Notify test script that a failure occured + end if + if (errflg == 0) then + if (trim(const_str) /= 'dyn const1') then + write(6, *) "ERROR: long name, '", trim(const_str), & + "' should be 'dyn const1'" + errflg_final = -1 ! Notify test script that a failure occured + end if + else + ! Reset error flag to continue testing other properties: + errflg = 0 + end if + ! Mass mixing ratio: call const_props(index_ice)%is_mass_mixing_ratio(const_log, errflg, & errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to get mass mixing ratio prop for cld_ice index = ", & index_ice, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if if (errflg == 0) then if (.not. const_log) then write(6, *) "ERROR: cloud ice is not a mass mixing_ratio" - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: + errflg = 0 + end if + ! Check mass mixing ratio for a dynamic constituent + call const_props(index_dyn2)%is_mass_mixing_ratio(const_log, errflg, & + errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to get mass mixing ratio prop for dyn_const2 index = ", & + index_dyn2, trim(errmsg) + errflg_final = -1 ! Notify test script that a failure occured + end if + if (errflg == 0) then + if (.not. const_log) then + write(6, *) "ERROR: dyn_const2 is not a mass mixing_ratio" + errflg_final = -1 ! Notify test script that a failure occured + end if + else + ! Reset error flag to continue testing other properties: errflg = 0 end if - !Dry mixing ratio: + ! Dry mixing ratio: call const_props(index_ice)%is_dry(const_log, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to get dry prop for cld_ice index = ", index_ice, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if if (errflg == 0) then if (.not. const_log) then @@ -459,110 +573,327 @@ subroutine test_host(retval, test_suites) errflg_final = -1 end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: + errflg = 0 + end if + ! Check moist mixing ratio for a dynamic constituent + call const_props(index_dyn2)%is_dry(const_log, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to get dry prop for dyn_const2 index = ", index_dyn2, trim(errmsg) + errflg_final = -1 ! Notify test script that a failure occurred + end if + if (errflg == 0) then + if (const_log) then + write(6, *) "ERROR: dyn_const2 is dry and should be moist" + errflg_final = -1 + end if + else + ! Reset error flag to continue testing other properties: + errflg = 0 + end if + call const_props(index_dyn2)%is_moist(const_log, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to get moist prop for dyn_const2 index = ", index_dyn2, trim(errmsg) + errflg_final = -1 ! Notify test script that a failure occurred + end if + if (errflg == 0) then + if (.not. const_log) then + write(6, *) "ERROR: dyn_const2 is not moist but should be" + errflg_final = -1 + end if + else + ! Reset error flag to continue testing other properties: + errflg = 0 + end if + + ! ------------------- + + ! ------------------- + ! minimum value tests: + ! ------------------- + + ! Check that a constituent's minimum value defaults to zero: + call const_props(index_dyn2)%minimum(check_value, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to get minimum value for dyn_const2 index = ", index_dyn2, & + trim(errmsg) + errflg_final = -1 ! Notify test script that a failure occurred + end if + if (errflg == 0) then + if (check_value /= 0._kind_phys) then ! Should be zero + write(6, *) "ERROR: 'minimum' should default to zero for all ", & + "constituents unless set by host model or scheme metadata." + errflg_final = -1 ! Notify test script that a failure occured + end if + else + ! Reset error flag to continue testing other properties: + errflg = 0 + end if + + ! Check that a constituent instantiated with a specified minimum value + ! actually contains that minimum value property: + call const_props(index_dyn1)%minimum(check_value, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to get minimum value for dyn_const1 index = ", index_dyn1, & + trim(errmsg) + errflg_final = -1 ! Notify test script that a failure occurred + end if + if (errflg == 0) then + if (check_value /= 1000._kind_phys) then !Should be 1000 + write(6, *) "ERROR: 'minimum' should give a value of 1000 ", & + "for dyn_const1, as was set during instantiation." + errflg_final = -1 ! Notify test script that a failure occured + end if + else + ! Reset error flag to continue testing other properties: + errflg = 0 + end if + + ! Check that setting a constituent's minimum value works + ! as expected: + call const_props(index_dyn1)%set_minimum(1._kind_phys, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to set minimum value for dyn_const1 index = ", index_dyn1, & + trim(errmsg) + errflg_final = -1 ! Notify test script that a failure occurred + end if + if (errflg == 0) then + call const_props(index_dyn1)%minimum(check_value, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, & + " trying to get minimum value for dyn_const1 index = ", & + index_dyn1, trim(errmsg) + errflg_final = -1 ! Notify test script that a failure occurred + end if + end if + if (errflg == 0) then + if (check_value /= 1._kind_phys) then ! Should now be one + write(6, *) "ERROR: 'set_minimum' did not set constituent", & + " minimum value correctly." + errflg_final = -1 ! Notify test script that a failure occurred + end if + else + ! Reset error flag to continue testing other properties: + errflg = 0 + end if + + ! ---------------------- + ! molecular weight tests: + ! ---------------------- + + ! Check that a constituent instantiated with a specified molecular + ! weight actually contains that molecular weight property value: + call const_props(index)%molar_mass(check_value, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to get molecular weight for specific humidity index = ", & + index, trim(errmsg) + errflg_final = -1 ! Notify test script that a failure occurred + end if + if (errflg == 0) then + if (check_value /= 2000._kind_phys) then ! Should be 2000 + write(6, *) "ERROR: 'molar_mass' should give a value of 2000 ", & + "for specific humidity, as was set during instantiation." + errflg_final = -1 ! Notify test script that a failure occured + end if + else + ! Reset error flag to continue testing other properties: + errflg = 0 + end if + + ! Check that setting a constituent's molecular weight works + ! as expected: + call const_props(index_ice)%set_molar_mass(1._kind_phys, errflg, & + errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to set molecular weight for cld_ice index = ", index_ice, & + trim(errmsg) + errflg_final = -1 ! Notify test script that a failure occurred + end if + if (errflg == 0) then + call const_props(index_ice)%molar_mass(check_value, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, & + " trying to get molecular weight for cld_ice index = ", & + index_ice, trim(errmsg) + errflg_final = -1 ! Notify test script that a failure occurred + end if + end if + if (errflg == 0) then + if (check_value /= 1._kind_phys) then ! Should be equal to one + write(6, *) "ERROR: 'set_molar_mass' did not set constituent", & + " molecular weight value correctly." + errflg_final = -1 ! Notify test script that a failure occurred + end if + else + ! Reset error flag to continue testing other properties: errflg = 0 end if - !Check that being thermodynamically active defaults to False: + ! ------------------- + ! thermo-active tests: + ! ------------------- + + ! Check that being thermodynamically active defaults to False: call const_props(index_ice)%is_thermo_active(check, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to get thermo_active prop for cld_ice index = ", index_ice, & trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if if (errflg == 0) then - if (check) then !Should be False + if (check) then ! Should be False write(6, *) "ERROR: 'is_thermo_active' should default to False ", & "for all constituents unless set by host model." - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if - !Check that setting a constituent to be thermodynamically active works - !as expected: + ! Check that setting a constituent to be thermodynamically active works + ! as expected: call const_props(index_ice)%set_thermo_active(.true., errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to set thermo_active prop for cld_ice index = ", index_ice, & trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if if (errflg == 0) then call const_props(index_ice)%is_thermo_active(check, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, & - " tryingto get thermo_active prop for cld_ice index = ", & + " trying to get thermo_active prop for cld_ice index = ", & index_ice, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if end if if (errflg == 0) then - if (.not.check) then !Should now be True + if (.not. check) then ! Should now be True write(6, *) "ERROR: 'set_thermo_active' did not set", & " thermo_active constituent property correctly." - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred + end if + else + ! Reset error flag to continue testing other properties: + errflg = 0 + end if + ! ------------------- + + ! ------------------- + ! water-species tests: + ! ------------------- + + ! Check that being a water species defaults to False: + call const_props(index_liq)%is_water_species(check, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to get water_species prop for cld_liq index = ", index_liq, & + trim(errmsg) + errflg_final = -1 ! Notify test script that a failure occurred + end if + if (errflg == 0) then + if (check) then ! Should be False + write(6, *) "ERROR: 'is_water_species' should default to False ", & + "for all constituents unless set by host model." + errflg_final = -1 ! Notify test script that a failure occured + end if + else + ! Reset error flag to continue testing other properties: + errflg = 0 + end if + + ! Check that setting a constituent to be a water species works + ! as expected: + call const_props(index_liq)%set_water_species(.true., errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to set water_species prop for cld_liq index = ", index_liq, & + trim(errmsg) + errflg_final = -1 ! Notify test script that a failure occurred + end if + if (errflg == 0) then + call const_props(index_liq)%is_water_species(check, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, & + " trying to get water_species prop for cld_liq index = ", & + index_liq, trim(errmsg) + errflg_final = -1 ! Notify test script that a failure occurred + end if + end if + if (errflg == 0) then + if (.not. check) then ! Should now be True + write(6, *) "ERROR: 'set_water_species' did not set", & + " water_species constituent property correctly." + errflg_final = -1 ! Notify test script that a failure occurred end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if + ! ------------------- - !Check that setting a constituent's default value works as expected + ! Check that setting a constituent's default value works as expected call const_props(index_liq)%has_default(has_default, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,2a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to check for default for cld_liq index = ", index_liq, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if if (errflg == 0) then if (has_default) then write(6, *) "ERROR: cloud liquid mass_mixing_ratio should not have default but does" - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if call const_props(index_ice)%has_default(has_default, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,2a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to check for default for cld_ice index = ", index_ice, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if if (errflg == 0) then if (.not. has_default) then write(6, *) "ERROR: cloud ice mass_mixing_ratio should have default but doesn't" - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if call const_props(index_ice)%default_value(default_value, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,2a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to grab default for cld_ice index = ", index_ice, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if if (errflg == 0) then if (default_value /= 0.0_kind_phys) then write(6, *) "ERROR: cloud ice mass_mixing_ratio default is ", default_value, & " but should be 0.0" - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if - !++++++++++++++++++++++++++++++++++ + ! ++++++++++++++++++++++++++++++++++ - !Set error flag to the "final" value, because any error - !above will likely result in a large number of failures - !below: + ! Set error flag to the "final" value, because any error + ! above will likely result in a large number of failures + ! below: errflg = errflg_final ! Use the suite information to setup the run @@ -659,12 +990,12 @@ subroutine test_host(retval, test_suites) end if end if - !Make sure "final" flag is non-zero if "errflg" is: + ! Make sure "final" flag is non-zero if "errflg" is: if (errflg /= 0) then - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if - !Set return value to False if any errors were found: + ! Set return value to False if any errors were found: retval = errflg_final == 0 end subroutine test_host diff --git a/test/advection_test/test_host_mod.F90 b/test/advection_test/test_host_mod.F90 index 560b7619..0ae75b3d 100644 --- a/test/advection_test/test_host_mod.F90 +++ b/test/advection_test/test_host_mod.F90 @@ -34,13 +34,14 @@ module test_host_mod contains - subroutine init_data(constituent_array, index_qv_use, index_liq, index_ice) + subroutine init_data(constituent_array, index_qv_use, index_liq, index_ice, index_dyn) ! Dummy arguments real(kind_phys), pointer :: constituent_array(:,:,:) ! From host & suites integer, intent(in) :: index_qv_use integer, intent(in) :: index_liq integer, intent(in) :: index_ice + integer, intent(in) :: index_dyn ! Local variables integer :: col @@ -60,6 +61,7 @@ subroutine init_data(constituent_array, index_qv_use, index_liq, index_ice) ind_ice = index_ice allocate(check_vals(ncols, pver, ncnst)) check_vals(:,:,:) = 0.0_kind_phys + check_vals(:,:,index_dyn) = 1.0_kind_phys do lev = 1, pver phys_state%temp(:, lev) = tfreeze + (10.0_kind_phys * (lev - 3)) qmax = real(lev, kind_phys) diff --git a/test/advection_test/test_reports.py b/test/advection_test/test_reports.py index 6d84616b..1a34462a 100644 --- a/test/advection_test/test_reports.py +++ b/test/advection_test/test_reports.py @@ -66,6 +66,7 @@ def usage(errmsg=None): _PROCESS_LIST = list() _MODULE_LIST = ["cld_ice", "cld_liq"] _SUITE_LIST = ["cld_suite"] +_DYN_CONST_ROUTINES = ["cld_ice_dynamic_constituents", "cld_liq_dynamic_constituents"] _REQUIRED_VARS_CLD = ["ccpp_error_code", "ccpp_error_message", "horizontal_loop_begin", "horizontal_loop_end", "surface_air_pressure", "temperature", @@ -154,6 +155,8 @@ 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 6798f584..e5ac6e12 100755 --- a/test/capgen_test/run_test +++ b/test/capgen_test/run_test @@ -128,7 +128,7 @@ ccpp_files="${utility_files}" ccpp_files="${ccpp_files},${build_dir}/ccpp/test_host_ccpp_cap.F90" ccpp_files="${ccpp_files},${build_dir}/ccpp/ccpp_ddt_suite_cap.F90" ccpp_files="${ccpp_files},${build_dir}/ccpp/ccpp_temp_suite_cap.F90" -process_list="setter=temp_set,adjusting=temp_calc_adjust" +process_list="adjusting=temp_calc_adjust,setter=temp_set" module_list="environ_conditions,make_ddt,setup_coeffs,temp_adjust,temp_calc_adjust,temp_set" dependencies="bar.F90,foo.F90,qux.F90" suite_list="ddt_suite;temp_suite" @@ -151,6 +151,7 @@ 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" required_vars_temp="${required_vars_temp},index_of_water_vapor_specific_humidity" +required_vars_temp="${required_vars_temp},number_of_tracers" required_vars_temp="${required_vars_temp},potential_temperature" required_vars_temp="${required_vars_temp},potential_temperature_at_interface" required_vars_temp="${required_vars_temp},potential_temperature_increment" @@ -164,6 +165,7 @@ 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" input_vars_temp="${input_vars_temp},index_of_water_vapor_specific_humidity" +input_vars_temp="${input_vars_temp},number_of_tracers" input_vars_temp="${input_vars_temp},potential_temperature" input_vars_temp="${input_vars_temp},potential_temperature_at_interface" input_vars_temp="${input_vars_temp},potential_temperature_increment" diff --git a/test/capgen_test/temp_adjust.F90 b/test/capgen_test/temp_adjust.F90 index 5aba4c0b..3c6b24f0 100644 --- a/test/capgen_test/temp_adjust.F90 +++ b/test/capgen_test/temp_adjust.F90 @@ -18,7 +18,7 @@ MODULE temp_adjust !! \htmlinclude arg_table_temp_adjust_run.html !! subroutine temp_adjust_run(foo, timestep, temp_prev, temp_layer, qv, ps, & - errmsg, errflg, innie, outie, optsie) + to_promote, promote_pcnst, errmsg, errflg, innie, outie, optsie) integer, intent(in) :: foo real(kind_phys), intent(in) :: timestep @@ -26,8 +26,10 @@ subroutine temp_adjust_run(foo, timestep, temp_prev, temp_layer, qv, ps, & real(kind_phys), intent(inout) :: ps(:) REAL(kind_phys), intent(in) :: temp_prev(:) REAL(kind_phys), intent(inout) :: temp_layer(foo) + real(kind_phys), intent(in) :: to_promote(:) + real(kind_phys), intent(in) :: promote_pcnst(:) character(len=512), intent(out) :: errmsg - integer, optional, intent(out) :: errflg + integer, intent(out) :: errflg real(kind_phys), optional, intent(in) :: innie real(kind_phys), optional, intent(out) :: outie real(kind_phys), optional, intent(inout) :: optsie @@ -36,9 +38,7 @@ subroutine temp_adjust_run(foo, timestep, temp_prev, temp_layer, qv, ps, & integer :: col_index errmsg = '' - if (present(errflg)) then - errflg = 0 - end if + errflg = 0 do col_index = 1, foo temp_layer(col_index) = temp_layer(col_index) + temp_prev(col_index) diff --git a/test/capgen_test/temp_adjust.meta b/test/capgen_test/temp_adjust.meta index 17eabcdb..02b5fa73 100644 --- a/test/capgen_test/temp_adjust.meta +++ b/test/capgen_test/temp_adjust.meta @@ -50,6 +50,20 @@ units = Pa dimensions = (horizontal_loop_extent) intent = inout +[ to_promote ] + standard_name = promote_this_variable_to_suite + units = K + dimensions = (horizontal_loop_extent) + type = real + kind = kind_phys + intent = in +[ promote_pcnst ] + standard_name = promote_this_variable_with_no_horizontal_dimension + units = K + dimensions = (number_of_tracers) + type = real + kind = kind_phys + intent = in [ errmsg ] standard_name = ccpp_error_message long_name = Error message for error handling in CCPP diff --git a/test/capgen_test/temp_set.F90 b/test/capgen_test/temp_set.F90 index fc2a9f39..a780433f 100644 --- a/test/capgen_test/temp_set.F90 +++ b/test/capgen_test/temp_set.F90 @@ -19,7 +19,7 @@ MODULE temp_set !! \htmlinclude arg_table_temp_set_run.html !! SUBROUTINE temp_set_run(ncol, lev, timestep, temp_level, temp, ps, & - errmsg, errflg) + to_promote, promote_pcnst, errmsg, errflg) !---------------------------------------------------------------- IMPLICIT NONE !---------------------------------------------------------------- @@ -29,6 +29,8 @@ SUBROUTINE temp_set_run(ncol, lev, timestep, temp_level, temp, ps, & real(kind_phys), intent(in) :: timestep real(kind_phys), intent(in) :: ps(:) REAL(kind_phys), INTENT(inout) :: temp_level(:, :) + real(kind_phys), intent(out) :: to_promote(:, :) + real(kind_phys), intent(out) :: promote_pcnst(:) character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg !---------------------------------------------------------------- diff --git a/test/capgen_test/temp_set.meta b/test/capgen_test/temp_set.meta index c3c919e5..b6c403ce 100644 --- a/test/capgen_test/temp_set.meta +++ b/test/capgen_test/temp_set.meta @@ -47,6 +47,20 @@ units = Pa dimensions = (horizontal_loop_extent) intent = in +[ to_promote ] + standard_name = promote_this_variable_to_suite + units = K + dimensions = (horizontal_loop_extent, vertical_layer_dimension) + type = real + kind = kind_phys + intent = out +[ promote_pcnst ] + standard_name = promote_this_variable_with_no_horizontal_dimension + units = K + dimensions = (number_of_tracers) + type = real + kind = kind_phys + intent = out [ errmsg ] standard_name = ccpp_error_message long_name = Error message for error handling in CCPP diff --git a/test/capgen_test/test_reports.py b/test/capgen_test/test_reports.py index 8682a7b0..c9fc452d 100644 --- a/test/capgen_test/test_reports.py +++ b/test/capgen_test/test_reports.py @@ -79,6 +79,7 @@ def usage(errmsg=None): _REQUIRED_VARS_DDT = _INPUT_VARS_DDT + _OUTPUT_VARS_DDT _PROT_VARS_TEMP = ["horizontal_loop_begin", "horizontal_loop_end", "horizontal_dimension", "vertical_layer_dimension", + "number_of_tracers", # Added for --debug "index_of_water_vapor_specific_humidity", "vertical_interface_dimension"] @@ -118,14 +119,14 @@ def fields_string(field_type, field_list, sep): return fmsg def check_datatable(database, report_type, check_list, - sep=',', excl_prot=False): + sep=',', exclude_protected=False): """Run a database report and check the return string. If an error is found, print an error message. Return the number of errors""" if sep is None: sep = ',' # end if - test_str = datatable_report(database, report_type, sep, excl_prot=excl_prot) + test_str = datatable_report(database, report_type, sep, exclude_protected=exclude_protected) test_list = [x for x in test_str.split(sep) if x] missing = list() unexpected = list() @@ -182,13 +183,13 @@ def check_datatable(database, report_type, check_list, _REQUIRED_VARS_TEMP + _PROT_VARS_TEMP) NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("required_variables", value="temp_suite"), - _REQUIRED_VARS_TEMP, excl_prot=True) + _REQUIRED_VARS_TEMP, exclude_protected=True) NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("input_variables", value="temp_suite"), _INPUT_VARS_TEMP + _PROT_VARS_TEMP) NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("input_variables", value="temp_suite"), - _INPUT_VARS_TEMP, excl_prot=True) + _INPUT_VARS_TEMP, exclude_protected=True) NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("output_variables", value="temp_suite"), _OUTPUT_VARS_TEMP) diff --git a/test/hash_table_tests/test_hash.F90 b/test/hash_table_tests/test_hash.F90 index 883ab17b..35536cdd 100644 --- a/test/hash_table_tests/test_hash.F90 +++ b/test/hash_table_tests/test_hash.F90 @@ -151,6 +151,7 @@ subroutine test_table(hash_table, table_size, num_tests, num_errs, errors) exit end if end do + call hash_iter%finalize() if (ANY(.not. hash_found)) then write(errmsg, '(a,i0,a)') "ERROR: ", & COUNT(.not. hash_found), " test keys not found in table." diff --git a/test/unit_tests/sample_files/test_host.meta b/test/unit_tests/sample_files/test_host.meta index f618f871..b3bbc001 100644 --- a/test/unit_tests/sample_files/test_host.meta +++ b/test/unit_tests/sample_files/test_host.meta @@ -2,6 +2,7 @@ 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 new file mode 100644 index 00000000..2eb43a89 --- /dev/null +++ b/test/unit_tests/sample_scheme_files/duplicate_dyn_const.F90 @@ -0,0 +1,96 @@ +! 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 new file mode 100644 index 00000000..f369f8b2 --- /dev/null +++ b/test/unit_tests/sample_scheme_files/duplicate_dyn_const.meta @@ -0,0 +1,104 @@ +[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 new file mode 100644 index 00000000..615f0467 --- /dev/null +++ b/test/unit_tests/sample_scheme_files/dyn_const_not_present.F90 @@ -0,0 +1,75 @@ +! 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 new file mode 100644 index 00000000..05638ded --- /dev/null +++ b/test/unit_tests/sample_scheme_files/dyn_const_not_present.meta @@ -0,0 +1,104 @@ +[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 new file mode 100644 index 00000000..259d4d66 --- /dev/null +++ b/test/unit_tests/sample_scheme_files/dyn_const_not_present_nested.F90 @@ -0,0 +1,100 @@ +! 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 new file mode 100644 index 00000000..05638ded --- /dev/null +++ b/test/unit_tests/sample_scheme_files/dyn_const_not_present_nested.meta @@ -0,0 +1,104 @@ +[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 2dcebe31..4b78642d 100644 --- a/test/unit_tests/sample_scheme_files/temp_adjust.F90 +++ b/test/unit_tests/sample_scheme_files/temp_adjust.F90 @@ -11,6 +11,7 @@ MODULE temp_adjust PUBLIC :: temp_adjust_init PUBLIC :: temp_adjust_run PUBLIC :: temp_adjust_finalize + PUBLIC :: dyn_consts CONTAINS @@ -72,4 +73,24 @@ 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 3c5ce877..063409d6 100644 --- a/test/unit_tests/sample_scheme_files/temp_adjust.meta +++ b/test/unit_tests/sample_scheme_files/temp_adjust.meta @@ -1,6 +1,7 @@ [ccpp-table-properties] name = temp_adjust type = scheme + dynamic_constituent_routine = dyn_consts ######################################################################## [ccpp-arg-table] diff --git a/test/unit_tests/test_metadata_scheme_file.py b/test/unit_tests/test_metadata_scheme_file.py index 595962c0..fb0ce2f4 100644 --- a/test/unit_tests/test_metadata_scheme_file.py +++ b/test/unit_tests/test_metadata_scheme_file.py @@ -35,6 +35,10 @@ - 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: @@ -341,6 +345,37 @@ 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 3a01f98b..fe6154ad 100755 --- a/test/unit_tests/test_metadata_table.py +++ b/test/unit_tests/test_metadata_table.py @@ -47,6 +47,7 @@ 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 @@ -55,6 +56,8 @@ 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") diff --git a/test/var_compatibility_test/test_reports.py b/test/var_compatibility_test/test_reports.py index aaf08953..f7c37897 100755 --- a/test/var_compatibility_test/test_reports.py +++ b/test/var_compatibility_test/test_reports.py @@ -101,14 +101,14 @@ def fields_string(field_type, field_list, sep): return fmsg def check_datatable(database, report_type, check_list, - sep=',', excl_prot=False): + sep=',', exclude_protected=False): """Run a database report and check the return string. If an error is found, print an error message. Return the number of errors""" if sep is None: sep = ',' # end if - test_str = datatable_report(database, report_type, sep, excl_prot=excl_prot) + test_str = datatable_report(database, report_type, sep, exclude_protected=exclude_protected) test_list = [x for x in test_str.split(sep) if x] missing = list() unexpected = list()