From 69e2bf95e2a4aa0f1b64553b010533d9b2632e1e Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 21 Aug 2023 13:33:43 -0600 Subject: [PATCH 01/11] initial stab at constituents-only PR --- scripts/ccpp_capgen.py | 36 +- scripts/ccpp_datafile.py | 3 +- scripts/ccpp_suite.py | 20 +- scripts/constituents.py | 310 +-- scripts/host_cap.py | 180 +- scripts/host_model.py | 47 +- scripts/metavar.py | 67 +- scripts/parse_tools/__init__.py | 3 +- scripts/parse_tools/parse_checkers.py | 53 + scripts/suite_objects.py | 16 + scripts/var_props.py | 20 +- src/ccpp_constituent_prop_mod.F90 | 2399 ++++++++++++++++-------- src/ccpp_constituent_prop_mod.meta | 47 + test/advection_test/test_host.F90 | 274 ++- test/advection_test/test_host_data.F90 | 19 +- test/advection_test/test_host_mod.F90 | 38 +- 16 files changed, 2438 insertions(+), 1094 deletions(-) create mode 100644 src/ccpp_constituent_prop_mod.meta diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index 7a12729a..8938a43f 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -26,11 +26,13 @@ from host_model import HostModel from metadata_table import parse_metadata_file, SCHEME_HEADER_TYPE from parse_tools import init_log, set_log_level, context_string +from parse_tools import register_fortran_ddt_name from parse_tools import CCPPError, ParseInternalError ## Capture the Framework root -__SCRIPT_PATH = os.path.dirname(__file__) -__FRAMEWORK_ROOT = os.path.abspath(os.path.join(__SCRIPT_PATH, os.pardir)) +_SCRIPT_PATH = os.path.dirname(__file__) +_FRAMEWORK_ROOT = os.path.abspath(os.path.join(__SCRIPT_PATH, os.pardir)) +_SRC_ROOT = os.path.join(_FRAMEWORK_ROOT, "src") ## Init this now so that all Exceptions can be trapped _LOGGER = init_log(os.path.basename(__file__)) @@ -43,6 +45,11 @@ ## Metadata table types where order is significant _ORDERED_TABLE_TYPES = [SCHEME_HEADER_TYPE] +## CCPP Framework supported DDT types +_CCPP_FRAMEWORK_DDT_TYPES = ["ccpp_hash_table_t", + "ccpp_hashable_t", + "ccpp_hashable_char_t"] + ############################################################################### def delete_pathnames_from_file(capfile, logger): ############################################################################### @@ -578,12 +585,22 @@ def capgen(run_env): # Try to create output_dir (let it crash if it fails) os.makedirs(run_env.output_dir) # end if + # Pre-register base CCPP DDT types: + for ddt_name in _CCPP_FRAMEWORK_DDT_TYPES: + register_fortran_ddt_name(ddt_name) + # end for + src_dir = os.path.join(_FRAMEWORK_ROOT, "src") host_files = run_env.host_files host_name = run_env.host_name scheme_files = run_env.scheme_files # We need to create three lists of files, hosts, schemes, and SDFs host_files = create_file_list(run_env.host_files, ['meta'], 'Host', run_env.logger) + # The host model needs to know about the constituents module + const_mod = os.path.join(_SRC_ROOT, "ccpp_constituent_prop_mod.meta") + if const_mod not in host_files: + host_files.append(const_mod) + # end if scheme_files = create_file_list(run_env.scheme_files, ['meta'], 'Scheme', run_env.logger) sdfs = create_file_list(run_env.suites, ['xml'], 'Suite', run_env.logger) @@ -594,11 +611,19 @@ def capgen(run_env): # end if # First up, handle the host files host_model = parse_host_model_files(host_files, host_name, run_env) + # We always need to parse the ccpp_constituent_prop_ptr_t DDT + ##XXgoldyXX: Should this be in framework_env.py? + const_prop_mod = os.path.join(src_dir, "ccpp_constituent_prop_mod.meta") + if const_prop_mod and not in 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) - ddts = host_model.ddt_lib.keys() - if ddts and run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG): - run_env.logger.debug("DDT definitions = {}".format(ddts)) + if run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG): + ddts = host_model.ddt_lib.keys() + if ddts: + run_env.logger.debug("DDT definitions = {}".format(ddts)) + # end if # end if plist = host_model.prop_list('local_name') if run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG): @@ -646,7 +671,6 @@ def capgen(run_env): # end if # Finally, create the database of generated files and caps # This can be directly in output_dir because it will not affect dependencies - src_dir = os.path.join(__FRAMEWORK_ROOT, "src") generate_ccpp_datatable(run_env, host_model, ccpp_api, scheme_headers, scheme_tdict, host_files, cap_filenames, kinds_file, src_dir) diff --git a/scripts/ccpp_datafile.py b/scripts/ccpp_datafile.py index 158d9dec..7acd37f1 100755 --- a/scripts/ccpp_datafile.py +++ b/scripts/ccpp_datafile.py @@ -657,7 +657,8 @@ def _new_var_entry(parent, var, full_entry=True): prop_list.extend(["allocatable", "active", "default_value", "diagnostic_name", "diagnostic_name_fixed", "kind", "persistence", "polymorphic", "protected", - "state_variable", "type", "units"]) + "state_variable", "type", "units", "molar_mass", + "advected"]) prop_list.extend(Var.constituent_property_names()) # end if ventry = ET.SubElement(parent, "var") diff --git a/scripts/ccpp_suite.py b/scripts/ccpp_suite.py index 8b9a1aeb..d28a026f 100644 --- a/scripts/ccpp_suite.py +++ b/scripts/ccpp_suite.py @@ -810,32 +810,46 @@ def write_req_vars_sub(self, ofile, errmsg_name, errcode_name): input_vars = [set(), set(), set()] # leaves, arrrays, leaf elements inout_vars = [set(), set(), set()] # leaves, arrrays, leaf elements output_vars = [set(), set(), set()] # leaves, arrrays, leaf elements + const_initialized_in_physics = {} for part in suite.groups: for var in part.call_list.variable_list(): + phase = part.phase() stdname = var.get_prop_value("standard_name") intent = var.get_prop_value("intent") protected = var.get_prop_value("protected") + constituent = var.is_constituent() + if stdname not in const_initialized_in_physics: + const_initialized_in_physics[stdname] = False + # end if if (parent is not None) and (not protected): pvar = parent.find_variable(standard_name=stdname) if pvar is not None: protected = pvar.get_prop_value("protected") # end if # end if - elements = var.intrinsic_elements(check_dict=self.parent) - if (intent == 'in') and (not protected): + elements = var.intrinsic_elements(check_dict=self.parent, + ddt_lib=self.__ddt_lib) + if (intent == 'in') and (not protected) and (not const_initialized_in_physics[stdname]): if isinstance(elements, list): input_vars[1].add(stdname) input_vars[2].update(elements) else: input_vars[0].add(stdname) # end if - elif intent == 'inout': + elif intent == 'inout' and (not const_initialized_in_physics[stdname]): if isinstance(elements, list): inout_vars[1].add(stdname) inout_vars[2].update(elements) else: inout_vars[0].add(stdname) # end if + elif intent == 'out' and phase != 'initialize' and constituent + and not const_initialized_in_physics[stdname]: + # constituents HAVE to be initialized in the init phase because the dycore needs to advect them + emsg = "constituent variable '{}' cannot be initialized in the '{}' phase" + raise CCPPError(emsg.format(stdname, phase)) + elif intent == 'out' and constituent and phase == 'initialize': + const_initialized_in_physics[stdname] = True elif intent == 'out': if isinstance(elements, list): output_vars[1].add(stdname) diff --git a/scripts/constituents.py b/scripts/constituents.py index be8d4774..7a60af04 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -8,20 +8,17 @@ to implement this support. """ -# Python library imports -from __future__ import print_function -import os # CCPP framework imports -from file_utils import KINDS_MODULE -from fortran_tools import FortranWriter -from parse_tools import ParseInternalError -from metavar import Var, VarDictionary +from parse_tools import ParseInternalError, type_name +from metavar import VarDictionary ######################################################################## CONST_DDT_NAME = "ccpp_model_constituents_t" CONST_DDT_MOD = "ccpp_constituent_prop_mod" CONST_PROP_TYPE = "ccpp_constituent_properties_t" +CONST_PROP_PTR_TYPE = "ccpp_constituent_prop_ptr_t" +CONST_OBJ_STDNAME = "ccpp_model_constituents_object" ######################################################################## @@ -34,7 +31,6 @@ class ConstituentVarDict(VarDictionary): __const_prop_array_name = "ccpp_constituent_array" __const_prop_init_name = "ccpp_constituents_initialized" __const_prop_init_consts = "ccpp_create_constituent_array" - __const_prop_type_name = "ccpp_constituent_properties_t" __constituent_type = "suite" def __init__(self, name, parent_dict, run_env, variables=None): @@ -47,9 +43,8 @@ def __init__(self, name, parent_dict, run_env, variables=None): because this dictionary must be connected to a host model. """ self.__run_env = run_env - super(ConstituentVarDict, self).__init__(name, run_env, - variables=variables, - parent_dict=parent_dict) + super().__init__(name, run_env, + variables=variables, parent_dict=parent_dict) def find_variable(self, standard_name=None, source_var=None, any_scope=True, clone=None, @@ -117,7 +112,7 @@ def find_variable(self, standard_name=None, source_var=None, # end for newdims.append(':'.join(new_dnames)) # end for - var = source_var.clone({'dimensions' : newdims}, remove_intent=True, + var = source_var.clone({'dimensions' : newdims}, remove_intent=False, source_type=self.__constituent_type) self.add_variable(var, self.__run_env) return var @@ -155,7 +150,7 @@ def declare_private_data(self, outfile, indent): outfile.write("! Private constituent module data", indent) if self: stmt = "type({}), private, allocatable :: {}(:)" - outfile.write(stmt.format(self.constituent_prop_type_name(), + outfile.write(stmt.format(CONST_PROP_TYPE, self.constituent_prop_array_name()), indent) # end if @@ -298,10 +293,15 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): len(self)), indent+1) outfile.write("index = 0", indent+1) # end if + for evar in err_vars: + self.__init_err_var(evar, outfile, indent+1) + # end for for std_name, var in self.items(): outfile.write("index = index + 1", indent+1) long_name = var.get_prop_value('long_name') + units = var.get_prop_value('units') dims = var.get_dim_stdnames() + default_value = var.get_prop_value('default_value') if 'vertical_layer_dimension' in dims: vertical_dim = 'vertical_layer_dimension' elif 'vertical_interface_dimension' in dims: @@ -310,13 +310,18 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): vertical_dim = '' # end if advect_str = self.TF_string(var.get_prop_value('advected')) - stmt = 'call {}(index)%initialize("{}", "{}", "{}", {}{})' + init_args = [f'std_name="{std_name}"', f'long_name="{long_name}"', + f'units="{units}"', f'vertical_dim="{vertical_dim}"', + f'advected={advect_str}', + f'errcode={errvar_names["ccpp_error_code"]}', + f'errmsg={errvar_names["ccpp_error_message"]}'] + if default_value is not None: + init_args.append(f'default_value={default_value}') + stmt = 'call {}(index)%instantiate({})' + outfile.write(f'if ({errvar_names["ccpp_error_code"]} == 0) then', indent+1) outfile.write(stmt.format(self.constituent_prop_array_name(), - std_name, long_name, vertical_dim, - advect_str, errvar_call2), indent+1) - # end for - for evar in err_vars: - self.__init_err_var(evar, outfile, indent+1) + ", ".join(init_args)), indent+2) + outfile.write("end if", indent+1) # end for outfile.write("{} = .true.".format(self.constituent_prop_init_name()), indent+1) @@ -363,9 +368,12 @@ 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 = "call {}(index)%standard_name(name_out{})" + 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(), - errvar_call2), indent+1) + ", ".join(init_args)), indent+1) # end if outfile.write("end subroutine {}".format(self.const_name_subname()), indent) @@ -377,8 +385,8 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): outfile.write("! Copy the data for a constituent", indent+1) outfile.write("! Dummy arguments", indent+1) outfile.write("integer, intent(in) :: index", indent+1) - stmt = "type({}), intent(out) :: cnst_out" - outfile.write(stmt.format(self.constituent_prop_type_name()), indent+1) + stmt = f"type({CONST_PROP_TYPE}), intent(out) :: cnst_out" + outfile.write(stmt, indent+1) for evar in err_vars: evar.write_def(outfile, indent+1, self, dummy=True) # end for @@ -398,7 +406,7 @@ def constituent_module_name(self): if not ((self.parent is not None) and hasattr(self.parent.parent, "constituent_module")): emsg = "ConstituentVarDict parent not HostModel?" - emsg += "\nparent is '{}'".format(type(self.parent.parent)) + emsg += f"\nparent is '{type_name(self.parent.parent)}'" raise ParseInternalError(emsg) # end if return self.parent.parent.constituent_module @@ -447,17 +455,23 @@ def write_constituent_use_statements(cap, suite_list, indent): # end for @staticmethod - def write_host_routines(cap, host, reg_funcname, num_const_funcname, + def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcname, copy_in_funcname, copy_out_funcname, const_obj_name, - const_names_name, const_indices_name, - suite_list, err_vars): + const_names_name, const_indices_name, const_array_func, + advect_array_func, prop_array_func, + const_index_func, suite_list, err_vars): """Write out the host model routine which will instantiate constituent fields for all the constituents in . is a list of the host model's error variables. Also write out the following routines: + : Initialize constituent data : Number of constituents : Collect constituent fields for host : Update constituent fields from host + : Return a pointer to the constituent array + : Return a pointer to the advected constituent array + : Return a pointer to the constituent properties array + : Return the index of a provided constituent name Output is written to . """ # XXgoldyXX: v need to generalize host model error var type support @@ -476,121 +490,157 @@ def write_host_routines(cap, host, reg_funcname, num_const_funcname, errmsg=herrmsg) # XXgoldyXX: ^ need to generalize host model error var type support # First up, the registration routine - substmt = "subroutine {}".format(reg_funcname) - stmt = "{}(suite_list, ncols, num_layers, num_interfaces, {})" - cap.write(stmt.format(substmt, err_dummy_str), 1) - cap.write("! Create constituent object for suites in ", 2) + substmt = f"subroutine {reg_funcname}" + args = "suite_list, host_constituents " + stmt = f"{substmt}({args}, {err_dummy_str})" + cap.write(stmt, 1) + cap.comment("Create constituent object for suites in ", 2) cap.write("", 0) ConstituentVarDict.write_constituent_use_statements(cap, suite_list, 2) cap.write("", 0) - cap.write("! Dummy arguments", 2) - cap.write("character(len=*), intent(in) :: suite_list(:)", 2) - cap.write("integer, intent(in) :: ncols", 2) - cap.write("integer, intent(in) :: num_layers", 2) - cap.write("integer, intent(in) :: num_interfaces", 2) + 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: evar.write_def(cap, 2, host, dummy=True, add_intent="out") # end for - cap.write("! Local variables", 2) + 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("type({}), pointer :: const_prop".format(CONST_PROP_TYPE), 2) + cap.write(f"type({CONST_PROP_TYPE}), pointer :: const_prop", 2) cap.write("", 0) cap.write("{} = 0".format(herrcode), 2) - cap.write("num_consts = 0", 2) + cap.write("num_consts = size(host_constituents, 1)", 2) for suite in suite_list: const_dict = suite.constituent_dictionary() funcname = const_dict.num_consts_funcname() - cap.write("! Number of suite constants for {}".format(suite.name), - 2) + cap.comment(f"Number of suite constants for {suite.name}", 2) errvar_str = ConstituentVarDict.__errcode_callstr(herrcode, herrmsg, suite) - cap.write("num_suite_consts = {}({})".format(funcname, - errvar_str), 2) + cap.write(f"num_suite_consts = {funcname}({errvar_str})", 2) cap.write("num_consts = num_consts + num_suite_consts", 2) # end for cap.write("if ({} == 0) then".format(herrcode), 2) - cap.write("! Initialize constituent data and field object", 3) + cap.comment("Initialize constituent data and field object", 3) stmt = "call {}%initialize_table(num_consts)" cap.write(stmt.format(const_obj_name), 3) + # 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.write("", 0) + # Register suite constituents for suite in suite_list: errvar_str = ConstituentVarDict.__errcode_callstr(herrcode, herrmsg, suite) - cap.write("if ({} == 0) then".format(herrcode), 2) - cap.write("! Add {} constituent metadata".format(suite.name), 3) + cap.write(f"if ({herrcode} == 0) then", 2) + cap.comment(f"Add {suite.name} constituent metadata", 3) const_dict = suite.constituent_dictionary() funcname = const_dict.num_consts_funcname() - cap.write("num_suite_consts = {}({})".format(funcname, - errvar_str), 3) + cap.write(f"num_suite_consts = {funcname}({errvar_str})", 3) cap.write("end if", 2) funcname = const_dict.copy_const_subname() - cap.write("do index = 1, num_suite_consts", 2) - cap.write("allocate(const_prop, stat={})".format(herrcode), 3) - cap.write("if ({} /= 0) then".format(herrcode), 3) - cap.write('{} = "ERROR allocating const_prop"'.format(herrmsg), 4) - cap.write("end if", 3) - cap.write("if ({} == 0) then".format(herrcode), 3) + 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), 4) - cap.write("end if", 3) - cap.write("if ({} == 0) then".format(herrcode), 3) + 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), 4) - cap.write("end if", 3) - cap.write("nullify(const_prop)", 3) - cap.write("if ({} /= 0) then".format(herrcode), 3) - cap.write("exit", 4) - cap.write("end if", 3) - cap.write("end do", 2) + 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("", 0) # end for - cap.write("if ({} == 0) then".format(herrcode), 2) - stmt = "call {}%lock_table(ncols, num_layers, num_interfaces, {})" + 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("! Set the index for each active constituent", 2) - cap.write("do index = 1, SIZE({})".format(const_indices_name), 2) - stmt = "field_ind = {}%field_index({}(index), {})" + 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), 3) - cap.write("if (field_ind > 0) then", 3) - cap.write("{}(index) = field_ind".format(const_indices_name), 4) - cap.write("else", 3) - cap.write("{} = 1".format(herrcode), 4) + 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), 4) - cap.write("end if", 3) - cap.write("if ({} /= 0) then".format(herrcode), 3) - cap.write("exit", 4) - cap.write("end if", 3) - cap.write("end do", 2) - cap.write("end {}".format(substmt), 1) - # Next, write num_consts routine - substmt = "function {}".format(num_const_funcname) + 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) + cap.write("end if", 2) + cap.write(f"end {substmt}", 1) + # Write constituent_init routine + substmt = f"subroutine {init_funcname}" + cap.write("", 0) + cap.write(f"{substmt}(ncols, num_layers, {err_dummy_str})", 1) + cap.comment("Initialize constituent data", 2) + cap.write("", 0) + cap.comment("Dummy arguments", 2) + cap.write("integer, intent(in) :: ncols", 2) + cap.write("integer, intent(in) :: num_layers", 2) + for evar in err_vars: + evar.write_def(cap, 2, host, dummy=True, add_intent="out") + # end for evar + cap.write("", 0) + call_str = f"call {const_obj_name}%lock_data(ncols, num_layers, {obj_err_callstr})" + cap.write(call_str, 2) + cap.write(f"end {substmt}", 1) + # Write num_consts routine + substmt = f"subroutine {num_const_funcname}" cap.write("", 0) - cap.write("integer {}({})".format(substmt, err_dummy_str), 1) - cap.write("! Return the number of constituent fields for this run", 2) + cap.write(f"{substmt}(num_flds, advected, {err_dummy_str})", 1) + cap.comment("Return the number of constituent fields for this run", 2) cap.write("", 0) - cap.write("! Dummy arguments", 2) + cap.comment("Dummy arguments", 2) + cap.write("integer, intent(out) :: num_flds", 2) + cap.write("logical, optional, intent(in) :: advected", 2) for evar in err_vars: evar.write_def(cap, 2, host, dummy=True, add_intent="out") # end for cap.write("", 0) - cap.write("{} = {}%num_constituents({})".format(num_const_funcname, - const_obj_name, - obj_err_callstr), 2) + 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) - # Next, write copy_in routine + # Write copy_in routine substmt = "subroutine {}".format(copy_in_funcname) cap.write("", 0) cap.write("{}(const_array, {})".format(substmt, err_dummy_str), 1) - cap.write("! Copy constituent field info into ", 2) + cap.comment("Copy constituent field info into ", 2) cap.write("", 0) - cap.write("! Dummy arguments", 2) + cap.comment("Dummy arguments", 2) cap.write("real(kind_phys), intent(out) :: const_array(:,:,:)", 2) for evar in err_vars: evar.write_def(cap, 2, host, dummy=True, add_intent="out") @@ -599,20 +649,77 @@ def write_host_routines(cap, host, reg_funcname, num_const_funcname, cap.write("call {}%copy_in(const_array, {})".format(const_obj_name, obj_err_callstr), 2) cap.write("end {}".format(substmt), 1) - # Next, write copy_out routine + # Write copy_out routine substmt = "subroutine {}".format(copy_out_funcname) cap.write("", 0) cap.write("{}(const_array, {})".format(substmt, err_dummy_str), 1) - cap.write("! Update constituent field info from ", 2) + cap.comment("Update constituent field info from ", 2) cap.write("", 0) - cap.write("! Dummy arguments", 2) + cap.comment("Dummy arguments", 2) cap.write("real(kind_phys), intent(in) :: const_array(:,:,:)", 2) for evar in err_vars: evar.write_def(cap, 2, host, dummy=True, add_intent="out") # end for cap.write("", 0) cap.write("call {}%copy_out(const_array, {})".format(const_obj_name, - obj_err_callstr), 2) + obj_err_callstr), + 2) + cap.write("end {}".format(substmt), 1) + # Write constituents routine + cap.write("", 0) + cap.write(f"function {const_array_func}() result(const_ptr)", 1) + cap.write("", 0) + cap.comment("Return pointer to constituent array", 2) + cap.write("", 0) + cap.comment("Dummy argument", 2) + cap.write("real(kind_phys), pointer :: const_ptr(:,:,:)", 2) + cap.write("", 0) + cap.write(f"const_ptr => {const_obj_name}%field_data_ptr()", 2) + cap.write(f"end function {const_array_func}", 1) + # Write advected constituents routine + cap.write("", 0) + cap.write(f"function {advect_array_func}() result(const_ptr)", 1) + cap.write("", 0) + cap.comment("Return pointer to advected constituent array", 2) + cap.write("", 0) + cap.comment("Dummy argument", 2) + cap.write("real(kind_phys), pointer :: const_ptr(:,:,:)", 2) + cap.write("", 0) + cap.write(f"const_ptr => {const_obj_name}%advected_constituents_ptr()", + 2) + cap.write(f"end function {advect_array_func}", 1) + # Write the constituent property array routine + cap.write("", 0) + cap.write(f"function {prop_array_func}() result(const_ptr)", 1) + cap.write(f"use {CONST_DDT_MOD}, only: {CONST_PROP_PTR_TYPE}", 2) + cap.write("", 0) + cap.comment("Return pointer to array of constituent properties", 2) + cap.write("", 0) + cap.comment("Dummy argument", 2) + cap.write("type(ccpp_constituent_prop_ptr_t), pointer :: const_ptr(:)", + 2) + cap.write("", 0) + cap.write(f"const_ptr => {const_obj_name}%constituent_props_ptr()", + 2) + cap.write(f"end function {prop_array_func}", 1) + # Write constituent index function + substmt = f"subroutine {const_index_func}" + cap.write("", 0) + cap.write(f"{substmt}(stdname, const_index, {err_dummy_str})", 1) + cap.comment("Set to the constituent array index " + \ + "for .", 2) + cap.comment("If is not found, set to -1 " + \ + "set an error condition", 2) + cap.write("", 0) + cap.comment("Dummy arguments", 2) + cap.write("character(len=*), intent(in) :: stdname", 2) + cap.write("integer, intent(out) :: const_index", 2) + for evar in err_vars: + evar.write_def(cap, 2, host, dummy=True, add_intent="out") + # end for + cap.write("", 0) + cap.write(f"call {const_obj_name}%const_index(const_index, " + \ + f"stdname, {obj_err_callstr})", 2) cap.write("end {}".format(substmt), 1) @staticmethod @@ -636,20 +743,13 @@ def constituent_prop_init_consts(): properties array for this suite""" return ConstituentVarDict.__const_prop_init_consts - @staticmethod - def constituent_prop_type_name(): - """Return the name of the derived type which holds constituent - properties.""" - return ConstituentVarDict.__const_prop_type_name - @staticmethod def write_suite_use(outfile, indent): """Write use statements for any modules needed by the suite cap. The statements are written to at indent, . """ - omsg = "use ccpp_constituent_prop_mod, only: {}" - cpt_name = ConstituentVarDict.constituent_prop_type_name() - outfile.write(omsg.format(cpt_name), indent) + omsg = f"use ccpp_constituent_prop_mod, only: {CONST_PROP_TYPE}" + outfile.write(omsg, indent) @staticmethod def TF_string(tf_val): diff --git a/scripts/host_cap.py b/scripts/host_cap.py index 9c88cf34..d833c649 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -8,9 +8,10 @@ import logging import os # CCPP framework imports -from ccpp_suite import API +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 ddt_library import DDTLibrary from file_utils import KINDS_MODULE from framework_env import CCPPFrameworkEnv @@ -32,9 +33,7 @@ end subroutine {host_model}_ccpp_physics_{stage} ''' -_API_SRC_NAME = "CCPP_API" - -_API_SOURCE = ParseSource(_API_SRC_NAME, "MODULE", +_API_SOURCE = ParseSource(API_SOURCE_NAME, "MODULE", ParseContext(filename="host_cap.F90")) _API_DUMMY_RUN_ENV = CCPPFrameworkEnv(None, ndict={'host_files':'', @@ -60,7 +59,7 @@ 'suites':''}) # Used to prevent loop substitution lookups -_BLANK_DICT = VarDictionary(_API_SRC_NAME, _MVAR_DUMMY_RUN_ENV) +_BLANK_DICT = VarDictionary(API_SOURCE_NAME, _MVAR_DUMMY_RUN_ENV) ############################################################################### def suite_part_list(suite, stage): @@ -79,14 +78,29 @@ def suite_part_list(suite, stage): # End if return spart_list +############################################################################### +def constituent_num_suite_subname(host_model): +############################################################################### + """Return the name of the number of suite constituents for this run + Because this is a user interface API function, the name is fixed.""" + return "{}_ccpp_num_suite_constituents".format(host_model.name) + ############################################################################### def constituent_register_subname(host_model): ############################################################################### - """Return the name of the subroutine used to register (initialize) the - constituents for this run. + """Return the name of the subroutine used to register the constituent + properties for this run. Because this is a user interface API function, the name is fixed.""" return "{}_ccpp_register_constituents".format(host_model.name) +############################################################################### +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 "{}_ccpp_initialize_constituents".format(host_model.name) + ############################################################################### def constituent_num_consts_funcname(host_model): ############################################################################### @@ -129,10 +143,13 @@ def unique_local_name(loc_name, host_model): ############################################################################### def constituent_model_object_name(host_model): ############################################################################### - """Return the variable name of the object which holds the constiteunt - medata and field information.""" - hstr = "{}_constituents_obj".format(host_model.name) - return unique_local_name(hstr, host_model) + """Return the variable name of the object which holds the constituent + metadata and field information.""" + hvar = host_model.find_variable(CONST_OBJ_STDNAME) + if not hvar: + raise CCPPError(f"Host model does not contain Var, {CONST_OBJ_STDNAME}") + # end if + return hvar.get_prop_value('local_name') ############################################################################### def constituent_model_const_stdnames(host_model): @@ -148,6 +165,37 @@ def constituent_model_const_indices(host_model): hstr = "{}_model_const_indices".format(host_model.name) 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 = "{}_constituents_array".format(host_model.name) + 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 = "{}_advected_constituents_array".format(host_model.name) + 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 = "{}_model_const_properties".format(host_model.name) + 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 = "{}_const_get_index".format(host_model.name) + return unique_local_name(hstr, host_model) + ############################################################################### def add_constituent_vars(cap, host_model, suite_list, run_env): ############################################################################### @@ -161,47 +209,24 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): to create the dictionary. """ # First create a MetadataTable for the constituents DDT - stdname_layer = "ccpp_constituents_num_layer_consts" - stdname_interface = "ccpp_constituents_num_interface_consts" - stdname_2d = "ccpp_constituents_num_2d_consts" + stdname_layer = "ccpp_num_constituents" horiz_dim = "horizontal_dimension" vert_layer_dim = "vertical_layer_dimension" vert_interface_dim = "vertical_interface_dimension" array_layer = "vars_layer" - array_interface = "vars_interface" - array_2d = "vars_2d" # Table preamble (leave off ccpp-table-properties header) ddt_mdata = [ #"[ccpp-table-properties]", - " name = {}".format(CONST_DDT_NAME), " type = ddt", + f" name = {CONST_DDT_NAME}", " type = ddt", "[ccpp-arg-table]", - " name = {}".format(CONST_DDT_NAME), " type = ddt", + f" name = {CONST_DDT_NAME}", " type = ddt", "[ num_layer_vars ]", - " standard_name = {}".format(stdname_layer), - " units = count", " dimensions = ()", " type = integer", - "[ num_interface_vars ]", - " standard_name = {}".format(stdname_interface), - " units = count", " dimensions = ()", " type = integer", - "[ num_2d_vars ]", - " standard_name = {}".format(stdname_2d), + f" standard_name = {stdname_layer}", " units = count", " dimensions = ()", " type = integer", - "[ {} ]".format(array_layer), - " standard_name = ccpp_constituents_array_of_layer_consts", + f"[ {array_layer} ]", + " standard_name = ccpp_constituent_array", " units = none", - " dimensions = ({}, {}, {})".format(horiz_dim, vert_layer_dim, - stdname_layer), - " type = real", " kind = kind_phys", - "[ {} ]".format(array_interface), - " standard_name = ccpp_constituents_array_of_interface_consts", - " units = none", - " dimensions = ({}, {}, {})".format(horiz_dim, - vert_interface_dim, - stdname_interface), - " type = real", " kind = kind_phys", - "[ {} ]".format(array_2d), - " standard_name = ccpp_constituents_array_of_2d_consts", - " units = none", - " dimensions = ({}, {})".format(horiz_dim, stdname_2d), + f" dimensions = ({horiz_dim}, {vert_layer_dim}, {stdname_layer})", " type = real", " kind = kind_phys"] # Add entries for each constituent (once per standard name) const_stdnames = set() @@ -235,8 +260,6 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): vdim = dims[1].split(':')[-1] if vdim == vert_layer_dim: cvar_array_name = array_layer - elif vdim == vert_interface_dim: - cvar_array_name = array_interface else: emsg = "Unsupported vertical constituent dimension, " emsg += "'{}', must be '{}' or '{}'" @@ -244,44 +267,47 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): vert_interface_dim)) # end if else: - cvar_array_name = array_2d + emsg = f"Unsupported 2-D variable, '{std_name}'" + raise CCPPError(emsg) # end if # First, create an index variable for ind_std_name = "index_of_{}".format(std_name) - loc_name = "{}(:,:,{})".format(cvar_array_name, ind_std_name) - ddt_mdata.append("[ {} ]".format(loc_name)) - ddt_mdata.append(" standard_name = {}".format(std_name)) + loc_name = f"{cvar_array_name}(:,:,{ind_std_name})" + ddt_mdata.append(f"[ {loc_name} ]") + ddt_mdata.append(f" standard_name = {std_name}") units = cvar.get_prop_value('units') - ddt_mdata.append(" units = {}".format(units)) - dimstr = "({})".format(", ".join(dims)) - ddt_mdata.append(" dimensions = {}".format(dimstr)) + ddt_mdata.append(f" units = {units}") + dimstr = f"({', '.join(dims)})" + ddt_mdata.append(f" dimensions = {dimstr}") vtype = cvar.get_prop_value('type') vkind = cvar.get_prop_value('kind') - ddt_mdata.append(" type = {} | kind = {}".format(vtype, vkind)) + ddt_mdata.append(f" type = {vtype} | kind = {vkind}") const_stdnames.add(std_name) # end if # end for # end for # Parse this table using a fake filename - parse_obj = ParseObject("{}_constituent_mod.meta".format(host_model.name), + parse_obj = ParseObject(f"{host_model.name}_constituent_mod.meta", ddt_mdata) ddt_table = MetadataTable(run_env, parse_object=parse_obj) - ddt_name = ddt_table.sections()[0].title - ddt_lib = DDTLibrary('{}_constituent_ddtlib'.format(host_model.name), + ddt_lib = DDTLibrary(f"{host_model.name}_constituent_ddtlib", run_env, ddts=ddt_table.sections()) # A bit of cleanup del parse_obj del ddt_mdata # Now, create the "host constituent module" dictionary - const_dict = VarDictionary("{}_constituents".format(host_model.name), + const_dict = VarDictionary(f"{host_model.name}_constituents", run_env, parent_dict=host_model) - # Add in the constituents object - prop_dict = {'standard_name' : "ccpp_model_constituents_object", - 'local_name' : constituent_model_object_name(host_model), - 'dimensions' : '()', 'units' : "None", 'ddt_type' : ddt_name} - const_var = Var(prop_dict, _API_SOURCE, run_env) - const_var.write_def(cap, 1, const_dict) - ddt_lib.collect_ddt_fields(const_dict, const_var, run_env) + # Add the constituents object to const_dict and write its declaration + const_var = host_model.find_variable(CONST_OBJ_STDNAME) + if const_var: + const_dict.add_variable(const_var, run_env) + const_var.write_def(cap, 1, const_dict) + else: + raise CCPPError(f"Missing Var, {CONST_OBJ_STDNAME}, in host model") + # end if + ddt_lib.collect_ddt_fields(const_dict, const_var, run_env, + skip_duplicates=True) # 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) @@ -377,10 +403,9 @@ def suite_part_call_list(host_model, const_dict, suite_part, subst_loop_vars): return ', '.join(hmvars) ############################################################################### -def write_host_cap(host_model, api, output_dir, run_env): +def write_host_cap(host_model, api, module_name, output_dir, run_env): ############################################################################### """Write an API to allow to call any configured CCPP suite""" - module_name = "{}_ccpp_cap".format(host_model.name) cap_filename = os.path.join(output_dir, '{}.F90'.format(module_name)) if run_env.logger is not None: msg = 'Writing CCPP Host Model Cap for {} to {}' @@ -404,14 +429,13 @@ def write_host_cap(host_model, api, output_dir, run_env): cap.write("use {}, {}only: {}".format(mod[0], mspc, mod[1]), 1) # End for mspc = ' '*(maxmod - len(CONST_DDT_MOD)) - cap.write("use {}, {}only: {}".format(CONST_DDT_MOD, mspc, - CONST_DDT_NAME), 1) + cap.write(f"use {CONST_DDT_MOD}, {mspc}only: {CONST_DDT_NAME}", 1) cap.write_preamble() max_suite_len = 0 for suite in api.suites: max_suite_len = max(max_suite_len, len(suite.module)) # End for - cap.write("! Public Interfaces", 1) + cap.comment("Public Interfaces", 1) # CCPP_STATE_MACH.transitions represents the host CCPP interface for stage in CCPP_STATE_MACH.transitions(): stmt = "public :: {host_model}_ccpp_physics_{stage}" @@ -421,20 +445,30 @@ def write_host_cap(host_model, api, output_dir, run_env): # Write the host-model interfaces for constituents reg_name = constituent_register_subname(host_model) cap.write("public :: {}".format(reg_name), 1) + init_name = constituent_initialize_subname(host_model) + cap.write("public :: {}".format(init_name), 1) numconsts_name = constituent_num_consts_funcname(host_model) cap.write("public :: {}".format(numconsts_name), 1) copyin_name = constituent_copyin_subname(host_model) cap.write("public :: {}".format(copyin_name), 1) copyout_name = constituent_copyout_subname(host_model) cap.write("public :: {}".format(copyout_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) + cap.write(f"public :: {advect_array_func}", 1) + prop_array_func = constituent_model_const_props(host_model) + cap.write(f"public :: {prop_array_func}", 1) + const_index_func = constituent_model_const_index(host_model) + cap.write(f"public :: {const_index_func}", 1) cap.write("", 0) cap.write("! Private module variables", 1) const_dict = add_constituent_vars(cap, host_model, api.suites, run_env) cap.end_module_header() for stage in CCPP_STATE_MACH.transitions(): # Create a dict of local variables for stage - host_local_vars = VarDictionary("{}_{}".format(host_model.name, - stage), run_env) + host_local_vars = VarDictionary(f"{host_model.name}_{stage}", + run_env) # Create part call lists # Look for any loop-variable mismatch for suite in api.suites: @@ -475,7 +509,7 @@ def write_host_cap(host_model, api, output_dir, run_env): subst_dict['intent'] = 'inout' # End if hdvars.append(hvar.clone(subst_dict, - source_name=_API_SRC_NAME)) + source_name=API_SOURCE_NAME)) # End for lnames = [x.get_prop_value('local_name') for x in apivars + hdvars] api_vlist = ", ".join(lnames) @@ -563,11 +597,15 @@ def write_host_cap(host_model, api, 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) - ConstituentVarDict.write_host_routines(cap, host_model, reg_name, + ConstituentVarDict.write_host_routines(cap, host_model, reg_name, init_name, numconsts_name, copyin_name, copyout_name, const_obj_name, const_names_name, const_indices_name, + const_array_func, + advect_array_func, + prop_array_func, + const_index_func, api.suites, err_vars) # End with return cap_filename diff --git a/scripts/host_model.py b/scripts/host_model.py index eae2479d..68a969e7 100644 --- a/scripts/host_model.py +++ b/scripts/host_model.py @@ -5,10 +5,11 @@ """ # CCPP framework imports -from metavar import VarDictionary +from constituents import CONST_DDT_NAME, CONST_PROP_TYPE, CONST_OBJ_STDNAME +from metavar import Var, VarDictionary from ddt_library import VarDDT, DDTLibrary -from parse_tools import ParseContext, CCPPError, ParseInternalError -from parse_tools import context_string +from parse_tools import ParseContext, ParseSource, CCPPError, ParseInternalError +from parse_tools import context_string, registered_fortran_ddt_name from parse_tools import FORTRAN_SCALAR_REF_RE ############################################################################### @@ -17,7 +18,7 @@ class HostModel(VarDictionary): def __init__(self, meta_tables, name_in, run_env): """Initialize this HostModel object. - is a list of parsed host metadata tables. + is a dictionary of parsed host metadata tables. is the name for this host model. is the CCPPFrameworkEnv object for this framework run. """ @@ -35,11 +36,12 @@ def __init__(self, meta_tables, name_in, run_env): # Initialize our dictionaries # Initialize variable dictionary super().__init__(self.name, run_env) + ddt_headers = [d for d in meta_headers if d.header_type == 'ddt'] self.__ddt_lib = DDTLibrary('{}_ddts'.format(self.name), run_env, - ddts=[d for d in meta_headers - if d.header_type == 'ddt']) + ddts=ddt_headers) self.__ddt_dict = VarDictionary("{}_ddt_vars".format(self.name), run_env, parent_dict=self) + del ddt_headers # Now, process the code headers by type self.__metadata_tables = meta_tables for header in [h for h in meta_headers if h.header_type != 'ddt']: @@ -111,6 +113,20 @@ def __init__(self, meta_tables, name_in, run_env): errmsg = 'No name found for host model, add a host metadata entry' raise CCPPError(errmsg) # End if + # Add in the constituents object + if registered_fortran_ddt_name(CONST_PROP_TYPE): + prop_dict = {'standard_name' : CONST_OBJ_STDNAME, + 'local_name' : self.constituent_model_object_name(), + 'dimensions' : '()', 'units' : "None", + 'ddt_type' : CONST_DDT_NAME, 'target' : 'True'} + host_source = ParseSource(self.ccpp_cap_name(), "MODULE", + ParseContext(filename=f"{self.ccpp_cap_name()}.F90")) + const_var = Var(prop_dict, host_source, run_env) + self.add_variable(const_var, run_env) + lname = const_var.get_prop_value('local_name') + self.__var_locations[lname] = self.ccpp_cap_name() + self.ddt_lib.collect_ddt_fields(self.__ddt_dict, const_var, run_env) + # end if # Finally, turn on the use meter so we know which module variables # to 'use' in a host cap. self.__used_variables = set() # Local names which have been requested @@ -131,12 +147,10 @@ def ddt_lib(self): """Return this host model's DDT library""" return self.__ddt_lib -# XXgoldyXX: v needed? @property def constituent_module(self): """Return the name of host model constituent module""" return "{}_ccpp_constituents".format(self.name) -# XXgoldyXX: ^ needed? def argument_list(self, loop_vars=True): """Return a string representing the host model variable arg list""" @@ -157,8 +171,9 @@ def host_variable_module(self, local_name): def variable_locations(self): """Return a set of module-variable and module-type pairs. - These represent the locations of all host model data with a listed - source location (variables with no source are omitted).""" + These represent the locations of all host model data with a listed + source location (variables with no source or for which the + source is the CCPP host cap are omitted).""" varset = set() lnames = self.prop_list('local_name') # Attempt to realize deferred lookups @@ -171,10 +186,11 @@ def variable_locations(self): # End for # End if # Now, find all the used module variables + cap_modname = self.ccpp_cap_name() for name in lnames: module = self.host_variable_module(name) used = self.__used_variables and (name in self.__used_variables) - if module and used: + if module and used and (module != cap_modname): varset.add((module, name)) # No else, either no module or a zero-length module name # End if @@ -299,6 +315,15 @@ def call_list(self, phase): # End for return hdvars + def constituent_model_object_name(self): + """Return the variable name of the object which holds the constituent + metadata and field information.""" + return "{}_constituents_obj".format(self.name) + + def ccpp_cap_name(self): + """Return the name of the CCPP host model cap module name.""" + return f"{self.name}_ccpp_cap" + ############################################################################### if __name__ == "__main__": diff --git a/scripts/metavar.py b/scripts/metavar.py index 71609580..29390a61 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -20,6 +20,7 @@ from parse_tools import check_units, check_dimensions, check_cf_standard_name from parse_tools import check_diagnostic_id, check_diagnostic_fixed from parse_tools import check_default_value, check_valid_values +from parse_tools import check_molar_mass from parse_tools import ParseContext, ParseSource from parse_tools import ParseInternalError, ParseSyntaxError, CCPPError from var_props import CCPP_LOOP_DIM_SUBSTS, VariableProperty, VarCompatObj @@ -196,6 +197,8 @@ class Var: VariableProperty('active', str, optional_in=True, default_in='.true.'), VariableProperty('polymorphic', bool, optional_in=True, + default_in='.false.'), + VariableProperty('target', bool, optional_in=True, default_in='.false.')] # XXgoldyXX: v debug only @@ -212,7 +215,10 @@ class Var: # Note that all constituent properties must be optional and contain either # a default value or default function. __constituent_props = [VariableProperty('advected', bool, - optional_in=True, default_in=False)] + optional_in=True, default_in=False), + VariableProperty('molar_mass', float, + optional_in=True, default_in=0.0, + check_fn_in=check_molar_mass)] __constituent_prop_dict = {x.name : x for x in __constituent_props} @@ -610,7 +616,8 @@ def call_dimstring(self, var_dicts=None, # end if # end for if dvar: - dnames.append(dvar.get_prop_value('local_name')) + # vdict is the dictionary where was found + dnames.append(dvar.call_string(vdict)) # end if if not dvar: emsg += sepstr + "No variable found in " @@ -687,9 +694,15 @@ def call_string(self, var_dict, loop_vars=None): dvar = var_dict.find_variable(standard_name=item, any_scope=False) if dvar is None: - iname = None + try: + dval = int(item) + iname = item + except ValueError: + iname = None + # end try else: - iname = dvar.get_prop_value('local_name') + iname = dvar.call_string(var_dict, + loop_vars=loop_vars) # end if else: iname = '' @@ -749,7 +762,7 @@ def array_ref(self, local_name=None): match = FORTRAN_SCALAR_REF_RE.match(local_name) return match - def intrinsic_elements(self, check_dict=None): + def intrinsic_elements(self, check_dict=None, ddt_lib=None): """Return a list of the standard names of this Var object's 'leaf' intrinsic elements or this Var object's standard name if it is an intrinsic 'leaf' variable. @@ -765,9 +778,23 @@ def intrinsic_elements(self, check_dict=None): Fortran does not support a way to reference those elements. """ if self.is_ddt(): - element_names = None - raise ValueError("shouldn't happen?") - # To Do, find and process named elements of DDT + dtitle = self.get_prop_value('type') + if ddt_lib and (dtitle in ddt_lib): + element_names = [] + ddt_def = ddt_lib[dtitle] + for dvar in ddt_def.variable_list(): + delems = dvar.intrinsic_elements(check_dict=check_dict, + ddt_lib=ddt_lib) + if delems: + element_names.extend(delems) + # end if + # end for + if not element_names: + element_names = None + # end if + else: + element_names = None + # end if # end if children = self.children() if (not children) and check_dict: @@ -844,6 +871,11 @@ def children(self): # end if return iter(children) if children else None + @property + def var(self): + "Return this object (base behavior for derived classes such as VarDDT)" + return self + @property def context(self): """Return this variable's parsed context""" @@ -932,7 +964,7 @@ def has_vertical_dimension(self, dims=None): return find_vertical_dimension(vdims)[0] def write_def(self, outfile, indent, wdict, allocatable=False, - dummy=False, add_intent=None, extra_space=0): + dummy=False, add_intent=None, extra_space=0, public=False): """Write the definition line for the variable to . If is True, include the variable's intent. If is True but the variable has no intent, add the @@ -994,10 +1026,9 @@ def write_def(self, outfile, indent, wdict, allocatable=False, elif intent is not None: alloval = self.get_prop_value('allocatable') if (intent.lower()[-3:] == 'out') and alloval: - intent_str = 'allocatable, intent({})'.format(intent) + intent_str = f"allocatable, intent({intent})" else: - intent_str = 'intent({}){}'.format(intent, - ' '*(5 - len(intent))) + intent_str = f"intent({intent}){' '*(5 - len(intent))}" # end if elif not dummy: intent_str = '' @@ -1009,6 +1040,13 @@ def write_def(self, outfile, indent, wdict, allocatable=False, else: comma = ' ' # end if + if self.get_prop_value('target'): + targ = ", target" + else: + targ = "" + # end if + comma = targ + comma + extra_space -= len(targ) if self.is_ddt(): if polymorphic: dstr = "class({kind}){cspc}{intent} :: {name}{dims} ! {sname}" @@ -1026,6 +1064,7 @@ def write_def(self, outfile, indent, wdict, allocatable=False, cspc = comma + ' '*(extra_space + 19 - len(vtype)) # end if # end if + outfile.write(dstr.format(type=vtype, kind=kind, intent=intent_str, name=name, dims=dimstr, cspc=cspc, sname=stdname), indent) @@ -1427,7 +1466,9 @@ def __init__(self, name, run_env, variables=None, self[stdname] = variables[key] # end for elif variables is not None: - raise ParseInternalError('Illegal type for variables, {} in {}'.format(type(variables), self.name)) + emsg = "Illegal type for variables, {} in {}" + raise ParseInternalError(emsg.format(type(variables), + self.name)) # end if @property diff --git a/scripts/parse_tools/__init__.py b/scripts/parse_tools/__init__.py index 03dd0429..4f1a7b76 100644 --- a/scripts/parse_tools/__init__.py +++ b/scripts/parse_tools/__init__.py @@ -22,7 +22,7 @@ from parse_checkers import registered_fortran_ddt_name from parse_checkers import register_fortran_ddt_name from parse_checkers import check_units, check_dimensions, check_cf_standard_name -from parse_checkers import check_default_value, check_valid_values +from parse_checkers import check_default_value, check_valid_values, check_molar_mass from parse_log import init_log, set_log_level, flush_log from parse_log import set_log_to_stdout, set_log_to_null from parse_log import set_log_to_file @@ -47,6 +47,7 @@ 'check_fortran_type', 'check_local_name', 'check_valid_values', + 'check_molar_mass', 'context_string', 'find_schema_file', 'find_schema_version', diff --git a/scripts/parse_tools/parse_checkers.py b/scripts/parse_tools/parse_checkers.py index 487478e6..29786797 100755 --- a/scripts/parse_tools/parse_checkers.py +++ b/scripts/parse_tools/parse_checkers.py @@ -13,6 +13,7 @@ ######################################################################## _UNITS_RE = re.compile(r"^[^/@#$%^&*()\|<>\[\]{}?,.]+$") +_MAX_MOLAR_MASS = 10000.0 def check_units(test_val, prop_dict, error): """Return if a valid unit, otherwise, None @@ -936,6 +937,58 @@ def check_diagnostic_id(test_val, prop_dict, error): ######################################################################## +def check_molar_mass(test_val, prop_dict, error): + """Return if valid molar mass, otherwise, None + if is True, raise an Exception if is not valid. + >>> check_molar_mass('1', None, True) + 1.0 + >>> check_molar_mass('1.0', None, True) + 1.0 + >>> check_molar_mass('1.0', None, False) + 1.0 + >>> check_molar_mass('-1', None, False) + + >>> check_molar_mass('-1.0', None, False) + + >>> check_molar_mass('string', None, False) + + >>> check_molar_mass(10001, None, False) + + >>> check_molar_mass('-1', None, True) #doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + CCPPError: '-1' is not a valid molar mass + >>> check_molar_mass('-1.0', None, True) #doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + CCPPError: '-1.0' is not a valid molar mass + >>> check_molar_mass('string', None, True) #doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + CCPPError: '-1.0' is not a valid molar mass + >>> check_molar_mass(10001, None, True) #doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + CCPPError: '10001' is not a valid molar mass + """ + # Check if input value is an int or float + try: + test_val = float(test_val) + if test_val < 0.0 or test_val > _MAX_MOLAR_MASS: + if error: + raise CCPPError("{} is not a valid molar mass".format(test_val)) + else: + test_val = None + # end if + # end if + except: + # not an int or float, conditionally throw error + if error: + raise CCPPError("{} is invalid; not a float or int".format(test_val)) + else: + test_val=None + # end if + # end try + return test_val + +######################################################################## + def check_balanced_paren(string, start=0, error=False): """Return indices delineating a balance set of parentheses. Parentheses in character context do not count. diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 472556cb..9bdc154c 100644 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -90,6 +90,22 @@ def add_vars(self, call_list, run_env, gen_unique=False): # end if # end for + def add_variable(self, newvar, run_env, exists_ok=False, gen_unique=False, + adjust_intent=False): + """Add as for VarDictionary but make sure that the variable + has an intent with the default being intent(in). + """ + # We really need an intent on a dummy argument + if newvar.get_prop_value("intent") is None: + subst_dict = {'intent' : 'in'} + oldvar = newvar + newvar = oldvar.clone(subst_dict, source_name=self.name, + source_type=_API_GROUP_VAR_NAME, + context=oldvar.context) + # end if + super().add_variable(newvar, run_env, exists_ok=exists_ok, + gen_unique=gen_unique, adjust_intent=adjust_intent) + def call_string(self, cldicts=None, is_func_call=False, subname=None): """Return a dummy argument string for this call list. may be a list of VarDictionary objects to search for diff --git a/scripts/var_props.py b/scripts/var_props.py index 69b6b766..e1287463 100755 --- a/scripts/var_props.py +++ b/scripts/var_props.py @@ -14,6 +14,7 @@ from conversion_tools import unit_conversion from framework_env import CCPPFrameworkEnv from parse_tools import check_local_name, check_fortran_type, context_string +from parse_tools import check_molar_mass from parse_tools import FORTRAN_DP_RE, FORTRAN_SCALAR_REF_RE, fortran_list_match from parse_tools import check_units, check_dimensions, check_cf_standard_name from parse_tools import check_diagnostic_id, check_diagnostic_fixed @@ -586,6 +587,8 @@ class VariableProperty: 'foo(bar)' >>> VariableProperty('local_name', str, check_fn_in=check_local_name).valid_value('q(:,:,index_of_water_vapor_specific_humidity)') 'q(:,:,index_of_water_vapor_specific_humidity)' + >>> VariableProperty('molar_mass', float, check_fn_in=check_molar_mass).valid_value('12.1') + 12.1 """ __true_vals = ['t', 'true', '.true.'] @@ -597,7 +600,7 @@ def __init__(self, name_in, type_in, valid_values_in=None, """Conduct sanity checks and initialize this variable property.""" self._name = name_in self._type = type_in - if self._type not in [bool, int, list, str]: + if self._type not in [bool, int, list, str, float]: emsg = "{} has invalid VariableProperty type, '{}'" raise CCPPError(emsg.format(name_in, type_in)) # end if @@ -688,6 +691,21 @@ def valid_value(self, test_value, prop_dict=None, error=False): valid_val = tval except CCPPError: valid_val = None # Redundant but more expressive than pass + elif self.ptype is float: + try: + tval = float(test_value) + if self._valid_values is not None: + if tval in self._valid_values: + valid_val = tval + else: + valid_val = None # i.e. pass + # end if + else: + valid_val = tval + # end if + except CCPPError: + valid_val = None + # end try elif self.ptype is list: if isinstance(test_value, str): tval = fortran_list_match(test_value) diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index 419af297..df80ec0d 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -10,99 +10,178 @@ module ccpp_constituent_prop_mod implicit none private - integer, parameter :: int_unassigned = -1 + !!XXgoldyXX: Implement "last_error" method so that functions do not + !! need to have output variables. + + ! Private module data + integer, parameter :: stdname_len = 256 + integer, parameter :: dimname_len = 32 + integer, parameter :: errmsg_len = 256 + integer, parameter :: dry_mixing_ratio = -2 + integer, parameter :: moist_mixing_ratio = -3 + integer, parameter :: wet_mixing_ratio = -4 + integer, parameter :: mass_mixing_ratio = -5 + integer, parameter :: volume_mixing_ratio = -6 + integer, parameter :: number_concentration = -7 + integer, parameter :: int_unassigned = -HUGE(1) real(kind_phys), parameter :: kphys_unassigned = HUGE(1.0_kind_phys) - !!XXgoldyXX: NB: We end up with two copies of each metadata object, FIX!! - type, public, extends(ccpp_hashable_char_t) :: ccpp_constituent_properties_t ! A ccpp_constituent_properties_t object holds relevant metadata ! for a constituent species and provides interfaces to access that data. character(len=:), private, allocatable :: var_std_name character(len=:), private, allocatable :: var_long_name + character(len=:), private, allocatable :: var_units character(len=:), private, allocatable :: vert_dim integer, private :: const_ind = int_unassigned - integer, private :: field_ind = int_unassigned logical, private :: advected = .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 + integer, private :: const_type = int_unassigned + ! const_water distinguishes dry, moist, and "wet" mixing ratios + 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 + ! default_value is the default value that the constituent array will be + ! initialized to + real(kind_phys), private :: const_default_value = kphys_unassigned contains ! Required hashable method procedure :: key => ccp_properties_get_key ! Informational methods - procedure :: is_initialized => ccp_is_initialized - procedure :: standard_name => ccp_get_standard_name - procedure :: long_name => ccp_get_long_name - procedure :: is_layer_var => ccp_is_layer_var - procedure :: is_interface_var => ccp_is_interface_var - procedure :: is_2d_var => ccp_is_2d_var - procedure :: vertical_dimension => ccp_get_vertical_dimension - procedure :: const_index => ccp_const_index - procedure :: field_index => ccp_field_index - procedure :: is_advected => ccp_is_advected - procedure :: equivalent => ccp_is_equivalent + procedure :: is_instantiated => ccp_is_instantiated + procedure :: standard_name => ccp_get_standard_name + procedure :: long_name => ccp_get_long_name + procedure :: is_layer_var => ccp_is_layer_var + procedure :: is_interface_var => ccp_is_interface_var + procedure :: is_2d_var => ccp_is_2d_var + procedure :: vertical_dimension => ccp_get_vertical_dimension + procedure :: const_index => ccp_const_index + procedure :: is_advected => ccp_is_advected + 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 + procedure :: is_number_concentration => ccp_is_number_concentration + procedure :: is_dry => ccp_is_dry + procedure :: is_moist => ccp_is_moist + procedure :: is_wet => ccp_is_wet + procedure :: minimum => ccp_min_val + procedure :: molec_weight => ccp_molec_weight + procedure :: default_value => ccp_default_value + procedure :: has_default => ccp_has_default ! Copy method (be sure to update this anytime fields are added) procedure :: copyConstituent generic :: assignment(=) => copyConstituent - ! Methods that change state - procedure :: initialize => ccp_initialize + ! Methods that change state (XXgoldyXX: make private?) + procedure :: instantiate => ccp_instantiate procedure :: deallocate => ccp_deallocate procedure :: set_const_index => ccp_set_const_index - procedure :: set_field_index => ccp_set_field_index end type ccpp_constituent_properties_t +!! \section arg_table_ccpp_constituent_prop_ptr_t +!! \htmlinclude ccpp_constituent_prop_ptr_t.html +!! + type, public :: ccpp_constituent_prop_ptr_t + type(ccpp_constituent_properties_t), private, pointer :: prop => NULL() + contains + ! Informational methods + procedure :: standard_name => ccpt_get_standard_name + procedure :: long_name => ccpt_get_long_name + procedure :: is_layer_var => ccpt_is_layer_var + procedure :: is_interface_var => ccpt_is_interface_var + procedure :: is_2d_var => ccpt_is_2d_var + procedure :: vertical_dimension => ccpt_get_vertical_dimension + procedure :: const_index => ccpt_const_index + procedure :: is_advected => ccpt_is_advected + 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 + procedure :: is_dry => ccpt_is_dry + procedure :: is_moist => ccpt_is_moist + procedure :: is_wet => ccpt_is_wet + procedure :: minimum => ccpt_min_val + procedure :: molec_weight => ccpt_molec_weight + procedure :: default_value => ccpt_default_value + procedure :: has_default => ccpt_has_default + ! ccpt_set: Set the internal pointer + procedure :: set => ccpt_set + ! Methods that change state (XXgoldyXX: make private?) + procedure :: deallocate => ccpt_deallocate + procedure :: set_const_index => ccpt_set_const_index + end type ccpp_constituent_prop_ptr_t + +!! \section arg_table_ccpp_model_constituents_t +!! \htmlinclude ccpp_model_constituents_t.html +!! type, public :: ccpp_model_constituents_t ! A ccpp_model_constituents_t object holds all the metadata and field ! data for a model run's constituents along with data and methods ! to initialize and access the data. - integer, private :: num_layer_vars = 0 - integer, private :: num_interface_vars = 0 - integer, private :: num_2d_vars = 0 + !!XXgoldyXX: To do: allow accessor functions as CCPP local variable + !! names so that members can be private. + integer :: num_layer_vars = 0 + integer :: num_advected_vars = 0 integer, private :: num_layers = 0 - integer, private :: num_interfaces = 0 type(ccpp_hash_table_t), private :: hash_table logical, private :: table_locked = .false. + logical, private :: data_locked = .false. ! These fields are public to allow for efficient (i.e., no copying) ! usage even though it breaks object independence real(kind_phys), allocatable :: vars_layer(:,:,:) - real(kind_phys), allocatable :: vars_interface(:,:,:) - real(kind_phys), allocatable :: vars_2d(:,:) + real(kind_phys), allocatable :: vars_minvalue(:) ! An array containing all the constituent metadata - ! XXgoldyXX: Is this needed? Source of duplicate metadata? - type(ccpp_constituent_properties_t), allocatable :: const_metadata(:) + ! Each element contains a pointer to a constituent from the hash table + type(ccpp_constituent_prop_ptr_t), allocatable :: const_metadata(:) contains ! Return .true. if a constituent matches pattern procedure, private :: is_match => ccp_model_const_is_match ! Return a constituent from the hash table procedure, private :: find_const => ccp_model_const_find_const - ! Is the table locked (i.e., ready to be used)? + ! Are both the properties table and data array locked (i.e., ready to be used)? procedure :: locked => ccp_model_const_locked + ! Is the properties table locked (i.e., ready to be used)? + procedure :: const_props_locked => ccp_model_const_props_locked + ! Is the data array locked (i.e., ready to be used)? + procedure :: const_data_locked => ccp_model_const_data_locked ! Is it okay to add new metadata fields? procedure :: okay_to_add => ccp_model_const_okay_to_add ! Add a constituent's metadata to the master hash table procedure :: new_field => ccp_model_const_add_metadata ! Initialize hash table procedure :: initialize_table => ccp_model_const_initialize - ! Freeze hash table and initialize constituent field arrays - procedure :: lock_table => ccp_model_const_lock + ! Freeze hash table and set constituents properties + procedure :: lock_table => ccp_model_const_table_lock + ! Freeze and initialize constituent field arrays + procedure :: lock_data => ccp_model_const_data_lock ! Empty (reset) the entire object procedure :: reset => ccp_model_const_reset ! Query number of constituents matching pattern procedure :: num_constituents => ccp_model_const_num_match + ! Return index of constituent matching standard name + procedure :: const_index => ccp_model_const_index + ! Return metadata matching standard name + procedure :: field_metadata => ccp_model_const_metadata ! Gather constituent fields matching pattern - !!XXgoldyXX: Might need a 2D version of this procedure :: copy_in => ccp_model_const_copy_in_3d ! Update constituent fields matching pattern - !!XXgoldyXX: Might need a 2D version of this procedure :: copy_out => ccp_model_const_copy_out_3d - ! Return index of constituent matching standard name - procedure :: const_index => ccp_model_const_index - ! Return index of field matching standard name - procedure :: field_index => ccp_model_const_field_index - ! Return metadata matching standard name - procedure :: field_metada => ccp_model_const_metadata + ! Return pointer to constituent array (for use by host model) + procedure :: field_data_ptr => ccp_field_data_ptr + ! Return pointer to advected constituent array (for use by host model) + procedure :: advected_constituents_ptr => ccp_advected_data_ptr + ! Return pointer to constituent properties array (for use by host model) + procedure :: constituent_props_ptr => ccp_constituent_props_ptr end type ccpp_model_constituents_t - private int_unassigned + ! Private interfaces + private to_str + private initialize_errvars + private set_errvars private handle_allocate_error + private check_var_bounds CONTAINS @@ -121,12 +200,91 @@ subroutine copyConstituent(outConst, inConst) outConst%var_long_name = inConst%var_long_name outConst%vert_dim = inConst%vert_dim outConst%const_ind = inConst%const_ind - outConst%field_ind = inConst%field_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 end subroutine copyConstituent !####################################################################### + character(len=10) function to_str(val) + ! return default integer as a left justified string + + ! Dummy argument + integer, intent(in) :: val + + write(to_str,'(i0)') val + + end function to_str + + !####################################################################### + + subroutine initialize_errvars(errcode, errmsg) + ! Initialize error variables, if present + + ! Dummy arguments + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + + if (present(errcode)) then + errcode = 0 + end if + if (present(errmsg)) then + errmsg = '' + end if + end subroutine initialize_errvars + + !####################################################################### + + subroutine set_errvars(errcode_val, errmsg_val, errcode, errmsg, & + errmsg2, errmsg3, errmsg4, errmsg5) + ! Set error variables, if present + + ! Dummy arguments + integer, optional, intent(in) :: errcode_val + character(len=*), optional, intent(in) :: errmsg_val + integer, optional, intent(inout) :: errcode + character(len=*), optional, intent(inout) :: errmsg + character(len=*), optional, intent(in) :: errmsg2 + character(len=*), optional, intent(in) :: errmsg3 + character(len=*), optional, intent(in) :: errmsg4 + character(len=*), optional, intent(in) :: errmsg5 + ! Local variable + integer :: emsg_len + + if (present(errcode)) then + errcode = errcode + errcode_val + end if + if (present(errmsg)) then + emsg_len = len_trim(errmsg) + if (emsg_len > 0) then + errmsg(emsg_len+1:) = '; ' + end if + emsg_len = len_trim(errmsg) + errmsg(emsg_len+1:) = trim(errmsg_val) + if (present(errmsg2)) then + emsg_len = len_trim(errmsg) + errmsg(emsg_len+1:) = trim(errmsg2) + end if + if (present(errmsg3)) then + emsg_len = len_trim(errmsg) + errmsg(emsg_len+1:) = trim(errmsg3) + end if + if (present(errmsg4)) then + emsg_len = len_trim(errmsg) + errmsg(emsg_len+1:) = trim(errmsg4) + end if + if (present(errmsg5)) then + emsg_len = len_trim(errmsg) + errmsg(emsg_len+1:) = trim(errmsg5) + end if + end if + end subroutine set_errvars + + !####################################################################### + subroutine handle_allocate_error(astat, fieldname, errcode, errmsg) ! Generate an error message if indicates an allocation failure @@ -136,28 +294,37 @@ subroutine handle_allocate_error(astat, fieldname, errcode, errmsg) integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg + call initialize_errvars(errcode, errmsg) if (astat /= 0) then - if (present(errcode)) then - errcode = astat - end if - if (present(errmsg)) then - write(errmsg, '(4a,i0)') 'Error allocating ', & - 'ccpp_constituent_properties_t object component, ', & - trim(fieldname), ', error code = ', astat - end if - else - if (present(errcode)) then - errcode = 0 - end if - if (present(errmsg)) then - errmsg = '' - end if + call set_errvars(astat, "Error allocating ", errcode=errcode, & + errmsg=errmsg, errmsg2="ccpp_constituent_properties_t", & + errmsg3="object component, "//trim(fieldname), & + errmsg4=", error code = ", errmsg5=to_str(astat)) end if end subroutine handle_allocate_error !####################################################################### + subroutine check_var_bounds(var, var_bound, varname, errcode, errmsg) + ! Generate an error message if indicates an allocation failure + + ! Dummy arguments + integer, intent(in) :: var + integer, intent(in) :: var_bound + character(len=*), intent(in) :: varname + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + + call initialize_errvars(errcode, errmsg) + if (var > var_bound) then + call set_errvars(1, trim(varname)//" exceeds its upper bound, ", & + errcode=errcode, errmsg=errmsg, errmsg2=to_str(var_bound)) + end if + end subroutine check_var_bounds + + !####################################################################### + function ccp_properties_get_key(hashable) ! Return the constituent properties class key (var_std_name) @@ -171,11 +338,11 @@ end function ccp_properties_get_key !####################################################################### - logical function ccp_is_initialized(this, errcode, errmsg) - ! Return .true. iff is initialized - ! If is *not* initialized and and/or is present, + logical function ccp_is_instantiated(this, errcode, errmsg) + ! Return .true. iff is instantiated + ! If is *not* instantiated and and/or is present, ! fill these fields with an error status - ! If *is* initialized and and/or is present, + ! If *is* instantiated and and/or is present, ! clear these fields. ! Dummy arguments @@ -183,44 +350,33 @@ logical function ccp_is_initialized(this, errcode, errmsg) integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg - ccp_is_initialized = allocated(this%var_std_name) - if (ccp_is_initialized) then - if (present(errcode)) then - errcode = 0 - end if - if (present(errmsg)) then - errmsg = '' - end if - else - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - write(errmsg, *) 'ccpp_constituent_properties_t object ', & - 'is not initialized' - end if + ccp_is_instantiated = allocated(this%var_std_name) + call initialize_errvars(errcode, errmsg) + if (.not. ccp_is_instantiated) then + call set_errvars(1, "ccpp_constituent_properties_t object ", & + errcode=errcode, errmsg=errmsg, errmsg2="is not initialized") end if - end function ccp_is_initialized + end function ccp_is_instantiated !####################################################################### - subroutine ccp_initialize(this, std_name, long_name, vertical_dim, & - advected, errcode, errmsg) + subroutine ccp_instantiate(this, std_name, long_name, units, vertical_dim, & + advected, default_value, errcode, errmsg) ! Initialize all fields in ! Dummy arguments class(ccpp_constituent_properties_t), intent(inout) :: this character(len=*), intent(in) :: std_name character(len=*), intent(in) :: long_name + character(len=*), intent(in) :: units character(len=*), intent(in) :: vertical_dim logical, optional, intent(in) :: advected + real(kind_phys), optional, intent(in) :: default_value integer, intent(out) :: errcode character(len=*), intent(out) :: errmsg - ! Local variable - integer :: astat - if (this%is_initialized()) then + if (this%is_instantiated()) then errcode = 1 write(errmsg, *) 'ccpp_constituent_properties_t object, ', & trim(std_name), ', is already initialized as ', this%var_std_name @@ -231,17 +387,43 @@ subroutine ccp_initialize(this, std_name, long_name, vertical_dim, & end if if (errcode == 0) then this%var_long_name = trim(long_name) + this%var_units = trim(units) this%vert_dim = trim(vertical_dim) if (present(advected)) then this%advected = advected else this%advected = .false. end if + if (present(default_value)) then + this%const_default_value = default_value + 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 + this%const_type = number_concentration + else + this%const_type = mass_mixing_ratio + end if + ! Determine if this is a (moist) mixing ratio or volume mixing ratio + 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 + else if (this%var_std_name == "specific_humidity") then + this%const_water = moist_mixing_ratio + else if (this%var_std_name == "wrt_total_mass") then + this%const_water = wet_mixing_ratio + else + this%const_water = dry_mixing_ratio + end if end if if (errcode /= 0) then call this%deallocate() end if - end subroutine ccp_initialize + end subroutine ccp_instantiate !####################################################################### @@ -260,8 +442,11 @@ subroutine ccp_deallocate(this) if (allocated(this%vert_dim)) then deallocate(this%vert_dim) end if - this%field_ind = int_unassigned + this%const_ind = int_unassigned this%advected = .false. + this%const_type = int_unassigned + this%const_water = int_unassigned + this%const_default_value = kphys_unassigned end subroutine ccp_deallocate @@ -276,9 +461,12 @@ subroutine ccp_get_standard_name(this, std_name, errcode, errmsg) integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg - if (this%is_initialized(errcode, errmsg)) then + if (this%is_instantiated(errcode, errmsg)) then std_name = this%var_std_name + else + std_name = '' end if + end subroutine ccp_get_standard_name !####################################################################### @@ -292,9 +480,12 @@ subroutine ccp_get_long_name(this, long_name, errcode, errmsg) integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg - if (this%is_initialized(errcode, errmsg)) then + if (this%is_instantiated(errcode, errmsg)) then long_name = this%var_long_name + else + long_name = '' end if + end subroutine ccp_get_long_name !####################################################################### @@ -308,9 +499,12 @@ subroutine ccp_get_vertical_dimension(this, vert_dim, errcode, errmsg) integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg - if (this%is_initialized(errcode, errmsg)) then + if (this%is_instantiated(errcode, errmsg)) then vert_dim = this%vert_dim + else + vert_dim = '' end if + end subroutine ccp_get_vertical_dimension !####################################################################### @@ -321,7 +515,7 @@ logical function ccp_is_layer_var(this) result(is_layer) ! Dummy arguments class(ccpp_constituent_properties_t), intent(in) :: this ! Local variable - character(len=32) :: dimname + character(len=dimname_len) :: dimname call this%vertical_dimension(dimname) is_layer = trim(dimname) == 'vertical_layer_dimension' @@ -336,7 +530,7 @@ logical function ccp_is_interface_var(this) result(is_interface) ! Dummy arguments class(ccpp_constituent_properties_t), intent(in) :: this ! Local variable - character(len=32) :: dimname + character(len=dimname_len) :: dimname call this%vertical_dimension(dimname) is_interface = trim(dimname) == 'vertical_interface_dimension' @@ -351,7 +545,7 @@ logical function ccp_is_2d_var(this) result(is_2d) ! Dummy arguments class(ccpp_constituent_properties_t), intent(in) :: this ! Local variable - character(len=32) :: dimname + character(len=dimname_len) :: dimname call this%vertical_dimension(dimname) is_2d = len_trim(dimname) == 0 @@ -361,33 +555,20 @@ end function ccp_is_2d_var !####################################################################### integer function ccp_const_index(this, errcode, errmsg) - ! Return this constituent's master index (or -1 of not assigned) + ! Return this constituent's array index (or -1 of not assigned) ! Dummy arguments class(ccpp_constituent_properties_t), intent(in) :: this integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg - if (this%is_initialized(errcode, errmsg)) then + if (this%is_instantiated(errcode, errmsg)) then ccp_const_index = this%const_ind - end if - end function ccp_const_index - - !####################################################################### - - integer function ccp_field_index(this, errcode, errmsg) - ! Return this constituent's field index (or -1 of not assigned) - - ! Dummy arguments - class(ccpp_constituent_properties_t), intent(in) :: this - integer, optional, intent(out) :: errcode - character(len=*), optional, intent(out) :: errmsg - - if (this%is_initialized(errcode, errmsg)) then - ccp_field_index = this%field_ind + else + ccp_const_index = int_unassigned end if - end function ccp_field_index + end function ccp_const_index !####################################################################### @@ -401,17 +582,13 @@ subroutine ccp_set_const_index(this, index, errcode, errmsg) integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg - if (this%is_initialized(errcode, errmsg)) then - if (this%const_ind /= int_unassigned) then + if (this%is_instantiated(errcode, errmsg)) then + if (this%const_ind == int_unassigned) then this%const_ind = index else - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - write(errmsg, *) 'ccpp_constituent_properties_t ', & - 'const index is already set' - end if + call set_errvars(1, "ccpp_constituent_properties_t ", & + errcode=errcode, errmsg=errmsg, & + errmsg2="const index is already set") end if end if @@ -419,304 +596,494 @@ end subroutine ccp_set_const_index !####################################################################### - subroutine ccp_set_field_index(this, findex, errcode, errmsg) - ! Set this constituent's field index - ! It is an error to try to set an index if it is already set - - ! Dummy arguments - class(ccpp_constituent_properties_t), intent(inout) :: this - integer, intent(in) :: findex - integer, optional, intent(out) :: errcode - character(len=*), optional, intent(out) :: errmsg - - if (this%is_initialized(errcode, errmsg)) then - if (this%field_ind == int_unassigned) then - this%field_ind = findex - else - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - write(errmsg, *) 'ccpp_constituent_properties_t ', & - 'field index is already set' - end if - end if - end if - end subroutine ccp_set_field_index - - !####################################################################### - - logical function ccp_is_advected(this, errcode, errmsg) + subroutine ccp_is_advected(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 (this%is_initialized(errcode, errmsg)) then - ccp_is_advected = this%advected + if (this%is_instantiated(errcode, errmsg)) then + val_out = this%advected + else + val_out = .false. end if - end function ccp_is_advected + end subroutine ccp_is_advected !####################################################################### - logical function ccp_is_equivalent(this, oconst, & - errcode, errmsg) result(equiv) + subroutine ccp_is_equivalent(this, oconst, equiv, errcode, errmsg) ! Dummy arguments class(ccpp_constituent_properties_t), intent(in) :: this type(ccpp_constituent_properties_t), intent(in) :: oconst + logical, intent(out) :: equiv integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg - if (this%is_initialized(errcode, errmsg) .and. & - oconst%is_initialized(errcode, errmsg)) then + 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) + (this%advected .eqv. oconst%advected) .and. & + (this%const_default_value == oconst%const_default_value) else equiv = .false. end if - end function ccp_is_equivalent + end subroutine ccp_is_equivalent - !######################################################################## - ! - ! CCPP_MODEL_CONSTITUENTS_T (constituent field data) methods - ! !######################################################################## - logical function ccp_model_const_locked(this, errcode, errmsg, warn_func) - ! Return .true. iff is locked (i.e., ready to use) - ! Optionally fill out and if object not initialized + subroutine ccp_is_mass_mixing_ratio(this, val_out, errcode, errmsg) ! Dummy arguments - class(ccpp_model_constituents_t), intent(in) :: this - integer, optional, intent(out) :: errcode - character(len=*), optional, intent(out) :: errmsg - character(len=*), optional, intent(in) :: warn_func - ! Local variable - character(len=*), parameter :: subname = 'ccp_model_const_locked' + class(ccpp_constituent_properties_t), intent(in) :: this + logical, intent(out) :: val_out + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg - if (present(errcode)) then - errcode = 0 - end if - if (present(errmsg)) then - errmsg = '' - end if - ccp_model_const_locked = .false. - ! Use an initialized hash table as double check - if (this%hash_table%is_initialized()) then - ccp_model_const_locked = this%table_locked - if ( (.not. this%table_locked) .and. & - present(errmsg) .and. present(warn_func)) then - ! Write a warning as a courtesy to calling function but do not set - ! errcode (let caller decide). - write(errmsg, *) trim(warn_func), & - ' WARNING: Model constituents not ready to use' - end if + if (this%is_instantiated(errcode, errmsg)) then + val_out = this%const_type == mass_mixing_ratio else - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - if (present(warn_func)) then - write(errmsg, *) trim(warn_func), & - ' WARNING: Model constituents not initialized' - else - write(errmsg, *) subname, & - ' WARNING: Model constituents not initialized' - end if - end if + val_out = .false. end if - - end function ccp_model_const_locked + end subroutine ccp_is_mass_mixing_ratio !######################################################################## - logical function ccp_model_const_okay_to_add(this, errcode, errmsg, & - warn_func) - ! Return .true. iff is initialized and not locked - ! Optionally fill out and if the conditions are not met. + subroutine ccp_is_volume_mixing_ratio(this, val_out, errcode, errmsg) ! Dummy arguments - class(ccpp_model_constituents_t), intent(inout) :: this - integer, optional, intent(out) :: errcode - character(len=*), optional, intent(out) :: errmsg - character(len=*), optional, intent(in) :: warn_func - ! Local variable - character(len=*), parameter :: subname = 'ccp_model_const_okay_to_add' + class(ccpp_constituent_properties_t), intent(in) :: this + logical, intent(out) :: val_out + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg - ccp_model_const_okay_to_add = this%hash_table%is_initialized() - if (ccp_model_const_okay_to_add) then - ccp_model_const_okay_to_add = .not. this%locked(errcode=errcode, & - errmsg=errmsg, warn_func=subname) - if (.not. ccp_model_const_okay_to_add) then - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - if (present(warn_func)) then - write(errmsg, *) trim(warn_func), & - ' WARNING: Model constituents are locked' - else - errmsg = subname//' WARNING: Model constituents are locked' - end if - end if - end if + if (this%is_instantiated(errcode, errmsg)) then + val_out = this%const_type == volume_mixing_ratio else - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - if (present(warn_func)) then - write(errmsg, *) trim(warn_func), & - ' WARNING: Model constituents not initialized' - else - errmsg = subname//' WARNING: Model constituents not initialized' - end if - end if + val_out = .false. end if + end subroutine ccp_is_volume_mixing_ratio - end function ccp_model_const_okay_to_add + !######################################################################## + + subroutine ccp_is_number_concentration(this, val_out, errcode, errmsg) + + ! Dummy arguments + class(ccpp_constituent_properties_t), intent(in) :: this + logical, intent(out) :: val_out + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg + + if (this%is_instantiated(errcode, errmsg)) then + val_out = this%const_type == number_concentration + else + val_out = .false. + end if + end subroutine ccp_is_number_concentration !######################################################################## - subroutine ccp_model_const_add_metadata(this, field_data, errcode, errmsg) - ! Add a constituent's metadata to the master hash table + subroutine ccp_is_dry(this, val_out, errcode, errmsg) ! Dummy arguments - class(ccpp_model_constituents_t), intent(inout) :: this - type(ccpp_constituent_properties_t), target, intent(in) :: field_data - integer, optional, intent(out) :: errcode - character(len=*), optional, intent(out) :: errmsg - ! Local variables - character(len=256) :: error - character(len=*), parameter :: subnam = 'ccp_model_const_add_metadata' + class(ccpp_constituent_properties_t), intent(in) :: this + logical, intent(out) :: val_out + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg - if (this%okay_to_add(errcode=errcode, errmsg=errmsg, warn_func=subnam)) then - error = '' -!!XXgoldyXX: Add check on key to see if incompatible item already there. - call this%hash_table%add_hash_key(field_data, error) - if (len_trim(error) > 0) then - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - errmsg = trim(error) - end if - else - ! If we get here we are successful, add to variable count - if (field_data%is_layer_var()) then - this%num_layer_vars = this%num_layer_vars + 1 - else if (field_data%is_interface_var()) then - this%num_interface_vars = this%num_interface_vars + 1 - else if (field_data%is_2d_var()) then - this%num_2d_vars = this%num_2d_vars + 1 - else - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - call field_data%vertical_dimension(error, & - errcode=errcode, errmsg=errmsg) - if (len_trim(errmsg) == 0) then - write(errmsg, *) "ERROR: Unknown vertical dimension, '", & - trim(error), "'" - end if - end if - end if - end if + if (this%is_instantiated(errcode, errmsg)) then + val_out = this%const_water == dry_mixing_ratio else - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - errmsg = 'ERROR: Model contituents are locked' - end if + val_out = .false. end if - end subroutine ccp_model_const_add_metadata + end subroutine ccp_is_dry !######################################################################## - subroutine ccp_model_const_initialize(this, num_elements) - ! Initialize hash table, is total number of elements + subroutine ccp_is_moist(this, val_out, errcode, errmsg) ! Dummy arguments - class(ccpp_model_constituents_t), intent(inout) :: this - integer, intent(in) :: num_elements - ! Local variable - integer :: tbl_size + class(ccpp_constituent_properties_t), intent(in) :: this + logical, intent(out) :: val_out + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg - ! Clear any data - this%num_layer_vars = 0 - this%num_interface_vars = 0 - this%num_2d_vars = 0 - if (allocated(this%vars_layer)) then - deallocate(this%vars_layer) - end if - if (allocated(this%vars_interface)) then - deallocate(this%vars_interface) - end if - if (allocated(this%vars_2d)) then - deallocate(this%vars_2d) - end if - if (allocated(this%const_metadata)) then - deallocate(this%const_metadata) + if (this%is_instantiated(errcode, errmsg)) then + val_out = this%const_water == moist_mixing_ratio + else + val_out = .false. end if - ! Figure a log base 2 for initializing hash table - tbl_size = num_elements * 10 ! Hash padding - tbl_size = int((log(real(tbl_size, kind_phys)) / log(2.0_kind_phys)) + & - 1.0_kind_phys) - ! Initialize hash table - call this%hash_table%initialize(tbl_size) - this%table_locked = .false. - end subroutine ccp_model_const_initialize + end subroutine ccp_is_moist !######################################################################## - function ccp_model_const_find_const(this, standard_name, errcode, errmsg) & - result(cprop) - ! Return a constituent with key, , from the hash table - ! must be locked to execute this function - ! Since this is a private function, error checking for locked status - ! is *not* performed. + subroutine ccp_is_wet(this, val_out, errcode, errmsg) ! Dummy arguments - class(ccpp_model_constituents_t), intent(in) :: this - character(len=*), intent(in) :: standard_name - integer, optional, intent(out) :: errcode + class(ccpp_constituent_properties_t), intent(in) :: this + logical, intent(out) :: val_out + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg + + if (this%is_instantiated(errcode, errmsg)) then + val_out = this%const_water == wet_mixing_ratio + else + val_out = .false. + end if + + end subroutine ccp_is_wet + + !######################################################################## + + subroutine ccp_min_val(this, val_out, errcode, errmsg) + + ! Dummy arguments + class(ccpp_constituent_properties_t), intent(in) :: this + real(kind_phys), intent(out) :: val_out + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg + + if (this%is_instantiated(errcode, errmsg)) then + val_out = this%min_val + else + val_out = kphys_unassigned + end if + + end subroutine ccp_min_val + + !######################################################################## + + subroutine ccp_molec_weight(this, val_out, errcode, errmsg) + + ! Dummy arguments + class(ccpp_constituent_properties_t), intent(in) :: this + real(kind_phys), intent(out) :: val_out + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg + + if (this%is_instantiated(errcode, errmsg)) then + val_out = this%molar_mass + else + val_out = kphys_unassigned + end if + + end subroutine ccp_molec_weight + + !######################################################################## + + subroutine ccp_default_value(this, val_out, errcode, errmsg) + + ! Dummy arguments + class(ccpp_constituent_properties_t), intent(in) :: this + real(kind_phys), intent(out) :: val_out + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg + + if (this%is_instantiated(errcode, errmsg)) then + val_out = this%const_default_value + else + val_out = kphys_unassigned + end if + + end subroutine ccp_default_value + + !######################################################################## + + subroutine ccp_has_default(this, val_out, errcode, errmsg) + + ! Dummy arguments + class(ccpp_constituent_properties_t), intent(in) :: this + logical, intent(out) :: val_out + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccp_has_default' + + if (this%is_instantiated(errcode, errmsg)) then + val_out = this%const_default_value /= kphys_unassigned + else + val_out = .false. + end if + + end subroutine ccp_has_default + + !######################################################################## + ! + ! CCPP_MODEL_CONSTITUENTS_T (constituent field data) methods + ! + !######################################################################## + + logical function ccp_model_const_locked(this, errcode, errmsg, warn_func) + ! Return .true. iff is locked (i.e., ready to use) + ! Optionally fill out and if object not initialized + + ! Dummy arguments + class(ccpp_model_constituents_t), intent(in) :: this + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + character(len=*), optional, intent(in) :: warn_func + ! Local variable + character(len=*), parameter :: subname = 'ccp_model_const_locked' + + call initialize_errvars(errcode, errmsg) + ccp_model_const_locked = .false. + ! Use an initialized hash table as double check + if (this%hash_table%is_initialized()) then + ccp_model_const_locked = this%table_locked .and. this%data_locked + if ( (.not. (this%table_locked .and. this%data_locked)) .and. & + present(errmsg) .and. present(warn_func)) then + ! Write a warning as a courtesy to calling function but do not set + ! errcode (let caller decide). + write(errmsg, *) trim(warn_func), & + ' WARNING: Model constituents not ready to use' + end if + else + if (present(warn_func)) then + call set_errvars(1, trim(warn_func), & + errcode=errcode, errmsg=errmsg, & + errmsg2=" WARNING: Model constituents not initialized") + else + call set_errvars(1, subname, & + errcode=errcode, errmsg=errmsg, & + errmsg2=" WARNING: Model constituents not initialized") + end if + end if + + end function ccp_model_const_locked + + !######################################################################## + + logical function ccp_model_const_props_locked(this, errcode, errmsg, warn_func) + ! Return .true. iff 's constituent properties are ready to use + ! Optionally fill out and if object not initialized + ! Dummy arguments + class(ccpp_model_constituents_t), intent(in) :: this + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + character(len=*), optional, intent(in) :: warn_func + ! Local variable + character(len=*), parameter :: subname = 'ccp_model_const_table_locked' + + call initialize_errvars(errcode, errmsg) + ccp_model_const_props_locked = .false. + ! Use an initialized hash table as double check + if (this%hash_table%is_initialized()) then + ccp_model_const_props_locked = this%table_locked + if ( .not. this%table_locked .and. & + present(errmsg) .and. present(warn_func)) then + ! Write a warning as a courtesy to calling function but do not set + ! errcode (let caller decide). + write(errmsg, *) trim(warn_func), & + ' WARNING: Model constituent properties not ready to use' + end if + else + if (present(warn_func)) then + call set_errvars(1, trim(warn_func), & + errcode=errcode, errmsg=errmsg, & + errmsg2=" WARNING: Model constituent properties not initialized") + else + call set_errvars(1, subname, & + errcode=errcode, errmsg=errmsg, & + errmsg2=" WARNING: Model constituent properties not initialized") + end if + end if + + end function ccp_model_const_props_locked + + !######################################################################## + + logical function ccp_model_const_data_locked(this, errcode, errmsg, warn_func) + ! Return .true. iff 's data are ready to use + ! Optionally fill out and if object not initialized + ! Dummy arguments + class(ccpp_model_constituents_t), intent(in) :: this + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + character(len=*), optional, intent(in) :: warn_func + ! Local variable + character(len=*), parameter :: subname = 'ccp_model_const_data_locked' + + call initialize_errvars(errcode, errmsg) + ccp_model_const_data_locked = .false. + ! Use an initialized hash table as double check + if (this%hash_table%is_initialized()) then + ccp_model_const_data_locked = this%data_locked + if ( .not. this%data_locked .and. & + present(errmsg) .and. present(warn_func)) then + ! Write a warning as a courtesy to calling function but do not set + ! errcode (let caller decide). + write(errmsg, *) trim(warn_func), & + ' WARNING: Model constituent data not ready to use' + end if + else + if (present(warn_func)) then + call set_errvars(1, trim(warn_func), & + errcode=errcode, errmsg=errmsg, & + errmsg2=" WARNING: Model constituent data not initialized") + else + call set_errvars(1, subname, & + errcode=errcode, errmsg=errmsg, & + errmsg2=" WARNING: Model constituent data not initialized") + end if + end if + + end function ccp_model_const_data_locked + + !######################################################################## + + logical function ccp_model_const_okay_to_add(this, errcode, errmsg, & + warn_func) + ! Return .true. iff is initialized and not locked + ! Optionally fill out and if the conditions + ! are not met. + + ! Dummy arguments + class(ccpp_model_constituents_t), intent(inout) :: this + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + character(len=*), optional, intent(in) :: warn_func + ! Local variable + character(len=*), parameter :: subname = 'ccp_model_const_okay_to_add' + + ccp_model_const_okay_to_add = this%hash_table%is_initialized() + if (ccp_model_const_okay_to_add) then + ccp_model_const_okay_to_add = .not. (this%const_props_locked(errcode=errcode, & + errmsg=errmsg, warn_func=subname) .or. this%const_data_locked(errcode=errcode, & + errmsg=errmsg, warn_func=subname)) + if (.not. ccp_model_const_okay_to_add) then + if (present(warn_func)) then + call set_errvars(1, trim(warn_func), & + errcode=errcode, errmsg=errmsg, & + errmsg2=" WARNING: Model constituents are locked") + else + call set_errvars(1, subname, & + errcode=errcode, errmsg=errmsg, & + errmsg2=" WARNING: Model constituents are locked") + end if + end if + else + if (present(warn_func)) then + call set_errvars(1, trim(warn_func), & + errcode=errcode, errmsg=errmsg, & + errmsg2=" WARNING: Model constituents not initialized") + else + call set_errvars(1, subname, & + errcode=errcode, errmsg=errmsg, & + errmsg2=" WARNING: Model constituents not initialized") + end if + end if + + end function ccp_model_const_okay_to_add + + !######################################################################## + + subroutine ccp_model_const_add_metadata(this, field_data, errcode, errmsg) + ! Add a constituent's metadata to the master hash table + + ! Dummy arguments + class(ccpp_model_constituents_t), intent(inout) :: this + type(ccpp_constituent_properties_t), target, intent(in) :: field_data + 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' + + 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. + call this%hash_table%add_hash_key(field_data, error) + if (len_trim(error) > 0) then + call set_errvars(1, trim(error), errcode=errcode, errmsg=errmsg) + else + ! If we get here we are successful, add to variable count + if (field_data%is_layer_var()) then + this%num_layer_vars = this%num_layer_vars + 1 + else + if (present(errmsg)) then + call field_data%vertical_dimension(error, & + errcode=errcode, errmsg=errmsg) + if (len_trim(errmsg) == 0) then + call set_errvars(1, & + "ERROR: Unknown vertical dimension, '", & + errcode=errcode, errmsg=errmsg, & + errmsg2=trim(error), errmsg3="'") + end if + end if + end if + end if + else + call set_errvars(1, subname, & + errcode=errcode, errmsg=errmsg, & + errmsg2=" WARNING: Model constituents are locked") + end if + + end subroutine ccp_model_const_add_metadata + + !######################################################################## + + subroutine ccp_model_const_initialize(this, num_elements) + ! Initialize hash table, is total number of elements + + ! Dummy arguments + class(ccpp_model_constituents_t), intent(inout) :: this + integer, intent(in) :: num_elements + ! Local variable + integer :: tbl_size + + ! Clear any data + call this%reset() + ! Figure a log base 2 for initializing hash table + tbl_size = num_elements * 10 ! Hash padding + tbl_size = int((log(real(tbl_size, kind_phys)) / log(2.0_kind_phys)) + & + 1.0_kind_phys) + ! Initialize hash table + call this%hash_table%initialize(tbl_size) + this%table_locked = .false. + + end subroutine ccp_model_const_initialize + + !######################################################################## + + function ccp_model_const_find_const(this, standard_name, errcode, errmsg) & + result(cprop) + ! Return a constituent with key, , from the hash table + ! must be locked to execute this function + ! Since this is a private function, error checking for locked status + ! is *not* performed. + + ! Dummy arguments + class(ccpp_model_constituents_t), intent(in) :: this + character(len=*), intent(in) :: standard_name + integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg type(ccpp_constituent_properties_t), pointer :: cprop ! Local variables class(ccpp_hashable_t), pointer :: hval - character(len=256) :: error + character(len=errmsg_len) :: error 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 - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - write(errmsg, *) subname, ': ', trim(error) - end if + call set_errvars(1, subname, errcode=errcode, errmsg=errmsg, & + errmsg2=": "//trim(error)) else select type(hval) type is (ccpp_constituent_properties_t) cprop => hval class default - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - write(errmsg, *) subname, ' ERROR: Bad hash table value', & - trim(standard_name) - end if + call set_errvars(1, subname, errcode=errcode, errmsg=errmsg, & + errmsg2=" ERROR: Bad hash table value", & + errmsg3=trim(standard_name)) end select end if @@ -724,118 +1091,121 @@ end function ccp_model_const_find_const !######################################################################## - subroutine ccp_model_const_lock(this, ncols, num_layers, num_interfaces, & - errcode, errmsg) - ! Freeze hash table and initialize constituent field arrays + subroutine ccp_model_const_table_lock(this, errcode, errmsg) + ! Freeze hash table and initialize constituent properties ! Dummy arguments class(ccpp_model_constituents_t), intent(inout) :: this - integer, intent(in) :: ncols - integer, intent(in) :: num_layers - integer, intent(in) :: num_interfaces integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg ! Local variables - integer :: index_layer - integer :: index_interface - integer :: index_2d integer :: index_const + integer :: index_advect + integer :: num_vars integer :: astat + logical :: check type(ccpp_hash_iterator_t) :: hiter class(ccpp_hashable_t), pointer :: hval type(ccpp_constituent_properties_t), pointer :: cprop - character(len=32) :: dimname - character(len=*), parameter :: subname = 'ccp_model_const_lock' - - if (this%locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - if (len_trim(errmsg) == 0) then - write(errmsg, *) subname, & - ' WARNING: Model constituents already locked, ignoring' - end if - end if + character(len=dimname_len) :: dimname + character(len=*), parameter :: subname = 'ccp_model_const_table_lock' + + astat = 0 + if (this%const_props_locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then + call set_errvars(1, subname, errcode=errcode, errmsg=errmsg, & + errmsg2=" WARNING: Model constituent properites already locked, ignoring") + astat = astat + 1 else - index_layer = 0 - index_interface = 0 - index_2d = 0 - index_const = 0 ! Make sure everything is really initialized - if (allocated(this%vars_layer)) then - deallocate(this%vars_layer) - end if - if (allocated(this%vars_interface)) then - deallocate(this%vars_interface) - end if - if (allocated(this%vars_2d)) then - deallocate(this%vars_2d) - end if - if (allocated(this%const_metadata)) then - deallocate(this%const_metadata) - end if + call this%reset(clear_hash_table=.false.) + this%num_advected_vars = 0 ! Allocate the constituent array - allocate(this%const_metadata(this%hash_table%num_values()), stat=astat) + num_vars = this%hash_table%num_values() + allocate(this%const_metadata(num_vars), stat=astat) call handle_allocate_error(astat, 'const_metadata', & errcode=errcode, errmsg=errmsg) - ! Iterate through the hash table to find entries + ! We want to pack the advected constituents at the beginning of + ! the field array so we need to know how many there are if (astat == 0) then call hiter%initialize(this%hash_table) do if (hiter%valid()) then - index_const = index_const + 1 - if (index_const > SIZE(this%const_metadata)) then - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - write(errmsg, *) subname, & - " ERROR: const index out of bounds" - end if - exit - end if hval => hiter%value() select type(hval) type is (ccpp_constituent_properties_t) cprop => hval - call cprop%set_const_index(index_const, & - errcode=errcode, errmsg=errmsg) - ! Figure out which type of variable this is - if (cprop%is_layer_var()) then - index_layer = index_layer + 1 - call cprop%set_field_index(index_layer, & - errcode=errcode, errmsg=errmsg) - else if (cprop%is_interface_var()) then - index_interface = index_interface + 1 - call cprop%set_field_index(index_interface, & - errcode=errcode, errmsg=errmsg) - else if (cprop%is_2d_var()) then - index_2d = index_2d + 1 - call cprop%set_field_index(index_2d, & - errcode=errcode, errmsg=errmsg) - else - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - call cprop%vertical_dimension(dimname, & - errcode=errcode, errmsg=errmsg) - if (len_trim(errmsg) == 0) then - write(errmsg, *) subname, & - " ERROR: Bad vertical dimension, '", & - trim(dimname), "'" - end if - end if - end if - this%const_metadata(index_const) = cprop - class default - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - write(errmsg, *) subname, 'ERROR: Bad hash table value' + call cprop%is_advected(check) + if (check) then + this%num_advected_vars = this%num_advected_vars + 1 end if + end select + call hiter%next() + else + exit + end if + end do + ! Sanity check on num_advect + if (this%num_advected_vars > num_vars) then + astat = 1 + call set_errvars(astat, subname, & + errcode=errcode, errmsg=errmsg, & + errmsg2=" ERROR: num_advected_vars index out of bounds") + astat = astat + 1 + end if + end if + index_advect = 0 + index_const = this%num_advected_vars + ! Iterate through the hash table to find entries + if (astat == 0) then + call hiter%initialize(this%hash_table) + do + if (hiter%valid()) then + hval => hiter%value() + select type(hval) + type is (ccpp_constituent_properties_t) + cprop => hval + call cprop%is_advected(check) + if (check) then + index_advect = index_advect + 1 + if (index_advect > this%num_advected_vars) then + call set_errvars(1, subname, & + errcode=errcode, errmsg=errmsg, & + errmsg2=" ERROR: const a index out of bounds") + astat = astat + 1 + exit + end if + call cprop%set_const_index(index_advect, & + errcode=errcode, errmsg=errmsg) + call this%const_metadata(index_advect)%set(cprop) + else + index_const = index_const + 1 + if (index_const > num_vars) then + call set_errvars(1, subname, & + errcode=errcode, errmsg=errmsg, & + errmsg2=" ERROR: const v index out of bounds") + astat = astat + 1 + exit + end if + call cprop%set_const_index(index_const, & + errcode=errcode, errmsg=errmsg) + call this%const_metadata(index_const)%set(cprop) + end if + ! Make sure this is a layer variable + if (.not. cprop%is_layer_var()) then + call cprop%vertical_dimension(dimname, & + errcode=errcode, errmsg=errmsg) + call set_errvars(1, subname, & + errcode=errcode, errmsg=errmsg, & + errmsg2=" ERROR: Bad vertical dimension, '", & + errmsg3=trim(dimname)) + astat = astat + 1 + exit + end if + class default + call set_errvars(1, subname, & + errcode=errcode, errmsg=errmsg, & + errmsg2="ERROR: Bad hash table value") + astat = astat + 1 exit end select call hiter%next() @@ -845,63 +1215,17 @@ subroutine ccp_model_const_lock(this, ncols, num_layers, num_interfaces, & end do ! Some size sanity checks if (index_const /= this%hash_table%num_values()) then - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - write(errmsg, *) subname, & - " ERROR: Too few constituents found in hash table" - end if - else if (index_layer /= this%num_layer_vars) then - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - write(errmsg, '(2a,i0,a,i0)') subname, & - " ERROR: Wrong number of layer variables found (", & - index_layer, ") should be ", this%num_layer_vars - end if - else if (index_interface /= this%num_interface_vars) then - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - write(errmsg, '(2a,i0,a,i0)') subname, & - " ERROR: Wrong number of interface variables found (", & - index_interface, ") should be ", this%num_interface_vars - end if - else if (index_2d /= this%num_2d_vars) then - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - write(errmsg, '(2a,i0,a,i0)') subname, & - " ERROR: Wrong number of 2D variables found (", & - index_2d, ") should be ", this%num_2d_vars - end if + call set_errvars(errcode + 1, subname, & + errcode=errcode, errmsg=errmsg, & + errmsg2=" ERROR: Too few constituents found in hash table") + astat = astat + 1 end if - ! Everything looks okay, allocate field arrays - allocate(this%vars_layer(ncols, num_layers, index_layer), & - stat=astat) - call handle_allocate_error(astat, 'vars_layer', & - errcode=errcode, errmsg=errmsg) - if (astat == 0) then - this%num_layers = num_layers - this%vars_layer = kphys_unassigned - allocate(this%vars_interface(ncols, num_interfaces, & - index_layer), stat=astat) - call handle_allocate_error(astat, 'vars_interface', & - errcode=errcode, errmsg=errmsg) - end if - if (astat == 0) then - this%num_interfaces = num_interfaces - this%vars_interface = kphys_unassigned - allocate(this%vars_2d(ncols, index_2d), stat=astat) - call handle_allocate_error(astat, 'vars_2d', & - errcode=errcode, errmsg=errmsg) - end if - if (astat == 0) then - this%vars_2d = kphys_unassigned + if (index_advect /= this%num_advected_vars) then + call set_errvars(1, subname, & + errcode=errcode, errmsg=errmsg, & + errmsg2=" ERROR: Too few advected constituents found ", & + errmsg3="in hash table") + astat = astat + 1 end if if (present(errcode)) then if (errcode /= 0) then @@ -914,351 +1238,912 @@ subroutine ccp_model_const_lock(this, ncols, num_layers, num_interfaces, & end if end if - end subroutine ccp_model_const_lock + end subroutine ccp_model_const_table_lock + + !######################################################################## + + subroutine ccp_model_const_data_lock(this, ncols, num_layers, errcode, errmsg) + ! Freeze hash table and initialize constituent arrays + + ! Dummy arguments + class(ccpp_model_constituents_t), intent(inout) :: this + integer, intent(in) :: ncols + integer, intent(in) :: num_layers + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + ! Local variables + integer :: astat, index + real(kind=kind_phys) :: default_value + character(len=*), parameter :: subname = 'ccp_model_const_data_lock' + + if (this%const_data_locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then + call set_errvars(1, subname, errcode=errcode, errmsg=errmsg, & + errmsg2=" WARNING: Model constituent data already locked, ignoring") + astat = astat + 1 + else if (.not. this%const_props_locked(errcode=errcode, errmsg=errmsg, & + warn_func=subname)) then + call set_errvars(1, subname, errcode=errcode, errmsg=errmsg, & + errmsg2=" WARNING: Model constituent properties not yet locked, ignoring") + astat = astat + 1 + else + allocate(this%vars_layer(ncols, num_layers, this%hash_table%num_values()), & + stat=astat) + call handle_allocate_error(astat, 'vars_layer', & + errcode=errcode, errmsg=errmsg) + if (astat == 0) then + allocate(this%vars_minvalue(this%hash_table%num_values()), stat=astat) + call handle_allocate_error(astat, 'vars_minvalue', & + errcode=errcode, errmsg=errmsg) + end if + if (astat == 0) then + this%num_layers = num_layers + do index = 1, this%hash_table%num_values() + call this%const_metadata(index)%default_value(default_value, & + errcode, errmsg) + this%vars_layer(:,:,index) = default_value + end do + this%vars_minvalue = 0.0_kind_phys + end if + if (present(errcode)) then + if (errcode /= 0) then + astat = 1 + end if + end if + if (astat == 0) then + this%data_locked = .true. + end if + end if + + end subroutine ccp_model_const_data_lock + + !######################################################################## + + subroutine ccp_model_const_reset(this, clear_hash_table) + ! Empty (reset) the entire object + ! Optionally do not clear the hash table (and its data) + + ! Dummy arguments + class(ccpp_model_constituents_t), intent(inout) :: this + logical, optional, intent(in) :: clear_hash_table + ! Local variables + logical :: clear_table + integer :: index + + if (present(clear_hash_table)) then + clear_table = clear_hash_table + else + clear_table = .true. + end if + if (allocated(this%vars_layer)) then + deallocate(this%vars_layer) + end if + if (allocated(this%vars_minvalue)) then + deallocate(this%vars_minvalue) + end if + if (allocated(this%const_metadata)) then + if (clear_table) then + do index = 1, size(this%const_metadata, 1) + call this%const_metadata(index)%deallocate() + end do + end if + deallocate(this%const_metadata) + end if + if (clear_table) then + this%num_layer_vars = 0 + this%num_advected_vars = 0 + this%num_layers = 0 + call this%hash_table%clear() + end if + + end subroutine ccp_model_const_reset + + !######################################################################## + + logical function ccp_model_const_is_match(this, index, advected) & + 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. + ! Since this is a private function, error checking for locked status + ! is *not* performed. + + ! Dummy arguments + class(ccpp_model_constituents_t), intent(in) :: this + integer, intent(in) :: index + logical, optional, intent(in) :: advected + ! Local variable + logical :: check + + ! By default, every constituent is a match + is_match = .true. + if (present(advected)) then + call this%const_metadata(index)%is_advected(check) + if (advected .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, errcode, errmsg) + ! Query number of constituents matching pattern + ! Each (optional) property which is present represents something + ! which is required as part of a match. + ! must be locked to execute this function + + ! Dummy arguments + class(ccpp_model_constituents_t), intent(in) :: this + integer, intent(out) :: nmatch + logical, optional, intent(in) :: advected + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + ! Local variables + integer :: index + character(len=*), parameter :: subname = "ccp_model_const_num_match" + + 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)) then + nmatch = nmatch + 1 + end if + end do + end if + + end subroutine ccp_model_const_num_match + + !######################################################################## + + subroutine ccp_model_const_index(this, index, standard_name, errcode, errmsg) + ! Return index of metadata matching . + ! must be locked to execute this function + + ! Dummy arguments + class(ccpp_model_constituents_t), intent(in) :: this + character(len=*), intent(in) :: standard_name + integer, intent(out) :: index + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + ! Local variables + type(ccpp_constituent_properties_t), pointer :: cprop + 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) + if (associated(cprop)) then + index = cprop%const_index() + else + index = int_unassigned + end if + else + index = int_unassigned + end if + + end subroutine ccp_model_const_index + + !######################################################################## + + subroutine ccp_model_const_metadata(this, standard_name, const_data, & + errcode, errmsg) + ! Return metadata matching standard name + ! must be locked to execute this function + + ! Dummy arguments + class(ccpp_model_constituents_t), intent(in) :: this + character(len=*), intent(in) :: standard_name + type(ccpp_constituent_properties_t), intent(out) :: const_data + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + ! Local variables + type(ccpp_constituent_properties_t), pointer :: cprop + character(len=*), parameter :: subname = "ccp_model_const_metadata" + + if (this%const_props_locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then + cprop => this%find_const(standard_name, errcode=errcode, errmsg=errmsg) + if (associated(cprop)) then + const_data = cprop + end if + end if + + end subroutine ccp_model_const_metadata + + !######################################################################## + + subroutine ccp_model_const_copy_in_3d(this, const_array, advected, & + errcode, errmsg) + ! Gather constituent fields matching pattern + ! Each (optional) property which is present represents something + ! which is required as part of a match. + ! must be locked to execute this function + + ! Dummy arguments + class(ccpp_model_constituents_t), intent(in) :: this + real(kind_phys), intent(out) :: const_array(:,:,:) + logical, optional, intent(in) :: advected + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + ! Local variables + integer :: index ! const_metadata index + integer :: cindex ! const_array index + integer :: fld_ind ! const field index + integer :: max_cind ! Size of const_array + integer :: num_levels ! Levels of const_array + character(len=stdname_len) :: std_name + character(len=*), parameter :: subname = "ccp_model_const_copy_in_3d" + + if (this%locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then + cindex = 0 + max_cind = SIZE(const_array, 3) + num_levels = SIZE(const_array, 2) + do index = 1, SIZE(this%const_metadata) + if (this%is_match(index, advected=advected)) then + ! See if we have room for another constituent + cindex = cindex + 1 + if (cindex > max_cind) then + call set_errvars(1, subname, & + errcode=errcode, errmsg=errmsg, & + errmsg2=": Too many constituents for ") + exit + end if + ! Copy this constituent's field data to + call this%const_metadata(index)%const_index(fld_ind) + if (fld_ind /= index) then + call this%const_metadata(index)%standard_name(std_name) + call set_errvars(1, subname//": ERROR: ", & + errcode=errcode, errmsg=errmsg, & + errmsg2="bad field index, "//to_str(fld_ind), & + errmsg3=" for '"//trim(std_name)//"', ", & + errmsg4="should have been "//to_str(index)) + exit + else if (this%const_metadata(index)%is_layer_var()) then + if (this%num_layers == num_levels) then + const_array(:,:,cindex) = this%vars_layer(:,:,fld_ind) + else + call this%const_metadata(index)%standard_name(std_name) + call set_errvars(1, subname, & + errcode=errcode, errmsg=errmsg, & + errmsg2=": Wrong number of vertical levels for '", & + errmsg3=trim(std_name)//"', "//to_str(num_levels), & + errmsg4=", expected"//to_str(this%num_layers)) + exit + end if + else + call this%const_metadata(index)%standard_name(std_name) + call set_errvars(1, subname//": Unsupported var type, ", & + errcode=errcode, errmsg=errmsg, & + errmsg2="wrong number of vertical levels for '", & + errmsg3=trim(std_name)//"', "//to_str(num_levels), & + errmsg4=", expected"//to_str(this%num_layers)) + exit + end if + end if + end do + end if + + end subroutine ccp_model_const_copy_in_3d + + !######################################################################## + + subroutine ccp_model_const_copy_out_3d(this, const_array, advected, & + errcode, errmsg) + ! Update constituent fields matching pattern + ! Each (optional) property which is present represents something + ! which is required as part of a match. + ! must be locked to execute this function + + ! Dummy argument + class(ccpp_model_constituents_t), intent(inout) :: this + real(kind_phys), intent(in) :: const_array(:,:,:) + logical, optional, intent(in) :: advected + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + ! Local variables + integer :: index ! const_metadata index + integer :: cindex ! const_array index + integer :: fld_ind ! const field index + integer :: max_cind ! Size of const_array + integer :: num_levels ! Levels of const_array + character(len=stdname_len) :: std_name + character(len=*), parameter :: subname = "ccp_model_const_copy_out_3d" + + if (this%locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then + cindex = 0 + max_cind = SIZE(const_array, 3) + num_levels = SIZE(const_array, 2) + do index = 1, SIZE(this%const_metadata) + if (this%is_match(index, advected=advected)) then + ! See if we have room for another constituent + cindex = cindex + 1 + if (cindex > max_cind) then + call set_errvars(1, subname, & + errcode=errcode, errmsg=errmsg, & + errmsg2=": Too many constituents for ") + exit + end if + ! Copy this field of to to constituent's field data + call this%const_metadata(index)%const_index(fld_ind) + if (fld_ind /= index) then + call this%const_metadata(index)%standard_name(std_name) + call set_errvars(1, subname//": ERROR: ", & + errcode=errcode, errmsg=errmsg, & + errmsg2="bad field index, "//to_str(fld_ind), & + errmsg3=" for '"//trim(std_name)//"', ", & + errmsg4="should have been "//to_str(index)) + exit + else if (this%const_metadata(index)%is_layer_var()) then + if (this%num_layers == num_levels) then + this%vars_layer(:,:,fld_ind) = const_array(:,:,cindex) + else + call this%const_metadata(index)%standard_name(std_name) + call set_errvars(1, subname, & + errcode=errcode, errmsg=errmsg, & + errmsg2=": Wrong number of vertical levels for '", & + errmsg3=trim(std_name)//"', "//to_str(num_levels), & + errmsg4=", expected"//to_str(this%num_layers)) + exit + end if + else + call this%const_metadata(index)%standard_name(std_name) + call set_errvars(1, subname//": Unsupported var type, ", & + errcode=errcode, errmsg=errmsg, & + errmsg2="wrong number of vertical levels for '", & + errmsg3=trim(std_name)//"', "//to_str(num_levels), & + errmsg4=", expected"//to_str(this%num_layers)) + exit + end if + end if + end do + end if + + end subroutine ccp_model_const_copy_out_3d + + !######################################################################## + + function ccp_field_data_ptr(this) result(const_ptr) + ! Return pointer to constituent array (for use by host model) + + ! Dummy arguments + class(ccpp_model_constituents_t), target, intent(inout) :: this + real(kind_phys), pointer :: const_ptr(:,:,:) + ! Local variables + integer :: errcode + character(len=errmsg_len) :: errmsg + character(len=*), parameter :: subname = 'ccp_field_data_ptr' + + if (this%locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then + const_ptr => this%vars_layer + else + ! We don't want output variables in a function so just nullify + ! See note above about creating a 'last_error' method + nullify(const_ptr) + end if + + end function ccp_field_data_ptr + + !######################################################################## + + function ccp_advected_data_ptr(this) result(const_ptr) + ! Return pointer to advected constituent array (for use by host model) + + ! Dummy arguments + class(ccpp_model_constituents_t), target, intent(inout) :: this + real(kind_phys), pointer :: const_ptr(:,:,:) + ! Local variables + integer :: errcode + character(len=errmsg_len) :: errmsg + character(len=*), parameter :: subname = 'ccp_advected_data_ptr' + + if (this%locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then + const_ptr => this%vars_layer(:,:,1:this%num_advected_vars) + else + ! We don't want output variables in a function so just nullify + ! See note above about creating a 'last_error' method + nullify(const_ptr) + end if + + end function ccp_advected_data_ptr + + function ccp_constituent_props_ptr(this) result(const_ptr) + ! Return pointer to constituent properties array (for use by host model) + + ! Dummy arguments + class(ccpp_model_constituents_t), target, intent(inout) :: this + type(ccpp_constituent_prop_ptr_t), pointer :: const_ptr(:) + ! Local variables + integer :: errcode + character(len=errmsg_len) :: errmsg + character(len=*), parameter :: subname = 'ccp_constituent_props_ptr' + + if (this%const_props_locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then + const_ptr => this%const_metadata + else + ! We don't want output variables in a function so just nullify + ! See note above about creating a 'last_error' method + nullify(const_ptr) + end if + + end function ccp_constituent_props_ptr + + !######################################################################## + + !##################################### + ! ccpp_constituent_prop_ptr_t methods + !##################################### + + !####################################################################### + + subroutine ccpt_get_standard_name(this, std_name, errcode, errmsg) + ! Return this constituent's standard name + + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(in) :: this + character(len=*), intent(out) :: std_name + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_get_standard_name' + + if (associated(this%prop)) then + call this%prop%standard_name(std_name, errcode, errmsg) + else + std_name = '' + call set_errvars(1, subname//": invalid constituent pointer", & + errcode=errcode, errmsg=errmsg) + end if + + end subroutine ccpt_get_standard_name + + !####################################################################### + + subroutine ccpt_get_long_name(this, long_name, errcode, errmsg) + ! Return this constituent's long name (description) + + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(in) :: this + character(len=*), intent(out) :: long_name + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_get_long_name' + + if (associated(this%prop)) then + call this%prop%long_name(long_name, errcode, errmsg) + else + long_name = '' + call set_errvars(1, subname//": invalid constituent pointer", & + errcode=errcode, errmsg=errmsg) + end if + + end subroutine ccpt_get_long_name + + !####################################################################### + + subroutine ccpt_get_vertical_dimension(this, vert_dim, errcode, errmsg) + ! Return the standard name of this constituent's vertical dimension + + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(in) :: this + character(len=*), intent(out) :: vert_dim + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_get_vertical_dimension' + + if (associated(this%prop)) then + if (this%prop%is_instantiated(errcode, errmsg)) then + vert_dim = this%prop%vert_dim + end if + else + vert_dim = '' + call set_errvars(1, subname//": invalid constituent pointer", & + errcode=errcode, errmsg=errmsg) + end if + + end subroutine ccpt_get_vertical_dimension + + !####################################################################### + + logical function ccpt_is_layer_var(this) result(is_layer) + ! Return .true. iff this constituent has a layer vertical dimension + + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(in) :: this + ! Local variables + character(len=dimname_len) :: dimname + character(len=*), parameter :: subname = 'ccpt_is_layer_var' + + if (associated(this%prop)) then + call this%prop%vertical_dimension(dimname) + is_layer = trim(dimname) == 'vertical_layer_dimension' + else + is_layer = .false. + end if + + end function ccpt_is_layer_var + + !####################################################################### + + logical function ccpt_is_interface_var(this) result(is_interface) + ! Return .true. iff this constituent has a interface vertical dimension + + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(in) :: this + ! Local variables + character(len=dimname_len) :: dimname + character(len=*), parameter :: subname = 'ccpt_is_interface_var' + + if (associated(this%prop)) then + call this%prop%vertical_dimension(dimname) + is_interface = trim(dimname) == 'vertical_interface_dimension' + else + is_interface = .false. + end if + + end function ccpt_is_interface_var + + !####################################################################### + + logical function ccpt_is_2d_var(this) result(is_2d) + ! Return .true. iff this constituent has a 2d vertical dimension + + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(in) :: this + ! Local variables + character(len=dimname_len) :: dimname + character(len=*), parameter :: subname = 'ccpt_is_2d_var' + + if (associated(this%prop)) then + call this%prop%vertical_dimension(dimname) + is_2d = len_trim(dimname) == 0 + else + is_2d = .false. + end if + + end function ccpt_is_2d_var + + !####################################################################### + + subroutine ccpt_const_index(this, index, errcode, errmsg) + ! Return this constituent's master index (or -1 of not assigned) + + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(in) :: this + integer, intent(out) :: index + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_const_index' + + if (associated(this%prop)) then + index = this%prop%const_index(errcode, errmsg) + else + index = int_unassigned + call set_errvars(1, subname//": invalid constituent pointer", & + errcode=errcode, errmsg=errmsg) + end if + + end subroutine ccpt_const_index + + !####################################################################### + + subroutine ccpt_is_advected(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_advected' + + if (associated(this%prop)) then + call this%prop%is_advected(val_out, errcode, errmsg) + else + val_out = .false. + call set_errvars(1, subname//": invalid constituent pointer", & + errcode=errcode, errmsg=errmsg) + end if + + end subroutine ccpt_is_advected + + !######################################################################## + + subroutine ccpt_is_mass_mixing_ratio(this, val_out, errcode, errmsg) + + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(in) :: this + logical, intent(out) :: val_out + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_is_mass_mixing_ratio' + + if (associated(this%prop)) then + call this%prop%is_mass_mixing_ratio(val_out, errcode, errmsg) + else + val_out = .false. + call set_errvars(1, subname//": invalid constituent pointer", & + errcode=errcode, errmsg=errmsg) + end if + + end subroutine ccpt_is_mass_mixing_ratio + + !######################################################################## + + subroutine ccpt_is_volume_mixing_ratio(this, val_out, errcode, errmsg) + + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(in) :: this + logical, intent(out) :: val_out + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_is_volume_mixing_ratio' + + if (associated(this%prop)) then + call this%prop%is_volume_mixing_ratio(val_out, errcode, errmsg) + else + val_out = .false. + call set_errvars(1, subname//": invalid constituent pointer", & + errcode=errcode, errmsg=errmsg) + end if + + end subroutine ccpt_is_volume_mixing_ratio !######################################################################## - subroutine ccp_model_const_reset(this) - ! Empty (reset) the entire object + subroutine ccpt_is_number_concentration(this, val_out, errcode, errmsg) - ! Dummy argument - class(ccpp_model_constituents_t), intent(inout) :: this + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(in) :: this + logical, intent(out) :: val_out + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_is_number_concentration' - if (allocated(this%vars_layer)) then - deallocate(this%vars_layer) - end if - if (allocated(this%vars_interface)) then - deallocate(this%vars_interface) + if (associated(this%prop)) then + call this%prop%is_number_concentration(val_out, errcode, errmsg) + else + val_out = .false. + call set_errvars(1, subname//": invalid constituent pointer", & + errcode=errcode, errmsg=errmsg) end if - if (allocated(this%vars_2d)) then - deallocate(this%vars_2d) + + end subroutine ccpt_is_number_concentration + + !######################################################################## + + subroutine ccpt_is_dry(this, val_out, errcode, errmsg) + + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(in) :: this + logical, intent(out) :: val_out + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_is_dry' + + if (associated(this%prop)) then + call this%prop%is_dry(val_out, errcode, errmsg) + else + val_out = .false. + call set_errvars(1, subname//": invalid constituent pointer", & + errcode=errcode, errmsg=errmsg) end if - if (allocated(this%const_metadata)) then - deallocate(this%const_metadata) + + end subroutine ccpt_is_dry + + !######################################################################## + + subroutine ccpt_is_moist(this, val_out, errcode, errmsg) + + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(in) :: this + logical, intent(out) :: val_out + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_is_moist' + + if (associated(this%prop)) then + call this%prop%is_moist(val_out, errcode, errmsg) + else + val_out = .false. + call set_errvars(1, subname//": invalid constituent pointer", & + errcode=errcode, errmsg=errmsg) end if - call this%hash_table%clear() - end subroutine ccp_model_const_reset + end subroutine ccpt_is_moist !######################################################################## - logical function ccp_model_const_is_match(this, index, advected) & - 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. - ! Since this is a private function, error checking for locked status - ! is *not* performed. + subroutine ccpt_is_wet(this, val_out, errcode, errmsg) ! Dummy arguments - class(ccpp_model_constituents_t), intent(in) :: this - integer, intent(in) :: index - logical, optional, intent(in) :: advected + class(ccpp_constituent_prop_ptr_t), intent(in) :: this + logical, intent(out) :: val_out + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_is_wet' - ! By default, every constituent is a match - is_match = .true. - if (present(advected)) then - if (advected .neqv. this%const_metadata(index)%is_advected()) then - is_match = .false. - end if + if (associated(this%prop)) then + call this%prop%is_wet(val_out, errcode, errmsg) + else + val_out = .false. + call set_errvars(1, subname//": invalid constituent pointer", & + errcode=errcode, errmsg=errmsg) end if - end function ccp_model_const_is_match + end subroutine ccpt_is_wet !######################################################################## - integer function ccp_model_const_num_match(this, advected, & - errcode, errmsg) result(nmatch) - ! Query number of constituents matching pattern - ! Each (optional) property which is present represents something - ! which is required as part of a match. - ! must be locked to execute this function + subroutine ccpt_min_val(this, val_out, errcode, errmsg) ! Dummy arguments - class(ccpp_model_constituents_t), intent(in) :: this - logical, optional, intent(in) :: advected - integer, optional, intent(out) :: errcode - character(len=*), optional, intent(out) :: errmsg - ! Local variables - integer :: index - character(len=*), parameter :: subname = "ccp_model_const_num_match" + class(ccpp_constituent_prop_ptr_t), intent(in) :: this + real(kind_phys), intent(out) :: val_out + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_min_val' - nmatch = 0 - if (this%locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then - do index = 1, SIZE(this%const_metadata) - if (this%is_match(index, advected=advected)) then - nmatch = nmatch + 1 - end if - end do + if (associated(this%prop)) then + call this%prop%minimum(val_out, errcode, errmsg) + else + val_out = kphys_unassigned + call set_errvars(1, subname//": invalid constituent pointer", & + errcode=errcode, errmsg=errmsg) end if - end function ccp_model_const_num_match + end subroutine ccpt_min_val !######################################################################## - subroutine ccp_model_const_copy_in_3d(this, const_array, advected, & - errcode, errmsg) - ! Gather constituent fields matching pattern - ! Each (optional) property which is present represents something - ! which is required as part of a match. - ! must be locked to execute this function + subroutine ccpt_molec_weight(this, val_out, errcode, errmsg) ! Dummy arguments - class(ccpp_model_constituents_t), intent(in) :: this - real(kind_phys), intent(out) :: const_array(:,:,:) - logical, optional, intent(in) :: advected - integer, optional, intent(out) :: errcode - character(len=*), optional, intent(out) :: errmsg - ! Local variables - integer :: index ! const_metadata index - integer :: cindex ! const_array index - integer :: fld_ind ! const field index - integer :: max_cind ! Size of const_array - integer :: num_levels ! Levels of const_array - character(len=64) :: std_name - character(len=*), parameter :: subname = "ccp_model_const_copy_in_3d" + class(ccpp_constituent_prop_ptr_t), intent(in) :: this + real(kind_phys), intent(out) :: val_out + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_molec_weight' - if (this%locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then - cindex = 0 - max_cind = SIZE(const_array, 3) - num_levels = SIZE(const_array, 2) - do index = 1, SIZE(this%const_metadata) - if (this%is_match(index, advected=advected)) then - ! See if we have room for another constituent - cindex = cindex + 1 - if (cindex > max_cind) then - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - write(errmsg, *) subname, & - ": Too many constituents for " - end if - exit - end if - ! Copy this constituent's field data to - fld_ind = this%const_metadata(index)%field_index() - if (fld_ind < 1) then - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - call this%const_metadata(index)%standard_name(std_name) - write(errmsg, '(4a,i0,a,i0)') subname, & - ": No field index for '", trim(std_name), "'" - end if - else if (this%const_metadata(index)%is_layer_var()) then - if (this%num_layers == num_levels) then - const_array(:,:,cindex) = this%vars_layer(:,:,fld_ind) - else - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - call this%const_metadata(index)%standard_name(std_name) - write(errmsg, '(4a,i0,a,i0)') subname, & - ": Wrong number of vertical levels for ", & - trim(std_name), ', ', num_levels, & - ', expected ', this%num_layers - end if - exit - end if - else if (this%const_metadata(index)%is_interface_var()) then - if (this%num_interfaces == num_levels) then - const_array(:,:,cindex) = this%vars_interface(:,:,fld_ind) - else - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - call this%const_metadata(index)%standard_name(std_name) - write(errmsg, '(4a,i0,a,i0)') subname, & - ": Wrong number of vertical levels for ", & - std_name, ', ', num_levels, ', expected ', & - this%num_interfaces - end if - exit - end if - end if - end if - end do + if (associated(this%prop)) then + call this%prop%molec_weight(val_out, errcode, errmsg) + else + val_out = kphys_unassigned + call set_errvars(1, subname//": invalid constituent pointer", & + errcode=errcode, errmsg=errmsg) end if - end subroutine ccp_model_const_copy_in_3d + end subroutine ccpt_molec_weight !######################################################################## - subroutine ccp_model_const_copy_out_3d(this, const_array, advected, & - errcode, errmsg) - ! Update constituent fields matching pattern - ! Each (optional) property which is present represents something - ! which is required as part of a match. - ! must be locked to execute this function + subroutine ccpt_default_value(this, val_out, errcode, errmsg) - ! Dummy argument - class(ccpp_model_constituents_t), intent(inout) :: this - real(kind_phys), intent(in) :: const_array(:,:,:) - logical, optional, intent(in) :: advected - integer, optional, intent(out) :: errcode - character(len=*), optional, intent(out) :: errmsg - ! Local variables - integer :: index ! const_metadata index - integer :: cindex ! const_array index - integer :: fld_ind ! const field index - integer :: max_cind ! Size of const_array - integer :: num_levels ! Levels of const_array - character(len=64) :: std_name - character(len=*), parameter :: subname = "ccp_model_const_copy_out_3d" + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(in) :: this + real(kind_phys), intent(out) :: val_out + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_default_value' - if (this%locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then - cindex = 0 - max_cind = SIZE(const_array, 3) - num_levels = SIZE(const_array, 2) - do index = 1, SIZE(this%const_metadata) - if (this%is_match(index, advected=advected)) then - ! See if we have room for another constituent - cindex = cindex + 1 - if (cindex > max_cind) then - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - write(errmsg, *) subname, & - ": Too many constituents for " - end if - exit - end if - ! Copy this field of to to constituent's field data - fld_ind = this%const_metadata(index)%field_index() - if (fld_ind < 1) then - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - call this%const_metadata(index)%standard_name(std_name) - write(errmsg, '(4a,i0,a,i0)') subname, & - ": No field index for '", trim(std_name), "'" - end if - else if (this%const_metadata(index)%is_layer_var()) then - if (this%num_layers == num_levels) then - this%vars_layer(:,:,fld_ind) = const_array(:,:,cindex) - else - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - call this%const_metadata(index)%standard_name(std_name) - write(errmsg, '(4a,i0,a,i0)') subname, & - ": Wrong number of vertical levels for ", & - trim(std_name), ', ', num_levels, & - ', expected ', this%num_layers - end if - exit - end if - else if (this%const_metadata(index)%is_interface_var()) then - if (this%num_interfaces == num_levels) then - this%vars_interface(:,:,fld_ind) = const_array(:,:,cindex) - else - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - call this%const_metadata(index)%standard_name(std_name) - write(errmsg, '(4a,i0,a,i0)') subname, & - ": Wrong number of vertical levels for ", & - std_name, ', ', num_levels, ', expected ', & - this%num_interfaces - end if - exit - end if - end if - end if - end do + if (associated(this%prop)) then + call this%prop%default_value(val_out, errcode, errmsg) + else + val_out = kphys_unassigned + call set_errvars(1, subname//": invalid constituent pointer", & + errcode=errcode, errmsg=errmsg) end if - end subroutine ccp_model_const_copy_out_3d + end subroutine ccpt_default_value !######################################################################## - integer function ccp_model_const_index(this, standard_name, errcode, errmsg) - ! Return index of metadata matching . - ! must be locked to execute this function + subroutine ccpt_has_default(this, val_out, errcode, errmsg) ! Dummy arguments - class(ccpp_model_constituents_t), intent(in) :: this - character(len=*), intent(in) :: standard_name - integer, optional, intent(out) :: errcode - character(len=*), optional, intent(out) :: errmsg - ! Local variables - type(ccpp_constituent_properties_t), pointer :: cprop - character(len=*), parameter :: subname = "ccp_model_const_index" + class(ccpp_constituent_prop_ptr_t), intent(in) :: this + logical, intent(out) :: val_out + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_has_default' - if (this%locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then - cprop => this%find_const(standard_name, errcode=errcode, errmsg=errmsg) - if (associated(cprop)) then - ccp_model_const_index = cprop%const_index() - else - ccp_model_const_index = int_unassigned - end if + if (associated(this%prop)) then + call this%prop%has_default(val_out, errcode, errmsg) else - ccp_model_const_index = int_unassigned + val_out = .false. + call set_errvars(1, subname//": invalid constituent pointer", & + errcode=errcode, errmsg=errmsg) end if - end function ccp_model_const_index + end subroutine ccpt_has_default !######################################################################## - integer function ccp_model_const_field_index(this, standard_name, & - errcode, errmsg) - ! Return index of field matching . - ! must be locked to execute this function + subroutine ccpt_set(this, const_ptr, errcode, errmsg) + ! Set the pointer to , however, an error is recorded if + ! the pointer is already set. ! Dummy arguments - class(ccpp_model_constituents_t), intent(in) :: this - character(len=*), intent(in) :: standard_name - integer, optional, intent(out) :: errcode - character(len=*), optional, intent(out) :: errmsg + class(ccpp_constituent_prop_ptr_t), intent(inout) :: this + type(ccpp_constituent_properties_t), pointer :: const_ptr + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg ! Local variables - type(ccpp_constituent_properties_t), pointer :: cprop - character(len=*), parameter :: subname = "ccp_model_field_index" - - if (this%locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then - cprop => this%find_const(standard_name, errcode=errcode, errmsg=errmsg) - if (associated(cprop)) then - ccp_model_const_field_index = cprop%field_index() - else - ccp_model_const_field_index = int_unassigned + character(len=stdname_len) :: stdname + character(len=errmsg_len) :: errmsg2 + + call initialize_errvars(errcode, errmsg) + if (associated(this%prop)) then + call this%standard_name(stdname, errcode=errcode, errmsg=errmsg2) + if (errcode == 0) then + write(errmsg2, *) "Pointer already allocated as '", & + trim(stdname), "'" end if + errcode = errcode + 1 + call set_errvars(1, "ccpt_set: ", errcode=errcode, errmsg=errmsg, & + errmsg2=trim(errmsg2)) else - ccp_model_const_field_index = int_unassigned + this%prop => const_ptr end if - end function ccp_model_const_field_index + end subroutine ccpt_set !######################################################################## - subroutine ccp_model_const_metadata(this, standard_name, const_data, & - errcode, errmsg) - ! Return metadata matching standard name - ! must be locked to execute this function + subroutine ccpt_deallocate(this) + ! Deallocate the constituent object pointer if it is allocated. + + ! Dummy argument + class(ccpp_constituent_prop_ptr_t), intent(inout) :: this + + if (associated(this%prop)) then + call this%prop%deallocate() + deallocate(this%prop) + end if + nullify(this%prop) + + end subroutine ccpt_deallocate + + !####################################################################### + + subroutine ccpt_set_const_index(this, index, errcode, errmsg) + ! Set this constituent's index in the master constituent array + ! It is an error to try to set an index if it is already set ! Dummy arguments - class(ccpp_model_constituents_t), intent(in) :: this - character(len=*), intent(in) :: standard_name - type(ccpp_constituent_properties_t), intent(out) :: const_data - integer, optional, intent(out) :: errcode - character(len=*), optional, intent(out) :: errmsg - ! Local variables - type(ccpp_constituent_properties_t), pointer :: cprop - character(len=*), parameter :: subname = "ccp_model_const_metadata" + class(ccpp_constituent_prop_ptr_t), intent(inout) :: this + integer, intent(in) :: index + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_set_const_index' - if (this%locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then - cprop => this%find_const(standard_name, errcode=errcode, errmsg=errmsg) - if (associated(cprop)) then - const_data = cprop + if (associated(this%prop)) then + if (this%prop%is_instantiated(errcode, errmsg)) then + if (this%prop%const_ind == int_unassigned) then + this%prop%const_ind = index + else + call set_errvars(1, "ccpp_constituent_prop_ptr_t ", & + errcode=errcode, errmsg=errmsg, & + errmsg2="const index is already set") + end if end if + else + call set_errvars(1, subname//": invalid constituent pointer", & + errcode=errcode, errmsg=errmsg) end if - end subroutine ccp_model_const_metadata + end subroutine ccpt_set_const_index end module ccpp_constituent_prop_mod diff --git a/src/ccpp_constituent_prop_mod.meta b/src/ccpp_constituent_prop_mod.meta new file mode 100644 index 00000000..dd60eb13 --- /dev/null +++ b/src/ccpp_constituent_prop_mod.meta @@ -0,0 +1,47 @@ +######################################################################## +[ccpp-table-properties] + name = ccpp_constituent_prop_ptr_t + type = ddt + +[ccpp-arg-table] + name = ccpp_constituent_prop_ptr_t + type = ddt + +######################################################################## +[ccpp-table-properties] + name = ccpp_model_constituents_t + type = ddt + +[ccpp-arg-table] + name = ccpp_model_constituents_t + type = ddt +[ num_layer_vars ] + standard_name = ccpp_num_constituents + long_name = Number of constituents managed by CCPP Framework + units = count + dimensions = () + type = integer +[ num_advected_vars ] + standard_name = ccpp_num_advected_constituents + long_name = Number of advected constituents managed by CCPP Framework + units = count + dimensions = () + type = integer +[ vars_layer ] + standard_name = ccpp_constituent_array + long_name = Array of constituents managed by CCPP Framework + units = none + state_variable = true + dimensions = (horizontal_dimension, vertical_layer_dimension, ccpp_num_constituents) + type = real | kind = kind_phys +[ const_metadata ] + standard_name = ccpp_constituent_properties_array + units = None + type = ccpp_constituent_prop_ptr_t + dimensions = (ccpp_num_constituents) +[ vars_minvalue ] + standard_name = ccpp_constituent_array_minimum_values + units = kg kg-1 + type = real | kind = kind_phys + dimensions = (ccpp_num_constituents) + protected = True diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index 61bd8657..9c7abd42 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -1,6 +1,7 @@ module test_prog - use ccpp_kinds, only: kind_phys + use ccpp_kinds, only: kind_phys + use ccpp_constituent_prop_mod, only: ccpp_constituent_properties_t implicit none private @@ -22,14 +23,29 @@ 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) + + private :: check_list private :: check_suite - private :: constituents_in ! Data from suites to dycore array - private :: constituents_out ! Data from dycore array to suires private :: advect_constituents ! Move data around + private :: check_errflg CONTAINS + subroutine check_errflg(subname, errflg, errmsg) + ! If errflg is not zero, print an error message + character(len=*), intent(in) :: subname + integer, intent(in) :: errflg + character(len=*), intent(in) :: errmsg + + if (errflg /= 0) then + write(6, '(a,i0,4a)') "Error ", errflg, " from ", trim(subname), & + ':', trim(errmsg) + end if + + end subroutine check_errflg + logical function check_list(test_list, chk_list, list_desc, suite_name) ! Check a list () against its expected value () @@ -120,7 +136,6 @@ logical function check_suite(test_suite) ! Dummy argument type(suite_info), intent(in) :: test_suite ! Local variables - integer :: sind logical :: check integer :: errflg character(len=512) :: errmsg @@ -185,54 +200,8 @@ logical function check_suite(test_suite) end if end function check_suite - logical function constituents_in(num_host_fields) result(okay) - ! Copy advected species from physics to 'dynamics' array - use test_host_mod, only: phys_state, ncnst, index_qv - use test_host_ccpp_cap, only: test_host_ccpp_gather_constituents - - ! Dummy argument - integer, intent(in) :: num_host_fields ! Packed at beginning of Q - ! Local variables - integer :: q_off - integer :: errflg - character(len=512) :: errmsg - - okay = .true. - q_off = num_host_fields + 1 - call test_host_ccpp_gather_constituents(phys_state%q(:,:,q_off:), & - errflg=errflg, errmsg=errmsg) - if (errflg /= 0) then - write(6, *) "ERROR: gather_constituents failed, '", trim(errmsg), "'" - okay = .false. - end if - - end function constituents_in - - logical function constituents_out(num_host_fields) result(okay) - ! Copy advected constituents back to physics - use test_host_mod, only: phys_state, ncnst, index_qv - use test_host_ccpp_cap, only: test_host_ccpp_update_constituents - - ! Dummy argument - integer, intent(in) :: num_host_fields ! Packed at beginning of Q - ! Local variables - integer :: q_off - integer :: errflg - character(len=512) :: errmsg - - okay = .true. - q_off = num_host_fields + 1 - call test_host_ccpp_update_constituents(phys_state%q(:,:,q_off:), & - errflg=errflg, errmsg=errmsg) - if (errflg /= 0) then - write(6, *) "ERROR: update_constituents failed, '", trim(errmsg), "'" - okay = .false. - end if - - end function constituents_out - subroutine advect_constituents() - use test_host_mod, only: phys_state, ncnst, index_qv, ncols, pver + use test_host_mod, only: phys_state, ncnst use test_host_mod, only: twist_array ! Local variables @@ -248,17 +217,22 @@ end subroutine advect_constituents !! subroutine test_host(retval, test_suites) - use test_host_mod, only: num_time_steps, num_host_advected + use ccpp_constituent_prop_mod, only: ccpp_constituent_prop_ptr_t + use test_host_mod, only: num_time_steps use test_host_mod, only: init_data, compare_data - use test_host_mod, only: ncols, pver, pverp + use test_host_mod, only: ncols, pver use test_host_ccpp_cap, only: test_host_ccpp_register_constituents + use test_host_ccpp_cap, only: test_host_ccpp_initialize_constituents use test_host_ccpp_cap, only: test_host_ccpp_number_constituents + use test_host_ccpp_cap, only: test_host_constituents_array use test_host_ccpp_cap, only: test_host_ccpp_physics_initialize use test_host_ccpp_cap, only: test_host_ccpp_physics_timestep_initial use test_host_ccpp_cap, only: test_host_ccpp_physics_run use test_host_ccpp_cap, only: test_host_ccpp_physics_timestep_final use test_host_ccpp_cap, only: test_host_ccpp_physics_finalize use test_host_ccpp_cap, only: ccpp_physics_suite_list + use test_host_ccpp_cap, only: test_host_const_get_index + use test_host_ccpp_cap, only: test_host_model_const_properties type(suite_info), intent(in) :: test_suites(:) logical, intent(out) :: retval @@ -266,12 +240,18 @@ subroutine test_host(retval, test_suites) logical :: check integer :: col_start, col_end integer :: index, sind + integer :: index_liq, index_ice integer :: time_step integer :: num_suites integer :: num_advected ! Num advected species + logical :: const_log character(len=128), allocatable :: suite_names(:) + character(len=256) :: const_str character(len=512) :: errmsg integer :: errflg + real(kind_phys), pointer :: const_ptr(:,:,:) + type(ccpp_constituent_prop_ptr_t), pointer :: const_props(:) + character(len=*), parameter :: subname = 'test_host' ! Gather and test the inspection routines num_suites = size(test_suites) @@ -301,36 +281,127 @@ subroutine test_host(retval, test_suites) end if ! Register the constituents to find out what needs advecting - call test_host_ccpp_register_constituents(suite_names(:), & - ncols, pver, pverp, errmsg=errmsg, errflg=errflg) - if (errflg /= 0) then - write(6, '(2a)') 'ERROR register_constituents: ', trim(errmsg) - end if - num_advected = test_host_ccpp_number_constituents(errmsg=errmsg, & - errflg=errflg) - if (num_advected /= 2) then - write(6, '(a,i0)') "ERROR: num advected constituents = ", num_advected - STOP 2 - end if - - ! Initialize our 'data' - call init_data(num_advected) + call host_constituents(1)%instantiate(std_name="specific_humidity", & + long_name="Specific humidity", units="kg kg-1", & + vertical_dim="vertical_layer_dimension", advected=.true., & + errcode=errflg, errmsg=errmsg) + call check_errflg(subname//'.initialize', errflg, errmsg) + if (errflg == 0) then + call test_host_ccpp_register_constituents(suite_names(:), & + host_constituents, errmsg=errmsg, errflg=errflg) + end if + if (errflg /= 0) then + write(6, '(2a)') 'ERROR register_constituents: ', trim(errmsg) + end if + ! Check number of advected constituents + if (errflg == 0) then + call test_host_ccpp_number_constituents(num_advected, errmsg=errmsg, & + errflg=errflg) + call check_errflg(subname//".num_advected", errflg, errmsg) + end if + if (num_advected /= 3) then + write(6, '(a,i0)') "ERROR: num advected constituents = ", num_advected + STOP 2 + end if + ! Initialize constituent data + call test_host_ccpp_initialize_constituents(ncols, pver, errflg, errmsg) + + ! Initialize our 'data' + if (errflg == 0) then + const_ptr => test_host_constituents_array() + call test_host_const_get_index('specific_humidity', index, & + errflg, errmsg) + call check_errflg(subname//".index_specific_humidity", errflg, errmsg) + end if + if (errflg == 0) then + call test_host_const_get_index('cloud_liquid_dry_mixing_ratio', & + index_liq, errflg, errmsg) + call check_errflg(subname//".index_cld_liq", errflg, errmsg) + end if + if (errflg == 0) then + call test_host_const_get_index('cloud_ice_dry_mixing_ratio', & + index_ice, errflg, errmsg) + call check_errflg(subname//".index_cld_ice", errflg, errmsg) + end if + call init_data(const_ptr, index, index_liq, index_ice) + ! Check some constituent properties + if (errflg == 0) then + const_props => test_host_model_const_properties() + call const_props(index)%standard_name(const_str, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to get standard_name for specific_humidity, index = ", & + index, trim(errmsg) + end if + 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 = -1 + end if + end if + if (errflg == 0) then + 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) + end if + 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 = -1 + end if + end if + if (errflg == 0) then + call const_props(index_ice)%is_mass_mixing_ratio(const_log, & + errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to get mass mixing ratio prop for cld_ice index = ", & + index_ice, trim(errmsg) + end if + end if + if (errflg == 0) then + if (.not. const_log) then + write(6, *) "ERROR: cloud ice is not a mass mixing_ratio" + errflg = -1 + end if + end if + if (errflg == 0) then + call const_props(index_ice)%is_dry(const_log, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to get dry prop for cld_ice index = ", index_ice, trim(errmsg) + end if + end if + if (errflg == 0) then + if (.not. const_log) then + write(6, *) "ERROR: cloud ice mass_mixing_ratio is not dry" + errflg = -1 + end if + end if ! Use the suite information to setup the run - do sind = 1, num_suites - call test_host_ccpp_physics_initialize(test_suites(sind)%suite_name,& - errmsg, errflg) - if (errflg /= 0) then - write(6, '(4a)') 'ERROR in initialize of ', & - trim(test_suites(sind)%suite_name), ': ', trim(errmsg) - exit - end if + do sind = 1, num_suites + if (errflg == 0) then + call test_host_ccpp_physics_initialize( & + test_suites(sind)%suite_name, errmsg, errflg) + if (errflg /= 0) then + write(6, '(4a)') 'ERROR in initialize of ', & + trim(test_suites(sind)%suite_name), ': ', trim(errmsg) + exit + end if + end if end do ! Loop over time steps do time_step = 1, num_time_steps ! Initialize the timestep do sind = 1, num_suites - if (retval) then + if (errflg == 0) then call test_host_ccpp_physics_timestep_initial( & test_suites(sind)%suite_name, errmsg, errflg) if (errflg /= 0) then @@ -348,15 +419,17 @@ subroutine test_host(retval, test_suites) do sind = 1, num_suites do index = 1, size(test_suites(sind)%suite_parts) - call test_host_ccpp_physics_run( & - test_suites(sind)%suite_name, & - test_suites(sind)%suite_parts(index), & - col_start, col_end, errmsg, errflg) - if (errflg /= 0) then - write(6, '(5a)') trim(test_suites(sind)%suite_name), & - '/', trim(test_suites(sind)%suite_parts(index)), & - ': ', trim(errmsg) - exit + if (errflg == 0) then + call test_host_ccpp_physics_run( & + test_suites(sind)%suite_name, & + test_suites(sind)%suite_parts(index), & + col_start, col_end, errmsg, errflg) + if (errflg /= 0) then + write(6, '(5a)') trim(test_suites(sind)%suite_name), & + '/', trim(test_suites(sind)%suite_parts(index)),& + ': ', trim(errmsg) + exit + end if end if end do end do @@ -370,16 +443,13 @@ subroutine test_host(retval, test_suites) if (errflg /= 0) then write(6, '(3a)') trim(test_suites(sind)%suite_name), ': ', & trim(errmsg) + exit end if end do ! Run "dycore" if (errflg == 0) then - check = constituents_in(num_host_advected) - end if - if (check) then call advect_constituents() - check = constituents_out(num_host_advected) end if end do ! End time step loop @@ -392,13 +462,14 @@ subroutine test_host(retval, test_suites) trim(errmsg) write(6,'(2a)') 'An error occurred in ccpp_timestep_final, ', & 'Exiting...' + exit end if end if end do if (errflg == 0) then ! Run finished without error, check answers - if (compare_data(num_advected + num_host_advected)) then + if (compare_data(num_advected)) then write(6, *) 'Answers are correct!' errflg = 0 else @@ -418,39 +489,42 @@ program test implicit none - character(len=cs), target :: test_parts1(1) = (/ 'physics ' /) - character(len=cm), target :: test_invars1(7) = (/ & + character(len=cs), target :: test_parts1(1) + character(len=cm), target :: test_invars1(6) + character(len=cm), target :: test_outvars1(5) + character(len=cm), target :: test_reqvars1(8) + + type(suite_info) :: test_suites(1) + logical :: run_okay + + test_parts1 = (/ 'physics '/) + test_invars1 = (/ & 'cloud_ice_dry_mixing_ratio ', & - 'cloud_liquid_dry_mixing_ratio ', & 'surface_air_pressure ', & 'temperature ', & 'time_step_for_physics ', & 'water_temperature_at_freezing ', & 'water_vapor_specific_humidity ' /) - character(len=cm), target :: test_outvars1(6) = (/ & + test_outvars1 = (/ & 'ccpp_error_message ', & 'ccpp_error_code ', & 'temperature ', & 'water_vapor_specific_humidity ', & - 'cloud_liquid_dry_mixing_ratio ', & 'cloud_ice_dry_mixing_ratio ' /) - character(len=cm), target :: test_reqvars1(9) = (/ & + test_reqvars1 = (/ & 'surface_air_pressure ', & 'temperature ', & 'time_step_for_physics ', & - 'cloud_liquid_dry_mixing_ratio ', & 'cloud_ice_dry_mixing_ratio ', & 'water_temperature_at_freezing ', & 'water_vapor_specific_humidity ', & 'ccpp_error_message ', & 'ccpp_error_code ' /) - type(suite_info) :: test_suites(1) - logical :: run_okay ! Setup expected test suite info test_suites(1)%suite_name = 'cld_suite' - test_suites(1)%suite_parts => test_parts1 + test_suites(1)%suite_parts => test_parts1 test_suites(1)%suite_input_vars => test_invars1 test_suites(1)%suite_output_vars => test_outvars1 test_suites(1)%suite_required_vars => test_reqvars1 diff --git a/test/advection_test/test_host_data.F90 b/test/advection_test/test_host_data.F90 index 10183cd6..fce25c66 100644 --- a/test/advection_test/test_host_data.F90 +++ b/test/advection_test/test_host_data.F90 @@ -5,12 +5,9 @@ module test_host_data !> \section arg_table_physics_state Argument Table !! \htmlinclude arg_table_physics_state.html type physics_state - real(kind_phys), dimension(:), allocatable :: & - ps ! surface pressure - real(kind_phys), dimension(:,:), allocatable :: & - temp ! temperature - real(kind_phys), dimension(:,:,:),allocatable :: & - q ! constituent mixing ratio (kg/kg moist or dry air depending on type) + real(kind_phys), allocatable :: ps(:) ! surface pressure + real(kind_phys), allocatable :: temp(:,:) ! temperature + real(kind_phys), pointer :: q(:,:,:) => NULL() ! constituent array end type physics_state public allocate_physics_state @@ -20,7 +17,7 @@ module test_host_data subroutine allocate_physics_state(cols, levels, constituents, state) integer, intent(in) :: cols integer, intent(in) :: levels - integer, intent(in) :: constituents + real(kind_phys), pointer :: constituents(:,:,:) type(physics_state), intent(out) :: state if (allocated(state%ps)) then @@ -31,10 +28,12 @@ subroutine allocate_physics_state(cols, levels, constituents, state) deallocate(state%temp) end if allocate(state%temp(cols, levels)) - if (allocated(state%q)) then - deallocate(state%q) + if (associated(state%q)) then + ! Do not deallocate (we do not own this array) + nullify(state%q) end if - allocate(state%q(cols, levels, constituents)) + ! Point to the advected constituents array + state%q => constituents end subroutine allocate_physics_state diff --git a/test/advection_test/test_host_mod.F90 b/test/advection_test/test_host_mod.F90 index 3e4f60a5..560b7619 100644 --- a/test/advection_test/test_host_mod.F90 +++ b/test/advection_test/test_host_mod.F90 @@ -15,9 +15,8 @@ module test_host_mod integer, parameter :: ncols = 10 integer, parameter :: pver = 5 integer, parameter :: pverP = pver + 1 - integer, parameter :: num_host_advected = 1 integer, protected :: ncnst = -1 - integer, parameter :: index_qv = 1 + integer, protected :: index_qv = -1 real(kind_phys) :: dt real(kind_phys), parameter :: tfreeze = 273.15_kind_phys type(physics_state) :: phys_state @@ -30,24 +29,35 @@ module test_host_mod real(kind_phys), private, allocatable :: check_vals(:,:,:) real(kind_phys), private :: check_temp(ncols, pver) + integer, private :: ind_liq = -1 + integer, private :: ind_ice = -1 contains - subroutine init_data(num_advected) + subroutine init_data(constituent_array, index_qv_use, index_liq, index_ice) - integer, intent(in) :: num_advected ! From suites + ! 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 :: col - integer :: lev - integer :: cind - integer :: itime - real(kind_phys) :: qmax + ! Local variables + integer :: col + integer :: lev + integer :: cind + integer :: itime + real(kind_phys) :: qmax + real(kind_phys), parameter :: inc = 0.1_kind_phys ! Allocate and initialize state ! Temperature starts above freezing and decreases to -30C ! water vapor is initialized in odd columns to different amounts - ncnst = num_advected + num_host_advected - call allocate_physics_state(ncols, pver, ncnst, phys_state) + ncnst = SIZE(constituent_array, 3) + call allocate_physics_state(ncols, pver, constituent_array, phys_state) + index_qv = index_qv_use + ind_liq = index_liq + ind_ice = index_ice allocate(check_vals(ncols, pver, ncnst)) check_vals(:,:,:) = 0.0_kind_phys do lev = 1, pver @@ -66,8 +76,8 @@ subroutine init_data(num_advected) ! Do timestep 1 do col = 1, ncols, 2 check_temp(col, 1) = check_temp(col, 1) + 0.5_kind_phys - check_vals(col, 1, 1) = check_vals(col, 1, 1) - 0.1_kind_phys - check_vals(col, 1, 3) = check_vals(col, 1, 3) + 0.1_kind_phys + check_vals(col, 1, index_qv) = check_vals(col, 1, index_qv) - inc + check_vals(col, 1, ind_liq) = check_vals(col, 1, ind_liq) + inc end do do itime = 1, num_time_steps do cind = 1, ncnst @@ -82,7 +92,6 @@ subroutine twist_array(array) real(kind_phys), intent(inout) :: array(:,:) ! Local variables - integer :: q_ind ! Constituent index integer :: icol, ilev ! Field coordinates integer :: idir ! 'w' sign integer :: levb, leve ! Starting and ending level indices @@ -111,7 +120,6 @@ logical function compare_data(ncnst) integer :: col integer :: lev integer :: cind - integer :: nind logical :: need_header real(kind_phys) :: check real(kind_phys) :: denom From 52e59859341e52bf56546670c6df6af2d9bbec01 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 28 Aug 2023 03:09:14 -0600 Subject: [PATCH 02/11] cleanup and fixes --- scripts/ccpp_capgen.py | 7 +- scripts/ccpp_suite.py | 4 +- scripts/constituents.py | 4 +- scripts/ddt_library.py | 107 ++++++----- scripts/fortran_tools/fortran_write.py | 200 ++++++++++++++++---- scripts/fortran_tools/parse_fortran_file.py | 52 +++-- scripts/host_cap.py | 9 +- scripts/parse_tools/xml_tools.py | 176 +++++++---------- 8 files changed, 338 insertions(+), 221 deletions(-) diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index 8938a43f..8522f352 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -31,7 +31,7 @@ ## Capture the Framework root _SCRIPT_PATH = os.path.dirname(__file__) -_FRAMEWORK_ROOT = os.path.abspath(os.path.join(__SCRIPT_PATH, os.pardir)) +_FRAMEWORK_ROOT = os.path.abspath(os.path.join(_SCRIPT_PATH, os.pardir)) _SRC_ROOT = os.path.join(_FRAMEWORK_ROOT, "src") ## Init this now so that all Exceptions can be trapped _LOGGER = init_log(os.path.basename(__file__)) @@ -614,7 +614,7 @@ def capgen(run_env): # We always need to parse the ccpp_constituent_prop_ptr_t DDT ##XXgoldyXX: Should this be in framework_env.py? const_prop_mod = os.path.join(src_dir, "ccpp_constituent_prop_mod.meta") - if const_prop_mod and not in scheme_files: + if const_prop_mod not in scheme_files: scheme_files = [const_prop_mod] + scheme_files # end if # Next, parse the scheme files @@ -653,7 +653,8 @@ def capgen(run_env): cap_filenames = ccpp_api.write(outtemp_dir, run_env) if run_env.generate_host_cap: # Create a cap file - host_files = [write_host_cap(host_model, ccpp_api, + cap_module = host_model.ccpp_cap_name() + host_files = [write_host_cap(host_model, ccpp_api, cap_module, outtemp_dir, run_env)] else: host_files = list() diff --git a/scripts/ccpp_suite.py b/scripts/ccpp_suite.py index d28a026f..cf34d43c 100644 --- a/scripts/ccpp_suite.py +++ b/scripts/ccpp_suite.py @@ -843,8 +843,8 @@ def write_req_vars_sub(self, ofile, errmsg_name, errcode_name): else: inout_vars[0].add(stdname) # end if - elif intent == 'out' and phase != 'initialize' and constituent - and not const_initialized_in_physics[stdname]: + elif (intent == 'out' and phase != 'initialize' and constituent + and not const_initialized_in_physics[stdname]): # constituents HAVE to be initialized in the init phase because the dycore needs to advect them emsg = "constituent variable '{}' cannot be initialized in the '{}' phase" raise CCPPError(emsg.format(stdname, phase)) diff --git a/scripts/constituents.py b/scripts/constituents.py index 7a60af04..d8935130 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -9,7 +9,7 @@ """ # CCPP framework imports -from parse_tools import ParseInternalError, type_name +from parse_tools import ParseInternalError from metavar import VarDictionary ######################################################################## @@ -406,7 +406,7 @@ def constituent_module_name(self): if not ((self.parent is not None) and hasattr(self.parent.parent, "constituent_module")): emsg = "ConstituentVarDict parent not HostModel?" - emsg += f"\nparent is '{type_name(self.parent.parent)}'" + emsg += f"\nparent is '{type(self.parent.parent)}'" raise ParseInternalError(emsg) # end if return self.parent.parent.constituent_module diff --git a/scripts/ddt_library.py b/scripts/ddt_library.py index 30614226..118fb3d7 100644 --- a/scripts/ddt_library.py +++ b/scripts/ddt_library.py @@ -44,11 +44,11 @@ def __init__(self, new_field, var_ref, run_env, recur=False): else: # Recurse to find correct (tail) location for self.__field = VarDDT(new_field, var_ref.field, run_env, recur=True) - # End if + # end if if ((not recur) and run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG)): run_env.logger.debug('Adding DDT field, {}'.format(self)) - # End if + # end if def is_ddt(self): """Return True iff is a DDT type.""" @@ -66,18 +66,18 @@ def get_prop_value(self, name): pvalue = super().get_prop_value(name) else: pvalue = self.field.get_prop_value(name) - # End if + # end if return pvalue def intrinsic_elements(self, check_dict=None): """Return the Var intrinsic elements for the leaf Var object. - See Var.intrinsic_elem for details + See Var.intrinsic_elements for details """ if self.field is None: pvalue = super().intrinsic_elements(check_dict=check_dict) else: pvalue = self.field.intrinsic_elements(check_dict=check_dict) - # End if + # end if return pvalue def clone(self, subst_dict, source_name=None, source_type=None, @@ -98,7 +98,7 @@ def clone(self, subst_dict, source_name=None, source_type=None, source_name=source_name, source_type=source_type, context=context) - # End if + # end if return clone_var def call_string(self, var_dict, loop_vars=None): @@ -109,7 +109,7 @@ def call_string(self, var_dict, loop_vars=None): if self.field is not None: call_str += '%' + self.field.call_string(var_dict, loop_vars=loop_vars) - # End if + # end if return call_str def write_def(self, outfile, indent, ddict, allocatable=False, dummy=False): @@ -122,7 +122,7 @@ def write_def(self, outfile, indent, ddict, allocatable=False, dummy=False): else: self.field.write_def(outfile, indent, ddict, allocatable=allocatable, dummy=dummy) - # End if + # end if @staticmethod def __var_rep(var, prefix=""): @@ -137,14 +137,14 @@ def __var_rep(var, prefix=""): lstr = '{}%{}({})'.format(prefix, lname, ', '.join(ldims)) else: lstr = '{}({})'.format(lname, ', '.join(ldims)) - # End if + # end if else: if prefix: lstr = '{}%{}'.format(prefix, lname) else: lstr = '{}'.format(lname) - # End if - # End if + # end if + # end if return lstr def __repr__(self): @@ -160,9 +160,9 @@ def __repr__(self): elif isinstance(field, Var): lstr = self.__var_rep(field, prefix=lstr) field = None - # End if + # end if sep = '%' - # End while + # end while return "".format(lstr) def __str__(self): @@ -201,33 +201,33 @@ def __init__(self, name, run_env, ddts=None, logger=None): ddts = list() elif not isinstance(ddts, list): ddts = [ddts] - # End if + # end if # Add all the DDT headers, then process for ddt in ddts: if not isinstance(ddt, MetadataSection): errmsg = 'Invalid DDT metadata type, {}' - raise ParseInternalError(errmsg.format(type(ddt))) - # End if + raise ParseInternalError(errmsg.format(type(ddt).__name__)) + # end if if not ddt.header_type == 'ddt': errmsg = 'Metadata table header is for a {}, should be DDT' raise ParseInternalError(errmsg.format(ddt.header_type)) - # End if + # end if if ddt.title in self: errmsg = "Duplicate DDT, {}, found{}, original{}" ctx = context_string(ddt.source.context) octx = context_string(self[ddt.title].source.context) raise CCPPError(errmsg.format(ddt.title, ctx, octx)) - # End if - if logger is not None: - lmsg = 'Adding DDT {} to {}' - logger.debug(lmsg.format(ddt.title, self.name)) - # End if + # end if + if logger and logger.isEnabledFor(logging.DEBUG): + lmsg = f"Adding DDT {ddt.title} to {self.name}" + logger.debug(lmsg) + # end if self[ddt.title] = ddt dlen = len(ddt.module) if dlen > self.__max_mod_name_len: self.__max_mod_name_len = dlen - # End if - # End for + # end if + # end for def check_ddt_type(self, var, header, lname=None): """If is a DDT, check to make sure it is in this DDT library. @@ -239,16 +239,21 @@ def check_ddt_type(self, var, header, lname=None): if vtype not in self: if lname is None: lname = var.get_prop_value('local_name') - # End if + # end if errmsg = 'Variable {} is of unknown type ({}) in {}' ctx = context_string(var.context) raise CCPPError(errmsg.format(lname, vtype, header.title, ctx)) - # End if - # End if (no else needed) + # end if + # end if (no else needed) - def collect_ddt_fields(self, var_dict, var, run_env, ddt=None): + def collect_ddt_fields(self, var_dict, var, run_env, + ddt=None, skip_duplicates=False): """Add all the reachable fields from DDT variable of type, - to . Each field is added as a VarDDT. + to . Each field is added as a VarDDT. + Note: By default, it is an error to try to add a duplicate + field to (i.e., the field already exists in + or one of its parents). To simply skip duplicate + fields, set to True. """ if ddt is None: vtype = var.get_prop_value('type') @@ -259,8 +264,8 @@ def collect_ddt_fields(self, var_dict, var, run_env, ddt=None): ctx = context_string(var.context) errmsg = "Variable, {}, is not a known DDT{}" raise ParseInternalError(errmsg.format(lname, ctx)) - # End if - # End if + # end if + # end if for dvar in ddt.variable_list(): subvar = VarDDT(dvar, var, self.run_env) dvtype = dvar.get_prop_value('type') @@ -268,22 +273,24 @@ def collect_ddt_fields(self, var_dict, var, run_env, ddt=None): # If DDT in our library, we need to add sub-fields recursively. subddt = self[dvtype] self.collect_ddt_fields(var_dict, subvar, run_env, ddt=subddt) - else: - # add_variable only checks the current dictionary. For a - # DDT, the variable also cannot be in our parent dictionaries. - stdname = dvar.get_prop_value('standard_name') - pvar = var_dict.find_variable(standard_name=stdname, - any_scope=True) - if pvar: - emsg = "Attempt to add duplicate DDT sub-variable, {}{}." - emsg += "\nVariable originally defined{}" - ntx = context_string(dvar.context) - ctx = context_string(pvar.context) - raise CCPPError(emsg.format(stdname, ntx, ctx)) - # end if - # Add this intrinsic to + # end if + # add_variable only checks the current dictionary. By default, + # for a DDT, the variable also cannot be in our parent + # dictionaries. + stdname = dvar.get_prop_value('standard_name') + pvar = var_dict.find_variable(standard_name=stdname, any_scope=True) + if pvar and (not skip_duplicates): + emsg = "Attempt to add duplicate DDT sub-variable, {}{}." + emsg += "\nVariable originally defined{}" + ntx = context_string(dvar.context) + ctx = context_string(pvar.context) + raise CCPPError(emsg.format(stdname, ntx, ctx)) + # end if + # Add this intrinsic to + if not pvar: var_dict.add_variable(subvar, run_env) - # End for + # end if + # end for def ddt_modules(self, variable_list, ddt_mods=None): """Collect information for module use statements. @@ -292,14 +299,14 @@ def ddt_modules(self, variable_list, ddt_mods=None): """ if ddt_mods is None: ddt_mods = set() # Need a new set for every call - # End if + # end if for var in variable_list: vtype = var.get_prop_value('type') if vtype in self: module = self[vtype].module ddt_mods.add((module, vtype)) - # End if - # End for + # end if + # end for return ddt_mods def write_ddt_use_statements(self, variable_list, outfile, indent, pad=0): @@ -313,7 +320,7 @@ def write_ddt_use_statements(self, variable_list, outfile, indent, pad=0): slen = ' '*(pad - len(dmod)) ustring = 'use {},{} only: {}' outfile.write(ustring.format(dmod, slen, dtype), indent) - # End for + # end for @property def name(self): diff --git a/scripts/fortran_tools/fortran_write.py b/scripts/fortran_tools/fortran_write.py index a238dcc5..f9bcfa3f 100644 --- a/scripts/fortran_tools/fortran_write.py +++ b/scripts/fortran_tools/fortran_write.py @@ -4,7 +4,9 @@ """Code to write Fortran code """ -class FortranWriter(object): +import math + +class FortranWriter: """Class to turn output into properly continued and indented Fortran code >>> FortranWriter("foo.F90", 'r', 'test', 'mod_name') #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): @@ -59,9 +61,9 @@ class FortranWriter(object): def indent(self, level=0, continue_line=False): 'Return an indent string for any level' - indent = self._indent * level + indent = self.indent_size * level if continue_line: - indent = indent + self._continue_indent + indent = indent + self.__continue_indent # End if return indent*' ' @@ -71,22 +73,55 @@ def find_best_break(self, choices, last=None): """Find the best line break point given . If is present, use it as a target line length.""" if last is None: - last = self._line_fill + last = self.__line_fill # End if # Find largest good break - possible = [x for x in choices if x < last] + possible = [x for x in choices if 0 < x < last] if not possible: - best = self._line_max + 1 + best = self.__line_max + 1 else: best = max(possible) # End if - if (best > self._line_max) and (last < self._line_max): - best = self.find_best_break(choices, last=self._line_max) + if (best > self.__line_max) and (last < self.__line_max): + best = self.find_best_break(choices, last=self.__line_max) # End if return best ########################################################################### + @staticmethod + def _in_quote(test_str): + """Return True if ends in a character context. + >>> FortranWriter._in_quote("hi'mom") + True + >>> FortranWriter._in_quote("hi mom") + False + >>> FortranWriter._in_quote("'hi mom'") + False + >>> FortranWriter._in_quote("'hi"" mom'") + False + """ + in_single_char = False + in_double_char = False + for char in test_str: + if in_single_char: + if char == "'": + in_single_char = False + # end if + elif in_double_char: + if char == '"': + in_double_char = False + # end if + elif char == "'": + in_single_char = True + elif char == '"': + in_double_char = True + # end if + # end for + return in_single_char or in_double_char + + ########################################################################### + def write(self, statement, indent_level, continue_line=False): """Write to the open file, indenting to (see self.indent). @@ -102,9 +137,18 @@ def write(self, statement, indent_level, continue_line=False): # End for else: istr = self.indent(indent_level, continue_line) - outstr = istr + statement.strip() + ostmt = statement.strip() + is_comment_stmt = ostmt and (ostmt[0] == '!') + in_comment = "" + if ostmt and (ostmt[0] != '&'): + # Skip indent for continue that is in the middle of a + # token or a quoted region + outstr = istr + ostmt + else: + outstr = ostmt + # end if line_len = len(outstr) - if line_len > self._line_fill: + if line_len > self.__line_fill: # Collect pretty break points spaces = list() commas = list() @@ -125,9 +169,14 @@ def write(self, statement, indent_level, continue_line=False): elif outstr[sptr] == '"': in_double_char = True elif outstr[sptr] == '!': - # Comment in non-character context, suck in rest of line + # Comment in non-character context spaces.append(sptr-1) - sptr = line_len - 1 + in_comment = "! " # No continue for comment + if ((not is_comment_stmt) and + (sptr >= self.__max_comment_start)): + # suck in rest of line + sptr = line_len - 1 + # end if elif outstr[sptr] == ' ': # Non-quote spaces are where we can break spaces.append(sptr) @@ -140,31 +189,62 @@ def write(self, statement, indent_level, continue_line=False): # End if (no else, other characters will be ignored) sptr = sptr + 1 # End while + # Before looking for best space, reject any that are on a + # comment line but before any significant characters + if outstr.lstrip()[0] == '!': + first_space = outstr.index('!') + 1 + while ((outstr[first_space] == '!' or + outstr[first_space] == ' ') and + (first_space < line_len)): + first_space += 1 + # end while + if min(spaces) < first_space: + spaces = [x for x in spaces if x >= first_space] + # end if best = self.find_best_break(spaces) - if best >= self._line_fill: - best = self.find_best_break(commas) + if best >= self.__line_fill: + best = min(best, self.find_best_break(commas)) # End if - if best > self._line_max: - # This is probably a bad situation that might not - # compile, just write the line and hope for the best. - line_continue = False - elif len(outstr) > best: - # If next line is just comment, do not use continue - # NB: Is this a Fortran issue or just a gfortran issue? - line_continue = outstr[best+1:].lstrip()[0] != '!' - else: - line_continue = True + line_continue = False + if best >= self.__line_max: + # This is probably a bad situation so we have to break + # in an ugly spot + best = self.__line_max - 1 + if len(outstr) > best: + line_continue = '&' + # end if + # end if + if len(outstr) > best: + if self._in_quote(outstr[0:best+1]): + line_continue = '&' + else: + # If next line is just comment, do not use continue + line_continue = outstr[best+1:].lstrip()[0] != '!' + # end if + elif not line_continue: + line_continue = len(outstr) > best # End if + if in_comment or is_comment_stmt: + line_continue = False + # end if if line_continue: - fill = "{}&".format((self._line_fill - best)*' ') + fill = "{}&".format((self.__line_fill - best)*' ') else: - fill = '' + fill = "" # End if - self._file.write("{}{}\n".format(outstr[0:best+1], fill)) - statement = outstr[best+1:] + outline = f"{outstr[0:best+1]}{fill}".rstrip() + self.__file.write(f"{outline}\n") + if best <= 0: + imsg = "Internal ERROR: Unable to break line" + raise ValueError(f"{imsg}, '{statement}'") + # end if + statement = in_comment + outstr[best+1:] + if isinstance(line_continue, str) and statement: + statement = line_continue + statement + # end if self.write(statement, indent_level, continue_line=line_continue) else: - self._file.write("{}\n".format(outstr)) + self.__file.write("{}\n".format(outstr)) # End if # End if @@ -175,7 +255,7 @@ def __init__(self, filename, mode, file_description, module_name, line_fill=None, line_max=None): """Initialize thie FortranWriter object. Some boilerplate is written automatically.""" - self.__file_desc = file_description + self.__file_desc = file_description.replace('\n', '\n!! ') self.__module = module_name # We only handle writing situations (for now) and only text if 'r' in mode: @@ -184,26 +264,27 @@ def __init__(self, filename, mode, file_description, module_name, if 'b' in mode: raise ValueError('Binary mode not allowed in FortranWriter object') # End if - self._file = open(filename, mode) + self.__file = open(filename, mode) if indent is None: - self._indent = FortranWriter.__INDENT + self.__indent = FortranWriter.__INDENT else: - self._indent = indent + self.__indent = indent # End if if continue_indent is None: - self._continue_indent = FortranWriter.__CONTINUE_INDENT + self.__continue_indent = FortranWriter.__CONTINUE_INDENT else: - self._continue_indent = continue_indent + self.__continue_indent = continue_indent # End if if line_fill is None: - self._line_fill = FortranWriter.__LINE_FILL + self.__line_fill = FortranWriter.__LINE_FILL else: - self._line_fill = line_fill + self.__line_fill = line_fill # End if + self.__max_comment_start = math.ceil(self.__line_fill * 3 / 4) if line_max is None: - self._line_max = FortranWriter.__LINE_MAX + self.__line_max = FortranWriter.__LINE_MAX else: - self._line_max = line_max + self.__line_max = line_max # End if ########################################################################### @@ -234,7 +315,7 @@ def __enter__(self, *args): def __exit__(self, *args): self.write(FortranWriter.__MOD_FOOTER.format(module=self.__module), 0) - self._file.close() + self.__file.close() return False ########################################################################### @@ -247,6 +328,45 @@ def module_header(self): ########################################################################### + def comment(self, comment, indent): + """Write a Fortran comment with contents, """ + mlcomment = comment.replace('\n', '\n! ') # No backslash in f string + self.write(f"! {mlcomment}", indent) + + ########################################################################### + + def blank_line(self): + """Write a blank line""" + self.write("", 0) + + ########################################################################### + + def include(self, filename): + """Insert the contents of verbatim.""" + with open(filename, 'r') as infile: + for line in infile: + self.__file.write(line) + # end for + # end with + + ########################################################################### + + @property + def line_fill(self): + """Return the target line length for this Fortran file""" + return self.__line_fill + + ########################################################################### + + @property + def indent_size(self): + """Return the number of spaces for each indent level for this + Fortran file + """ + return self.__indent + + ########################################################################### + @classmethod def copyright(cls): """Return the standard Fortran file copyright string""" diff --git a/scripts/fortran_tools/parse_fortran_file.py b/scripts/fortran_tools/parse_fortran_file.py index f37b3377..7c6493e7 100755 --- a/scripts/fortran_tools/parse_fortran_file.py +++ b/scripts/fortran_tools/parse_fortran_file.py @@ -563,29 +563,39 @@ def parse_preamble_data(statements, pobj, spec_name, endmatch, run_env): module=spec_name, var_dict=var_dict) mheaders.append(mheader) - if run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG): + if (run_env.logger and + run_env.logger.isEnabledFor(logging.DEBUG)): ctx = context_string(pobj, nodir=True) msg = 'Adding header {}{}' run_env.logger.debug(msg.format(mheader.table_name, ctx)) + # end if break - elif ((type_def is not None) and (active_table is not None) and - (type_def[0].lower() == active_table.lower())): + elif type_def is not None: # Put statement back so caller knows where we are statements.insert(0, statement) - statements, ddt = parse_type_def(statements, type_def, - spec_name, pobj, run_env) - if ddt is None: - ctx = context_string(pobj, nodir=True) - msg = "No DDT found at '{}'{}" - raise CCPPError(msg.format(statement, ctx)) - # End if - mheaders.append(ddt) - if run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG): - ctx = context_string(pobj, nodir=True) - msg = 'Adding DDT {}{}' - run_env.logger.debug(msg.format(ddt.table_name, ctx)) - # End if - active_table = None + if ((active_table is not None) and + (type_def[0].lower() == active_table.lower())): + statements, ddt = parse_type_def(statements, type_def, + spec_name, pobj, run_env) + if ddt is None: + ctx = context_string(pobj, nodir=True) + msg = "No DDT found at '{}'{}" + raise CCPPError(msg.format(statement, ctx)) + # End if + mheaders.append(ddt) + if (run_env.logger and + run_env.logger.isEnabledFor(logging.DEBUG)): + ctx = context_string(pobj, nodir=True) + msg = 'Adding DDT {}{}' + run_env.logger.debug(msg.format(ddt.table_name, ctx)) + # End if + active_table = None + else: + # We found a type definition but it is not one with + # metadata. Just parse it and throw away what is found. + _ = parse_type_def(statements, type_def, + spec_name, pobj, run_env) + # end if elif active_table is not None: # We should have a variable definition to add if ((not is_comment_statement(statement)) and @@ -786,6 +796,7 @@ def parse_specification(pobj, statements, run_env, mod_name=None, # End program or module pmatch = endmatch.match(statement) asmatch = _ARG_TABLE_START_RE.match(statement) + type_def = fortran_type_definition(statement) if pmatch is not None: # We never found a contains statement inspec = False @@ -812,6 +823,13 @@ def parse_specification(pobj, statements, run_env, mod_name=None, # End if inspec = pobj.in_region('MODULE', region_name=mod_name) break + elif type_def: + # We have a type definition without metadata + # Just parse it and throw away what is found. + # Put statement back so caller knows where we are + statements.insert(0, statement) + _ = parse_type_def(statements, type_def, + spec_name, pobj, run_env) elif is_contains_statement(statement, inmod): inspec = False break diff --git a/scripts/host_cap.py b/scripts/host_cap.py index d833c649..479f688b 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -8,7 +8,7 @@ import logging import os # CCPP framework imports -from ccpp_suite import API, API_SOURCE_NAME +from ccpp_suite import API from ccpp_state_machine import CCPP_STATE_MACH from constituents import ConstituentVarDict, CONST_DDT_NAME, CONST_DDT_MOD from constituents import CONST_OBJ_STDNAME @@ -33,7 +33,8 @@ end subroutine {host_model}_ccpp_physics_{stage} ''' -_API_SOURCE = ParseSource(API_SOURCE_NAME, "MODULE", +_API_SRC_NAME = "CCPP_API" +_API_SOURCE = ParseSource(_API_SRC_NAME, "MODULE", ParseContext(filename="host_cap.F90")) _API_DUMMY_RUN_ENV = CCPPFrameworkEnv(None, ndict={'host_files':'', @@ -59,7 +60,7 @@ 'suites':''}) # Used to prevent loop substitution lookups -_BLANK_DICT = VarDictionary(API_SOURCE_NAME, _MVAR_DUMMY_RUN_ENV) +_BLANK_DICT = VarDictionary(_API_SRC_NAME, _MVAR_DUMMY_RUN_ENV) ############################################################################### def suite_part_list(suite, stage): @@ -509,7 +510,7 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): subst_dict['intent'] = 'inout' # End if hdvars.append(hvar.clone(subst_dict, - source_name=API_SOURCE_NAME)) + source_name=_API_SRC_NAME)) # End for lnames = [x.get_prop_value('local_name') for x in apivars + hdvars] api_vlist = ", ".join(lnames) diff --git a/scripts/parse_tools/xml_tools.py b/scripts/parse_tools/xml_tools.py index 414ffc5a..07074935 100644 --- a/scripts/parse_tools/xml_tools.py +++ b/scripts/parse_tools/xml_tools.py @@ -7,35 +7,35 @@ # Python library imports from __future__ import print_function import os -import os.path import re +import shutil import subprocess import sys import xml.etree.ElementTree as ET sys.path.insert(0, os.path.dirname(__file__)) -# pylint: disable=wrong-import-position -try: - from distutils.spawn import find_executable - _XMLLINT = find_executable('xmllint') -except ImportError: - _XMLLINT = None -# End try # CCPP framework imports from parse_source import CCPPError from parse_log import init_log, set_log_to_null -# pylint: enable=wrong-import-position # Global data _INDENT_STR = " " +_XMLLINT = shutil.which('xmllint') # Blank if not installed beg_tag_re = re.compile(r"([<][^/][^<>]*[^/][>])") end_tag_re = re.compile(r"([<][/][^<>/]+[>])") simple_tag_re = re.compile(r"([<][^/][^<>/]+[/][>])") # Find python version -PY3 = sys.version_info[0] > 2 PYSUBVER = sys.version_info[1] _LOGGER = None +############################################################################### +class XMLToolsInternalError(ValueError): +############################################################################### + """Error class for reporting internal errors""" + def __init__(self, message): + """Initialize this exception""" + super().__init__(message) + ############################################################################### def call_command(commands, logger, silent=False): ############################################################################### @@ -53,35 +53,12 @@ def call_command(commands, logger, silent=False): result = False outstr = '' try: - if PY3: - if PYSUBVER > 6: - cproc = subprocess.run(commands, check=True, - capture_output=True) - if not silent: - logger.debug(cproc.stdout) - # End if - result = cproc.returncode == 0 - elif PYSUBVER >= 5: - cproc = subprocess.run(commands, check=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - if not silent: - logger.debug(cproc.stdout) - # End if - result = cproc.returncode == 0 - else: - raise ValueError("Python 3 must be at least version 3.5") - # End if - else: - pproc = subprocess.Popen(commands, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - output, _ = pproc.communicate() - if not silent: - logger.debug(output) - # End if - result = pproc.returncode == 0 - # End if + cproc = subprocess.run(commands, check=True, + capture_output=True) + if not silent: + logger.debug(cproc.stdout) + # end if + result = cproc.returncode == 0 except (OSError, CCPPError, subprocess.CalledProcessError) as err: if silent: result = False @@ -90,9 +67,9 @@ def call_command(commands, logger, silent=False): emsg = "Execution of '{}' failed with code:\n" outstr = emsg.format(cmd, err.returncode) outstr += "{}".format(err.output) - raise CCPPError(outstr) - # End if - # End of try + raise CCPPError(outstr) from err + # end if + # end of try return result ############################################################################### @@ -102,6 +79,8 @@ def find_schema_version(root): Find the version of the host registry file represented by root >>> find_schema_version(ET.fromstring('')) [1, 0] + >>> find_schema_version(ET.fromstring('')) + [2, 0] >>> find_schema_version(ET.fromstring('')) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: Illegal version string, '1.a' @@ -118,33 +97,33 @@ def find_schema_version(root): verbits = None if 'version' not in root.attrib: raise CCPPError("version attribute required") - # End if + # end if version = root.attrib['version'] versplit = version.split('.') try: if len(versplit) != 2: raise CCPPError('oops') - # End if (no else needed) + # end if (no else needed) try: verbits = [int(x) for x in versplit] except ValueError as verr: - raise CCPPError(verr) - # End try + raise CCPPError(verr) from verr + # end try if verbits[0] < 1: raise CCPPError('Major version must be at least 1') - # End if + # end if if verbits[1] < 0: raise CCPPError('Minor version must be non-negative') - # End if + # end if except CCPPError as verr: errstr = """Illegal version string, '{}' Format must be .""" ve_str = str(verr) if ve_str: errstr = ve_str + '\n' + errstr - # End if - raise CCPPError(errstr.format(version)) - # End try + # end if + raise CCPPError(errstr.format(version)) from verr + # end try return verbits ############################################################################### @@ -161,10 +140,10 @@ def find_schema_file(schema_root, version, schema_path=None): schema_file = os.path.join(schema_path, schema_filename) else: schema_file = schema_filename - # End if + # end if if os.path.exists(schema_file): return schema_file - # End if + # end if return None ############################################################################### @@ -178,38 +157,43 @@ def validate_xml_file(filename, schema_root, version, logger, # Check the filename if not os.path.isfile(filename): raise CCPPError("validate_xml_file: Filename, '{}', does not exist".format(filename)) - # End if + # end if if not os.access(filename, os.R_OK): raise CCPPError("validate_xml_file: Cannot open '{}'".format(filename)) - # End if - if not schema_path: - # Find the schema, based on the model version - thispath = os.path.abspath(__file__) - pdir = os.path.dirname(os.path.dirname(os.path.dirname(thispath))) - schema_path = os.path.join(pdir, 'schema') - # End if - schema_file = find_schema_file(schema_root, version, schema_path) - if not (schema_file and os.path.isfile(schema_file)): - verstring = '.'.join([str(x) for x in version]) - emsg = """validate_xml_file: Cannot find schema for version {}, - {} does not exist""" - raise CCPPError(emsg.format(verstring, schema_file)) - # End if + # end if + if os.path.isfile(schema_root): + # We already have a file, just use it + schema_file = schema_root + else: + if not schema_path: + # Find the schema, based on the model version + thispath = os.path.abspath(__file__) + pdir = os.path.dirname(os.path.dirname(os.path.dirname(thispath))) + schema_path = os.path.join(pdir, 'schema') + # end if + schema_file = find_schema_file(schema_root, version, schema_path) + if not (schema_file and os.path.isfile(schema_file)): + verstring = '.'.join([str(x) for x in version]) + emsg = """validate_xml_file: Cannot find schema for version {}, + {} does not exist""" + raise CCPPError(emsg.format(verstring, schema_file)) + # end if + # end if if not os.access(schema_file, os.R_OK): emsg = "validate_xml_file: Cannot open schema, '{}'" raise CCPPError(emsg.format(schema_file)) - # End if - if _XMLLINT is not None: + # end if + if _XMLLINT: logger.debug("Checking file {} against schema {}".format(filename, schema_file)) cmd = [_XMLLINT, '--noout', '--schema', schema_file, filename] result = call_command(cmd, logger) return result - # End if + # end if lmsg = "xmllint not found, could not validate file {}" if error_on_noxmllint: raise CCPPError("validate_xml_file: " + lmsg.format(filename)) - # End if + # end if logger.warning(lmsg.format(filename)) return True # We could not check but still need to proceed @@ -218,27 +202,23 @@ def read_xml_file(filename, logger=None): ############################################################################### """Read the XML file, , and return its tree and root""" if os.path.isfile(filename) and os.access(filename, os.R_OK): - if PY3: - file_open = (lambda x: open(x, 'r', encoding='utf-8')) - else: - file_open = (lambda x: open(x, 'r')) - # End if + file_open = (lambda x: open(x, 'r', encoding='utf-8')) with file_open(filename) as file_: try: tree = ET.parse(file_) root = tree.getroot() except ET.ParseError as perr: emsg = "read_xml_file: Cannot read {}, {}" - raise CCPPError(emsg.format(filename, perr)) + raise CCPPError(emsg.format(filename, perr)) from perr elif not os.access(filename, os.R_OK): raise CCPPError("read_xml_file: Cannot open '{}'".format(filename)) else: emsg = "read_xml_file: Filename, '{}', does not exist" raise CCPPError(emsg.format(filename)) - # End if + # end if if logger: logger.debug("Read XML file, '{}'".format(filename)) - # End if + # end if return tree, root ############################################################################### @@ -248,7 +228,7 @@ class PrettyElementTree(ET.ElementTree): def __init__(self, element=None, file=None): """Initialize a PrettyElementTree object""" - super(PrettyElementTree, self).__init__(element, file) + super().__init__(element, file) def _write(self, outfile, line, indent, eol=os.linesep): """Write as an ASCII string to """ @@ -268,35 +248,25 @@ def _inc_pos(outstr, text, txt_beg): # end if emsg = "No output at {} of {}\n{}".format(txt_beg, len(text), text[txt_beg:txt_end]) - raise DatatableInternalError(emsg) + raise XMLToolsInternalError(emsg) def write(self, file, encoding="us-ascii", xml_declaration=None, default_namespace=None, method="xml", short_empty_elements=True): """Subclassed write method to format output.""" - if PY3 and (PYSUBVER >= 4): - if PYSUBVER >= 8: - input = ET.tostring(self.getroot(), - encoding=encoding, method=method, - xml_declaration=xml_declaration, - default_namespace=default_namespace, - short_empty_elements=short_empty_elements) - else: - input = ET.tostring(self.getroot(), - encoding=encoding, method=method, - short_empty_elements=short_empty_elements) - # end if - else: - input = ET.tostring(self.getroot(), - encoding=encoding, method=method) - # end if - if PY3: - fmode = 'wt' - root = str(input, encoding="utf-8") + if PYSUBVER >= 8: + et_str = ET.tostring(self.getroot(), + encoding=encoding, method=method, + xml_declaration=xml_declaration, + default_namespace=default_namespace, + short_empty_elements=short_empty_elements) else: - fmode = 'w' - root = input + et_str = ET.tostring(self.getroot(), + encoding=encoding, method=method, + short_empty_elements=short_empty_elements) # end if + fmode = 'wt' + root = str(et_str, encoding="utf-8") indent = 0 last_write_text = False with open(file, fmode) as outfile: From a5aa37b427d7102b71c771c8d1d8b7cee7a22ec4 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Fri, 1 Sep 2023 09:50:27 -0600 Subject: [PATCH 03/11] add is_scheme_constituent interface --- scripts/constituents.py | 36 +++++++++++++++++++++++++++++------- scripts/host_cap.py | 38 ++++++++++++++++++++++++-------------- 2 files changed, 53 insertions(+), 21 deletions(-) diff --git a/scripts/constituents.py b/scripts/constituents.py index d8935130..e01d2eae 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -9,7 +9,7 @@ """ # CCPP framework imports -from parse_tools import ParseInternalError +from parse_tools import ParseInternalError, type_name from metavar import VarDictionary ######################################################################## @@ -28,7 +28,7 @@ class ConstituentVarDict(VarDictionary): allocation and support for these variables. """ - __const_prop_array_name = "ccpp_constituent_array" + __const_prop_array_name = "ccpp_constituents" __const_prop_init_name = "ccpp_constituents_initialized" __const_prop_init_consts = "ccpp_create_constituent_array" __constituent_type = "suite" @@ -112,7 +112,7 @@ def find_variable(self, standard_name=None, source_var=None, # end for newdims.append(':'.join(new_dnames)) # end for - var = source_var.clone({'dimensions' : newdims}, remove_intent=False, + var = source_var.clone({'dimensions' : newdims}, remove_intent=True, source_type=self.__constituent_type) self.add_variable(var, self.__run_env) return var @@ -406,7 +406,7 @@ def constituent_module_name(self): if not ((self.parent is not None) and hasattr(self.parent.parent, "constituent_module")): emsg = "ConstituentVarDict parent not HostModel?" - emsg += f"\nparent is '{type(self.parent.parent)}'" + emsg += f"\nparent is '{type_name(self.parent.parent)}'" raise ParseInternalError(emsg) # end if return self.parent.parent.constituent_module @@ -456,9 +456,9 @@ def write_constituent_use_statements(cap, suite_list, indent): @staticmethod def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcname, - copy_in_funcname, copy_out_funcname, const_obj_name, - const_names_name, const_indices_name, const_array_func, - advect_array_func, prop_array_func, + query_const_funcname, copy_in_funcname, copy_out_funcname, + const_obj_name, const_names_name, const_indices_name, + const_array_func, advect_array_func, prop_array_func, const_index_func, suite_list, err_vars): """Write out the host model routine which will instantiate constituent fields for all the constituents in . @@ -466,6 +466,7 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna Also write out the following routines: : Initialize constituent data : Number of constituents + : Check if standard name matches existing constituent : Collect constituent fields for host : Update constituent fields from host : Return a pointer to the constituent array @@ -634,6 +635,27 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna 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) + # Write query_consts routine + substmt = f"subroutine {query_const_funcname}" + cap.blank_line() + cap.write(f"{substmt}(var_name, constituent_exists, {err_dummy_str})", 1) + cap.comment(f"Return constituent_exists = true iff var_name appears in {host.name}_model_const_stdnames", 2) + cap.blank_line() + cap.write("character(len=*), intent(in) :: var_name", 2) + cap.write("logical, intent(out) :: constituent_exists", 2) + for evar in err_vars: + evar.write_def(cap, 2, host, dummy=True, add_intent="out") + # end for + cap.blank_line() + cap.write(f"{herrcode} = 0", 2) + cap.write(f"{herrmsg} = ''", 2) + cap.blank_line() + cap.write("constituent_exists = .false.", 2) + cap.write(f"if (any({host.name}_model_const_stdnames == var_name)) then", 2) + cap.write("constituent_exists = .true.", 3) + cap.write("end if", 2) + cap.blank_line() + cap.write(f"end {substmt}", 1) # Write copy_in routine substmt = "subroutine {}".format(copy_in_funcname) cap.write("", 0) diff --git a/scripts/host_cap.py b/scripts/host_cap.py index 479f688b..d2b3066f 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -8,7 +8,7 @@ import logging import os # CCPP framework imports -from ccpp_suite import API +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 @@ -33,8 +33,7 @@ end subroutine {host_model}_ccpp_physics_{stage} ''' -_API_SRC_NAME = "CCPP_API" -_API_SOURCE = ParseSource(_API_SRC_NAME, "MODULE", +_API_SOURCE = ParseSource(API_SOURCE_NAME, "MODULE", ParseContext(filename="host_cap.F90")) _API_DUMMY_RUN_ENV = CCPPFrameworkEnv(None, ndict={'host_files':'', @@ -60,7 +59,7 @@ 'suites':''}) # Used to prevent loop substitution lookups -_BLANK_DICT = VarDictionary(_API_SRC_NAME, _MVAR_DUMMY_RUN_ENV) +_BLANK_DICT = VarDictionary(API_SOURCE_NAME, _MVAR_DUMMY_RUN_ENV) ############################################################################### def suite_part_list(suite, stage): @@ -110,6 +109,14 @@ def constituent_num_consts_funcname(host_model): Because this is a user interface API function, the name is fixed.""" return "{}_ccpp_number_constituents".format(host_model.name) +############################################################################### +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): ############################################################################### @@ -210,7 +217,7 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): to create the dictionary. """ # First create a MetadataTable for the constituents DDT - stdname_layer = "ccpp_num_constituents" + stdname_layer = "number_of_ccpp_constituents" horiz_dim = "horizontal_dimension" vert_layer_dim = "vertical_layer_dimension" vert_interface_dim = "vertical_interface_dimension" @@ -225,7 +232,7 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): f" standard_name = {stdname_layer}", " units = count", " dimensions = ()", " type = integer", f"[ {array_layer} ]", - " standard_name = ccpp_constituent_array", + " standard_name = ccpp_constituents", " units = none", f" dimensions = ({horiz_dim}, {vert_layer_dim}, {stdname_layer})", " type = real", " kind = kind_phys"] @@ -445,15 +452,17 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): API.declare_inspection_interfaces(cap) # Write the host-model interfaces for constituents reg_name = constituent_register_subname(host_model) - cap.write("public :: {}".format(reg_name), 1) + cap.write(f"public :: {reg_name}", 1) init_name = constituent_initialize_subname(host_model) - cap.write("public :: {}".format(init_name), 1) + cap.write(f"public :: {init_name}", 1) numconsts_name = constituent_num_consts_funcname(host_model) - cap.write("public :: {}".format(numconsts_name), 1) + cap.write(f"public :: {numconsts_name}", 1) + queryconsts_name = query_scheme_constituents_funcname(host_model) + cap.write(f"public :: {queryconsts_name}", 1) copyin_name = constituent_copyin_subname(host_model) - cap.write("public :: {}".format(copyin_name), 1) + cap.write(f"public :: {copyin_name}", 1) copyout_name = constituent_copyout_subname(host_model) - cap.write("public :: {}".format(copyout_name), 1) + cap.write(f"public :: {copyout_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) @@ -510,7 +519,7 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): subst_dict['intent'] = 'inout' # End if hdvars.append(hvar.clone(subst_dict, - source_name=_API_SRC_NAME)) + source_name=API_SOURCE_NAME)) # End for lnames = [x.get_prop_value('local_name') for x in apivars + hdvars] api_vlist = ", ".join(lnames) @@ -599,8 +608,9 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): const_names_name = constituent_model_const_stdnames(host_model) const_indices_name = constituent_model_const_indices(host_model) ConstituentVarDict.write_host_routines(cap, host_model, reg_name, init_name, - numconsts_name, copyin_name, - copyout_name, const_obj_name, + numconsts_name, queryconsts_name, + copyin_name, copyout_name, + const_obj_name, const_names_name, const_indices_name, const_array_func, From 9897870d873b0fdc6cc96158798e49d5fa828276 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Fri, 1 Sep 2023 10:02:33 -0600 Subject: [PATCH 04/11] fix tests and add is_thermo_active property --- scripts/constituents.py | 4 +- scripts/host_cap.py | 10 +- src/ccpp_constituent_prop_mod.F90 | 142 ++++++++++++-- test/advection_test/cld_ice.F90 | 12 +- test/advection_test/cld_ice.meta | 6 +- test/advection_test/cld_liq.F90 | 12 +- test/advection_test/cld_liq.meta | 4 +- test/advection_test/test_host.F90 | 310 ++++++++++++++++++++++++------ 8 files changed, 401 insertions(+), 99 deletions(-) diff --git a/scripts/constituents.py b/scripts/constituents.py index e01d2eae..a828e5e5 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -9,7 +9,7 @@ """ # CCPP framework imports -from parse_tools import ParseInternalError, type_name +from parse_tools import ParseInternalError from metavar import VarDictionary ######################################################################## @@ -406,7 +406,7 @@ def constituent_module_name(self): if not ((self.parent is not None) and hasattr(self.parent.parent, "constituent_module")): emsg = "ConstituentVarDict parent not HostModel?" - emsg += f"\nparent is '{type_name(self.parent.parent)}'" + emsg += f"\nparent is '{type(self.parent.parent)}'" raise ParseInternalError(emsg) # end if return self.parent.parent.constituent_module diff --git a/scripts/host_cap.py b/scripts/host_cap.py index d2b3066f..8a2193cb 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -8,7 +8,7 @@ import logging import os # CCPP framework imports -from ccpp_suite import API, API_SOURCE_NAME +from ccpp_suite import API from ccpp_state_machine import CCPP_STATE_MACH from constituents import ConstituentVarDict, CONST_DDT_NAME, CONST_DDT_MOD from constituents import CONST_OBJ_STDNAME @@ -33,7 +33,9 @@ end subroutine {host_model}_ccpp_physics_{stage} ''' -_API_SOURCE = ParseSource(API_SOURCE_NAME, "MODULE", +_API_SRC_NAME = "CCPP_API" + +_API_SOURCE = ParseSource(_API_SRC_NAME, "MODULE", ParseContext(filename="host_cap.F90")) _API_DUMMY_RUN_ENV = CCPPFrameworkEnv(None, ndict={'host_files':'', @@ -59,7 +61,7 @@ 'suites':''}) # Used to prevent loop substitution lookups -_BLANK_DICT = VarDictionary(API_SOURCE_NAME, _MVAR_DUMMY_RUN_ENV) +_BLANK_DICT = VarDictionary(_API_SRC_NAME, _MVAR_DUMMY_RUN_ENV) ############################################################################### def suite_part_list(suite, stage): @@ -519,7 +521,7 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): subst_dict['intent'] = 'inout' # End if hdvars.append(hvar.clone(subst_dict, - source_name=API_SOURCE_NAME)) + source_name=_API_SRC_NAME)) # End for lnames = [x.get_prop_value('local_name') for x in apivars + hdvars] api_vlist = ", ".join(lnames) diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index df80ec0d..c9f5322b 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -35,6 +35,7 @@ module ccpp_constituent_prop_mod character(len=:), private, allocatable :: vert_dim integer, private :: const_ind = int_unassigned logical, private :: advected = .false. + logical, private :: thermo_active = .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 @@ -61,6 +62,7 @@ module ccpp_constituent_prop_mod procedure :: vertical_dimension => ccp_get_vertical_dimension procedure :: const_index => ccp_const_index procedure :: is_advected => ccp_is_advected + procedure :: is_thermo_active => ccp_is_thermo_active 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 @@ -76,9 +78,10 @@ module ccpp_constituent_prop_mod procedure :: copyConstituent generic :: assignment(=) => copyConstituent ! Methods that change state (XXgoldyXX: make private?) - procedure :: instantiate => ccp_instantiate - procedure :: deallocate => ccp_deallocate - procedure :: set_const_index => ccp_set_const_index + procedure :: instantiate => ccp_instantiate + procedure :: deallocate => ccp_deallocate + procedure :: set_const_index => ccp_set_const_index + procedure :: set_thermo_active => ccp_set_thermo_active end type ccpp_constituent_properties_t !! \section arg_table_ccpp_constituent_prop_ptr_t @@ -96,6 +99,7 @@ module ccpp_constituent_prop_mod procedure :: vertical_dimension => ccpt_get_vertical_dimension procedure :: const_index => ccpt_const_index procedure :: is_advected => ccpt_is_advected + procedure :: is_thermo_active => ccpt_is_thermo_active 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 @@ -109,8 +113,9 @@ module ccpp_constituent_prop_mod ! ccpt_set: Set the internal pointer procedure :: set => ccpt_set ! Methods that change state (XXgoldyXX: make private?) - procedure :: deallocate => ccpt_deallocate - procedure :: set_const_index => ccpt_set_const_index + procedure :: deallocate => ccpt_deallocate + procedure :: set_const_index => ccpt_set_const_index + procedure :: set_thermo_active => ccpt_set_thermo_active end type ccpp_constituent_prop_ptr_t !! \section arg_table_ccpp_model_constituents_t @@ -596,6 +601,45 @@ end subroutine ccp_set_const_index !####################################################################### + subroutine ccp_set_thermo_active(this, thermo_flag, errcode, errmsg) + ! Set whether this constituent is thermodynamically active, which + ! means that certain physics schemes will use this constitutent + ! when calculating thermodynamic quantities (e.g. enthalpy). + + ! Dummy arguments + class(ccpp_constituent_properties_t), intent(inout) :: this + logical, intent(in) :: thermo_flag + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + + !Set thermodynamically active flag for this constituent: + if (this%is_instantiated(errcode, errmsg)) then + this%thermo_active = thermo_flag + end if + + end subroutine ccp_set_thermo_active + + !####################################################################### + + subroutine ccp_is_thermo_active(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 + !thermodynamically active, otherwise return false: + if (this%is_instantiated(errcode, errmsg)) then + val_out = this%thermo_active + else + val_out = .false. + end if + end subroutine ccp_is_thermo_active + + !####################################################################### + subroutine ccp_is_advected(this, val_out, errcode, errmsg) ! Dummy arguments @@ -628,7 +672,8 @@ subroutine ccp_is_equivalent(this, oconst, equiv, errcode, errmsg) (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) + (this%const_default_value == oconst%const_default_value) .and. & + (this%thermo_active .eqv. oconst%thermo_active) else equiv = .false. end if @@ -1339,8 +1384,8 @@ end subroutine ccp_model_const_reset !######################################################################## - logical function ccp_model_const_is_match(this, index, advected) & - result(is_match) + logical function ccp_model_const_is_match(this, index, advected, & + thermo_active) 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. @@ -1351,6 +1396,7 @@ logical function ccp_model_const_is_match(this, index, advected) & class(ccpp_model_constituents_t), intent(in) :: this integer, intent(in) :: index logical, optional, intent(in) :: advected + logical, optional, intent(in) :: thermo_active ! Local variable logical :: check @@ -1363,11 +1409,20 @@ logical function ccp_model_const_is_match(this, index, advected) & end if end if + if (present(thermo_active)) then + call this%const_metadata(index)%is_thermo_active(check) + if (thermo_active .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, errcode, errmsg) + subroutine ccp_model_const_num_match(this, nmatch, advected, thermo_active, & + errcode, errmsg) ! Query number of constituents matching pattern ! Each (optional) property which is present represents something ! which is required as part of a match. @@ -1377,6 +1432,7 @@ subroutine ccp_model_const_num_match(this, nmatch, advected, errcode, errmsg) class(ccpp_model_constituents_t), intent(in) :: this integer, intent(out) :: nmatch logical, optional, intent(in) :: advected + logical, optional, intent(in) :: thermo_active integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg ! Local variables @@ -1386,7 +1442,7 @@ subroutine ccp_model_const_num_match(this, nmatch, advected, errcode, errmsg) 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)) then + if (this%is_match(index, advected=advected, thermo_active=thermo_active)) then nmatch = nmatch + 1 end if end do @@ -1452,7 +1508,7 @@ end subroutine ccp_model_const_metadata !######################################################################## subroutine ccp_model_const_copy_in_3d(this, const_array, advected, & - errcode, errmsg) + thermo_active, errcode, errmsg) ! Gather constituent fields matching pattern ! Each (optional) property which is present represents something ! which is required as part of a match. @@ -1462,6 +1518,7 @@ subroutine ccp_model_const_copy_in_3d(this, const_array, advected, & class(ccpp_model_constituents_t), intent(in) :: this real(kind_phys), intent(out) :: const_array(:,:,:) logical, optional, intent(in) :: advected + logical, optional, intent(in) :: thermo_active integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg ! Local variables @@ -1478,7 +1535,8 @@ subroutine ccp_model_const_copy_in_3d(this, const_array, advected, & max_cind = SIZE(const_array, 3) num_levels = SIZE(const_array, 2) do index = 1, SIZE(this%const_metadata) - if (this%is_match(index, advected=advected)) then + if (this%is_match(index, advected=advected, & + thermo_active=thermo_active)) then ! See if we have room for another constituent cindex = cindex + 1 if (cindex > max_cind) then @@ -1527,7 +1585,7 @@ end subroutine ccp_model_const_copy_in_3d !######################################################################## subroutine ccp_model_const_copy_out_3d(this, const_array, advected, & - errcode, errmsg) + thermo_active, errcode, errmsg) ! Update constituent fields matching pattern ! Each (optional) property which is present represents something ! which is required as part of a match. @@ -1537,6 +1595,7 @@ subroutine ccp_model_const_copy_out_3d(this, const_array, advected, & class(ccpp_model_constituents_t), intent(inout) :: this real(kind_phys), intent(in) :: const_array(:,:,:) logical, optional, intent(in) :: advected + logical, optional, intent(in) :: thermo_active integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg ! Local variables @@ -1553,7 +1612,8 @@ subroutine ccp_model_const_copy_out_3d(this, const_array, advected, & max_cind = SIZE(const_array, 3) num_levels = SIZE(const_array, 2) do index = 1, SIZE(this%const_metadata) - if (this%is_match(index, advected=advected)) then + if (this%is_match(index, advected=advected, & + thermo_active=thermo_active)) then ! See if we have room for another constituent cindex = cindex + 1 if (cindex > max_cind) then @@ -1828,6 +1888,28 @@ end subroutine ccpt_const_index !####################################################################### + subroutine ccpt_is_thermo_active(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_thermo_active' + + if (associated(this%prop)) then + call this%prop%is_thermo_active(val_out, errcode, errmsg) + else + val_out = .false. + call set_errvars(1, subname//": invalid constituent pointer", & + errcode=errcode, errmsg=errmsg) + end if + + end subroutine ccpt_is_thermo_active + + !####################################################################### + subroutine ccpt_is_advected(this, val_out, errcode, errmsg) ! Dummy arguments @@ -2123,9 +2205,9 @@ subroutine ccpt_set_const_index(this, index, errcode, errmsg) ! Dummy arguments class(ccpp_constituent_prop_ptr_t), intent(inout) :: this - integer, intent(in) :: index - integer, optional, intent(out) :: errcode - character(len=*), optional, intent(out) :: errmsg + integer, intent(in) :: index + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg ! Local variable character(len=*), parameter :: subname = 'ccpt_set_const_index' @@ -2146,4 +2228,30 @@ subroutine ccpt_set_const_index(this, index, errcode, errmsg) end subroutine ccpt_set_const_index + !####################################################################### + + subroutine ccpt_set_thermo_active(this, thermo_flag, errcode, errmsg) + ! Set whether this constituent is thermodynamically active, which + ! means that certain physics schemes will use this constitutent + ! when calculating thermodynamic quantities (e.g. enthalpy). + + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(inout) :: this + logical, intent(in) :: thermo_flag + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_set_thermo_active' + + if (associated(this%prop)) then + if (this%prop%is_instantiated(errcode, errmsg)) then + this%prop%thermo_active = thermo_flag + end if + else + call set_errvars(1, subname//": invalid constituent pointer", & + errcode=errcode, errmsg=errmsg) + end if + + end subroutine ccpt_set_thermo_active + end module ccpp_constituent_prop_mod diff --git a/test/advection_test/cld_ice.F90 b/test/advection_test/cld_ice.F90 index 5a91282f..9c1e769a 100644 --- a/test/advection_test/cld_ice.F90 +++ b/test/advection_test/cld_ice.F90 @@ -19,7 +19,7 @@ MODULE cld_ice !> \section arg_table_cld_ice_run Argument Table !! \htmlinclude arg_table_cld_ice_run.html !! - subroutine cld_ice_run(ncol, timestep, temp, qv, ps, cld_ice, & + subroutine cld_ice_run(ncol, timestep, temp, qv, ps, cld_ice_array, & errmsg, errflg) integer, intent(in) :: ncol @@ -27,7 +27,7 @@ subroutine cld_ice_run(ncol, timestep, temp, qv, ps, cld_ice, & real(kind_phys), intent(inout) :: temp(:,:) real(kind_phys), intent(inout) :: qv(:,:) real(kind_phys), intent(in) :: ps(:) - REAL(kind_phys), intent(inout) :: cld_ice(:,:) + REAL(kind_phys), intent(inout) :: cld_ice_array(:,:) character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg !---------------------------------------------------------------- @@ -44,7 +44,7 @@ subroutine cld_ice_run(ncol, timestep, temp, qv, ps, cld_ice, & do ilev = 1, size(temp, 2) if (temp(icol, ilev) < tcld) then frz = MAX(qv(icol, ilev) - 0.5_kind_phys, 0.0_kind_phys) - cld_ice(icol, ilev) = cld_ice(icol, ilev) + frz + cld_ice_array(icol, ilev) = cld_ice_array(icol, ilev) + frz qv(icol, ilev) = qv(icol, ilev) - frz if (frz > 0.0_kind_phys) then temp(icol, ilev) = temp(icol, ilev) + 1.0_kind_phys @@ -58,16 +58,16 @@ END SUBROUTINE cld_ice_run !> \section arg_table_cld_ice_init Argument Table !! \htmlinclude arg_table_cld_ice_init.html !! - subroutine cld_ice_init(tfreeze, cld_ice, errmsg, errflg) + subroutine cld_ice_init(tfreeze, cld_ice_array, errmsg, errflg) real(kind_phys), intent(in) :: tfreeze - real(kind_phys), intent(inout) :: cld_ice(:,:) + real(kind_phys), intent(inout) :: cld_ice_array(:,:) character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg errmsg = '' errflg = 0 - cld_ice = 0.0_kind_phys + cld_ice_array = 0.0_kind_phys tcld = tfreeze - 20.0_kind_phys end subroutine cld_ice_init diff --git a/test/advection_test/cld_ice.meta b/test/advection_test/cld_ice.meta index e4a961ab..010fb419 100644 --- a/test/advection_test/cld_ice.meta +++ b/test/advection_test/cld_ice.meta @@ -41,9 +41,10 @@ units = Pa dimensions = (horizontal_loop_extent) intent = in -[ cld_ice ] +[ cld_ice_array ] standard_name = cloud_ice_dry_mixing_ratio advected = .true. + default_value = 0.0_kind_phys units = kg kg-1 dimensions = (horizontal_loop_extent, vertical_layer_dimension) type = real | kind = kind_phys @@ -73,9 +74,10 @@ dimensions = () type = real | kind = kind_phys intent = in -[ cld_ice ] +[ cld_ice_array ] standard_name = cloud_ice_dry_mixing_ratio advected = .true. + default_value = 0.0_kind_phys units = kg kg-1 dimensions = (horizontal_dimension, vertical_layer_dimension) type = real | kind = kind_phys diff --git a/test/advection_test/cld_liq.F90 b/test/advection_test/cld_liq.F90 index 2e1e5a57..b9b841d4 100644 --- a/test/advection_test/cld_liq.F90 +++ b/test/advection_test/cld_liq.F90 @@ -16,7 +16,7 @@ MODULE cld_liq !> \section arg_table_cld_liq_run Argument Table !! \htmlinclude arg_table_cld_liq_run.html !! - subroutine cld_liq_run(ncol, timestep, tcld, temp, qv, ps, cld_liq, & + subroutine cld_liq_run(ncol, timestep, tcld, temp, qv, ps, cld_liq_array, & errmsg, errflg) integer, intent(in) :: ncol @@ -25,7 +25,7 @@ subroutine cld_liq_run(ncol, timestep, tcld, temp, qv, ps, cld_liq, & real(kind_phys), intent(inout) :: temp(:,:) real(kind_phys), intent(inout) :: qv(:,:) real(kind_phys), intent(in) :: ps(:) - REAL(kind_phys), intent(inout) :: cld_liq(:,:) + REAL(kind_phys), intent(inout) :: cld_liq_array(:,:) character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg !---------------------------------------------------------------- @@ -43,7 +43,7 @@ subroutine cld_liq_run(ncol, timestep, tcld, temp, qv, ps, cld_liq, & if ( (qv(icol, ilev) > 0.0_kind_phys) .and. & (temp(icol, ilev) <= tcld)) then cond = MIN(qv(icol, ilev), 0.1_kind_phys) - cld_liq(icol, ilev) = cld_liq(icol, ilev) + cond + cld_liq_array(icol, ilev) = cld_liq_array(icol, ilev) + cond qv(icol, ilev) = qv(icol, ilev) - cond if (cond > 0.0_kind_phys) then temp(icol, ilev) = temp(icol, ilev) + (cond * 5.0_kind_phys) @@ -57,10 +57,10 @@ END SUBROUTINE cld_liq_run !> \section arg_table_cld_liq_init Argument Table !! \htmlinclude arg_table_cld_liq_init.html !! - subroutine cld_liq_init(tfreeze, cld_liq, tcld, errmsg, errflg) + subroutine cld_liq_init(tfreeze, cld_liq_array, tcld, errmsg, errflg) real(kind_phys), intent(in) :: tfreeze - real(kind_phys), intent(out) :: cld_liq(:,:) + real(kind_phys), intent(out) :: cld_liq_array(:,:) real(kind_phys), intent(out) :: tcld character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg @@ -69,7 +69,7 @@ subroutine cld_liq_init(tfreeze, cld_liq, tcld, errmsg, errflg) errmsg = '' errflg = 0 - cld_liq = 0.0_kind_phys + cld_liq_array = 0.0_kind_phys tcld = tfreeze - 20.0_kind_phys end subroutine cld_liq_init diff --git a/test/advection_test/cld_liq.meta b/test/advection_test/cld_liq.meta index 1186d741..4c87f4b7 100644 --- a/test/advection_test/cld_liq.meta +++ b/test/advection_test/cld_liq.meta @@ -47,7 +47,7 @@ units = Pa dimensions = (horizontal_loop_extent) intent = in -[ cld_liq ] +[ cld_liq_array ] standard_name = cloud_liquid_dry_mixing_ratio advected = .true. units = kg kg-1 @@ -79,7 +79,7 @@ dimensions = () type = real | kind = kind_phys intent = in -[ cld_liq ] +[ cld_liq_array ] standard_name = cloud_liquid_dry_mixing_ratio advected = .true. units = kg kg-1 diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index 9c7abd42..49a3c602 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -33,15 +33,19 @@ module test_prog CONTAINS - subroutine check_errflg(subname, errflg, errmsg) + subroutine check_errflg(subname, errflg, errmsg, errflg_final) ! If errflg is not zero, print an error message - character(len=*), intent(in) :: subname - integer, intent(in) :: errflg - character(len=*), intent(in) :: errmsg + character(len=*), intent(in) :: subname + integer, intent(in) :: errflg + character(len=*), intent(in) :: errmsg + + integer, intent(out) :: errflg_final if (errflg /= 0) then write(6, '(a,i0,4a)') "Error ", errflg, " from ", trim(subname), & ':', trim(errmsg) + !Notify test script that a failure occurred: + errflg_final = -1 !Notify test script that a failure occured end if end subroutine check_errflg @@ -222,6 +226,7 @@ subroutine test_host(retval, test_suites) 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_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 use test_host_ccpp_cap, only: test_host_ccpp_number_constituents use test_host_ccpp_cap, only: test_host_constituents_array @@ -245,14 +250,22 @@ subroutine test_host(retval, test_suites) integer :: num_suites integer :: num_advected ! Num advected species logical :: const_log + logical :: is_constituent + logical :: has_default character(len=128), allocatable :: suite_names(:) character(len=256) :: const_str character(len=512) :: errmsg integer :: errflg + integer :: errflg_final ! Used to notify testing script of test failure real(kind_phys), pointer :: const_ptr(:,:,:) + real(kind_phys) :: default_value type(ccpp_constituent_prop_ptr_t), pointer :: const_props(:) character(len=*), parameter :: subname = 'test_host' + ! Initialized "final" error flag used to report a failure to the larged + ! testing script: + errflg_final = 0 + ! Gather and test the inspection routines num_suites = size(test_suites) call ccpp_physics_suite_list(suite_names) @@ -280,112 +293,279 @@ subroutine test_host(retval, test_suites) return end if - ! Register the constituents to find out what needs advecting - call host_constituents(1)%instantiate(std_name="specific_humidity", & + errflg = 0 + errmsg = '' + + ! Check that is_scheme_constituent works as expected + call test_host_ccpp_is_scheme_constituent('specific_humidity', & + is_constituent, errflg, errmsg) + call check_errflg(subname//"_ccpp_is_scheme_constituent", errflg, & + errmsg, errflg_final) + ! 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 + end if + call test_host_ccpp_is_scheme_constituent('cloud_ice_dry_mixing_ratio', & + is_constituent, errflg, errmsg) + call check_errflg(subname//"_ccpp_is_scheme_constituent", errflg, & + errmsg, errflg_final) + ! cloud_ice_dry_mixing_ratio should be an existing constituent + 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 + end if + + + ! Register the constituents to find out what needs advecting + call host_constituents(1)%instantiate(std_name="specific_humidity", & long_name="Specific humidity", units="kg kg-1", & vertical_dim="vertical_layer_dimension", advected=.true., & errcode=errflg, errmsg=errmsg) - call check_errflg(subname//'.initialize', errflg, 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) end if if (errflg /= 0) then write(6, '(2a)') 'ERROR register_constituents: ', trim(errmsg) + retval = .false. + return end if ! Check number of advected constituents if (errflg == 0) then call test_host_ccpp_number_constituents(num_advected, errmsg=errmsg, & errflg=errflg) - call check_errflg(subname//".num_advected", errflg, errmsg) + call check_errflg(subname//".num_advected", errflg, errmsg, errflg_final) end if if (num_advected /= 3) then write(6, '(a,i0)') "ERROR: num advected constituents = ", num_advected - STOP 2 + retval = .false. + return end if ! Initialize constituent data call test_host_ccpp_initialize_constituents(ncols, pver, errflg, errmsg) - ! Initialize our 'data' - if (errflg == 0) then - const_ptr => test_host_constituents_array() - call test_host_const_get_index('specific_humidity', index, & - errflg, errmsg) - call check_errflg(subname//".index_specific_humidity", errflg, errmsg) - end if - if (errflg == 0) then - call test_host_const_get_index('cloud_liquid_dry_mixing_ratio', & - index_liq, errflg, errmsg) - call check_errflg(subname//".index_cld_liq", errflg, errmsg) + !Stop tests here if initialization failed (as all other tests will likely + !fail as well: + if (errflg /= 0) then + retval = .false. + return end if - if (errflg == 0) then - call test_host_const_get_index('cloud_ice_dry_mixing_ratio', & - index_ice, errflg, errmsg) - call check_errflg(subname//".index_cld_ice", errflg, errmsg) + + ! Initialize our 'data' + const_ptr => test_host_constituents_array() + + !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: + 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: + 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: + if (errflg_final /= 0) then + retval = .false. + return end if + call init_data(const_ptr, index, index_liq, index_ice) + ! Check some constituent properties - if (errflg == 0) then - const_props => test_host_model_const_properties() - call const_props(index)%standard_name(const_str, errflg, errmsg) - if (errflg /= 0) then - write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & - "to get standard_name for specific_humidity, index = ", & - index, trim(errmsg) - end if + !++++++++++++++++++++++++++++++++++ + + const_props => test_host_model_const_properties() + + !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 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 = -1 + 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 - if (errflg == 0) then - 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) - end if + + !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 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 = -1 + 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 end if if (errflg == 0) then - call const_props(index_ice)%is_mass_mixing_ratio(const_log, & - errflg, errmsg) - if (errflg /= 0) then - write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & - "to get mass mixing ratio prop for cld_ice index = ", & - index_ice, trim(errmsg) + 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 end if + else + !Reset error flag to continue testing other properties: + errflg = 0 + end if + + !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 end if if (errflg == 0) then if (.not. const_log) then - write(6, *) "ERROR: cloud ice is not a mass mixing_ratio" - errflg = -1 + write(6, *) "ERROR: cloud ice mass_mixing_ratio is not dry" + errflg_final = -1 + end if + else + !Reset error flag to continue testing other properties: + errflg = 0 + end if + + !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 + end if + if (errflg == 0) then + 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 end if + else + !Reset error flag to continue testing other properties: + errflg = 0 + end if + + !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 end if if (errflg == 0) then - call const_props(index_ice)%is_dry(const_log, errflg, errmsg) + 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, " trying ", & - "to get dry prop for cld_ice index = ", index_ice, trim(errmsg) + write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, & + " tryingto get thermo_active prop 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 (.not. const_log) then - write(6, *) "ERROR: cloud ice mass_mixing_ratio is not dry" - errflg = -1 + 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 + end if + else + !Reset error flag to continue testing other properties: + errflg = 0 + end if + + !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 + 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 + end if + else + !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 + 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 end if + else + !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 + 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 + end if + else + !Reset error flag to continue testing other properties: + errflg = 0 + end if + !++++++++++++++++++++++++++++++++++ - ! Use the suite information to setup the run + !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 do sind = 1, num_suites if (errflg == 0) then call test_host_ccpp_physics_initialize( & @@ -397,6 +577,7 @@ subroutine test_host(retval, test_suites) end if end if end do + ! Loop over time steps do time_step = 1, num_time_steps ! Initialize the timestep @@ -478,7 +659,13 @@ subroutine test_host(retval, test_suites) end if end if - retval = errflg == 0 + !Make sure "final" flag is non-zero if "errflg" is: + if (errflg /= 0) then + errflg_final = -1 !Notify test script that a failure occured + end if + + !Set return value to False if any errors were found: + retval = errflg_final == 0 end subroutine test_host @@ -490,9 +677,9 @@ program test implicit none character(len=cs), target :: test_parts1(1) - character(len=cm), target :: test_invars1(6) - character(len=cm), target :: test_outvars1(5) - character(len=cm), target :: test_reqvars1(8) + character(len=cm), target :: test_invars1(7) + character(len=cm), target :: test_outvars1(6) + character(len=cm), target :: test_reqvars1(9) type(suite_info) :: test_suites(1) logical :: run_okay @@ -500,6 +687,7 @@ program test test_parts1 = (/ 'physics '/) test_invars1 = (/ & 'cloud_ice_dry_mixing_ratio ', & + 'cloud_liquid_dry_mixing_ratio ', & 'surface_air_pressure ', & 'temperature ', & 'time_step_for_physics ', & @@ -510,11 +698,13 @@ program test 'ccpp_error_code ', & 'temperature ', & 'water_vapor_specific_humidity ', & + 'cloud_liquid_dry_mixing_ratio ', & 'cloud_ice_dry_mixing_ratio ' /) test_reqvars1 = (/ & 'surface_air_pressure ', & 'temperature ', & 'time_step_for_physics ', & + 'cloud_liquid_dry_mixing_ratio ', & 'cloud_ice_dry_mixing_ratio ', & 'water_temperature_at_freezing ', & 'water_vapor_specific_humidity ', & From 73ef0a397058be29b1ad4c7a3bfd0f8d73f689a5 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Fri, 1 Sep 2023 10:19:26 -0600 Subject: [PATCH 05/11] add ddt testing and update constituent standard names --- src/ccpp_constituent_prop_mod.meta | 16 +- .../sample_host_files/data1_mod.F90 | 11 + .../sample_host_files/data1_mod.meta | 25 ++ test/unit_tests/sample_host_files/ddt1.F90 | 17 ++ test/unit_tests/sample_host_files/ddt1.meta | 20 ++ .../sample_host_files/ddt1_plus.F90 | 33 +++ .../sample_host_files/ddt1_plus.meta | 20 ++ test/unit_tests/sample_host_files/ddt2.F90 | 24 ++ test/unit_tests/sample_host_files/ddt2.meta | 29 ++ .../sample_host_files/ddt2_extra_var.F90 | 34 +++ .../sample_host_files/ddt2_extra_var.meta | 34 +++ .../sample_host_files/ddt_data1_mod.F90 | 30 ++ .../sample_host_files/ddt_data1_mod.meta | 56 ++++ test/unit_tests/test_metadata_host_file.py | 261 ++++++++++++++++++ 14 files changed, 602 insertions(+), 8 deletions(-) create mode 100644 test/unit_tests/sample_host_files/data1_mod.F90 create mode 100644 test/unit_tests/sample_host_files/data1_mod.meta create mode 100644 test/unit_tests/sample_host_files/ddt1.F90 create mode 100644 test/unit_tests/sample_host_files/ddt1.meta create mode 100644 test/unit_tests/sample_host_files/ddt1_plus.F90 create mode 100644 test/unit_tests/sample_host_files/ddt1_plus.meta create mode 100644 test/unit_tests/sample_host_files/ddt2.F90 create mode 100644 test/unit_tests/sample_host_files/ddt2.meta create mode 100644 test/unit_tests/sample_host_files/ddt2_extra_var.F90 create mode 100644 test/unit_tests/sample_host_files/ddt2_extra_var.meta create mode 100644 test/unit_tests/sample_host_files/ddt_data1_mod.F90 create mode 100644 test/unit_tests/sample_host_files/ddt_data1_mod.meta create mode 100644 test/unit_tests/test_metadata_host_file.py diff --git a/src/ccpp_constituent_prop_mod.meta b/src/ccpp_constituent_prop_mod.meta index dd60eb13..99cf3145 100644 --- a/src/ccpp_constituent_prop_mod.meta +++ b/src/ccpp_constituent_prop_mod.meta @@ -16,32 +16,32 @@ name = ccpp_model_constituents_t type = ddt [ num_layer_vars ] - standard_name = ccpp_num_constituents + standard_name = number_of_ccpp_constituents long_name = Number of constituents managed by CCPP Framework units = count dimensions = () type = integer [ num_advected_vars ] - standard_name = ccpp_num_advected_constituents + standard_name = number_of_ccpp_advected_constituents long_name = Number of advected constituents managed by CCPP Framework units = count dimensions = () type = integer [ vars_layer ] - standard_name = ccpp_constituent_array + standard_name = ccpp_constituents long_name = Array of constituents managed by CCPP Framework units = none state_variable = true - dimensions = (horizontal_dimension, vertical_layer_dimension, ccpp_num_constituents) + dimensions = (horizontal_dimension, vertical_layer_dimension, number_of_ccpp_constituents) type = real | kind = kind_phys [ const_metadata ] - standard_name = ccpp_constituent_properties_array + standard_name = ccpp_constituent_properties units = None type = ccpp_constituent_prop_ptr_t - dimensions = (ccpp_num_constituents) + dimensions = (number_of_ccpp_constituents) [ vars_minvalue ] - standard_name = ccpp_constituent_array_minimum_values + standard_name = ccpp_constituent_minimum_values units = kg kg-1 type = real | kind = kind_phys - dimensions = (ccpp_num_constituents) + dimensions = (number_of_ccpp_constituents) protected = True diff --git a/test/unit_tests/sample_host_files/data1_mod.F90 b/test/unit_tests/sample_host_files/data1_mod.F90 new file mode 100644 index 00000000..b85db315 --- /dev/null +++ b/test/unit_tests/sample_host_files/data1_mod.F90 @@ -0,0 +1,11 @@ +module data1_mod + + use ccpp_kinds, only: kind_phys + + !> \section arg_table_data1_mod Argument Table + !! \htmlinclude arg_table_data1_mod.html + real(kind_phys) :: ps1 + real(kind_phys), allocatable :: xbox(:,:) + real(kind_phys), allocatable :: switch(:,:) + +end module data1_mod diff --git a/test/unit_tests/sample_host_files/data1_mod.meta b/test/unit_tests/sample_host_files/data1_mod.meta new file mode 100644 index 00000000..37e2de96 --- /dev/null +++ b/test/unit_tests/sample_host_files/data1_mod.meta @@ -0,0 +1,25 @@ +[ccpp-table-properties] + name = data1_mod + type = module +[ccpp-arg-table] + name = data1_mod + type = module +[ ps1 ] + standard_name = play_station + state_variable = true + type = real | kind = kind_phys + units = Pa + dimensions = () +[ xbox ] + standard_name = xbox + state_variable = true + type = real | kind = kind_phys + units = m s-1 + dimensions = (horizontal_dimension, vertical_layer_dimension) +[ switch ] + standard_name = nintendo_switch + long_name = Incompatible junk + state_variable = true + type = real | kind = kind_phys + units = m s-1 + dimensions = (horizontal_dimension, vertical_layer_dimension) diff --git a/test/unit_tests/sample_host_files/ddt1.F90 b/test/unit_tests/sample_host_files/ddt1.F90 new file mode 100644 index 00000000..966a62a6 --- /dev/null +++ b/test/unit_tests/sample_host_files/ddt1.F90 @@ -0,0 +1,17 @@ +module ddt1 + + use ccpp_kinds, only: kind_phys + + private + implicit none + + !! \section arg_table_ddt1_t + !! \htmlinclude ddt1_t.html + !! + type, public :: ddt1_t + integer, private :: num_vars = 0 + real(kind_phys), allocatable :: vars(:,:,:) + + end type ddt1_t + +end module ddt1 diff --git a/test/unit_tests/sample_host_files/ddt1.meta b/test/unit_tests/sample_host_files/ddt1.meta new file mode 100644 index 00000000..e1a0f1ac --- /dev/null +++ b/test/unit_tests/sample_host_files/ddt1.meta @@ -0,0 +1,20 @@ +######################################################################## +[ccpp-table-properties] + name = ddt1_t + type = ddt + +[ccpp-arg-table] + name = ddt1_t + type = ddt +[ num_vars ] + standard_name = ddt_var_array_dimension + long_name = Number of vars managed by ddt1 + units = count + dimensions = () + type = integer +[ vars ] + standard_name = vars_array + long_name = Array of vars managed by ddt1 + units = none + dimensions = (horizontal_dimension, vertical_layer_dimension, number_of_ccpp_constituents) + type = real | kind = kind_phys diff --git a/test/unit_tests/sample_host_files/ddt1_plus.F90 b/test/unit_tests/sample_host_files/ddt1_plus.F90 new file mode 100644 index 00000000..90571a73 --- /dev/null +++ b/test/unit_tests/sample_host_files/ddt1_plus.F90 @@ -0,0 +1,33 @@ +module ddt1_plus + + use ccpp_kinds, only: kind_phys + + private + implicit none + + !! + type, public :: ddt1_t + real, pointer :: undocumented_array(:) => NULL() + contains + procedure :: this_is_a_documented_object + end type ddt1_t + + !! \section arg_table_ddt2_t + !! \htmlinclude ddt2_t.html + !! + type, public :: ddt2_t + integer, private :: num_vars = 0 + real(kind_phys), allocatable :: vars(:,:,:) + + end type ddt2_t + +CONTAINS + + logical function this_is_a_documented_object(this) + class(ddt1_t) :: intent(in) :: this + + this_is_a_documented_object = .false. + + end function this_is_a_documented_object + +end module ddt1_plus diff --git a/test/unit_tests/sample_host_files/ddt1_plus.meta b/test/unit_tests/sample_host_files/ddt1_plus.meta new file mode 100644 index 00000000..ca3a92ab --- /dev/null +++ b/test/unit_tests/sample_host_files/ddt1_plus.meta @@ -0,0 +1,20 @@ +######################################################################## +[ccpp-table-properties] + name = ddt2_t + type = ddt + +[ccpp-arg-table] + name = ddt2_t + type = ddt +[ num_vars ] + standard_name = ddt_var_array_dimension + long_name = Number of vars managed by ddt2 + units = count + dimensions = () + type = integer +[ vars ] + standard_name = vars_array + long_name = Array of vars managed by ddt2 + units = none + dimensions = (horizontal_dimension, vertical_layer_dimension, number_of_ccpp_constituents) + type = real | kind = kind_phys diff --git a/test/unit_tests/sample_host_files/ddt2.F90 b/test/unit_tests/sample_host_files/ddt2.F90 new file mode 100644 index 00000000..f4ed9cd0 --- /dev/null +++ b/test/unit_tests/sample_host_files/ddt2.F90 @@ -0,0 +1,24 @@ +module ddt2 + + use ccpp_kinds, only: kind_phys + + private + implicit none + + !! \section arg_table_ddt1_t + !! \htmlinclude ddt1_t.html + !! + type, public :: ddt1_t + real, pointer :: undocumented_array(:) => NULL() + end type ddt1_t + + !! \section arg_table_ddt2_t + !! \htmlinclude ddt2_t.html + !! + type, public :: ddt2_t + integer, private :: num_vars = 0 + real(kind_phys), allocatable :: vars(:,:,:) + + end type ddt2_t + +end module ddt2 diff --git a/test/unit_tests/sample_host_files/ddt2.meta b/test/unit_tests/sample_host_files/ddt2.meta new file mode 100644 index 00000000..159f08b0 --- /dev/null +++ b/test/unit_tests/sample_host_files/ddt2.meta @@ -0,0 +1,29 @@ +######################################################################## +[ccpp-table-properties] + name = ddt1_t + type = ddt + +[ccpp-arg-table] + name = ddt1_t + type = ddt + +######################################################################## +[ccpp-table-properties] + name = ddt2_t + type = ddt + +[ccpp-arg-table] + name = ddt2_t + type = ddt +[ num_vars ] + standard_name = ddt_var_array_dimension + long_name = Number of vars managed by ddt2 + units = count + dimensions = () + type = integer +[ vars ] + standard_name = vars_array + long_name = Array of vars managed by ddt2 + units = none + dimensions = (horizontal_dimension, vertical_layer_dimension, number_of_ccpp_constituents) + type = real | kind = kind_phys diff --git a/test/unit_tests/sample_host_files/ddt2_extra_var.F90 b/test/unit_tests/sample_host_files/ddt2_extra_var.F90 new file mode 100644 index 00000000..07bafe4b --- /dev/null +++ b/test/unit_tests/sample_host_files/ddt2_extra_var.F90 @@ -0,0 +1,34 @@ +module ddt2_extra_var + + use ccpp_kinds, only: kind_phys + + private + implicit none + + !! \section arg_table_ddt1_t + !! \htmlinclude ddt1_t.html + !! + type, public :: ddt1_t + real, pointer :: undocumented_array(:) => NULL() + end type ddt1_t + + !! \section arg_table_ddt2_t + !! \htmlinclude ddt2_t.html + !! + type, public :: ddt2_t + integer, private :: num_vars = 0 + real(kind_phys), allocatable :: vars(:,:,:) + contains + procedure :: get_num_vars + end type ddt2_t + +CONTAINS + + integer function get_num_vars(this) + class(ddt2_t), intent(in) :: this + + get_num_vars = this%num_vars + + end function get_num_vars + +end module ddt2_extra_var diff --git a/test/unit_tests/sample_host_files/ddt2_extra_var.meta b/test/unit_tests/sample_host_files/ddt2_extra_var.meta new file mode 100644 index 00000000..867720e5 --- /dev/null +++ b/test/unit_tests/sample_host_files/ddt2_extra_var.meta @@ -0,0 +1,34 @@ +######################################################################## +[ccpp-table-properties] + name = ddt1_t + type = ddt + +[ccpp-arg-table] + name = ddt1_t + type = ddt + +######################################################################## +[ccpp-table-properties] + name = ddt2_t + type = ddt + +[ccpp-arg-table] + name = ddt2_t + type = ddt +[ num_vars ] + standard_name = ddt_var_array_dimension + long_name = Number of vars managed by ddt2 + units = count + dimensions = () + type = integer +[ vars ] + standard_name = vars_array + long_name = Array of vars managed by ddt2 + units = none + dimensions = (horizontal_dimension, vertical_layer_dimension, number_of_ccpp_constituents) + type = real | kind = kind_phys +[ bogus ] + standard_name = misplaced_variable + units = count + dimensions = () + type = integer diff --git a/test/unit_tests/sample_host_files/ddt_data1_mod.F90 b/test/unit_tests/sample_host_files/ddt_data1_mod.F90 new file mode 100644 index 00000000..0607f11c --- /dev/null +++ b/test/unit_tests/sample_host_files/ddt_data1_mod.F90 @@ -0,0 +1,30 @@ +module ddt_data1_mod + + use ccpp_kinds, only: kind_phys + + private + implicit none + + !! \section arg_table_ddt1_t + !! \htmlinclude ddt1_t.html + !! + type, public :: ddt1_t + real, pointer :: undocumented_array(:) => NULL() + end type ddt1_t + + !! \section arg_table_ddt2_t + !! \htmlinclude ddt2_t.html + !! + type, public :: ddt2_t + integer, private :: num_vars = 0 + real(kind_phys), allocatable :: vars(:,:,:) + + end type ddt2_t + + !> \section arg_table_ddt_data1_mod Argument Table + !! \htmlinclude arg_table_ddt_data1_mod.html + real(kind_phys) :: ps1 + real(kind_phys), allocatable :: xbox(:,:) + real(kind_phys), allocatable :: switch(:,:) + +end module ddt_data1_mod diff --git a/test/unit_tests/sample_host_files/ddt_data1_mod.meta b/test/unit_tests/sample_host_files/ddt_data1_mod.meta new file mode 100644 index 00000000..e149c07b --- /dev/null +++ b/test/unit_tests/sample_host_files/ddt_data1_mod.meta @@ -0,0 +1,56 @@ +######################################################################## +[ccpp-table-properties] + name = ddt1_t + type = ddt + +[ccpp-arg-table] + name = ddt1_t + type = ddt + +######################################################################## +[ccpp-table-properties] + name = ddt2_t + type = ddt + +[ccpp-arg-table] + name = ddt2_t + type = ddt +[ num_vars ] + standard_name = ddt_var_array_dimension + long_name = Number of vars managed by ddt2 + units = count + dimensions = () + type = integer +[ vars ] + standard_name = vars_array + long_name = Array of vars managed by ddt2 + units = none + dimensions = (horizontal_dimension, vertical_layer_dimension, number_of_ccpp_constituents) + type = real | kind = kind_phys + +######################################################################## +[ccpp-table-properties] + name = ddt_data1_mod + type = module +[ccpp-arg-table] + name = ddt_data1_mod + type = module +[ ps1 ] + standard_name = play_station + state_variable = true + type = real | kind = kind_phys + units = Pa + dimensions = () +[ xbox ] + standard_name = xbox + state_variable = true + type = real | kind = kind_phys + units = m s-1 + dimensions = (horizontal_dimension, vertical_layer_dimension) +[ switch ] + standard_name = nintendo_switch + long_name = Incompatible junk + state_variable = true + type = real | kind = kind_phys + units = m s-1 + dimensions = (horizontal_dimension, vertical_layer_dimension) diff --git a/test/unit_tests/test_metadata_host_file.py b/test/unit_tests/test_metadata_host_file.py new file mode 100644 index 00000000..d01fda97 --- /dev/null +++ b/test/unit_tests/test_metadata_host_file.py @@ -0,0 +1,261 @@ +#! /usr/bin/env python3 + +""" +----------------------------------------------------------------------- + Description: capgen needs to compare a metadata header against the + associated CCPP Fortran interface routine. This set of + tests is testing the parse_host_model_files function in + ccpp_capgen.py which performs the operations in the first + bullet below. Each test calls this function. + + * This script contains unit tests that do the following: + 1) Read one or more metadata files (to collect + the metadata headers) + 2) Read the associated CCPP Fortran host file(s) (to + collect Fortran interfaces) + 3) Create a CCPP host model object + 3) Test the properties of the CCPP host model object + + * Tests include: + - Correctly parse and match a simple module file with + data fields (a data block) + - Correctly parse and match a simple module file with + a DDT definition + - Correctly parse and match a simple module file with + two DDT definitions + - Correctly parse and match a simple module file with + two DDT definitions and a data block + + Assumptions: + + Command line arguments: none + + Usage: python3 test_metadata_host_file.py # run the unit tests +----------------------------------------------------------------------- +""" +import sys +import os +import logging +import unittest + +_TEST_DIR = os.path.dirname(os.path.abspath(__file__)) +_SCRIPTS_DIR = os.path.abspath(os.path.join(_TEST_DIR, os.pardir, + os.pardir, "scripts")) +if not os.path.exists(_SCRIPTS_DIR): + raise ImportError("Cannot find scripts directory") + +sys.path.append(_SCRIPTS_DIR) + +# pylint: disable=wrong-import-position +from ccpp_capgen import parse_host_model_files +from framework_env import CCPPFrameworkEnv +from parse_tools import CCPPError +# pylint: enable=wrong-import-position + +class MetadataHeaderTestCase(unittest.TestCase): + """Unit tests for parse_host_model_files""" + + def setUp(self): + """Setup important directories and logging""" + self._sample_files_dir = os.path.join(_TEST_DIR, "sample_host_files") + logger = logging.getLogger(self.__class__.__name__) + self._run_env = CCPPFrameworkEnv(logger, ndict={'host_files':'', + 'scheme_files':'', + 'suites':''}) + + def test_module_with_data(self): + """Test that a module containing a data block is parsed and matched + correctly.""" + # Setup + module_files = [os.path.join(self._sample_files_dir, "data1_mod.meta")] + # Exercise + hname = 'host_name_data1' + host_model = parse_host_model_files(module_files, hname, self._run_env) + # Verify the name of the host model + self.assertEqual(host_model.name, hname) + module_headers = host_model.metadata_tables() + self.assertEqual(len(module_headers), 1) + # Verify header titles + self.assertTrue('data1_mod' in module_headers) + # Verify host model variable list + vlist = host_model.variable_list() + self.assertEqual(len(vlist), 3) + std_names = [x.get_prop_value('standard_name') for x in vlist] + self.assertTrue('play_station' in std_names) + self.assertTrue('xbox' in std_names) + self.assertTrue('nintendo_switch' in std_names) + + def test_module_with_one_ddt(self): + """Test that a module containing a DDT definition is parsed and matched + correctly.""" + # Setup + ddt_name = 'ddt1_t' + module_files = [os.path.join(self._sample_files_dir, "ddt1.meta")] + # Exercise + hname = 'host_name_ddt1' + host_model = parse_host_model_files(module_files, hname, self._run_env) + # Verify the name of the host model + self.assertEqual(host_model.name, hname) + module_headers = host_model.metadata_tables() + self.assertEqual(len(module_headers), 1) + # Verify header titles + self.assertTrue(ddt_name in module_headers) + # Verify host model variable list + vlist = host_model.variable_list() + self.assertEqual(len(vlist), 0) + # Verify that the DDT was found and parsed + ddt_lib = host_model.ddt_lib + self.assertEqual(ddt_lib.name, f"{hname}_ddts_ddt_lib") + # Check DDT variables + ddt_mod = ddt_lib[ddt_name] + self.assertEqual(ddt_mod.name, ddt_name) + vlist = ddt_mod.variable_list() + self.assertEqual(len(vlist), 2) + std_names = [x.get_prop_value('standard_name') for x in vlist] + self.assertTrue('ddt_var_array_dimension' in std_names) + self.assertTrue('vars_array' in std_names) + + def test_module_with_two_ddts(self): + """Test that a module containing two DDT definitions is parsed and + matched correctly.""" + # Setup + ddt_names = ['ddt1_t', 'ddt2_t'] + ddt_vars = [(), ('ddt_var_array_dimension', 'vars_array')] + + module_files = [os.path.join(self._sample_files_dir, "ddt2.meta")] + # Exercise + hname = 'host_name_ddt2' + host_model = parse_host_model_files(module_files, hname, self._run_env) + # Verify the name of the host model + self.assertEqual(host_model.name, hname) + module_headers = host_model.metadata_tables() + self.assertEqual(len(module_headers), len(ddt_names)) + # Verify header titles + for ddt_name in ddt_names: + self.assertTrue(ddt_name in module_headers) + # end for + # Verify host model variable list + vlist = host_model.variable_list() + self.assertEqual(len(vlist), 0) + # Verify that each DDT was found and parsed + ddt_lib = host_model.ddt_lib + self.assertEqual(ddt_lib.name, f"{hname}_ddts_ddt_lib") + # Check DDT variables + for index, ddt_name in enumerate(ddt_names): + ddt_mod = ddt_lib[ddt_name] + self.assertEqual(ddt_mod.name, ddt_name) + vlist = ddt_mod.variable_list() + self.assertEqual(len(vlist), len(ddt_vars[index])) + std_names = [x.get_prop_value('standard_name') for x in vlist] + for sname in ddt_vars[index]: + self.assertTrue(sname in std_names) + # end for + # end for + + def test_module_with_two_ddts_and_data(self): + """Test that a module containing two DDT definitions and a block of + module data is parsed and matched correctly.""" + # Setup + ddt_names = ['ddt1_t', 'ddt2_t'] + ddt_vars = [(), ('ddt_var_array_dimension', 'vars_array')] + + module_files = [os.path.join(self._sample_files_dir, + "ddt_data1_mod.meta")] + # Exercise + hname = 'host_name_ddt_data' + host_model = parse_host_model_files(module_files, hname, self._run_env) + # Verify the name of the host model + self.assertEqual(host_model.name, hname) + module_headers = host_model.metadata_tables() + self.assertEqual(len(module_headers), len(ddt_names) + 1) + # Verify header titles + for ddt_name in ddt_names: + self.assertTrue(ddt_name in module_headers) + # end for + # Verify host model variable list + vlist = host_model.variable_list() + self.assertEqual(len(vlist), 3) + # Verify that each DDT was found and parsed + ddt_lib = host_model.ddt_lib + self.assertEqual(ddt_lib.name, f"{hname}_ddts_ddt_lib") + # Check DDT variables + for index, ddt_name in enumerate(ddt_names): + ddt_mod = ddt_lib[ddt_name] + self.assertEqual(ddt_mod.name, ddt_name) + vlist = ddt_mod.variable_list() + self.assertEqual(len(vlist), len(ddt_vars[index])) + std_names = [x.get_prop_value('standard_name') for x in vlist] + for sname in ddt_vars[index]: + self.assertTrue(sname in std_names) + # end for + # end for + # Verify header titles + self.assertTrue('ddt_data1_mod' in module_headers) + # Verify host model variable list + vlist = host_model.variable_list() + self.assertEqual(len(vlist), 3) + std_names = [x.get_prop_value('standard_name') for x in vlist] + self.assertTrue('play_station' in std_names) + self.assertTrue('xbox' in std_names) + self.assertTrue('nintendo_switch' in std_names) + + def test_module_with_one_ddt_plus_undoc(self): + """Test that a module containing a one documented DDT definition + (i.e., with metadata) and one DDT without (i.e., no metadata) + is parsed and matched correctly.""" + # Setup + ddt_name = 'ddt2_t' + module_files = [os.path.join(self._sample_files_dir, "ddt1_plus.meta")] + # Exercise + hname = 'host_name_ddt1_plus' + host_model = parse_host_model_files(module_files, hname, self._run_env) + # Verify the name of the host model + self.assertEqual(host_model.name, hname) + module_headers = host_model.metadata_tables() + self.assertEqual(len(module_headers), 1) + # Verify header titles + self.assertTrue(ddt_name in module_headers) + # Verify host model variable list + vlist = host_model.variable_list() + self.assertEqual(len(vlist), 0) + # Verify that the DDT was found and parsed + ddt_lib = host_model.ddt_lib + self.assertEqual(ddt_lib.name, f"{hname}_ddts_ddt_lib") + # Check DDT variables + ddt_mod = ddt_lib[ddt_name] + self.assertEqual(ddt_mod.name, ddt_name) + vlist = ddt_mod.variable_list() + self.assertEqual(len(vlist), 2) + std_names = [x.get_prop_value('standard_name') for x in vlist] + self.assertTrue('ddt_var_array_dimension' in std_names) + self.assertTrue('vars_array' in std_names) + + def test_module_with_two_ddts_and_extra_var(self): + """Test that a module containing two DDT definitions is parsed and + a useful error message is produced if the DDT metadata has an + extra variable.""" + # Setup + ddt_names = ['ddt1_t', 'ddt2_t'] + ddt_vars = [(), ('ddt_var_array_dimension', 'vars_array')] + + module_files = [os.path.join(self._sample_files_dir, + "ddt2_extra_var.meta")] + # Exercise + hname = 'host_name_ddt_extra_var' + with self.assertRaises(CCPPError) as context: + host_model = parse_host_model_files(module_files, hname, + self._run_env) + # end with + # Check error messages + except_str = str(context.exception) + emsgs = ["Variable mismatch in ddt2_t, variables missing from Fortran ddt.", + + "No Fortran variable for bogus in ddt2_t", + "2 errors found comparing"] + for emsg in emsgs: + self.assertTrue(emsg in except_str) + # end for + +if __name__ == "__main__": + unittest.main() + From 071687e57fdda69bb918b51dd1d2db0f2e919fa5 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Fri, 1 Sep 2023 10:51:36 -0600 Subject: [PATCH 06/11] add testing files --- .../fortran_files/comments_test.F90 | 33 ++ .../fortran_files/linebreak_test.F90 | 39 +++ test/unit_tests/test_fortran_write.py | 127 +++++++ test/unit_tests/test_metadata_scheme_file.py | 326 +++++++++++------- 4 files changed, 393 insertions(+), 132 deletions(-) create mode 100644 test/unit_tests/sample_files/fortran_files/comments_test.F90 create mode 100644 test/unit_tests/sample_files/fortran_files/linebreak_test.F90 create mode 100644 test/unit_tests/test_fortran_write.py diff --git a/test/unit_tests/sample_files/fortran_files/comments_test.F90 b/test/unit_tests/sample_files/fortran_files/comments_test.F90 new file mode 100644 index 00000000..d4820a36 --- /dev/null +++ b/test/unit_tests/sample_files/fortran_files/comments_test.F90 @@ -0,0 +1,33 @@ +! +! This work (Common Community Physics Package Framework), identified by +! NOAA, NCAR, CU/CIRES, is free of known copyright restrictions and is +! placed in the public domain. +! +! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +! THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +! IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +! CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +!> +!! @brief Auto-generated Test of comment writing for FortranWriter +!! +! +module comments_test + +! We can write comments in the module header + ! We can write indented comments in the header + integer :: foo ! Comment at end of line works + integer :: bar ! + ! xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + ! + integer :: baz ! + ! yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + ! yyyyy + +CONTAINS + ! We can write comments in the module body + +end module comments_test diff --git a/test/unit_tests/sample_files/fortran_files/linebreak_test.F90 b/test/unit_tests/sample_files/fortran_files/linebreak_test.F90 new file mode 100644 index 00000000..4f89441f --- /dev/null +++ b/test/unit_tests/sample_files/fortran_files/linebreak_test.F90 @@ -0,0 +1,39 @@ +! +! This work (Common Community Physics Package Framework), identified by +! NOAA, NCAR, CU/CIRES, is free of known copyright restrictions and is +! placed in the public domain. +! +! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +! THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +! IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +! CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +!> +!! @brief Auto-generated Test of line breaking for FortranWriter +!! +! +module linebreak_test + + character(len=7) :: data = (/ name000, name001, name002, name003, name004, name005, name006, & + name007, name008, name009, name010, name011, name012, name013, name014, name015, & + name016, name017, name018, name019, name020, name021, name022, name023, name024, & + name025, name026, name027, name028, name029, name030, name031, name032, name033, & + name034, name035, name036, name037, name038, name039, name040, name041, name042, & + name043, name044, name045, name046, name047, name048, name049, name050, name051, & + name052, name053, name054, name055, name056, name057, name058, name059, name060, & + name061, name062, name063, name064, name065, name066, name067, name068, name069, & + name070, name071, name072, name073, name074, name075, name076, name077, name078, & + name079, name080, name081, name082, name083, name084, name085, name086, name087, & + name088, name089, name090, name091, name092, name093, name094, name095, name096, & + name097, name098, name099 /) + +CONTAINS + call & + endrun('Cannot read columns_on_task from file'// & + ', columns_on_task has no horizontal dimension; columns_on_task is a protected variable') + + +end module linebreak_test diff --git a/test/unit_tests/test_fortran_write.py b/test/unit_tests/test_fortran_write.py new file mode 100644 index 00000000..87e64baa --- /dev/null +++ b/test/unit_tests/test_fortran_write.py @@ -0,0 +1,127 @@ +#! /usr/bin/env python3 +""" +----------------------------------------------------------------------- + Description: Contains unit tests for FortranWriter + in scripts file fortran/fortran_write.py + + Assumptions: + + Command line arguments: none + + Usage: python3 test_fortran_write.py # run the unit tests +----------------------------------------------------------------------- +""" + +import filecmp +import glob +import os +import sys +import unittest + +_TEST_DIR = os.path.dirname(os.path.abspath(__file__)) +_SCRIPTS_DIR = os.path.abspath(os.path.join(_TEST_DIR, os.pardir, + os.pardir, "scripts")) +_SAMPLE_FILES_DIR = os.path.join(_TEST_DIR, "sample_files", "fortran_files") +_PRE_TMP_DIR = os.path.join(_TEST_DIR, "tmp") +_TMP_DIR = os.path.join(_PRE_TMP_DIR, "fortran_files") + +if not os.path.exists(_SCRIPTS_DIR): + raise ImportError(f"Cannot find scripts directory, {_SCRIPTS_DIR}") + +sys.path.append(_SCRIPTS_DIR) + +# pylint: disable=wrong-import-position +from fortran_tools import FortranWriter +# pylint: enable=wrong-import-position + +############################################################################### +def remove_files(file_list): +############################################################################### + """Remove files in if they exist""" + if isinstance(file_list, str): + file_list = [file_list] + # end if + for fpath in file_list: + if os.path.exists(fpath): + os.remove(fpath) + # End if + # End for + +class MetadataTableTestCase(unittest.TestCase): + + """Tests for `FortranWriter`.""" + + @classmethod + def setUpClass(cls): + """Clean output directory (tmp) before running tests""" + #Does "tmp" directory exist? If not then create it: + if not os.path.exists(_PRE_TMP_DIR): + os.mkdir(_PRE_TMP_DIR) + # Ensure the "sample_files/fortran_files" directory exists and is empty + if os.path.exists(_TMP_DIR): + # Clear out all files: + remove_files(glob.iglob(os.path.join(_TMP_DIR, '*.*'))) + else: + os.makedirs(_TMP_DIR) + # end if + + #Run inherited setup method: + super().setUpClass() + + def test_line_breaking(self): + """Test that FortranWriter correctly breaks long lines""" + # Setup + testname = "linebreak_test" + compare = os.path.join(_SAMPLE_FILES_DIR, f"{testname}.F90") + generate = os.path.join(_TMP_DIR, f"{testname}.F90") + # Exercise + header = "Test of line breaking for FortranWriter" + with FortranWriter(generate, 'w', header, f"{testname}") as gen: + # Test long declaration + data_items = ', '.join([f"name{x:03}" for x in range(100)]) + gen.write(f"character(len=7) :: data = (/ {data_items} /)", 1) + gen.end_module_header() + # Test long code lines + line_items = ["call endrun('Cannot read columns_on_task from ", + "file'//', columns_on_task has no horizontal ", + "dimension; columns_on_task is a ", + "protected variable')"] + gen.write(f"{''.join(line_items)}", 2) + # end with + + # Check that file was generated + amsg = f"{generate} does not exist" + self.assertTrue(os.path.exists(generate), msg=amsg) + amsg = f"{generate} does not match {compare}" + self.assertTrue(filecmp.cmp(generate, compare, shallow=False), msg=amsg) + + def test_good_comments(self): + """Test that comments are written and broken correctly.""" + # Setup + testname = "comments_test" + compare = os.path.join(_SAMPLE_FILES_DIR, f"{testname}.F90") + generate = os.path.join(_TMP_DIR, f"{testname}.F90") + # Exercise + header = "Test of comment writing for FortranWriter" + with FortranWriter(generate, 'w', header, f"{testname}") as gen: + gen.comment("We can write comments in the module header", 0) + gen.comment("We can write indented comments in the header", 1) + gen.write("integer :: foo ! Comment at end of line works", 1) + # Test long comments at end of line + gen.write(f"integer :: bar ! {'x'*100}", 1) + gen.write(f"integer :: baz ! {'y'*130}", 1) + gen.end_module_header() + # Test comment line in body + gen.comment("We can write comments in the module body", 1) + + # end with + + # Check that file was generated + amsg = f"{generate} does not exist" + self.assertTrue(os.path.exists(generate), msg=amsg) + amsg = f"{generate} does not match {compare}" + self.assertTrue(filecmp.cmp(generate, compare, shallow=False), msg=amsg) + +if __name__ == "__main__": + unittest.main() + diff --git a/test/unit_tests/test_metadata_scheme_file.py b/test/unit_tests/test_metadata_scheme_file.py index 52f62aa1..03f446e3 100644 --- a/test/unit_tests/test_metadata_scheme_file.py +++ b/test/unit_tests/test_metadata_scheme_file.py @@ -59,6 +59,7 @@ # pylint: disable=wrong-import-position from ccpp_capgen import parse_scheme_files from framework_env import CCPPFrameworkEnv +from parse_tools import CCPPError # pylint: enable=wrong-import-position class MetadataHeaderTestCase(unittest.TestCase): @@ -67,6 +68,7 @@ class MetadataHeaderTestCase(unittest.TestCase): def setUp(self): """Setup important directories and logging""" self._sample_files_dir = os.path.join(_TEST_DIR, "sample_scheme_files") + self._host_files_dir = os.path.join(_TEST_DIR, "sample_host_files") logger = logging.getLogger(self.__class__.__name__) self._run_env = CCPPFrameworkEnv(logger, ndict={'host_files':'', 'scheme_files':'', @@ -85,203 +87,263 @@ def setUp(self): 'CCPP=2'}) def test_good_scheme_file(self): - """Test that good metadata file matches the Fortran, with routines in the same order """ - #Setup + """Test that good metadata file matches the Fortran, + with routines in the same order """ + # Setup scheme_files = [os.path.join(self._sample_files_dir, "temp_adjust.meta")] - #Exercise + # Exercise scheme_headers, table_dict = parse_scheme_files(scheme_files, self._run_env) - #Verify size of returned list equals number of scheme headers in the test file - # and that header (subroutine) names are 'temp_adjust_[init,run,finalize]' + # Verify size of returned list equals number of scheme headers + # in the test file and that header (subroutine) names are + # 'temp_adjust_[init,run,finalize]' self.assertEqual(len(scheme_headers), 3) - #Verify header titles + # Verify header titles titles = [elem.title for elem in scheme_headers] self.assertTrue('temp_adjust_init' in titles) self.assertTrue('temp_adjust_run' in titles) self.assertTrue('temp_adjust_finalize' in titles) - #Verify size and name of table_dict matches scheme name + # Verify size and name of table_dict matches scheme name self.assertEqual(len(table_dict), 1) self.assertTrue('temp_adjust' in table_dict) def test_reordered_scheme_file(self): - """Test that metadata file matches the Fortran when the routines are not in the same order """ - #Setup + """Test that metadata file matches the Fortran when the + routines are not in the same order """ + # Setup scheme_files = [os.path.join(self._sample_files_dir, "reorder.meta")] - #Exercise + # Exercise scheme_headers, table_dict = parse_scheme_files(scheme_files, self._run_env) - #Verify size of returned list equals number of scheme headers in the test file - # and that header (subroutine) names are 'reorder_[init,run,finalize]' + # Verify size of returned list equals number of scheme headers + # in the test file and that header (subroutine) names are + # 'reorder_[init,run,finalize]' self.assertEqual(len(scheme_headers), 3) - #Verify header titles + # Verify header titles titles = [elem.title for elem in scheme_headers] self.assertTrue('reorder_init' in titles) self.assertTrue('reorder_run' in titles) self.assertTrue('reorder_finalize' in titles) - #Verify size and name of table_dict matches scheme name + # Verify size and name of table_dict matches scheme name self.assertEqual(len(table_dict), 1) self.assertTrue('reorder' in table_dict) def test_missing_metadata_header(self): - """Test that a missing metadata header (aka arg table) is corretly detected """ - #Setup - scheme_files = [os.path.join(self._sample_files_dir, "missing_arg_table.meta")] - #Exercise - with self.assertRaises(Exception) as context: + """Test that a missing metadata header (aka arg table) is + corretly detected """ + # Setup + scheme_files = [os.path.join(self._sample_files_dir, + "missing_arg_table.meta")] + # Exercise + with self.assertRaises(CCPPError) as context: parse_scheme_files(scheme_files, self._run_env) - #Verify correct error message returned + # Verify correct error message returned emsg = "No matching metadata header found for missing_arg_table_run in" self.assertTrue(emsg in str(context.exception)) def test_missing_fortran_header(self): """Test that a missing fortran header is corretly detected """ - #Setup - scheme_files = [os.path.join(self._sample_files_dir, "missing_fort_header.meta")] - #Exercise - with self.assertRaises(Exception) as context: + # Setup + scheme_files = [os.path.join(self._sample_files_dir, + "missing_fort_header.meta")] + # Exercise + with self.assertRaises(CCPPError) as context: parse_scheme_files(scheme_files, self._run_env) - #Verify correct error message returned + # Verify correct error message returned emsg = "No matching Fortran routine found for missing_fort_header_run in" self.assertTrue(emsg in str(context.exception)) def test_mismatch_intent(self): - """Test that differing intent, kind, rank, and type between metadata and fortran is corretly detected """ - #Setup - scheme_files = [os.path.join(self._sample_files_dir, "mismatch_intent.meta")] - #Exercise - with self.assertRaises(Exception) as context: + """Test that differing intent, kind, rank, and type between + metadata and fortran is corretly detected """ + # Setup + scheme_files = [os.path.join(self._sample_files_dir, + "mismatch_intent.meta")] + # Exercise + with self.assertRaises(CCPPError) as context: parse_scheme_files(scheme_files, self._run_env) - #Verify 4 correct error messages returned - self.assertTrue('intent mismatch (in != inout) in mismatch_intent_run, at' in str(context.exception)) - self.assertTrue('kind mismatch (kind_fizz != kind_phys) in mismatch_intent_run, at' in str(context.exception)) - self.assertTrue('rank mismatch in mismatch_intent_run/potential_temperature (0 != 1), at' in str(context.exception)) - self.assertTrue('type mismatch (integer != real) in mismatch_intent_run, at' in str(context.exception)) - self.assertTrue('4 errors found comparing' in str(context.exception)) + # Verify 4 correct error messages returned + emsg = "intent mismatch (in != inout) in mismatch_intent_run, at" + self.assertTrue(emsg in str(context.exception)) + emsg = "kind mismatch (kind_fizz != kind_phys) in mismatch_intent_run, at" + self.assertTrue(emsg in str(context.exception)) + emsg = "rank mismatch in mismatch_intent_run/potential_temperature (0 != 1), at" + self.assertTrue(emsg in str(context.exception)) + emsg = "type mismatch (integer != real) in mismatch_intent_run, at" + self.assertTrue(emsg in str(context.exception)) + self.assertTrue("4 errors found comparing" in str(context.exception)) def test_invalid_subr_stmnt(self): - """Test that invalid Fortran subroutine statements are correctly detected """ - #Setup - scheme_files = [os.path.join(self._sample_files_dir, "invalid_subr_stmnt.meta")] - #Exercise - with self.assertRaises(Exception) as context: + """Test that invalid Fortran subroutine statements are correctly + detected """ + # Setup + scheme_files = [os.path.join(self._sample_files_dir, + "invalid_subr_stmnt.meta")] + # Exercise + with self.assertRaises(CCPPError) as context: parse_scheme_files(scheme_files, self._run_env) - #Verify correct error message returned - self.assertTrue("Invalid dummy argument, 'errmsg', at" in str(context.exception)) + # Verify correct error message returned + self.assertTrue("Invalid dummy argument, 'errmsg', at" + in str(context.exception)) def test_invalid_dummy_arg(self): - """Test that invalid dummy argument statements are correctly detected """ - #Setup - scheme_files = [os.path.join(self._sample_files_dir, "invalid_dummy_arg.meta")] - #Exercise - with self.assertRaises(Exception) as context: + """Test that invalid dummy argument statements are correctly detected""" + # Setup + scheme_files = [os.path.join(self._sample_files_dir, + "invalid_dummy_arg.meta")] + # Exercise + with self.assertRaises(CCPPError) as context: parse_scheme_files(scheme_files, self._run_env) - #Verify correct error message returned - self.assertTrue("Invalid dummy argument, 'woohoo', at" in str(context.exception)) + # Verify correct error message returned + emsg = "Invalid dummy argument, 'woohoo', at" + self.assertTrue(emsg in str(context.exception)) -# pylint: disable=invalid-name - def test_CCPPnotset_var_missing_in_meta(self): - """Test for correct detection of a variable that REMAINS in the subroutine argument list - (due to an undefined pre-processor directive: #ifndef CCPP), BUT IS NOT PRESENT in meta file""" - #Setup - scheme_files = [os.path.join(self._sample_files_dir, "CCPPnotset_var_missing_in_meta.meta")] - #Exercise - with self.assertRaises(Exception) as context: + def test_ccpp_notset_var_missing_in_meta(self): + """Test for correct detection of a variable that REMAINS in the + subroutine argument list + (due to an undefined pre-processor directive: #ifndef CCPP), + BUT IS NOT PRESENT in meta file""" + # Setup + scheme_files = [os.path.join(self._sample_files_dir, + "CCPPnotset_var_missing_in_meta.meta")] + # Exercise + with self.assertRaises(CCPPError) as context: parse_scheme_files(scheme_files, self._run_env) - #Verify 3 correct error messages returned - self.assertTrue('Variable mismatch in CCPPnotset_var_missing_in_meta_run, variables missing from metadata header.' - in str(context.exception)) - self.assertTrue('Out of order argument, errmsg in CCPPnotset_var_missing_in_meta_run' in str(context.exception)) - self.assertTrue('Out of order argument, errflg in CCPPnotset_var_missing_in_meta_run' in str(context.exception)) - self.assertTrue('3 errors found comparing' in str(context.exception)) + # Verify 3 correct error messages returned + emsg = "Variable mismatch in CCPPnotset_var_missing_in_meta_run, " + \ + "variables missing from metadata header." + self.assertTrue(emsg in str(context.exception)) + emsg = "Out of order argument, errmsg in CCPPnotset_var_missing_in_meta_run" + self.assertTrue(emsg in str(context.exception)) + emsg = "Out of order argument, errflg in CCPPnotset_var_missing_in_meta_run" + self.assertTrue(emsg in str(context.exception)) + self.assertTrue("3 errors found comparing" in str(context.exception)) - def test_CCPPeq1_var_missing_in_fort(self): - """Test for correct detection of a variable that IS REMOVED the subroutine argument list - (due to a pre-processor directive: #ifndef CCPP), but IS PRESENT in meta file""" - #Setup - scheme_files = [os.path.join(self._sample_files_dir, "CCPPeq1_var_missing_in_fort.meta")] - #Exercise - with self.assertRaises(Exception) as context: + def test_ccpp_eq1_var_missing_in_fort(self): + """Test for correct detection of a variable that IS REMOVED the + subroutine argument list + (due to a pre-processor directive: #ifndef CCPP), + but IS PRESENT in meta file""" + # Setup + scheme_files = [os.path.join(self._sample_files_dir, + "CCPPeq1_var_missing_in_fort.meta")] + # Exercise + with self.assertRaises(CCPPError) as context: parse_scheme_files(scheme_files, self._run_env_ccpp) - #Verify 3 correct error messages returned - self.assertTrue('Variable mismatch in CCPPeq1_var_missing_in_fort_run, variables missing from Fortran scheme.' - in str(context.exception)) - self.assertTrue('Variable mismatch in CCPPeq1_var_missing_in_fort_run, no Fortran variable bar.' - in str(context.exception)) - self.assertTrue('Out of order argument, errmsg in CCPPeq1_var_missing_in_fort_run' in str(context.exception)) - self.assertTrue('3 errors found comparing' in str(context.exception)) + # Verify 3 correct error messages returned + emsg = "Variable mismatch in CCPPeq1_var_missing_in_fort_run, " + \ + "variables missing from Fortran scheme." + self.assertTrue(emsg in str(context.exception)) + emsg = "Variable mismatch in CCPPeq1_var_missing_in_fort_run, " + \ + "no Fortran variable bar." + self.assertTrue(emsg in str(context.exception)) + emsg = "Out of order argument, errmsg in CCPPeq1_var_missing_in_fort_run" + self.assertTrue(emsg in str(context.exception)) + self.assertTrue("3 errors found comparing" in str(context.exception)) - def test_CCPPeq1_var_in_fort_meta(self): - """Test positive case of a variable that IS PRESENT the subroutine argument list - (due to a pre-processor directive: #ifdef CCPP), and IS PRESENT in meta file""" - #Setup - scheme_files = [os.path.join(self._sample_files_dir, "CCPPeq1_var_in_fort_meta.meta")] - #Exercise + def test_ccpp_eq1_var_in_fort_meta(self): + """Test positive case of a variable that IS PRESENT the + subroutine argument list + (due to a pre-processor directive: #ifdef CCPP), + and IS PRESENT in meta file""" + # Setup + scheme_files = [os.path.join(self._sample_files_dir, + "CCPPeq1_var_in_fort_meta.meta")] + # Exercise scheme_headers, table_dict = parse_scheme_files(scheme_files, self._run_env_ccpp) - #Verify size of returned list equals number of scheme headers in the test file (1) - # and that header (subroutine) name is 'CCPPeq1_var_in_fort_meta_run' + # Verify size of returned list equals number of scheme headers in + # the test file (1) and that header (subroutine) name is + # 'CCPPeq1_var_in_fort_meta_run' self.assertEqual(len(scheme_headers), 1) - #Verify header titles + # Verify header titles titles = [elem.title for elem in scheme_headers] - self.assertTrue('CCPPeq1_var_in_fort_meta_run' in titles) + self.assertTrue("CCPPeq1_var_in_fort_meta_run" in titles) - #Verify size and name of table_dict matches scheme name + # Verify size and name of table_dict matches scheme name self.assertEqual(len(table_dict), 1) - self.assertTrue('CCPPeq1_var_in_fort_meta' in table_dict) + self.assertTrue("CCPPeq1_var_in_fort_meta" in table_dict) - def test_CCPPgt1_var_in_fort_meta(self): - """Test positive case of a variable that IS PRESENT the subroutine argument list - (due to a pre-processor directive: #if CCPP > 1), and IS PRESENT in meta file""" - #Setup - scheme_files = [os.path.join(self._sample_files_dir, "CCPPgt1_var_in_fort_meta.meta")] - #Exercise + def test_ccpp_gt1_var_in_fort_meta(self): + """Test positive case of a variable that IS PRESENT the + subroutine argument list + (due to a pre-processor directive: #if CCPP > 1), + and IS PRESENT in meta file""" + # Setup + scheme_files = [os.path.join(self._sample_files_dir, + "CCPPgt1_var_in_fort_meta.meta")] + # Exercise # Set CCPP directive to > 1 scheme_headers, table_dict = parse_scheme_files(scheme_files, self._run_env_ccpp2) - #Verify size of returned list equals number of scheme headers in the test file (1) - # and that header (subroutine) name is 'CCPPgt1_var_in_fort_meta_init' + # Verify size of returned list equals number of scheme headers + # in the test file (1) and that header (subroutine) name is + # 'CCPPgt1_var_in_fort_meta_init' self.assertEqual(len(scheme_headers), 1) - #Verify header titles + # Verify header titles titles = [elem.title for elem in scheme_headers] - self.assertTrue('CCPPgt1_var_in_fort_meta_init' in titles) + self.assertTrue("CCPPgt1_var_in_fort_meta_init" in titles) - #Verify size and name of table_dict matches scheme name + # Verify size and name of table_dict matches scheme name self.assertEqual(len(table_dict), 1) - self.assertTrue('CCPPgt1_var_in_fort_meta' in table_dict) + self.assertTrue("CCPPgt1_var_in_fort_meta" in table_dict) - def test_CCPPgt1_var_in_fort_meta2(self): - """Test correct detection of a variable that IS NOT PRESENT the subroutine argument list - (due to a pre-processor directive: #if CCPP > 1), but IS PRESENT in meta file""" - #Setup - scheme_files = [os.path.join(self._sample_files_dir, "CCPPgt1_var_in_fort_meta.meta")] - #Exercise - with self.assertRaises(Exception) as context: - parse_scheme_files(scheme_files, self._run_env_ccpp) - #Verify 3 correct error messages returned - self.assertTrue('Variable mismatch in CCPPgt1_var_in_fort_meta_init, variables missing from Fortran scheme.' - in str(context.exception)) - self.assertTrue('Variable mismatch in CCPPgt1_var_in_fort_meta_init, no Fortran variable bar.' - in str(context.exception)) - self.assertTrue('Out of order argument, errmsg in CCPPgt1_var_in_fort_meta_init' in str(context.exception)) - self.assertTrue('3 errors found comparing' in str(context.exception)) + def test_ccpp_gt1_var_in_fort_meta2(self): + """Test correct detection of a variable that + IS NOT PRESENT the subroutine argument list + (due to a pre-processor directive: #if CCPP > 1), + but IS PRESENT in meta file""" + # Setup + scheme_files = [os.path.join(self._sample_files_dir, + "CCPPgt1_var_in_fort_meta.meta")] + # Exercise + with self.assertRaises(CCPPError) as context: + _, _ = parse_scheme_files(scheme_files, self._run_env_ccpp) + # Verify 3 correct error messages returned + emsg = "Variable mismatch in CCPPgt1_var_in_fort_meta_init, " + \ + "variables missing from Fortran scheme." + self.assertTrue(emsg in str(context.exception)) + emsg = "Variable mismatch in CCPPgt1_var_in_fort_meta_init, " + \ + "no Fortran variable bar." + self.assertTrue(emsg in str(context.exception)) + emsg = "Out of order argument, errmsg in CCPPgt1_var_in_fort_meta_init" + self.assertTrue(emsg in str(context.exception)) + self.assertTrue("3 errors found comparing" in str(context.exception)) - def test_CCPPeq1_var_missing_in_meta(self): - """Test correct detection of a variable that IS PRESENT the subroutine argument list - (due to a pre-processor directive: #ifdef CCPP), and IS NOT PRESENT in meta file""" - #Setup - scheme_files = [os.path.join(self._sample_files_dir, "CCPPeq1_var_missing_in_meta.meta")] - #Exercise - with self.assertRaises(Exception) as context: - parse_scheme_files(scheme_files, self._run_env_ccpp) - #Verify 3 correct error messages returned - self.assertTrue('Variable mismatch in CCPPeq1_var_missing_in_meta_finalize, variables missing from metadata header.' - in str(context.exception)) - self.assertTrue('Out of order argument, errmsg in CCPPeq1_var_missing_in_meta_finalize' in str(context.exception)) - self.assertTrue('Out of order argument, errflg in CCPPeq1_var_missing_in_meta_finalize' in str(context.exception)) - self.assertTrue('3 errors found comparing' in str(context.exception)) + def test_ccpp_eq1_var_missing_in_meta(self): + """Test correct detection of a variable that + IS PRESENT the subroutine argument list + (due to a pre-processor directive: #ifdef CCPP), + and IS NOT PRESENT in meta file""" + # Setup + scheme_files = [os.path.join(self._sample_files_dir, + "CCPPeq1_var_missing_in_meta.meta")] + # Exercise + with self.assertRaises(CCPPError) as context: + _, _ = parse_scheme_files(scheme_files, self._run_env_ccpp) + # Verify 3 correct error messages returned + emsg = "Variable mismatch in CCPPeq1_var_missing_in_meta_finalize, "+ \ + "variables missing from metadata header." + self.assertTrue(emsg in str(context.exception)) + emsg = "Out of order argument, errmsg in CCPPeq1_var_missing_in_meta_finalize" + self.assertTrue(emsg in str(context.exception)) + emsg = "Out of order argument, errflg in CCPPeq1_var_missing_in_meta_finalize" + self.assertTrue(emsg in str(context.exception)) + self.assertTrue("3 errors found comparing" in str(context.exception)) -# pylint: enable=invalid-name + def test_scheme_ddt_only(self): + """Test correct detection of a "scheme" file which contains only + DDT definitions""" + # Setup + scheme_files = [os.path.join(self._host_files_dir, "ddt2.meta")] + # Exercise + scheme_headers, table_dict = parse_scheme_files(scheme_files, + self._run_env_ccpp) +# with self.assertRaises(CCPPError) as context: +# _, _ = parse_scheme_files(scheme_files, self._run_env_ccpp) +# # Verify correct error messages returned if __name__ == '__main__': unittest.main() + From 55608754d9f7d707d409ab8b70fe3e6af75c6d78 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Thu, 12 Oct 2023 16:27:38 -0600 Subject: [PATCH 07/11] fix merge --- scripts/parse_tools/parse_checkers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/parse_tools/parse_checkers.py b/scripts/parse_tools/parse_checkers.py index b89d62e2..0dbd627a 100755 --- a/scripts/parse_tools/parse_checkers.py +++ b/scripts/parse_tools/parse_checkers.py @@ -19,6 +19,7 @@ _UNIT_REGEX = f"[a-zA-Z]+{_UNIT_EXPONENT}?" _UNITS_REGEX = f"^({_UNIT_REGEX}(\s{_UNIT_REGEX})*|{_UNITLESS_REGEX})$" _UNITS_RE = re.compile(_UNITS_REGEX) +_MAX_MOLAR_MASS = 10000.0 def check_units(test_val, prop_dict, error): """Return if a valid unit, otherwise, None From 370e78d5a05d911bc963d0f96ba8bb620e1bbce3 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Tue, 31 Oct 2023 22:27:13 -0600 Subject: [PATCH 08/11] address reviewer comments --- .github/workflows/python.yaml | 4 +- scripts/ccpp_capgen.py | 5 +- scripts/ccpp_suite.py | 12 +- scripts/constituents.py | 68 ++-- scripts/ddt_library.py | 12 +- scripts/fortran_tools/fortran_write.py | 2 +- scripts/fortran_tools/parse_fortran_file.py | 13 +- scripts/framework_env.py | 6 + scripts/host_cap.py | 26 +- scripts/host_model.py | 3 +- scripts/metadata_table.py | 4 +- scripts/metavar.py | 2 - scripts/parse_tools/__init__.py | 2 +- scripts/parse_tools/parse_checkers.py | 6 +- scripts/parse_tools/parse_log.py | 4 + scripts/parse_tools/xml_tools.py | 21 +- scripts/suite_objects.py | 2 +- src/ccpp_constituent_prop_mod.F90 | 367 ++++++++---------- test/advection_test/test_host_data.F90 | 2 +- test/advection_test/test_reports.py | 4 +- test/capgen_test/test_reports.py | 4 +- test/unit_tests/sample_host_files/ddt1.F90 | 2 +- .../sample_host_files/ddt1_plus.F90 | 2 +- test/unit_tests/sample_host_files/ddt2.F90 | 2 +- .../sample_host_files/ddt2_extra_var.F90 | 2 +- .../sample_host_files/ddt_data1_mod.F90 | 2 +- test/unit_tests/test_metadata_scheme_file.py | 3 - test/var_action_test/test_reports.py | 4 +- 28 files changed, 281 insertions(+), 305 deletions(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index 3775a985..bf1e7c36 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python-version: ['3.8', '3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v3 @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python-version: ['3.8', '3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v3 diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index 406bff14..2c50eed1 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -613,21 +613,20 @@ def capgen(run_env, return_db=False): # First up, handle the host files host_model = parse_host_model_files(host_files, host_name, run_env) # We always need to parse the ccpp_constituent_prop_ptr_t DDT - ##XXgoldyXX: Should this be in framework_env.py? const_prop_mod = os.path.join(src_dir, "ccpp_constituent_prop_mod.meta") if const_prop_mod not in scheme_files: scheme_files = [const_prop_mod] + scheme_files # end if # Next, parse the scheme files scheme_headers, scheme_tdict = parse_scheme_files(scheme_files, run_env) - if run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG): + if run_env.debug_on(): ddts = host_model.ddt_lib.keys() if ddts: run_env.logger.debug("DDT definitions = {}".format(ddts)) # end if # end if plist = host_model.prop_list('local_name') - if run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG): + if run_env.debug_on(): run_env.logger.debug("{} variables = {}".format(host_model.name, plist)) run_env.logger.debug("schemes = {}".format([x.title for x in scheme_headers])) diff --git a/scripts/ccpp_suite.py b/scripts/ccpp_suite.py index 49fe0c48..488d4a27 100644 --- a/scripts/ccpp_suite.py +++ b/scripts/ccpp_suite.py @@ -427,7 +427,7 @@ def analyze(self, host_model, scheme_library, ddt_library, run_env): phase = RUN_PHASE_NAME # end if lmsg = "Group {}, schemes = {}" - if run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG): + if run_env.debug_on(): run_env.logger.debug(lmsg.format(item.name, [x.name for x in item.schemes()])) @@ -490,7 +490,7 @@ def write(self, output_dir, run_env): (calling the group caps one after another)""" # Set name of module and filename of cap filename = '{module_name}.F90'.format(module_name=self.module) - if run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG): + if run_env.debug_on(): run_env.logger.debug('Writing CCPP suite file, {}'.format(filename)) # end if # Retrieve the name of the constituent module for Group use statements @@ -841,11 +841,11 @@ def write_req_vars_sub(self, ofile, errmsg_name, errcode_name): else: inout_vars[0].add(stdname) # end if - elif (intent == 'out' and phase != 'initialize' and constituent - and not const_initialized_in_physics[stdname]): + elif constituent and (intent == 'out' and phase != 'initialize' and not + const_initialized_in_physics[stdname]): # constituents HAVE to be initialized in the init phase because the dycore needs to advect them - emsg = "constituent variable '{}' cannot be initialized in the '{}' phase" - raise CCPPError(emsg.format(stdname, phase)) + emsg = f"constituent variable '{stdname}' cannot be initialized in the '{phase}' phase" + raise CCPPError(emsg) elif intent == 'out' and constituent and phase == 'initialize': const_initialized_in_physics[stdname] = True elif intent == 'out': diff --git a/scripts/constituents.py b/scripts/constituents.py index d3fc37be..119c0275 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -310,8 +310,8 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): vertical_dim = '' # end if advect_str = self.TF_string(var.get_prop_value('advected')) - init_args = [f'std_name="{std_name}"', f'long_name="{long_name}"', - f'units="{units}"', f'vertical_dim="{vertical_dim}"', + init_args = [f'{std_name=}', f'{long_name=}', + f'{units=}', f'{vertical_dim=}', f'advected={advect_str}', f'errcode={errvar_names["ccpp_error_code"]}', f'errmsg={errvar_names["ccpp_error_message"]}'] @@ -496,9 +496,9 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna stmt = f"{substmt}({args}, {err_dummy_str})" cap.write(stmt, 1) cap.comment("Create constituent object for suites in ", 2) - cap.write("", 0) + cap.blank_line() ConstituentVarDict.write_constituent_use_statements(cap, suite_list, 2) - cap.write("", 0) + 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) :: " + \ @@ -513,7 +513,7 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna 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("", 0) + cap.blank_line() cap.write("{} = 0".format(herrcode), 2) cap.write("num_consts = size(host_constituents, 1)", 2) for suite in suite_list: @@ -543,7 +543,7 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.write("end if", 4) cap.write("end do", 3) cap.write("end if", 2) - cap.write("", 0) + cap.blank_line() # Register suite constituents for suite in suite_list: errvar_str = ConstituentVarDict.__errcode_callstr(herrcode, @@ -578,7 +578,7 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.write("end if", 4) cap.write("end do", 3) cap.write("end if", 2) - cap.write("", 0) + cap.blank_line() # end for cap.write(f"if ({herrcode} == 0) then", 2) stmt = "call {}%lock_table({})" @@ -605,33 +605,33 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.write(f"end {substmt}", 1) # Write constituent_init routine substmt = f"subroutine {init_funcname}" - cap.write("", 0) + cap.blank_line() cap.write(f"{substmt}(ncols, num_layers, {err_dummy_str})", 1) cap.comment("Initialize constituent data", 2) - cap.write("", 0) + cap.blank_line() cap.comment("Dummy arguments", 2) cap.write("integer, intent(in) :: ncols", 2) cap.write("integer, intent(in) :: num_layers", 2) for evar in err_vars: evar.write_def(cap, 2, host, dummy=True, add_intent="out") # end for evar - cap.write("", 0) + cap.blank_line() call_str = f"call {const_obj_name}%lock_data(ncols, num_layers, {obj_err_callstr})" cap.write(call_str, 2) cap.write(f"end {substmt}", 1) # Write num_consts routine substmt = f"subroutine {num_const_funcname}" - cap.write("", 0) + cap.blank_line() cap.write(f"{substmt}(num_flds, advected, {err_dummy_str})", 1) cap.comment("Return the number of constituent fields for this run", 2) - cap.write("", 0) + cap.blank_line() cap.comment("Dummy arguments", 2) cap.write("integer, intent(out) :: num_flds", 2) cap.write("logical, optional, intent(in) :: advected", 2) for evar in err_vars: evar.write_def(cap, 2, host, dummy=True, add_intent="out") # end for - cap.write("", 0) + 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) @@ -658,88 +658,88 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.write(f"end {substmt}", 1) # Write copy_in routine substmt = "subroutine {}".format(copy_in_funcname) - cap.write("", 0) + cap.blank_line() cap.write("{}(const_array, {})".format(substmt, err_dummy_str), 1) cap.comment("Copy constituent field info into ", 2) - cap.write("", 0) + cap.blank_line() cap.comment("Dummy arguments", 2) cap.write("real(kind_phys), intent(out) :: const_array(:,:,:)", 2) for evar in err_vars: evar.write_def(cap, 2, host, dummy=True, add_intent="out") # end for - cap.write("", 0) + cap.blank_line() cap.write("call {}%copy_in(const_array, {})".format(const_obj_name, obj_err_callstr), 2) cap.write("end {}".format(substmt), 1) # Write copy_out routine substmt = "subroutine {}".format(copy_out_funcname) - cap.write("", 0) + cap.blank_line() cap.write("{}(const_array, {})".format(substmt, err_dummy_str), 1) cap.comment("Update constituent field info from ", 2) - cap.write("", 0) + cap.blank_line() cap.comment("Dummy arguments", 2) cap.write("real(kind_phys), intent(in) :: const_array(:,:,:)", 2) for evar in err_vars: evar.write_def(cap, 2, host, dummy=True, add_intent="out") # end for - cap.write("", 0) + cap.blank_line() cap.write("call {}%copy_out(const_array, {})".format(const_obj_name, obj_err_callstr), 2) cap.write("end {}".format(substmt), 1) # Write constituents routine - cap.write("", 0) + cap.blank_line() cap.write(f"function {const_array_func}() result(const_ptr)", 1) - cap.write("", 0) + cap.blank_line() cap.comment("Return pointer to constituent array", 2) - cap.write("", 0) + cap.blank_line() cap.comment("Dummy argument", 2) cap.write("real(kind_phys), pointer :: const_ptr(:,:,:)", 2) - cap.write("", 0) + cap.blank_line() cap.write(f"const_ptr => {const_obj_name}%field_data_ptr()", 2) cap.write(f"end function {const_array_func}", 1) # Write advected constituents routine - cap.write("", 0) + cap.blank_line() cap.write(f"function {advect_array_func}() result(const_ptr)", 1) - cap.write("", 0) + cap.blank_line() cap.comment("Return pointer to advected constituent array", 2) - cap.write("", 0) + cap.blank_line() cap.comment("Dummy argument", 2) cap.write("real(kind_phys), pointer :: const_ptr(:,:,:)", 2) - cap.write("", 0) + cap.blank_line() cap.write(f"const_ptr => {const_obj_name}%advected_constituents_ptr()", 2) cap.write(f"end function {advect_array_func}", 1) # Write the constituent property array routine - cap.write("", 0) + cap.blank_line() cap.write(f"function {prop_array_func}() result(const_ptr)", 1) cap.write(f"use {CONST_DDT_MOD}, only: {CONST_PROP_PTR_TYPE}", 2) - cap.write("", 0) + cap.blank_line() cap.comment("Return pointer to array of constituent properties", 2) - cap.write("", 0) + cap.blank_line() cap.comment("Dummy argument", 2) cap.write("type(ccpp_constituent_prop_ptr_t), pointer :: const_ptr(:)", 2) - cap.write("", 0) + cap.blank_line() cap.write(f"const_ptr => {const_obj_name}%constituent_props_ptr()", 2) cap.write(f"end function {prop_array_func}", 1) # Write constituent index function substmt = f"subroutine {const_index_func}" - cap.write("", 0) + cap.blank_line() cap.write(f"{substmt}(stdname, const_index, {err_dummy_str})", 1) cap.comment("Set to the constituent array index " + \ "for .", 2) cap.comment("If is not found, set to -1 " + \ "set an error condition", 2) - cap.write("", 0) + cap.blank_line() cap.comment("Dummy arguments", 2) cap.write("character(len=*), intent(in) :: stdname", 2) cap.write("integer, intent(out) :: const_index", 2) for evar in err_vars: evar.write_def(cap, 2, host, dummy=True, add_intent="out") # end for - cap.write("", 0) + 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) diff --git a/scripts/ddt_library.py b/scripts/ddt_library.py index 118fb3d7..18bc397a 100644 --- a/scripts/ddt_library.py +++ b/scripts/ddt_library.py @@ -46,7 +46,7 @@ def __init__(self, new_field, var_ref, run_env, recur=False): self.__field = VarDDT(new_field, var_ref.field, run_env, recur=True) # end if if ((not recur) and - run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG)): + run_env.debug_on()): run_env.logger.debug('Adding DDT field, {}'.format(self)) # end if @@ -188,7 +188,7 @@ class DDTLibrary(dict): The dictionary holds known standard names. """ - def __init__(self, name, run_env, ddts=None, logger=None): + def __init__(self, name, run_env, ddts=None): "Our dict is DDT definition headers, key is type" self.__name = '{}_ddt_lib'.format(name) # XXgoldyXX: v remove? @@ -218,9 +218,9 @@ def __init__(self, name, run_env, ddts=None, logger=None): octx = context_string(self[ddt.title].source.context) raise CCPPError(errmsg.format(ddt.title, ctx, octx)) # end if - if logger and logger.isEnabledFor(logging.DEBUG): + if run_env.debug_on(): lmsg = f"Adding DDT {ddt.title} to {self.name}" - logger.debug(lmsg) + run_env.logger.debug(lmsg) # end if self[ddt.title] = ddt dlen = len(ddt.module) @@ -280,10 +280,10 @@ def collect_ddt_fields(self, var_dict, var, run_env, stdname = dvar.get_prop_value('standard_name') pvar = var_dict.find_variable(standard_name=stdname, any_scope=True) if pvar and (not skip_duplicates): - emsg = "Attempt to add duplicate DDT sub-variable, {}{}." - emsg += "\nVariable originally defined{}" ntx = context_string(dvar.context) ctx = context_string(pvar.context) + emsg = f"Attempt to add duplicate DDT sub-variable, {stdname}{ntx}." + emsg += "\nVariable originally defined{ctx}" raise CCPPError(emsg.format(stdname, ntx, ctx)) # end if # Add this intrinsic to diff --git a/scripts/fortran_tools/fortran_write.py b/scripts/fortran_tools/fortran_write.py index f9bcfa3f..5b186c1c 100644 --- a/scripts/fortran_tools/fortran_write.py +++ b/scripts/fortran_tools/fortran_write.py @@ -191,7 +191,7 @@ def write(self, statement, indent_level, continue_line=False): # End while # Before looking for best space, reject any that are on a # comment line but before any significant characters - if outstr.lstrip()[0] == '!': + if outstr.lstrip().startswith('!'): first_space = outstr.index('!') + 1 while ((outstr[first_space] == '!' or outstr[first_space] == ' ') and diff --git a/scripts/fortran_tools/parse_fortran_file.py b/scripts/fortran_tools/parse_fortran_file.py index 7c6493e7..d6ec1f74 100644 --- a/scripts/fortran_tools/parse_fortran_file.py +++ b/scripts/fortran_tools/parse_fortran_file.py @@ -563,8 +563,7 @@ def parse_preamble_data(statements, pobj, spec_name, endmatch, run_env): module=spec_name, var_dict=var_dict) mheaders.append(mheader) - if (run_env.logger and - run_env.logger.isEnabledFor(logging.DEBUG)): + if run_env.debug_on(): ctx = context_string(pobj, nodir=True) msg = 'Adding header {}{}' run_env.logger.debug(msg.format(mheader.table_name, ctx)) @@ -584,7 +583,7 @@ def parse_preamble_data(statements, pobj, spec_name, endmatch, run_env): # End if mheaders.append(ddt) if (run_env.logger and - run_env.logger.isEnabledFor(logging.DEBUG)): + run_env.debug_on()): ctx = context_string(pobj, nodir=True) msg = 'Adding DDT {}{}' run_env.logger.debug(msg.format(ddt.table_name, ctx)) @@ -626,7 +625,7 @@ def parse_scheme_metadata(statements, pobj, spec_name, table_name, run_env): inpreamble = False insub = True seen_contains = False - if run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG): + if run_env.debug_on(): ctx = context_string(pobj, nodir=True) msg = "Parsing specification of {}{}" run_env.logger.debug(msg.format(table_name, ctx)) @@ -813,7 +812,7 @@ def parse_specification(pobj, statements, run_env, mod_name=None, errmsg = duplicate_header(mtables[title], tbl) raise CCPPError(errmsg) # end if - if run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG): + if run_env.debug_on(): ctx = tbl.start_context() mtype = tbl.table_type msg = "Adding metadata from {}, {}{}" @@ -894,7 +893,7 @@ def parse_module(pobj, statements, run_env): # End if mod_name = pmatch.group(1) pobj.enter_region('MODULE', region_name=mod_name, nested_ok=False) - if run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG): + if run_env.debug_on(): ctx = context_string(pobj, nodir=True) msg = "Parsing Fortran module, {}{}" run_env.logger.debug(msg.format(mod_name, ctx)) @@ -930,7 +929,7 @@ def parse_module(pobj, statements, run_env): errmsg = duplicate_header(mtables[title], mheader) raise CCPPError(errmsg) # end if - if run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG): + if run_env.debug_on(): mtype = mheader.table_type ctx = mheader.start_context() msg = "Adding metadata from {}, {}{}" diff --git a/scripts/framework_env.py b/scripts/framework_env.py index 6456134e..af941bd4 100644 --- a/scripts/framework_env.py +++ b/scripts/framework_env.py @@ -10,6 +10,7 @@ # Python library imports import argparse import os +from parse_tools import debug_enabled _EPILOG = ''' ''' @@ -275,6 +276,11 @@ def kind_types(self): CCPPFrameworkEnv object.""" return self.__kind_dict.keys() + def debug_on(self): + """Return true if debug enabled for the CCPPFrameworkEnv's + logger object.""" + return (self.logger and debug_enabled(self.logger)) + @property def use_error_obj(self): """Return the property for this diff --git a/scripts/host_cap.py b/scripts/host_cap.py index d2b3066f..268a1855 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -83,7 +83,7 @@ def constituent_num_suite_subname(host_model): ############################################################################### """Return the name of the number of suite constituents for this run Because this is a user interface API function, the name is fixed.""" - return "{}_ccpp_num_suite_constituents".format(host_model.name) + return f"{host_model.name}_ccpp_num_suite_constituents" ############################################################################### def constituent_register_subname(host_model): @@ -91,7 +91,7 @@ def constituent_register_subname(host_model): """Return the name of the subroutine used to register the constituent properties for this run. Because this is a user interface API function, the name is fixed.""" - return "{}_ccpp_register_constituents".format(host_model.name) + return f"{host_model.name}_ccpp_register_constituents" ############################################################################### def constituent_initialize_subname(host_model): @@ -99,7 +99,7 @@ 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 "{}_ccpp_initialize_constituents".format(host_model.name) + return f"{host_model.name}_ccpp_initialize_constituents" ############################################################################### def constituent_num_consts_funcname(host_model): @@ -107,7 +107,7 @@ def constituent_num_consts_funcname(host_model): """Return the name of the function to return the number of constituents for this run. Because this is a user interface API function, the name is fixed.""" - return "{}_ccpp_number_constituents".format(host_model.name) + return f"{host_model.name}_ccpp_number_constituents" ############################################################################### def query_scheme_constituents_funcname(host_model): @@ -123,7 +123,7 @@ def constituent_copyin_subname(host_model): """Return the name of the subroutine to copy constituent fields to the host model. Because this is a user interface API function, the name is fixed.""" - return "{}_ccpp_gather_constituents".format(host_model.name) + return f"{host_model.name}_ccpp_gather_constituents" ############################################################################### def constituent_copyout_subname(host_model): @@ -131,7 +131,7 @@ def constituent_copyout_subname(host_model): """Return the name of the subroutine to update constituent fields from the host model. Because this is a user interface API function, the name is fixed.""" - return "{}_ccpp_update_constituents".format(host_model.name) + return f"{host_model.name}_ccpp_update_constituents" ############################################################################### def unique_local_name(loc_name, host_model): @@ -163,14 +163,14 @@ def constituent_model_object_name(host_model): def constituent_model_const_stdnames(host_model): ############################################################################### """Return the name of the array of constituent standard names""" - hstr = "{}_model_const_stdnames".format(host_model.name) + hstr = f"{host_model.name}_model_const_stdnames" return unique_local_name(hstr, host_model) ############################################################################### def constituent_model_const_indices(host_model): ############################################################################### """Return the name of the array of constituent field array indices""" - hstr = "{}_model_const_indices".format(host_model.name) + hstr = f"{host_model.name}_model_const_indices" return unique_local_name(hstr, host_model) ############################################################################### @@ -178,7 +178,7 @@ def constituent_model_consts(host_model): ############################################################################### """Return the name of the function that will return a pointer to the array of all constituents""" - hstr = "{}_constituents_array".format(host_model.name) + hstr = f"{host_model.name}_constituents_array" return unique_local_name(hstr, host_model) ############################################################################### @@ -186,14 +186,14 @@ 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 = "{}_advected_constituents_array".format(host_model.name) + 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 = "{}_model_const_properties".format(host_model.name) + hstr = f"{host_model.name}_model_const_properties" return unique_local_name(hstr, host_model) ############################################################################### @@ -201,7 +201,7 @@ 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 = "{}_const_get_index".format(host_model.name) + hstr = f"{host_model.name}_const_get_index" return unique_local_name(hstr, host_model) ############################################################################### @@ -239,7 +239,7 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): # Add entries for each constituent (once per standard name) const_stdnames = set() for suite in suite_list: - if run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG): + if run_env.debug_on(): lmsg = "Adding constituents from {} to {}" run_env.logger.debug(lmsg.format(suite.name, host_model.name)) # end if diff --git a/scripts/host_model.py b/scripts/host_model.py index 68a969e7..c655421b 100644 --- a/scripts/host_model.py +++ b/scripts/host_model.py @@ -19,6 +19,7 @@ class HostModel(VarDictionary): def __init__(self, meta_tables, name_in, run_env): """Initialize this HostModel object. is a dictionary of parsed host metadata tables. + - dictionary key is title of metadata argtable is the name for this host model. is the CCPPFrameworkEnv object for this framework run. """ @@ -150,7 +151,7 @@ def ddt_lib(self): @property def constituent_module(self): """Return the name of host model constituent module""" - return "{}_ccpp_constituents".format(self.name) + return f"{self.name}_ccpp_constituents" def argument_list(self, loop_vars=True): """Return a string representing the host model variable arg list""" diff --git a/scripts/metadata_table.py b/scripts/metadata_table.py index 49f4ffb1..0afe30d3 100755 --- a/scripts/metadata_table.py +++ b/scripts/metadata_table.py @@ -792,7 +792,7 @@ def __init_from_file(self, table_name, table_type, known_ddts, run_env): self.__pobj.add_syntax_err(mismatch) self.__section_valid = False # end if - if run_env.logger and run_env.logger.isEnabledFor(logging.INFO): + if run_env.debug_on(): run_env.logger.info("Parsing {} {}{}".format(self.header_type, self.title, start_ctx)) # end if @@ -812,7 +812,7 @@ def __init_from_file(self, table_name, table_type, known_ddts, run_env): newvar, curr_line = self.parse_variable(curr_line, known_ddts) valid_lines = newvar is not None if valid_lines: - if run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG): + if run_env.debug_on(): dmsg = 'Adding {} to {}' lname = newvar.get_prop_value('local_name') run_env.logger.debug(dmsg.format(lname, self.title)) diff --git a/scripts/metavar.py b/scripts/metavar.py index eb1c28f8..b91e42ce 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -792,8 +792,6 @@ def intrinsic_elements(self, check_dict=None, ddt_lib=None): if not element_names: element_names = None # end if - else: - element_names = None # end if # end if children = self.children() diff --git a/scripts/parse_tools/__init__.py b/scripts/parse_tools/__init__.py index fbe05357..736910f0 100644 --- a/scripts/parse_tools/__init__.py +++ b/scripts/parse_tools/__init__.py @@ -25,7 +25,7 @@ from parse_checkers import check_default_value, check_valid_values, check_molar_mass from parse_log import init_log, set_log_level, flush_log from parse_log import set_log_to_stdout, set_log_to_null -from parse_log import set_log_to_file +from parse_log import set_log_to_file, debug_enabled from preprocess import PreprocStack from xml_tools import find_schema_file, find_schema_version from xml_tools import read_xml_file, validate_xml_file diff --git a/scripts/parse_tools/parse_checkers.py b/scripts/parse_tools/parse_checkers.py index 0dbd627a..f32df45d 100755 --- a/scripts/parse_tools/parse_checkers.py +++ b/scripts/parse_tools/parse_checkers.py @@ -19,7 +19,7 @@ _UNIT_REGEX = f"[a-zA-Z]+{_UNIT_EXPONENT}?" _UNITS_REGEX = f"^({_UNIT_REGEX}(\s{_UNIT_REGEX})*|{_UNITLESS_REGEX})$" _UNITS_RE = re.compile(_UNITS_REGEX) -_MAX_MOLAR_MASS = 10000.0 +_MAX_MOLAR_MASS = 10000.0 def check_units(test_val, prop_dict, error): """Return if a valid unit, otherwise, None @@ -978,7 +978,7 @@ def check_molar_mass(test_val, prop_dict, error): test_val = float(test_val) if test_val < 0.0 or test_val > _MAX_MOLAR_MASS: if error: - raise CCPPError("{} is not a valid molar mass".format(test_val)) + raise CCPPError(f"{test_val} is not a valid molar mass") else: test_val = None # end if @@ -986,7 +986,7 @@ def check_molar_mass(test_val, prop_dict, error): except: # not an int or float, conditionally throw error if error: - raise CCPPError("{} is invalid; not a float or int".format(test_val)) + raise CCPPError(f"{test_val} is invalid; not a float or int") else: test_val=None # end if diff --git a/scripts/parse_tools/parse_log.py b/scripts/parse_tools/parse_log.py index e6561427..9707042a 100644 --- a/scripts/parse_tools/parse_log.py +++ b/scripts/parse_tools/parse_log.py @@ -47,3 +47,7 @@ def flush_log(logger): """Flush all pending output from """ for handler in list(logger.handlers): handler.flush() + +def debug_enabled(logger): + """Return true if debug is enabled for this logger""" + return logger.isEnabledFor(logging.DEBUG) diff --git a/scripts/parse_tools/xml_tools.py b/scripts/parse_tools/xml_tools.py index d982da45..0ff56b3a 100644 --- a/scripts/parse_tools/xml_tools.py +++ b/scripts/parse_tools/xml_tools.py @@ -176,9 +176,9 @@ def validate_xml_file(filename, schema_root, version, logger, schema_file = find_schema_file(schema_root, version, schema_path) if not (schema_file and os.path.isfile(schema_file)): verstring = '.'.join([str(x) for x in version]) - emsg = """validate_xml_file: Cannot find schema for version {}, - {} does not exist""" - raise CCPPError(emsg.format(verstring, schema_file)) + emsg = f"""validate_xml_file: Cannot find schema for version {verstring}, + {schema_file} does not exist""" + raise CCPPError(emsg) # end if # end if if not os.access(schema_file, os.R_OK): @@ -256,16 +256,11 @@ def write(self, file, encoding="us-ascii", xml_declaration=None, default_namespace=None, method="xml", short_empty_elements=True): """Subclassed write method to format output.""" - if PYSUBVER >= 8: - et_str = ET.tostring(self.getroot(), - encoding=encoding, method=method, - xml_declaration=xml_declaration, - default_namespace=default_namespace, - short_empty_elements=short_empty_elements) - else: - et_str = ET.tostring(self.getroot(), - encoding=encoding, method=method, - short_empty_elements=short_empty_elements) + et_str = ET.tostring(self.getroot(), + encoding=encoding, method=method, + xml_declaration=xml_declaration, + default_namespace=default_namespace, + short_empty_elements=short_empty_elements) # end if fmode = 'wt' root = str(et_str, encoding="utf-8") diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 5a710548..0be6de15 100644 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1287,7 +1287,7 @@ def __init__(self, index_name, context, parent, run_env, items=None): # self._local_dim_name is the variable name for self._dim_name self._local_dim_name = None super().__init__(index_name, context, parent, run_env) - if run_env.logger and run_env.logger.isEnabledFor(logging.DEBUG): + if run_env.debug_on(): lmsg = "Adding VerticalLoop for '{}'" run_env.logger.debug(lmsg.format(index_name)) # end if diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index c9f5322b..7f0637e9 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -184,7 +184,7 @@ module ccpp_constituent_prop_mod ! Private interfaces private to_str private initialize_errvars - private set_errvars + private append_errvars private handle_allocate_error private check_var_bounds @@ -243,19 +243,14 @@ end subroutine initialize_errvars !####################################################################### - subroutine set_errvars(errcode_val, errmsg_val, errcode, errmsg, & - errmsg2, errmsg3, errmsg4, errmsg5) - ! Set error variables, if present + subroutine append_errvars(errcode_val, errmsg_val, errcode, errmsg) + ! Append to error variables, if present ! Dummy arguments - integer, optional, intent(in) :: errcode_val - character(len=*), optional, intent(in) :: errmsg_val + integer, intent(in) :: errcode_val + character(len=*), intent(in) :: errmsg_val integer, optional, intent(inout) :: errcode character(len=*), optional, intent(inout) :: errmsg - character(len=*), optional, intent(in) :: errmsg2 - character(len=*), optional, intent(in) :: errmsg3 - character(len=*), optional, intent(in) :: errmsg4 - character(len=*), optional, intent(in) :: errmsg5 ! Local variable integer :: emsg_len @@ -269,24 +264,8 @@ subroutine set_errvars(errcode_val, errmsg_val, errcode, errmsg, & end if emsg_len = len_trim(errmsg) errmsg(emsg_len+1:) = trim(errmsg_val) - if (present(errmsg2)) then - emsg_len = len_trim(errmsg) - errmsg(emsg_len+1:) = trim(errmsg2) - end if - if (present(errmsg3)) then - emsg_len = len_trim(errmsg) - errmsg(emsg_len+1:) = trim(errmsg3) - end if - if (present(errmsg4)) then - emsg_len = len_trim(errmsg) - errmsg(emsg_len+1:) = trim(errmsg4) - end if - if (present(errmsg5)) then - emsg_len = len_trim(errmsg) - errmsg(emsg_len+1:) = trim(errmsg5) - end if end if - end subroutine set_errvars + end subroutine append_errvars !####################################################################### @@ -301,10 +280,8 @@ subroutine handle_allocate_error(astat, fieldname, errcode, errmsg) call initialize_errvars(errcode, errmsg) if (astat /= 0) then - call set_errvars(astat, "Error allocating ", errcode=errcode, & - errmsg=errmsg, errmsg2="ccpp_constituent_properties_t", & - errmsg3="object component, "//trim(fieldname), & - errmsg4=", error code = ", errmsg5=to_str(astat)) + call append_errvars(astat, "Error allocating ccpp_constituent_properties_t object component " // & + trim(fieldname) // ", error code = " // to_str(astat), errcode=errcode, errmsg=errmsg) end if end subroutine handle_allocate_error @@ -323,8 +300,8 @@ subroutine check_var_bounds(var, var_bound, varname, errcode, errmsg) call initialize_errvars(errcode, errmsg) if (var > var_bound) then - call set_errvars(1, trim(varname)//" exceeds its upper bound, ", & - errcode=errcode, errmsg=errmsg, errmsg2=to_str(var_bound)) + call append_errvars(1, trim(varname)//" exceeds its upper bound, " // & + to_str(var_bound), errcode=errcode, errmsg=errmsg) end if end subroutine check_var_bounds @@ -358,8 +335,8 @@ logical function ccp_is_instantiated(this, errcode, errmsg) ccp_is_instantiated = allocated(this%var_std_name) call initialize_errvars(errcode, errmsg) if (.not. ccp_is_instantiated) then - call set_errvars(1, "ccpp_constituent_properties_t object ", & - errcode=errcode, errmsg=errmsg, errmsg2="is not initialized") + call append_errvars(1, "ccpp_constituent_properties_t object is not initialized", & + errcode=errcode, errmsg=errmsg) end if end function ccp_is_instantiated @@ -402,8 +379,7 @@ subroutine ccp_instantiate(this, std_name, long_name, units, vertical_dim, & if (present(default_value)) then this%const_default_value = default_value end if - end if - if (errcode == 0) then + ! Determine if this is a (moist) mixing ratio or volume mixing ratio 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 @@ -411,9 +387,6 @@ subroutine ccp_instantiate(this, std_name, long_name, units, vertical_dim, & else this%const_type = mass_mixing_ratio end if - ! Determine if this is a (moist) mixing ratio or volume mixing ratio - 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 @@ -591,9 +564,8 @@ subroutine ccp_set_const_index(this, index, errcode, errmsg) if (this%const_ind == int_unassigned) then this%const_ind = index else - call set_errvars(1, "ccpp_constituent_properties_t ", & - errcode=errcode, errmsg=errmsg, & - errmsg2="const index is already set") + call append_errvars(1, "ccpp_constituent_properties_t const index " // & + "is already set", errcode=errcode, errmsg=errmsg) end if end if @@ -891,13 +863,13 @@ logical function ccp_model_const_locked(this, errcode, errmsg, warn_func) end if else if (present(warn_func)) then - call set_errvars(1, trim(warn_func), & - errcode=errcode, errmsg=errmsg, & - errmsg2=" WARNING: Model constituents not initialized") + call append_errvars(1, trim(warn_func) // & + " WARNING: Model constituents not initialized", & + errcode=errcode, errmsg=errmsg) else - call set_errvars(1, subname, & - errcode=errcode, errmsg=errmsg, & - errmsg2=" WARNING: Model constituents not initialized") + call append_errvars(1, subname // & + " WARNING: Model constituents not initialized", & + errcode=errcode, errmsg=errmsg) end if end if @@ -930,13 +902,13 @@ logical function ccp_model_const_props_locked(this, errcode, errmsg, warn_func) end if else if (present(warn_func)) then - call set_errvars(1, trim(warn_func), & - errcode=errcode, errmsg=errmsg, & - errmsg2=" WARNING: Model constituent properties not initialized") + call append_errvars(1, trim(warn_func) // & + " WARNING: Model constituent properties not initialized", & + errcode=errcode, errmsg=errmsg) else - call set_errvars(1, subname, & - errcode=errcode, errmsg=errmsg, & - errmsg2=" WARNING: Model constituent properties not initialized") + call append_errvars(1, subname // & + " WARNING: Model constituent properties not initialized", & + errcode=errcode, errmsg=errmsg) end if end if @@ -969,13 +941,13 @@ logical function ccp_model_const_data_locked(this, errcode, errmsg, warn_func) end if else if (present(warn_func)) then - call set_errvars(1, trim(warn_func), & - errcode=errcode, errmsg=errmsg, & - errmsg2=" WARNING: Model constituent data not initialized") + call append_errvars(1, trim(warn_func) // & + " WARNING: Model constituent data not initialized", & + errcode=errcode, errmsg=errmsg) else - call set_errvars(1, subname, & - errcode=errcode, errmsg=errmsg, & - errmsg2=" WARNING: Model constituent data not initialized") + call append_errvars(1, subname // & + " WARNING: Model constituent data not initialized", & + errcode=errcode, errmsg=errmsg) end if end if @@ -1004,24 +976,24 @@ logical function ccp_model_const_okay_to_add(this, errcode, errmsg, & errmsg=errmsg, warn_func=subname)) if (.not. ccp_model_const_okay_to_add) then if (present(warn_func)) then - call set_errvars(1, trim(warn_func), & - errcode=errcode, errmsg=errmsg, & - errmsg2=" WARNING: Model constituents are locked") + call append_errvars(1, trim(warn_func) // & + " WARNING: Model constituents are locked", & + errcode=errcode, errmsg=errmsg) else - call set_errvars(1, subname, & - errcode=errcode, errmsg=errmsg, & - errmsg2=" WARNING: Model constituents are locked") + call append_errvars(1, subname // & + " WARNING: Model constituents are locked", & + errcode=errcode, errmsg=errmsg) end if end if else if (present(warn_func)) then - call set_errvars(1, trim(warn_func), & - errcode=errcode, errmsg=errmsg, & - errmsg2=" WARNING: Model constituents not initialized") + call append_errvars(1, trim(warn_func) // & + " WARNING: Model constituents not initialized", & + errcode=errcode, errmsg=errmsg) else - call set_errvars(1, subname, & - errcode=errcode, errmsg=errmsg, & - errmsg2=" WARNING: Model constituents not initialized") + call append_errvars(1, subname // & + " WARNING: Model constituents not initialized", & + errcode=errcode, errmsg=errmsg) end if end if @@ -1047,7 +1019,7 @@ subroutine ccp_model_const_add_metadata(this, field_data, errcode, errmsg) !!XXgoldyXX: Add check on key to see if incompatible item already there. call this%hash_table%add_hash_key(field_data, error) if (len_trim(error) > 0) then - call set_errvars(1, trim(error), errcode=errcode, errmsg=errmsg) + call append_errvars(1, trim(error), errcode=errcode, errmsg=errmsg) else ! If we get here we are successful, add to variable count if (field_data%is_layer_var()) then @@ -1056,19 +1028,19 @@ subroutine ccp_model_const_add_metadata(this, field_data, errcode, errmsg) if (present(errmsg)) then call field_data%vertical_dimension(error, & errcode=errcode, errmsg=errmsg) - if (len_trim(errmsg) == 0) then - call set_errvars(1, & - "ERROR: Unknown vertical dimension, '", & - errcode=errcode, errmsg=errmsg, & - errmsg2=trim(error), errmsg3="'") + if (errcode /= 0) then + call append_errvars(1, & + "ERROR: Unknown vertical dimension, '" // & + trim(error) // "'", & + errcode=errcode, errmsg=errmsg) end if end if end if end if else - call set_errvars(1, subname, & - errcode=errcode, errmsg=errmsg, & - errmsg2=" WARNING: Model constituents are locked") + call append_errvars(1, subname // & + "WARNING: Model constituents are locked", & + errcode=errcode, errmsg=errmsg) end if end subroutine ccp_model_const_add_metadata @@ -1119,16 +1091,15 @@ function ccp_model_const_find_const(this, standard_name, errcode, errmsg) & nullify(cprop) hval => this%hash_table%table_value(standard_name, errmsg=error) if (len_trim(error) > 0) then - call set_errvars(1, subname, errcode=errcode, errmsg=errmsg, & - errmsg2=": "//trim(error)) + call append_errvars(1, subname // ": "//trim(error), errcode=errcode, & + errmsg=errmsg) else select type(hval) type is (ccpp_constituent_properties_t) cprop => hval class default - call set_errvars(1, subname, errcode=errcode, errmsg=errmsg, & - errmsg2=" ERROR: Bad hash table value", & - errmsg3=trim(standard_name)) + call append_errvars(1, subname // " ERROR: Bad hash table value " // & + trim(standard_name), errcode=errcode, errmsg=errmsg) end select end if @@ -1148,6 +1119,7 @@ subroutine ccp_model_const_table_lock(this, errcode, errmsg) integer :: index_advect integer :: num_vars integer :: astat + integer :: errcode_local logical :: check type(ccpp_hash_iterator_t) :: hiter class(ccpp_hashable_t), pointer :: hval @@ -1156,10 +1128,12 @@ subroutine ccp_model_const_table_lock(this, errcode, errmsg) character(len=*), parameter :: subname = 'ccp_model_const_table_lock' astat = 0 + errcode_local = 0 if (this%const_props_locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then - call set_errvars(1, subname, errcode=errcode, errmsg=errmsg, & - errmsg2=" WARNING: Model constituent properites already locked, ignoring") - astat = astat + 1 + call append_errvars(1, subname // & + " WARNING: Model constituents properties already locked, ignoring", & + errcode=errcode, errmsg=errmsg) + errcode_local = 1 else ! Make sure everything is really initialized call this%reset(clear_hash_table=.false.) @@ -1191,17 +1165,18 @@ subroutine ccp_model_const_table_lock(this, errcode, errmsg) end do ! Sanity check on num_advect if (this%num_advected_vars > num_vars) then - astat = 1 - call set_errvars(astat, subname, & - errcode=errcode, errmsg=errmsg, & - errmsg2=" ERROR: num_advected_vars index out of bounds") - astat = astat + 1 + call append_errvars(1, subname // & + " ERROR: num_advected_vars index " // & + to_str(this%num_advected_vars) // & + " out of bounds " // to_str(num_vars), & + errcode=errcode, errmsg=errmsg) + errcode_local = 1 end if end if index_advect = 0 index_const = this%num_advected_vars ! Iterate through the hash table to find entries - if (astat == 0) then + if (errcode_local == 0) then call hiter%initialize(this%hash_table) do if (hiter%valid()) then @@ -1213,10 +1188,11 @@ subroutine ccp_model_const_table_lock(this, errcode, errmsg) if (check) then index_advect = index_advect + 1 if (index_advect > this%num_advected_vars) then - call set_errvars(1, subname, & - errcode=errcode, errmsg=errmsg, & - errmsg2=" ERROR: const a index out of bounds") - astat = astat + 1 + call append_errvars(1, subname // " ERROR: const a index " // & + to_str(index_advect) // " out of bounds " // & + to_str(this%num_advected_vars), & + errcode=errcode, errmsg=errmsg) + errcode_local = errcode_local + 1 exit end if call cprop%set_const_index(index_advect, & @@ -1225,10 +1201,10 @@ subroutine ccp_model_const_table_lock(this, errcode, errmsg) else index_const = index_const + 1 if (index_const > num_vars) then - call set_errvars(1, subname, & - errcode=errcode, errmsg=errmsg, & - errmsg2=" ERROR: const v index out of bounds") - astat = astat + 1 + call append_errvars(1, subname // " ERROR: const v index " // & + to_str(index_const) // " out of bounds " // to_str(num_vars), & + errcode=errcode, errmsg=errmsg) + errcode_local = errcode_local + 1 exit end if call cprop%set_const_index(index_const, & @@ -1239,18 +1215,15 @@ subroutine ccp_model_const_table_lock(this, errcode, errmsg) if (.not. cprop%is_layer_var()) then call cprop%vertical_dimension(dimname, & errcode=errcode, errmsg=errmsg) - call set_errvars(1, subname, & - errcode=errcode, errmsg=errmsg, & - errmsg2=" ERROR: Bad vertical dimension, '", & - errmsg3=trim(dimname)) - astat = astat + 1 + call append_errvars(1, subname // " ERROR: Bad vertical dimension, '" // & + trim(dimname), errcode=errcode, errmsg=errmsg) + errcode_local = errcode_local + 1 exit end if class default - call set_errvars(1, subname, & - errcode=errcode, errmsg=errmsg, & - errmsg2="ERROR: Bad hash table value") - astat = astat + 1 + call append_errvars(1, subname // " ERROR: Bad hash table value", & + errcode=errcode, errmsg=errmsg) + errcode_local = errcode_local + 1 exit end select call hiter%next() @@ -1260,24 +1233,25 @@ subroutine ccp_model_const_table_lock(this, errcode, errmsg) end do ! Some size sanity checks if (index_const /= this%hash_table%num_values()) then - call set_errvars(errcode + 1, subname, & - errcode=errcode, errmsg=errmsg, & - errmsg2=" ERROR: Too few constituents found in hash table") - astat = astat + 1 + call append_errvars(1, subname // & + " ERROR: Too few constituents " // to_str(index_const) // & + " found in hash table " // to_str(this%hash_table%num_values()), & + errcode=errcode, errmsg=errmsg) + errcode_local = errcode_local + 1 end if if (index_advect /= this%num_advected_vars) then - call set_errvars(1, subname, & - errcode=errcode, errmsg=errmsg, & - errmsg2=" ERROR: Too few advected constituents found ", & - errmsg3="in hash table") - astat = astat + 1 + call append_errvars(1, subname // & + " ERROR: Too few advected constituents " // to_str(index_const) // & + " found in hash table " // to_str(this%hash_table%num_values()), & + errcode=errcode, errmsg=errmsg) + errcode_local = errcode_local + 1 end if if (present(errcode)) then if (errcode /= 0) then - astat = 1 + errcode_local = 1 end if end if - if (astat == 0) then + if (errcode_local == 0) then this%table_locked = .true. end if end if @@ -1297,30 +1271,35 @@ subroutine ccp_model_const_data_lock(this, ncols, num_layers, errcode, errmsg) integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg ! Local variables - integer :: astat, index + integer :: astat, index, errcode_local real(kind=kind_phys) :: default_value character(len=*), parameter :: subname = 'ccp_model_const_data_lock' + errcode_local = 0 if (this%const_data_locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then - call set_errvars(1, subname, errcode=errcode, errmsg=errmsg, & - errmsg2=" WARNING: Model constituent data already locked, ignoring") - astat = astat + 1 + call append_errvars(1, subname // & + " WARNING: Model constituent data already locked, ignoring", & + errcode=errcode, errmsg=errmsg) + errcode_local = errcode_local + 1 else if (.not. this%const_props_locked(errcode=errcode, errmsg=errmsg, & warn_func=subname)) then - call set_errvars(1, subname, errcode=errcode, errmsg=errmsg, & - errmsg2=" WARNING: Model constituent properties not yet locked, ignoring") - astat = astat + 1 + call append_errvars(1, subname // & + " WARNING: Model constituent properties not yet locked, ignoring", & + errcode=errcode, errmsg=errmsg) + errcode_local = errcode_local + 1 else allocate(this%vars_layer(ncols, num_layers, this%hash_table%num_values()), & stat=astat) call handle_allocate_error(astat, 'vars_layer', & errcode=errcode, errmsg=errmsg) + errcode_local = astat if (astat == 0) then allocate(this%vars_minvalue(this%hash_table%num_values()), stat=astat) call handle_allocate_error(astat, 'vars_minvalue', & errcode=errcode, errmsg=errmsg) + errcode_local = astat end if - if (astat == 0) then + if (errcode_local == 0) then this%num_layers = num_layers do index = 1, this%hash_table%num_values() call this%const_metadata(index)%default_value(default_value, & @@ -1331,10 +1310,10 @@ subroutine ccp_model_const_data_lock(this, ncols, num_layers, errcode, errmsg) end if if (present(errcode)) then if (errcode /= 0) then - astat = 1 + errcode_local = 1 end if end if - if (astat == 0) then + if (errcode_local == 0) then this%data_locked = .true. end if end if @@ -1540,40 +1519,39 @@ subroutine ccp_model_const_copy_in_3d(this, const_array, advected, & ! See if we have room for another constituent cindex = cindex + 1 if (cindex > max_cind) then - call set_errvars(1, subname, & - errcode=errcode, errmsg=errmsg, & - errmsg2=": Too many constituents for ") + call append_errvars(1, subname // & + ": Too many constituents for ", & + errcode=errcode, errmsg=errmsg) exit end if ! Copy this constituent's field data to call this%const_metadata(index)%const_index(fld_ind) if (fld_ind /= index) then call this%const_metadata(index)%standard_name(std_name) - call set_errvars(1, subname//": ERROR: ", & - errcode=errcode, errmsg=errmsg, & - errmsg2="bad field index, "//to_str(fld_ind), & - errmsg3=" for '"//trim(std_name)//"', ", & - errmsg4="should have been "//to_str(index)) + call append_errvars(1, subname//": ERROR: "// & + "bad field index, "//to_str(fld_ind)// & + " for '"//trim(std_name)//"', should have been "// & + to_str(index), errcode=errcode, errmsg=errmsg) exit else if (this%const_metadata(index)%is_layer_var()) then if (this%num_layers == num_levels) then const_array(:,:,cindex) = this%vars_layer(:,:,fld_ind) else call this%const_metadata(index)%standard_name(std_name) - call set_errvars(1, subname, & - errcode=errcode, errmsg=errmsg, & - errmsg2=": Wrong number of vertical levels for '", & - errmsg3=trim(std_name)//"', "//to_str(num_levels), & - errmsg4=", expected"//to_str(this%num_layers)) + call append_errvars(1, subname//": ERROR: "// & + "Wrong number of vertical levels for '"// & + trim(std_name)//"', "//to_str(num_levels)// & + ", expected "//to_str(this%num_layers), & + errcode=errcode, errmsg=errmsg) exit end if else call this%const_metadata(index)%standard_name(std_name) - call set_errvars(1, subname//": Unsupported var type, ", & - errcode=errcode, errmsg=errmsg, & - errmsg2="wrong number of vertical levels for '", & - errmsg3=trim(std_name)//"', "//to_str(num_levels), & - errmsg4=", expected"//to_str(this%num_layers)) + call append_errvars(1, subname//": Unsupported var type,"// & + " wrong number of vertical levels for '"// & + trim(std_name)//"', "//to_str(num_levels)// & + ", expected"//to_str(this%num_layers), & + errcode=errcode, errmsg=errmsg) exit end if end if @@ -1617,40 +1595,39 @@ subroutine ccp_model_const_copy_out_3d(this, const_array, advected, & ! See if we have room for another constituent cindex = cindex + 1 if (cindex > max_cind) then - call set_errvars(1, subname, & - errcode=errcode, errmsg=errmsg, & - errmsg2=": Too many constituents for ") + call append_errvars(1, subname// & + ": Too many constituents for ", & + errcode=errcode, errmsg=errmsg) exit end if ! Copy this field of to to constituent's field data call this%const_metadata(index)%const_index(fld_ind) if (fld_ind /= index) then call this%const_metadata(index)%standard_name(std_name) - call set_errvars(1, subname//": ERROR: ", & - errcode=errcode, errmsg=errmsg, & - errmsg2="bad field index, "//to_str(fld_ind), & - errmsg3=" for '"//trim(std_name)//"', ", & - errmsg4="should have been "//to_str(index)) + call append_errvars(1, subname//": ERROR: "// & + "bad field index, "//to_str(fld_ind)// & + " for '"//trim(std_name)//"', should have been"// & + to_str(index), errcode=errcode, errmsg=errmsg) exit else if (this%const_metadata(index)%is_layer_var()) then if (this%num_layers == num_levels) then this%vars_layer(:,:,fld_ind) = const_array(:,:,cindex) else call this%const_metadata(index)%standard_name(std_name) - call set_errvars(1, subname, & - errcode=errcode, errmsg=errmsg, & - errmsg2=": Wrong number of vertical levels for '", & - errmsg3=trim(std_name)//"', "//to_str(num_levels), & - errmsg4=", expected"//to_str(this%num_layers)) + call append_errvars(1, subname// & + ": Wrong number of vertical levels for '"// & + trim(std_name)//"', "//to_str(num_levels)// & + ", expected"//to_str(this%num_layers), & + errcode=errcode, errmsg=errmsg) exit end if else call this%const_metadata(index)%standard_name(std_name) - call set_errvars(1, subname//": Unsupported var type, ", & - errcode=errcode, errmsg=errmsg, & - errmsg2="wrong number of vertical levels for '", & - errmsg3=trim(std_name)//"', "//to_str(num_levels), & - errmsg4=", expected"//to_str(this%num_layers)) + call append_errvars(1, subname//": Unsupported var type,"// & + " wrong number of vertical levels for'"// & + trim(std_name)//"', "//to_str(num_levels)// & + ", expected "//to_str(this%num_layers), & + errcode=errcode, errmsg=errmsg) exit end if end if @@ -1666,7 +1643,7 @@ function ccp_field_data_ptr(this) result(const_ptr) ! Dummy arguments class(ccpp_model_constituents_t), target, intent(inout) :: this - real(kind_phys), pointer :: const_ptr(:,:,:) + real(kind_phys), pointer :: const_ptr(:,:,:) ! Local variables integer :: errcode character(len=errmsg_len) :: errmsg @@ -1749,7 +1726,7 @@ subroutine ccpt_get_standard_name(this, std_name, errcode, errmsg) call this%prop%standard_name(std_name, errcode, errmsg) else std_name = '' - call set_errvars(1, subname//": invalid constituent pointer", & + call append_errvars(1, subname//": invalid constituent pointer", & errcode=errcode, errmsg=errmsg) end if @@ -1772,7 +1749,7 @@ subroutine ccpt_get_long_name(this, long_name, errcode, errmsg) call this%prop%long_name(long_name, errcode, errmsg) else long_name = '' - call set_errvars(1, subname//": invalid constituent pointer", & + call append_errvars(1, subname//": invalid constituent pointer", & errcode=errcode, errmsg=errmsg) end if @@ -1793,11 +1770,11 @@ subroutine ccpt_get_vertical_dimension(this, vert_dim, errcode, errmsg) if (associated(this%prop)) then if (this%prop%is_instantiated(errcode, errmsg)) then - vert_dim = this%prop%vert_dim + call this%prop%vertical_dimension(vert_dim, errcode, errmsg) end if else vert_dim = '' - call set_errvars(1, subname//": invalid constituent pointer", & + call append_errvars(1, subname//": invalid constituent pointer", & errcode=errcode, errmsg=errmsg) end if @@ -1880,7 +1857,7 @@ subroutine ccpt_const_index(this, index, errcode, errmsg) index = this%prop%const_index(errcode, errmsg) else index = int_unassigned - call set_errvars(1, subname//": invalid constituent pointer", & + call append_errvars(1, subname//": invalid constituent pointer", & errcode=errcode, errmsg=errmsg) end if @@ -1902,7 +1879,7 @@ subroutine ccpt_is_thermo_active(this, val_out, errcode, errmsg) call this%prop%is_thermo_active(val_out, errcode, errmsg) else val_out = .false. - call set_errvars(1, subname//": invalid constituent pointer", & + call append_errvars(1, subname//": invalid constituent pointer", & errcode=errcode, errmsg=errmsg) end if @@ -1924,7 +1901,7 @@ subroutine ccpt_is_advected(this, val_out, errcode, errmsg) call this%prop%is_advected(val_out, errcode, errmsg) else val_out = .false. - call set_errvars(1, subname//": invalid constituent pointer", & + call append_errvars(1, subname//": invalid constituent pointer", & errcode=errcode, errmsg=errmsg) end if @@ -1946,7 +1923,7 @@ subroutine ccpt_is_mass_mixing_ratio(this, val_out, errcode, errmsg) call this%prop%is_mass_mixing_ratio(val_out, errcode, errmsg) else val_out = .false. - call set_errvars(1, subname//": invalid constituent pointer", & + call append_errvars(1, subname//": invalid constituent pointer", & errcode=errcode, errmsg=errmsg) end if @@ -1968,7 +1945,7 @@ subroutine ccpt_is_volume_mixing_ratio(this, val_out, errcode, errmsg) call this%prop%is_volume_mixing_ratio(val_out, errcode, errmsg) else val_out = .false. - call set_errvars(1, subname//": invalid constituent pointer", & + call append_errvars(1, subname//": invalid constituent pointer", & errcode=errcode, errmsg=errmsg) end if @@ -1990,7 +1967,7 @@ subroutine ccpt_is_number_concentration(this, val_out, errcode, errmsg) call this%prop%is_number_concentration(val_out, errcode, errmsg) else val_out = .false. - call set_errvars(1, subname//": invalid constituent pointer", & + call append_errvars(1, subname//": invalid constituent pointer", & errcode=errcode, errmsg=errmsg) end if @@ -2012,7 +1989,7 @@ subroutine ccpt_is_dry(this, val_out, errcode, errmsg) call this%prop%is_dry(val_out, errcode, errmsg) else val_out = .false. - call set_errvars(1, subname//": invalid constituent pointer", & + call append_errvars(1, subname//": invalid constituent pointer", & errcode=errcode, errmsg=errmsg) end if @@ -2034,7 +2011,7 @@ subroutine ccpt_is_moist(this, val_out, errcode, errmsg) call this%prop%is_moist(val_out, errcode, errmsg) else val_out = .false. - call set_errvars(1, subname//": invalid constituent pointer", & + call append_errvars(1, subname//": invalid constituent pointer", & errcode=errcode, errmsg=errmsg) end if @@ -2056,7 +2033,7 @@ subroutine ccpt_is_wet(this, val_out, errcode, errmsg) call this%prop%is_wet(val_out, errcode, errmsg) else val_out = .false. - call set_errvars(1, subname//": invalid constituent pointer", & + call append_errvars(1, subname//": invalid constituent pointer", & errcode=errcode, errmsg=errmsg) end if @@ -2078,7 +2055,7 @@ subroutine ccpt_min_val(this, val_out, errcode, errmsg) call this%prop%minimum(val_out, errcode, errmsg) else val_out = kphys_unassigned - call set_errvars(1, subname//": invalid constituent pointer", & + call append_errvars(1, subname//": invalid constituent pointer", & errcode=errcode, errmsg=errmsg) end if @@ -2100,7 +2077,7 @@ subroutine ccpt_molec_weight(this, val_out, errcode, errmsg) call this%prop%molec_weight(val_out, errcode, errmsg) else val_out = kphys_unassigned - call set_errvars(1, subname//": invalid constituent pointer", & + call append_errvars(1, subname//": invalid constituent pointer", & errcode=errcode, errmsg=errmsg) end if @@ -2122,7 +2099,7 @@ subroutine ccpt_default_value(this, val_out, errcode, errmsg) call this%prop%default_value(val_out, errcode, errmsg) else val_out = kphys_unassigned - call set_errvars(1, subname//": invalid constituent pointer", & + call append_errvars(1, subname//": invalid constituent pointer", & errcode=errcode, errmsg=errmsg) end if @@ -2144,7 +2121,7 @@ subroutine ccpt_has_default(this, val_out, errcode, errmsg) call this%prop%has_default(val_out, errcode, errmsg) else val_out = .false. - call set_errvars(1, subname//": invalid constituent pointer", & + call append_errvars(1, subname//": invalid constituent pointer", & errcode=errcode, errmsg=errmsg) end if @@ -2173,8 +2150,8 @@ subroutine ccpt_set(this, const_ptr, errcode, errmsg) trim(stdname), "'" end if errcode = errcode + 1 - call set_errvars(1, "ccpt_set: ", errcode=errcode, errmsg=errmsg, & - errmsg2=trim(errmsg2)) + call append_errvars(1, "ccpt_set: "//trim(errmsg2), errcode=errcode, & + errmsg=errmsg) else this%prop => const_ptr end if @@ -2216,13 +2193,13 @@ subroutine ccpt_set_const_index(this, index, errcode, errmsg) if (this%prop%const_ind == int_unassigned) then this%prop%const_ind = index else - call set_errvars(1, "ccpp_constituent_prop_ptr_t ", & - errcode=errcode, errmsg=errmsg, & - errmsg2="const index is already set") + call append_errvars(1, "ccpp_constituent_prop_ptr_t "// & + "const index is already set", & + errcode=errcode, errmsg=errmsg) end if end if else - call set_errvars(1, subname//": invalid constituent pointer", & + call append_errvars(1, subname//": invalid constituent pointer", & errcode=errcode, errmsg=errmsg) end if @@ -2248,7 +2225,7 @@ subroutine ccpt_set_thermo_active(this, thermo_flag, errcode, errmsg) this%prop%thermo_active = thermo_flag end if else - call set_errvars(1, subname//": invalid constituent pointer", & + call append_errvars(1, subname//": invalid constituent pointer", & errcode=errcode, errmsg=errmsg) end if diff --git a/test/advection_test/test_host_data.F90 b/test/advection_test/test_host_data.F90 index fce25c66..c2d99798 100644 --- a/test/advection_test/test_host_data.F90 +++ b/test/advection_test/test_host_data.F90 @@ -7,7 +7,7 @@ module test_host_data type physics_state real(kind_phys), allocatable :: ps(:) ! surface pressure real(kind_phys), allocatable :: temp(:,:) ! temperature - real(kind_phys), pointer :: q(:,:,:) => NULL() ! constituent array + real(kind_phys), dimension(:,:,:), pointer :: q => NULL() ! constituent array end type physics_state public allocate_physics_state diff --git a/test/advection_test/test_reports.py b/test/advection_test/test_reports.py index c28fe38a..17e9a2ad 100644 --- a/test/advection_test/test_reports.py +++ b/test/advection_test/test_reports.py @@ -22,8 +22,8 @@ # end if if ((sys.version_info[0] < 3) or - (sys.version_info[0] == 3) and (sys.version_info[1] < 7)): - raise Exception("Python 3.7 or greater required") + (sys.version_info[0] == 3) and (sys.version_info[1] < 8)): + raise Exception("Python 3.8 or greater required") # end if sys.path.append(_SCRIPTS_DIR) diff --git a/test/capgen_test/test_reports.py b/test/capgen_test/test_reports.py index 3749e8ac..e8237476 100644 --- a/test/capgen_test/test_reports.py +++ b/test/capgen_test/test_reports.py @@ -23,8 +23,8 @@ # end if if ((sys.version_info[0] < 3) or - (sys.version_info[0] == 3) and (sys.version_info[1] < 7)): - raise Exception("Python 3.7 or greater required") + (sys.version_info[0] == 3) and (sys.version_info[1] < 8)): + raise Exception("Python 3.8 or greater required") # end if sys.path.append(_SCRIPTS_DIR) diff --git a/test/unit_tests/sample_host_files/ddt1.F90 b/test/unit_tests/sample_host_files/ddt1.F90 index 966a62a6..71b22b4f 100644 --- a/test/unit_tests/sample_host_files/ddt1.F90 +++ b/test/unit_tests/sample_host_files/ddt1.F90 @@ -9,7 +9,7 @@ module ddt1 !! \htmlinclude ddt1_t.html !! type, public :: ddt1_t - integer, private :: num_vars = 0 + integer, public :: num_vars = 0 real(kind_phys), allocatable :: vars(:,:,:) end type ddt1_t diff --git a/test/unit_tests/sample_host_files/ddt1_plus.F90 b/test/unit_tests/sample_host_files/ddt1_plus.F90 index 90571a73..d1806932 100644 --- a/test/unit_tests/sample_host_files/ddt1_plus.F90 +++ b/test/unit_tests/sample_host_files/ddt1_plus.F90 @@ -16,7 +16,7 @@ module ddt1_plus !! \htmlinclude ddt2_t.html !! type, public :: ddt2_t - integer, private :: num_vars = 0 + integer, public :: num_vars = 0 real(kind_phys), allocatable :: vars(:,:,:) end type ddt2_t diff --git a/test/unit_tests/sample_host_files/ddt2.F90 b/test/unit_tests/sample_host_files/ddt2.F90 index f4ed9cd0..22d5af0e 100644 --- a/test/unit_tests/sample_host_files/ddt2.F90 +++ b/test/unit_tests/sample_host_files/ddt2.F90 @@ -16,7 +16,7 @@ module ddt2 !! \htmlinclude ddt2_t.html !! type, public :: ddt2_t - integer, private :: num_vars = 0 + integer, public :: num_vars = 0 real(kind_phys), allocatable :: vars(:,:,:) end type ddt2_t diff --git a/test/unit_tests/sample_host_files/ddt2_extra_var.F90 b/test/unit_tests/sample_host_files/ddt2_extra_var.F90 index 07bafe4b..00b4c170 100644 --- a/test/unit_tests/sample_host_files/ddt2_extra_var.F90 +++ b/test/unit_tests/sample_host_files/ddt2_extra_var.F90 @@ -16,7 +16,7 @@ module ddt2_extra_var !! \htmlinclude ddt2_t.html !! type, public :: ddt2_t - integer, private :: num_vars = 0 + integer, public :: num_vars = 0 real(kind_phys), allocatable :: vars(:,:,:) contains procedure :: get_num_vars diff --git a/test/unit_tests/sample_host_files/ddt_data1_mod.F90 b/test/unit_tests/sample_host_files/ddt_data1_mod.F90 index 0607f11c..5efe0845 100644 --- a/test/unit_tests/sample_host_files/ddt_data1_mod.F90 +++ b/test/unit_tests/sample_host_files/ddt_data1_mod.F90 @@ -16,7 +16,7 @@ module ddt_data1_mod !! \htmlinclude ddt2_t.html !! type, public :: ddt2_t - integer, private :: num_vars = 0 + integer, public :: num_vars = 0 real(kind_phys), allocatable :: vars(:,:,:) end type ddt2_t diff --git a/test/unit_tests/test_metadata_scheme_file.py b/test/unit_tests/test_metadata_scheme_file.py index 6651c2de..595962c0 100644 --- a/test/unit_tests/test_metadata_scheme_file.py +++ b/test/unit_tests/test_metadata_scheme_file.py @@ -340,9 +340,6 @@ def test_scheme_ddt_only(self): # Exercise scheme_headers, table_dict = parse_scheme_files(scheme_files, self._run_env_ccpp) -# with self.assertRaises(CCPPError) as context: -# _, _ = parse_scheme_files(scheme_files, self._run_env_ccpp) -# # Verify correct error messages returned if __name__ == "__main__": unittest.main() diff --git a/test/var_action_test/test_reports.py b/test/var_action_test/test_reports.py index 3d9a8637..fff8603d 100755 --- a/test/var_action_test/test_reports.py +++ b/test/var_action_test/test_reports.py @@ -23,8 +23,8 @@ # end if if ((sys.version_info[0] < 3) or - (sys.version_info[0] == 3) and (sys.version_info[1] < 7)): - raise Exception("Python 3.7 or greater required") + (sys.version_info[0] == 3) and (sys.version_info[1] < 8)): + raise Exception("Python 3.8 or greater required") # end if sys.path.append(_SCRIPTS_DIR) From ac18c448999dbbf2d2fa0f4ac32c0356a5369ea3 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Tue, 31 Oct 2023 22:33:02 -0600 Subject: [PATCH 09/11] fix tests --- scripts/constituents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/constituents.py b/scripts/constituents.py index 119c0275..6b0ac759 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -315,7 +315,7 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): f'advected={advect_str}', f'errcode={errvar_names["ccpp_error_code"]}', f'errmsg={errvar_names["ccpp_error_message"]}'] - if default_value is not None: + if default_value is not None and default_value != '': init_args.append(f'default_value={default_value}') stmt = 'call {}(index)%instantiate({})' outfile.write(f'if ({errvar_names["ccpp_error_code"]} == 0) then', indent+1) From 9db947704b718440da68cd284b4781526a6ecb6f Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Wed, 1 Nov 2023 11:51:51 -0600 Subject: [PATCH 10/11] address reviewer comments --- scripts/ddt_library.py | 2 +- scripts/fortran_tools/parse_fortran_file.py | 3 +- scripts/metavar.py | 4 + src/ccpp_constituent_prop_mod.F90 | 283 +++++++++----------- 4 files changed, 137 insertions(+), 155 deletions(-) diff --git a/scripts/ddt_library.py b/scripts/ddt_library.py index 18bc397a..9644787b 100644 --- a/scripts/ddt_library.py +++ b/scripts/ddt_library.py @@ -283,7 +283,7 @@ def collect_ddt_fields(self, var_dict, var, run_env, ntx = context_string(dvar.context) ctx = context_string(pvar.context) emsg = f"Attempt to add duplicate DDT sub-variable, {stdname}{ntx}." - emsg += "\nVariable originally defined{ctx}" + emsg += f"\nVariable originally defined{ctx}" raise CCPPError(emsg.format(stdname, ntx, ctx)) # end if # Add this intrinsic to diff --git a/scripts/fortran_tools/parse_fortran_file.py b/scripts/fortran_tools/parse_fortran_file.py index d6ec1f74..80b1c486 100644 --- a/scripts/fortran_tools/parse_fortran_file.py +++ b/scripts/fortran_tools/parse_fortran_file.py @@ -582,8 +582,7 @@ def parse_preamble_data(statements, pobj, spec_name, endmatch, run_env): raise CCPPError(msg.format(statement, ctx)) # End if mheaders.append(ddt) - if (run_env.logger and - run_env.debug_on()): + if run_env.debug_on(): ctx = context_string(pobj, nodir=True) msg = 'Adding DDT {}{}' run_env.logger.debug(msg.format(ddt.table_name, ctx)) diff --git a/scripts/metavar.py b/scripts/metavar.py index 02a02c2d..c7be0f5c 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -783,6 +783,7 @@ def intrinsic_elements(self, check_dict=None, ddt_lib=None): Currently, an array of DDTs is not processed (return None) since Fortran does not support a way to reference those elements. """ + element_names = None if self.is_ddt(): dtitle = self.get_prop_value('type') if ddt_lib and (dtitle in ddt_lib): @@ -798,6 +799,9 @@ def intrinsic_elements(self, check_dict=None, ddt_lib=None): if not element_names: element_names = None # end if + else: + errmsg = f'No ddt_lib or ddt {dtitle} not in ddt_lib' + raise CCPPError(errmsg) # end if # end if children = self.children() diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index 7f0637e9..41d8213f 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -243,14 +243,16 @@ end subroutine initialize_errvars !####################################################################### - subroutine append_errvars(errcode_val, errmsg_val, errcode, errmsg) + subroutine append_errvars(errcode_val, errmsg_val, subname, errcode, errmsg, caller) ! Append to error variables, if present ! Dummy arguments integer, intent(in) :: errcode_val character(len=*), intent(in) :: errmsg_val + character(len=*), intent(in) :: subname integer, optional, intent(inout) :: errcode character(len=*), optional, intent(inout) :: errmsg + character(len=*), optional, intent(in) :: caller ! Local variable integer :: emsg_len @@ -263,45 +265,51 @@ subroutine append_errvars(errcode_val, errmsg_val, errcode, errmsg) errmsg(emsg_len+1:) = '; ' end if emsg_len = len_trim(errmsg) - errmsg(emsg_len+1:) = trim(errmsg_val) + if (present(caller)) then + errmsg(emsg_len+1:) = trim(caller)//" "//trim(errmsg_val) + else + errmsg(emsg_len+1:) = trim(subname)//" "//trim(errmsg_val) + end if end if end subroutine append_errvars !####################################################################### - subroutine handle_allocate_error(astat, fieldname, errcode, errmsg) + subroutine handle_allocate_error(astat, fieldname, subname, errcode, errmsg) ! Generate an error message if indicates an allocation failure ! Dummy arguments integer, intent(in) :: astat character(len=*), intent(in) :: fieldname + character(len=*), intent(in) :: subname integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg call initialize_errvars(errcode, errmsg) if (astat /= 0) then call append_errvars(astat, "Error allocating ccpp_constituent_properties_t object component " // & - trim(fieldname) // ", error code = " // to_str(astat), errcode=errcode, errmsg=errmsg) + trim(fieldname) // ", error code = " // to_str(astat), subname, errcode=errcode, errmsg=errmsg) end if end subroutine handle_allocate_error !####################################################################### - subroutine check_var_bounds(var, var_bound, varname, errcode, errmsg) + subroutine check_var_bounds(var, var_bound, varname, subname, errcode, errmsg) ! Generate an error message if indicates an allocation failure ! Dummy arguments integer, intent(in) :: var integer, intent(in) :: var_bound character(len=*), intent(in) :: varname + character(len=*), intent(in) :: subname integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg call initialize_errvars(errcode, errmsg) if (var > var_bound) then call append_errvars(1, trim(varname)//" exceeds its upper bound, " // & - to_str(var_bound), errcode=errcode, errmsg=errmsg) + to_str(var_bound), subname, errcode=errcode, errmsg=errmsg) end if end subroutine check_var_bounds @@ -331,12 +339,13 @@ logical function ccp_is_instantiated(this, errcode, errmsg) class(ccpp_constituent_properties_t), intent(in) :: this integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg + character(len=*), parameter :: subname = 'ccp_is_instantiated' ccp_is_instantiated = allocated(this%var_std_name) call initialize_errvars(errcode, errmsg) if (.not. ccp_is_instantiated) then call append_errvars(1, "ccpp_constituent_properties_t object is not initialized", & - errcode=errcode, errmsg=errmsg) + subname, errcode=errcode, errmsg=errmsg) end if end function ccp_is_instantiated @@ -559,13 +568,14 @@ subroutine ccp_set_const_index(this, index, errcode, errmsg) integer, intent(in) :: index integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg + character(len=*), parameter :: subname = 'ccp_set_const_index' if (this%is_instantiated(errcode, errmsg)) then if (this%const_ind == int_unassigned) then this%const_ind = index else call append_errvars(1, "ccpp_constituent_properties_t const index " // & - "is already set", errcode=errcode, errmsg=errmsg) + "is already set", subname, errcode=errcode, errmsg=errmsg) end if end if @@ -854,7 +864,7 @@ logical function ccp_model_const_locked(this, errcode, errmsg, warn_func) ! Use an initialized hash table as double check if (this%hash_table%is_initialized()) then ccp_model_const_locked = this%table_locked .and. this%data_locked - if ( (.not. (this%table_locked .and. this%data_locked)) .and. & + if ( (.not. (this%table_locked .and. this%data_locked)) .and. & present(errmsg) .and. present(warn_func)) then ! Write a warning as a courtesy to calling function but do not set ! errcode (let caller decide). @@ -862,15 +872,8 @@ logical function ccp_model_const_locked(this, errcode, errmsg, warn_func) ' WARNING: Model constituents not ready to use' end if else - if (present(warn_func)) then - call append_errvars(1, trim(warn_func) // & - " WARNING: Model constituents not initialized", & - errcode=errcode, errmsg=errmsg) - else - call append_errvars(1, subname // & - " WARNING: Model constituents not initialized", & - errcode=errcode, errmsg=errmsg) - end if + call append_errvars(1, "WARNING: Model constituents not initialized", & + subname, errcode=errcode, errmsg=errmsg, caller=warn_func) end if end function ccp_model_const_locked @@ -901,15 +904,9 @@ logical function ccp_model_const_props_locked(this, errcode, errmsg, warn_func) ' WARNING: Model constituent properties not ready to use' end if else - if (present(warn_func)) then - call append_errvars(1, trim(warn_func) // & - " WARNING: Model constituent properties not initialized", & - errcode=errcode, errmsg=errmsg) - else - call append_errvars(1, subname // & - " WARNING: Model constituent properties not initialized", & - errcode=errcode, errmsg=errmsg) - end if + call append_errvars(1, & + "WARNING: Model constituent properties not initialized", & + subname, errcode=errcode, errmsg=errmsg, caller=warn_func) end if end function ccp_model_const_props_locked @@ -936,19 +933,13 @@ logical function ccp_model_const_data_locked(this, errcode, errmsg, warn_func) present(errmsg) .and. present(warn_func)) then ! Write a warning as a courtesy to calling function but do not set ! errcode (let caller decide). - write(errmsg, *) trim(warn_func), & + write(errmsg, *) trim(warn_func), & ' WARNING: Model constituent data not ready to use' end if else - if (present(warn_func)) then - call append_errvars(1, trim(warn_func) // & - " WARNING: Model constituent data not initialized", & - errcode=errcode, errmsg=errmsg) - else - call append_errvars(1, subname // & - " WARNING: Model constituent data not initialized", & - errcode=errcode, errmsg=errmsg) - end if + call append_errvars(1, & + "WARNING: Model constituent data not initialized", & + subname, errcode=errcode, errmsg=errmsg, caller=warn_func) end if end function ccp_model_const_data_locked @@ -975,26 +966,14 @@ logical function ccp_model_const_okay_to_add(this, errcode, errmsg, & errmsg=errmsg, warn_func=subname) .or. this%const_data_locked(errcode=errcode, & errmsg=errmsg, warn_func=subname)) if (.not. ccp_model_const_okay_to_add) then - if (present(warn_func)) then - call append_errvars(1, trim(warn_func) // & - " WARNING: Model constituents are locked", & - errcode=errcode, errmsg=errmsg) - else - call append_errvars(1, subname // & - " WARNING: Model constituents are locked", & - errcode=errcode, errmsg=errmsg) - end if + call append_errvars(1, & + "WARNING: Model constituents are locked", & + subname, errcode=errcode, errmsg=errmsg, caller=warn_func) end if else - if (present(warn_func)) then - call append_errvars(1, trim(warn_func) // & - " WARNING: Model constituents not initialized", & - errcode=errcode, errmsg=errmsg) - else - call append_errvars(1, subname // & - " WARNING: Model constituents not initialized", & - errcode=errcode, errmsg=errmsg) - end if + call append_errvars(1, & + "WARNING: Model constituents not initialized", & + subname, errcode=errcode, errmsg=errmsg, caller=warn_func) end if end function ccp_model_const_okay_to_add @@ -1019,7 +998,7 @@ subroutine ccp_model_const_add_metadata(this, field_data, errcode, errmsg) !!XXgoldyXX: Add check on key to see if incompatible item already there. call this%hash_table%add_hash_key(field_data, error) if (len_trim(error) > 0) then - call append_errvars(1, trim(error), errcode=errcode, errmsg=errmsg) + call append_errvars(1, trim(error), subname, errcode=errcode, errmsg=errmsg) else ! If we get here we are successful, add to variable count if (field_data%is_layer_var()) then @@ -1031,16 +1010,15 @@ subroutine ccp_model_const_add_metadata(this, field_data, errcode, errmsg) if (errcode /= 0) then call append_errvars(1, & "ERROR: Unknown vertical dimension, '" // & - trim(error) // "'", & + trim(error) // "'", subname, & errcode=errcode, errmsg=errmsg) end if end if end if end if else - call append_errvars(1, subname // & - "WARNING: Model constituents are locked", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, "WARNING: Model constituents are locked", & + subname, errcode=errcode, errmsg=errmsg) end if end subroutine ccp_model_const_add_metadata @@ -1091,15 +1069,15 @@ function ccp_model_const_find_const(this, standard_name, errcode, errmsg) & nullify(cprop) hval => this%hash_table%table_value(standard_name, errmsg=error) if (len_trim(error) > 0) then - call append_errvars(1, subname // ": "//trim(error), errcode=errcode, & - errmsg=errmsg) + call append_errvars(1, trim(error), subname, & + errcode=errcode, errmsg=errmsg) else select type(hval) type is (ccpp_constituent_properties_t) cprop => hval class default - call append_errvars(1, subname // " ERROR: Bad hash table value " // & - trim(standard_name), errcode=errcode, errmsg=errmsg) + call append_errvars(1, "ERROR: Bad hash table value " // & + trim(standard_name), subname, errcode=errcode, errmsg=errmsg) end select end if @@ -1130,9 +1108,9 @@ subroutine ccp_model_const_table_lock(this, errcode, errmsg) astat = 0 errcode_local = 0 if (this%const_props_locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then - call append_errvars(1, subname // & - " WARNING: Model constituents properties already locked, ignoring", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, & + "WARNING: Model constituents properties already locked, ignoring", & + subname, errcode=errcode, errmsg=errmsg) errcode_local = 1 else ! Make sure everything is really initialized @@ -1142,7 +1120,7 @@ subroutine ccp_model_const_table_lock(this, errcode, errmsg) num_vars = this%hash_table%num_values() allocate(this%const_metadata(num_vars), stat=astat) call handle_allocate_error(astat, 'const_metadata', & - errcode=errcode, errmsg=errmsg) + subname, errcode=errcode, errmsg=errmsg) ! We want to pack the advected constituents at the beginning of ! the field array so we need to know how many there are if (astat == 0) then @@ -1165,11 +1143,10 @@ subroutine ccp_model_const_table_lock(this, errcode, errmsg) end do ! Sanity check on num_advect if (this%num_advected_vars > num_vars) then - call append_errvars(1, subname // & - " ERROR: num_advected_vars index " // & + call append_errvars(1, "ERROR: num_advected_vars index " // & to_str(this%num_advected_vars) // & " out of bounds " // to_str(num_vars), & - errcode=errcode, errmsg=errmsg) + subname, errcode=errcode, errmsg=errmsg) errcode_local = 1 end if end if @@ -1188,10 +1165,10 @@ subroutine ccp_model_const_table_lock(this, errcode, errmsg) if (check) then index_advect = index_advect + 1 if (index_advect > this%num_advected_vars) then - call append_errvars(1, subname // " ERROR: const a index " // & - to_str(index_advect) // " out of bounds " // & - to_str(this%num_advected_vars), & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, "ERROR: const a index " // & + to_str(index_advect) // " out of bounds " // & + to_str(this%num_advected_vars), & + subname, errcode=errcode, errmsg=errmsg) errcode_local = errcode_local + 1 exit end if @@ -1201,9 +1178,10 @@ subroutine ccp_model_const_table_lock(this, errcode, errmsg) else index_const = index_const + 1 if (index_const > num_vars) then - call append_errvars(1, subname // " ERROR: const v index " // & - to_str(index_const) // " out of bounds " // to_str(num_vars), & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, "ERROR: const v index " // & + to_str(index_const) // " out of bounds " // & + to_str(num_vars), subname, errcode=errcode, & + errmsg=errmsg) errcode_local = errcode_local + 1 exit end if @@ -1215,14 +1193,14 @@ subroutine ccp_model_const_table_lock(this, errcode, errmsg) if (.not. cprop%is_layer_var()) then call cprop%vertical_dimension(dimname, & errcode=errcode, errmsg=errmsg) - call append_errvars(1, subname // " ERROR: Bad vertical dimension, '" // & - trim(dimname), errcode=errcode, errmsg=errmsg) + call append_errvars(1, "ERROR: Bad vertical dimension, '" // & + trim(dimname), subname, errcode=errcode, errmsg=errmsg) errcode_local = errcode_local + 1 exit end if class default - call append_errvars(1, subname // " ERROR: Bad hash table value", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, "ERROR: Bad hash table value", & + subname, errcode=errcode, errmsg=errmsg) errcode_local = errcode_local + 1 exit end select @@ -1233,16 +1211,16 @@ subroutine ccp_model_const_table_lock(this, errcode, errmsg) end do ! Some size sanity checks if (index_const /= this%hash_table%num_values()) then - call append_errvars(1, subname // & - " ERROR: Too few constituents " // to_str(index_const) // & - " found in hash table " // to_str(this%hash_table%num_values()), & + call append_errvars(1, "ERROR: Too few constituents "// & + to_str(index_const) // " found in hash table " // & + to_str(this%hash_table%num_values()), subname, & errcode=errcode, errmsg=errmsg) errcode_local = errcode_local + 1 end if if (index_advect /= this%num_advected_vars) then - call append_errvars(1, subname // & - " ERROR: Too few advected constituents " // to_str(index_const) // & - " found in hash table " // to_str(this%hash_table%num_values()), & + call append_errvars(1, "ERROR: Too few advected constituents " // & + to_str(index_const) // " found in hash table " // & + to_str(this%hash_table%num_values()), subname, & errcode=errcode, errmsg=errmsg) errcode_local = errcode_local + 1 end if @@ -1277,26 +1255,26 @@ subroutine ccp_model_const_data_lock(this, ncols, num_layers, errcode, errmsg) errcode_local = 0 if (this%const_data_locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then - call append_errvars(1, subname // & - " WARNING: Model constituent data already locked, ignoring", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, & + "WARNING: Model constituent data already locked, ignoring", & + subname, errcode=errcode, errmsg=errmsg) errcode_local = errcode_local + 1 else if (.not. this%const_props_locked(errcode=errcode, errmsg=errmsg, & warn_func=subname)) then - call append_errvars(1, subname // & - " WARNING: Model constituent properties not yet locked, ignoring", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, & + "WARNING: Model constituent properties not yet locked, ignoring", & + subname, errcode=errcode, errmsg=errmsg) errcode_local = errcode_local + 1 else allocate(this%vars_layer(ncols, num_layers, this%hash_table%num_values()), & stat=astat) call handle_allocate_error(astat, 'vars_layer', & - errcode=errcode, errmsg=errmsg) + subname, errcode=errcode, errmsg=errmsg) errcode_local = astat if (astat == 0) then allocate(this%vars_minvalue(this%hash_table%num_values()), stat=astat) call handle_allocate_error(astat, 'vars_minvalue', & - errcode=errcode, errmsg=errmsg) + subname, errcode=errcode, errmsg=errmsg) errcode_local = astat end if if (errcode_local == 0) then @@ -1519,39 +1497,39 @@ subroutine ccp_model_const_copy_in_3d(this, const_array, advected, & ! See if we have room for another constituent cindex = cindex + 1 if (cindex > max_cind) then - call append_errvars(1, subname // & + call append_errvars(1, & ": Too many constituents for ", & - errcode=errcode, errmsg=errmsg) + subname, errcode=errcode, errmsg=errmsg) exit end if ! Copy this constituent's field data to call this%const_metadata(index)%const_index(fld_ind) if (fld_ind /= index) then call this%const_metadata(index)%standard_name(std_name) - call append_errvars(1, subname//": ERROR: "// & + call append_errvars(1, ": ERROR: "// & "bad field index, "//to_str(fld_ind)// & " for '"//trim(std_name)//"', should have been "// & - to_str(index), errcode=errcode, errmsg=errmsg) + to_str(index), subname, errcode=errcode, errmsg=errmsg) exit else if (this%const_metadata(index)%is_layer_var()) then if (this%num_layers == num_levels) then const_array(:,:,cindex) = this%vars_layer(:,:,fld_ind) else call this%const_metadata(index)%standard_name(std_name) - call append_errvars(1, subname//": ERROR: "// & + call append_errvars(1, ": ERROR: "// & "Wrong number of vertical levels for '"// & trim(std_name)//"', "//to_str(num_levels)// & ", expected "//to_str(this%num_layers), & - errcode=errcode, errmsg=errmsg) + subname, errcode=errcode, errmsg=errmsg) exit end if else call this%const_metadata(index)%standard_name(std_name) - call append_errvars(1, subname//": Unsupported var type,"// & + call append_errvars(1, ": Unsupported var type,"// & " wrong number of vertical levels for '"// & trim(std_name)//"', "//to_str(num_levels)// & ", expected"//to_str(this%num_layers), & - errcode=errcode, errmsg=errmsg) + subname, errcode=errcode, errmsg=errmsg) exit end if end if @@ -1595,39 +1573,39 @@ subroutine ccp_model_const_copy_out_3d(this, const_array, advected, & ! See if we have room for another constituent cindex = cindex + 1 if (cindex > max_cind) then - call append_errvars(1, subname// & + call append_errvars(1, & ": Too many constituents for ", & - errcode=errcode, errmsg=errmsg) + subname, errcode=errcode, errmsg=errmsg) exit end if ! Copy this field of to to constituent's field data call this%const_metadata(index)%const_index(fld_ind) if (fld_ind /= index) then call this%const_metadata(index)%standard_name(std_name) - call append_errvars(1, subname//": ERROR: "// & + call append_errvars(1, ": ERROR: "// & "bad field index, "//to_str(fld_ind)// & " for '"//trim(std_name)//"', should have been"// & - to_str(index), errcode=errcode, errmsg=errmsg) + to_str(index), subname, errcode=errcode, errmsg=errmsg) exit else if (this%const_metadata(index)%is_layer_var()) then if (this%num_layers == num_levels) then this%vars_layer(:,:,fld_ind) = const_array(:,:,cindex) else call this%const_metadata(index)%standard_name(std_name) - call append_errvars(1, subname// & + call append_errvars(1, & ": Wrong number of vertical levels for '"// & trim(std_name)//"', "//to_str(num_levels)// & ", expected"//to_str(this%num_layers), & - errcode=errcode, errmsg=errmsg) + subname, errcode=errcode, errmsg=errmsg) exit end if else call this%const_metadata(index)%standard_name(std_name) - call append_errvars(1, subname//": Unsupported var type,"// & + call append_errvars(1, ": Unsupported var type,"// & " wrong number of vertical levels for'"// & trim(std_name)//"', "//to_str(num_levels)// & ", expected "//to_str(this%num_layers), & - errcode=errcode, errmsg=errmsg) + subname, errcode=errcode, errmsg=errmsg) exit end if end if @@ -1726,8 +1704,8 @@ subroutine ccpt_get_standard_name(this, std_name, errcode, errmsg) call this%prop%standard_name(std_name, errcode, errmsg) else std_name = '' - call append_errvars(1, subname//": invalid constituent pointer", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) end if end subroutine ccpt_get_standard_name @@ -1749,8 +1727,8 @@ subroutine ccpt_get_long_name(this, long_name, errcode, errmsg) call this%prop%long_name(long_name, errcode, errmsg) else long_name = '' - call append_errvars(1, subname//": invalid constituent pointer", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) end if end subroutine ccpt_get_long_name @@ -1774,8 +1752,8 @@ subroutine ccpt_get_vertical_dimension(this, vert_dim, errcode, errmsg) end if else vert_dim = '' - call append_errvars(1, subname//": invalid constituent pointer", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) end if end subroutine ccpt_get_vertical_dimension @@ -1857,8 +1835,8 @@ subroutine ccpt_const_index(this, index, errcode, errmsg) index = this%prop%const_index(errcode, errmsg) else index = int_unassigned - call append_errvars(1, subname//": invalid constituent pointer", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) end if end subroutine ccpt_const_index @@ -1879,8 +1857,8 @@ subroutine ccpt_is_thermo_active(this, val_out, errcode, errmsg) call this%prop%is_thermo_active(val_out, errcode, errmsg) else val_out = .false. - call append_errvars(1, subname//": invalid constituent pointer", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) end if end subroutine ccpt_is_thermo_active @@ -1901,8 +1879,8 @@ subroutine ccpt_is_advected(this, val_out, errcode, errmsg) call this%prop%is_advected(val_out, errcode, errmsg) else val_out = .false. - call append_errvars(1, subname//": invalid constituent pointer", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) end if end subroutine ccpt_is_advected @@ -1923,8 +1901,8 @@ subroutine ccpt_is_mass_mixing_ratio(this, val_out, errcode, errmsg) call this%prop%is_mass_mixing_ratio(val_out, errcode, errmsg) else val_out = .false. - call append_errvars(1, subname//": invalid constituent pointer", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) end if end subroutine ccpt_is_mass_mixing_ratio @@ -1945,8 +1923,8 @@ subroutine ccpt_is_volume_mixing_ratio(this, val_out, errcode, errmsg) call this%prop%is_volume_mixing_ratio(val_out, errcode, errmsg) else val_out = .false. - call append_errvars(1, subname//": invalid constituent pointer", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) end if end subroutine ccpt_is_volume_mixing_ratio @@ -1967,8 +1945,8 @@ subroutine ccpt_is_number_concentration(this, val_out, errcode, errmsg) call this%prop%is_number_concentration(val_out, errcode, errmsg) else val_out = .false. - call append_errvars(1, subname//": invalid constituent pointer", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) end if end subroutine ccpt_is_number_concentration @@ -1989,8 +1967,8 @@ subroutine ccpt_is_dry(this, val_out, errcode, errmsg) call this%prop%is_dry(val_out, errcode, errmsg) else val_out = .false. - call append_errvars(1, subname//": invalid constituent pointer", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) end if end subroutine ccpt_is_dry @@ -2011,8 +1989,8 @@ subroutine ccpt_is_moist(this, val_out, errcode, errmsg) call this%prop%is_moist(val_out, errcode, errmsg) else val_out = .false. - call append_errvars(1, subname//": invalid constituent pointer", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) end if end subroutine ccpt_is_moist @@ -2033,8 +2011,8 @@ subroutine ccpt_is_wet(this, val_out, errcode, errmsg) call this%prop%is_wet(val_out, errcode, errmsg) else val_out = .false. - call append_errvars(1, subname//": invalid constituent pointer", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) end if end subroutine ccpt_is_wet @@ -2055,8 +2033,8 @@ subroutine ccpt_min_val(this, val_out, errcode, errmsg) call this%prop%minimum(val_out, errcode, errmsg) else val_out = kphys_unassigned - call append_errvars(1, subname//": invalid constituent pointer", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) end if end subroutine ccpt_min_val @@ -2077,8 +2055,8 @@ subroutine ccpt_molec_weight(this, val_out, errcode, errmsg) call this%prop%molec_weight(val_out, errcode, errmsg) else val_out = kphys_unassigned - call append_errvars(1, subname//": invalid constituent pointer", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) end if end subroutine ccpt_molec_weight @@ -2099,8 +2077,8 @@ subroutine ccpt_default_value(this, val_out, errcode, errmsg) call this%prop%default_value(val_out, errcode, errmsg) else val_out = kphys_unassigned - call append_errvars(1, subname//": invalid constituent pointer", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) end if end subroutine ccpt_default_value @@ -2121,8 +2099,8 @@ subroutine ccpt_has_default(this, val_out, errcode, errmsg) call this%prop%has_default(val_out, errcode, errmsg) else val_out = .false. - call append_errvars(1, subname//": invalid constituent pointer", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) end if end subroutine ccpt_has_default @@ -2141,6 +2119,7 @@ subroutine ccpt_set(this, const_ptr, errcode, errmsg) ! Local variables character(len=stdname_len) :: stdname character(len=errmsg_len) :: errmsg2 + character(len=*), parameter :: subname = 'ccpt_set' call initialize_errvars(errcode, errmsg) if (associated(this%prop)) then @@ -2150,7 +2129,7 @@ subroutine ccpt_set(this, const_ptr, errcode, errmsg) trim(stdname), "'" end if errcode = errcode + 1 - call append_errvars(1, "ccpt_set: "//trim(errmsg2), errcode=errcode, & + call append_errvars(1, trim(errmsg2), subname, errcode=errcode, & errmsg=errmsg) else this%prop => const_ptr @@ -2193,14 +2172,14 @@ subroutine ccpt_set_const_index(this, index, errcode, errmsg) if (this%prop%const_ind == int_unassigned) then this%prop%const_ind = index else - call append_errvars(1, "ccpp_constituent_prop_ptr_t "// & - "const index is already set", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, "ccpp_constituent_prop_ptr_t "// & + "const index is already set", & + subname, errcode=errcode, errmsg=errmsg) end if end if else - call append_errvars(1, subname//": invalid constituent pointer", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) end if end subroutine ccpt_set_const_index @@ -2225,8 +2204,8 @@ subroutine ccpt_set_thermo_active(this, thermo_flag, errcode, errmsg) this%prop%thermo_active = thermo_flag end if else - call append_errvars(1, subname//": invalid constituent pointer", & - errcode=errcode, errmsg=errmsg) + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) end if end subroutine ccpt_set_thermo_active From 4c1807cdcccce884c0df3c88e463e7157173cb17 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Tue, 28 Nov 2023 20:44:58 -0700 Subject: [PATCH 11/11] change debug_on subroutine in framework_env to verbose property --- scripts/ccpp_capgen.py | 4 ++-- scripts/ccpp_suite.py | 4 ++-- scripts/ddt_library.py | 4 ++-- scripts/fortran_tools/parse_fortran_file.py | 16 ++++++++-------- scripts/framework_env.py | 7 ++++--- scripts/host_cap.py | 2 +- scripts/metadata_table.py | 4 ++-- scripts/parse_tools/__init__.py | 2 +- scripts/parse_tools/parse_log.py | 2 +- scripts/suite_objects.py | 2 +- 10 files changed, 24 insertions(+), 23 deletions(-) diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index 2c50eed1..cbd46599 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -619,14 +619,14 @@ def capgen(run_env, return_db=False): # end if # Next, parse the scheme files scheme_headers, scheme_tdict = parse_scheme_files(scheme_files, run_env) - if run_env.debug_on(): + if run_env.verbose: ddts = host_model.ddt_lib.keys() if ddts: run_env.logger.debug("DDT definitions = {}".format(ddts)) # end if # end if plist = host_model.prop_list('local_name') - if run_env.debug_on(): + if run_env.verbose: run_env.logger.debug("{} variables = {}".format(host_model.name, plist)) run_env.logger.debug("schemes = {}".format([x.title for x in scheme_headers])) diff --git a/scripts/ccpp_suite.py b/scripts/ccpp_suite.py index 488d4a27..c5c6f4ab 100644 --- a/scripts/ccpp_suite.py +++ b/scripts/ccpp_suite.py @@ -427,7 +427,7 @@ def analyze(self, host_model, scheme_library, ddt_library, run_env): phase = RUN_PHASE_NAME # end if lmsg = "Group {}, schemes = {}" - if run_env.debug_on(): + if run_env.verbose: run_env.logger.debug(lmsg.format(item.name, [x.name for x in item.schemes()])) @@ -490,7 +490,7 @@ def write(self, output_dir, run_env): (calling the group caps one after another)""" # Set name of module and filename of cap filename = '{module_name}.F90'.format(module_name=self.module) - if run_env.debug_on(): + if run_env.verbose: run_env.logger.debug('Writing CCPP suite file, {}'.format(filename)) # end if # Retrieve the name of the constituent module for Group use statements diff --git a/scripts/ddt_library.py b/scripts/ddt_library.py index 9644787b..72fe48b8 100644 --- a/scripts/ddt_library.py +++ b/scripts/ddt_library.py @@ -46,7 +46,7 @@ def __init__(self, new_field, var_ref, run_env, recur=False): self.__field = VarDDT(new_field, var_ref.field, run_env, recur=True) # end if if ((not recur) and - run_env.debug_on()): + run_env.verbose): run_env.logger.debug('Adding DDT field, {}'.format(self)) # end if @@ -218,7 +218,7 @@ def __init__(self, name, run_env, ddts=None): octx = context_string(self[ddt.title].source.context) raise CCPPError(errmsg.format(ddt.title, ctx, octx)) # end if - if run_env.debug_on(): + if run_env.verbose: lmsg = f"Adding DDT {ddt.title} to {self.name}" run_env.logger.debug(lmsg) # end if diff --git a/scripts/fortran_tools/parse_fortran_file.py b/scripts/fortran_tools/parse_fortran_file.py index 80b1c486..a67816e4 100644 --- a/scripts/fortran_tools/parse_fortran_file.py +++ b/scripts/fortran_tools/parse_fortran_file.py @@ -563,7 +563,7 @@ def parse_preamble_data(statements, pobj, spec_name, endmatch, run_env): module=spec_name, var_dict=var_dict) mheaders.append(mheader) - if run_env.debug_on(): + if run_env.verbose: ctx = context_string(pobj, nodir=True) msg = 'Adding header {}{}' run_env.logger.debug(msg.format(mheader.table_name, ctx)) @@ -580,13 +580,13 @@ def parse_preamble_data(statements, pobj, spec_name, endmatch, run_env): ctx = context_string(pobj, nodir=True) msg = "No DDT found at '{}'{}" raise CCPPError(msg.format(statement, ctx)) - # End if + # end if mheaders.append(ddt) - if run_env.debug_on(): + if run_env.verbose: ctx = context_string(pobj, nodir=True) msg = 'Adding DDT {}{}' run_env.logger.debug(msg.format(ddt.table_name, ctx)) - # End if + # end if active_table = None else: # We found a type definition but it is not one with @@ -624,7 +624,7 @@ def parse_scheme_metadata(statements, pobj, spec_name, table_name, run_env): inpreamble = False insub = True seen_contains = False - if run_env.debug_on(): + if run_env.verbose: ctx = context_string(pobj, nodir=True) msg = "Parsing specification of {}{}" run_env.logger.debug(msg.format(table_name, ctx)) @@ -811,7 +811,7 @@ def parse_specification(pobj, statements, run_env, mod_name=None, errmsg = duplicate_header(mtables[title], tbl) raise CCPPError(errmsg) # end if - if run_env.debug_on(): + if run_env.verbose: ctx = tbl.start_context() mtype = tbl.table_type msg = "Adding metadata from {}, {}{}" @@ -892,7 +892,7 @@ def parse_module(pobj, statements, run_env): # End if mod_name = pmatch.group(1) pobj.enter_region('MODULE', region_name=mod_name, nested_ok=False) - if run_env.debug_on(): + if run_env.verbose: ctx = context_string(pobj, nodir=True) msg = "Parsing Fortran module, {}{}" run_env.logger.debug(msg.format(mod_name, ctx)) @@ -928,7 +928,7 @@ def parse_module(pobj, statements, run_env): errmsg = duplicate_header(mtables[title], mheader) raise CCPPError(errmsg) # end if - if run_env.debug_on(): + if run_env.verbose: mtype = mheader.table_type ctx = mheader.start_context() msg = "Adding metadata from {}, {}{}" diff --git a/scripts/framework_env.py b/scripts/framework_env.py index af941bd4..c0bb4139 100644 --- a/scripts/framework_env.py +++ b/scripts/framework_env.py @@ -10,7 +10,7 @@ # Python library imports import argparse import os -from parse_tools import debug_enabled +from parse_tools import verbose _EPILOG = ''' ''' @@ -276,10 +276,11 @@ def kind_types(self): CCPPFrameworkEnv object.""" return self.__kind_dict.keys() - def debug_on(self): + @property + def verbose(self): """Return true if debug enabled for the CCPPFrameworkEnv's logger object.""" - return (self.logger and debug_enabled(self.logger)) + return (self.logger and verbose(self.logger)) @property def use_error_obj(self): diff --git a/scripts/host_cap.py b/scripts/host_cap.py index 268a1855..d2c4ed7e 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -239,7 +239,7 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): # Add entries for each constituent (once per standard name) const_stdnames = set() for suite in suite_list: - if run_env.debug_on(): + if run_env.verbose: lmsg = "Adding constituents from {} to {}" run_env.logger.debug(lmsg.format(suite.name, host_model.name)) # end if diff --git a/scripts/metadata_table.py b/scripts/metadata_table.py index 0afe30d3..946e9782 100755 --- a/scripts/metadata_table.py +++ b/scripts/metadata_table.py @@ -792,7 +792,7 @@ def __init_from_file(self, table_name, table_type, known_ddts, run_env): self.__pobj.add_syntax_err(mismatch) self.__section_valid = False # end if - if run_env.debug_on(): + if run_env.verbose: run_env.logger.info("Parsing {} {}{}".format(self.header_type, self.title, start_ctx)) # end if @@ -812,7 +812,7 @@ def __init_from_file(self, table_name, table_type, known_ddts, run_env): newvar, curr_line = self.parse_variable(curr_line, known_ddts) valid_lines = newvar is not None if valid_lines: - if run_env.debug_on(): + if run_env.verbose: dmsg = 'Adding {} to {}' lname = newvar.get_prop_value('local_name') run_env.logger.debug(dmsg.format(lname, self.title)) diff --git a/scripts/parse_tools/__init__.py b/scripts/parse_tools/__init__.py index 736910f0..4990da88 100644 --- a/scripts/parse_tools/__init__.py +++ b/scripts/parse_tools/__init__.py @@ -25,7 +25,7 @@ from parse_checkers import check_default_value, check_valid_values, check_molar_mass from parse_log import init_log, set_log_level, flush_log from parse_log import set_log_to_stdout, set_log_to_null -from parse_log import set_log_to_file, debug_enabled +from parse_log import set_log_to_file, verbose from preprocess import PreprocStack from xml_tools import find_schema_file, find_schema_version from xml_tools import read_xml_file, validate_xml_file diff --git a/scripts/parse_tools/parse_log.py b/scripts/parse_tools/parse_log.py index 9707042a..f85a5d09 100644 --- a/scripts/parse_tools/parse_log.py +++ b/scripts/parse_tools/parse_log.py @@ -48,6 +48,6 @@ def flush_log(logger): for handler in list(logger.handlers): handler.flush() -def debug_enabled(logger): +def verbose(logger): """Return true if debug is enabled for this logger""" return logger.isEnabledFor(logging.DEBUG) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 0be6de15..c52c7c4d 100644 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1287,7 +1287,7 @@ def __init__(self, index_name, context, parent, run_env, items=None): # self._local_dim_name is the variable name for self._dim_name self._local_dim_name = None super().__init__(index_name, context, parent, run_env) - if run_env.debug_on(): + if run_env.verbose: lmsg = "Adding VerticalLoop for '{}'" run_env.logger.debug(lmsg.format(index_name)) # end if