From cea2531559b44e40afeeca52fde6f7c6dd317b6a Mon Sep 17 00:00:00 2001 From: Steve Goldhaber Date: Wed, 13 Jul 2022 21:45:58 -0600 Subject: [PATCH 001/159] Refactor constituent mixing ratio properties Allow schema file to be passed to validate Add Fortran comment write method Add metadata table type and name to CCPP datatable variable entries Improve generated constituent-handling code Fix issues with output of long comments Ensure more instance variables are 'private' Have capgen create database object Add line fill (target line length) interface to FortranWriter New Fortran interface, indent_size New Fortran interface, blank_line Added insert function for verbatim copy to file Allow arbitrary break for long lines Make sure call list variables have an intent Added link to constituent dictionary Add metadata to CCPP constituent object DDT Add Fortran writing unit tests and fix line break bugs Added constituent props array host interface, improved unit conversion error Use +ELLIPSIS instead of +IGNORE_EXCEPTION_DETAILS for doctests Constituent cleanup, no function side effects in interface Completed and optimized constituent accessor routines Added minimum allowed value property --- doc/HelloWorld/CMakeLists.txt | 5 +- scripts/ccpp_capgen.py | 48 +- scripts/ccpp_database_obj.py | 87 + scripts/ccpp_datafile.py | 10 +- scripts/ccpp_suite.py | 34 +- scripts/constituents.py | 223 ++- scripts/ddt_library.py | 107 +- scripts/fortran_tools/fortran_write.py | 200 +- scripts/fortran_tools/parse_fortran.py | 10 +- scripts/fortran_tools/parse_fortran_file.py | 52 +- scripts/framework_env.py | 6 +- scripts/host_cap.py | 146 +- scripts/host_model.py | 47 +- scripts/metadata_table.py | 38 +- scripts/metavar.py | 80 +- scripts/parse_tools/__init__.py | 3 +- scripts/parse_tools/parse_checkers.py | 2 +- scripts/parse_tools/parse_object.py | 8 +- scripts/parse_tools/parse_source.py | 73 +- scripts/parse_tools/xml_tools.py | 138 +- scripts/suite_objects.py | 24 +- scripts/var_props.py | 120 +- src/ccpp_constituent_prop_mod.F90 | 1622 ++++++++++++----- src/ccpp_constituent_prop_mod.meta | 47 + test/advection_test/test_host.F90 | 245 ++- test/advection_test/test_host_data.F90 | 19 +- test/advection_test/test_host_mod.F90 | 36 +- test/advection_test/test_reports.py | 4 +- .../fortran_files/comments_test.F90 | 33 + .../fortran_files/linebreak_test.F90 | 39 + .../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_fortran_write.py | 126 ++ test/unit_tests/test_metadata_host_file.py | 260 +++ test/unit_tests/test_metadata_scheme_file.py | 329 ++-- test/unit_tests/test_metadata_table.py | 2 +- 46 files changed, 3302 insertions(+), 1254 deletions(-) create mode 100644 scripts/ccpp_database_obj.py create mode 100644 src/ccpp_constituent_prop_mod.meta 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/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_fortran_write.py create mode 100644 test/unit_tests/test_metadata_host_file.py diff --git a/doc/HelloWorld/CMakeLists.txt b/doc/HelloWorld/CMakeLists.txt index 4b0cb208..7b480203 100644 --- a/doc/HelloWorld/CMakeLists.txt +++ b/doc/HelloWorld/CMakeLists.txt @@ -1,4 +1,4 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.8) +CMAKE_MINIMUM_REQUIRED(VERSION 3.10) PROJECT(HelloWorld) ENABLE_LANGUAGE(Fortran) @@ -166,7 +166,8 @@ list(APPEND DTABLE_CMD "${CCPP_CAP_FILES}/datatable.xml") list(APPEND DTABLE_CMD "--ccpp-files") list(APPEND DTABLE_CMD "--separator=\\;") string(REPLACE ";" " " DTABLE_STRING "${DTABLE_CMD}") -MESSAGE(STATUS "Running: ${DTABLE_STRING}") +string(STRIP ${DTABLE_STRING} DTABLE_STRING) +MESSAGE(STATUS "Running: ${DTABLE_STRING};") EXECUTE_PROCESS(COMMAND ${DTABLE_CMD} OUTPUT_VARIABLE CCPP_CAPS RESULT_VARIABLE RES OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE) diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index 7a12729a..3173dc54 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -15,6 +15,7 @@ import logging import re # CCPP framework imports +from ccpp_database_obj import CCPPDatabaseObj from ccpp_datafile import generate_ccpp_datatable from ccpp_suite import API from file_utils import check_for_writeable_file, remove_dir, replace_paths @@ -26,11 +27,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 +46,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): ############################################################################### @@ -559,7 +567,7 @@ def clean_capgen(cap_output_file, logger): set_log_level(logger, log_level) ############################################################################### -def capgen(run_env): +def capgen(run_env, return_db=False): ############################################################################### """Parse indicated host, scheme, and suite files. Generate code to allow host model to run indicated CCPP suites.""" @@ -578,12 +586,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) @@ -595,10 +613,18 @@ def capgen(run_env): # First up, handle the host files host_model = parse_host_model_files(host_files, host_name, run_env) # Next, parse the scheme files + # We always need to parse the ccpp_constituent_prop_ptr_t DDT + ##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 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): @@ -628,7 +654,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() @@ -646,10 +673,13 @@ 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) + if return_db: + return CCPPDatabaseObj(run_env, host_model=host_model, api=ccpp_api) + # end if + return None ############################################################################### def _main_func(): @@ -665,7 +695,7 @@ def _main_func(): if framework_env.clean: clean_capgen(framework_env.datatable_file, framework_env.logger) else: - capgen(framework_env) + _ = capgen(framework_env) # end if (clean) ############################################################################### diff --git a/scripts/ccpp_database_obj.py b/scripts/ccpp_database_obj.py new file mode 100644 index 00000000..f18516b2 --- /dev/null +++ b/scripts/ccpp_database_obj.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 + +""" +Define the CCPPDatabaseObj object +Ojbect definition and methods to provide information from a run of capgen. +""" + +from host_model import HostModel +from ccpp_suite import API + +class CCPPDatabaseObjError(ValueError): + """Error class specific to CCPPDatabaseObj. + All uses of this error should be internal (i.e., programmer error, + not user error).""" + + def __init__(self, message): + """Initialize this exception""" + super().__init__(message) + +class CCPPDatabaseObj: + """Ojbect with data and methods to provide information from a run of capgen. + """ + + def __init__(self, run_env, host_model=None, api=None, database_file=None): + """Initialize this CCPPDatabaseObj. + If is not None, all other inputs MUST be None and + the object is created from the database table created by capgen. + To initialize the object from an in-memory capgen run, ALL other + inputs MUST be passed (i.e., not None) and it is an error to pass + a value for . + """ + + runtime_obj = all([host_model is not None, api is not None]) + self.__host_model = None + self.__api = None + self.__database_file = None + if runtime_obj and database_file: + emsg = "Cannot provide both runtime arguments and database_file." + elif (not runtime_obj) and (not database_file): + emsg = "Must provide either database_file or all runtime arguments." + else: + emsg = "" + # end if + if emsg: + raise CCPPDatabaseObjError(f"ERROR: {emsg}") + # end if + if runtime_obj: + self.__host_model = host_model + self.__api = api + else: + self.db_from_file(run_env, database_file) + # end if + + def db_from_file(self, run_env, database_file): + """Create the necessary internal data structures from a CCPP + datatable.xml file created by capgen. + """ + metadata_tables = {} + host_name = "cam" + self.__host_model = HostModel(metadata_tables, host_name, run_env) + self.__api = API(sdfs, host_model, scheme_headers, run_env) + raise CCPPDatabaseObjError("ERROR: not supported") + + def host_model_dict(self): + """Return the host model dictionary for this CCPP DB object""" + if self.__host_model is not None: + return self.__host_model + # end if + raise CCPPDatabaseObjError("ERROR: not supported") + + def suite_list(self): + """Return a list of suites built into the API""" + if self.__api is not None: + return list(self.__api.suites) + # end if + raise CCPPDatabaseObjError("ERROR: not supported") + + def constituent_dictionary(self, suite): + """Return the constituent dictionary for """ + return suite.constituent_dictionary() + + def call_list(self, phase): + """Return the API call list for """ + if self.__api is not None: + return self.__api.call_list(phase) + # end if + raise CCPPDatabaseObjError("ERROR: not supported") diff --git a/scripts/ccpp_datafile.py b/scripts/ccpp_datafile.py index 158d9dec..23db0887 100755 --- a/scripts/ccpp_datafile.py +++ b/scripts/ccpp_datafile.py @@ -652,7 +652,7 @@ def _new_var_entry(parent, var, full_entry=True): """Create a variable sub-element of with information from . If is False, only include standard name and intent. """ - prop_list = ["intent"] + prop_list = ["intent", "local_name"] if full_entry: prop_list.extend(["allocatable", "active", "default_value", "diagnostic_name", "diagnostic_name_fixed", @@ -671,9 +671,13 @@ def _new_var_entry(parent, var, full_entry=True): if full_entry: dims = var.get_dimensions() if dims: - dim_entry = ET.SubElement(ventry, "dimensions") - dim_entry.text = " ".join(dims) + v_entry = ET.SubElement(ventry, "dimensions") + v_entry.text = " ".join(dims) # end if + v_entry = ET.SubElement(ventry, "source_type") + v_entry.text = var.source.ptype.lower() + v_entry = ET.SubElement(ventry, "source_name") + v_entry.text = var.source.name.lower() # end if ############################################################################### diff --git a/scripts/ccpp_suite.py b/scripts/ccpp_suite.py index 8b9a1aeb..6e25510c 100644 --- a/scripts/ccpp_suite.py +++ b/scripts/ccpp_suite.py @@ -17,8 +17,7 @@ from fortran_tools import FortranWriter from framework_env import CCPPFrameworkEnv from metavar import Var, VarDictionary, ccpp_standard_var -from metavar import CCPP_CONSTANT_VARS, CCPP_LOOP_VAR_STDNAMES -from parse_tools import ParseContext, ParseSource, context_string +from parse_tools import ParseContext, ParseSource from parse_tools import ParseInternalError, CCPPError from parse_tools import read_xml_file, validate_xml_file, find_schema_version from parse_tools import init_log, set_log_to_null @@ -31,12 +30,12 @@ ############################################################################### # Source for internally generated variables. -_API_SOURCE_NAME = "CCPP_API" +API_SOURCE_NAME = "CCPP_API" # Use the constituent source type for consistency _API_SUITE_VAR_NAME = ConstituentVarDict.constitutent_source_type() _API_SCHEME_VAR_NAME = "scheme" _API_CONTEXT = ParseContext(filename="ccpp_suite.py") -_API_SOURCE = ParseSource(_API_SOURCE_NAME, _API_SCHEME_VAR_NAME, _API_CONTEXT) +_API_SOURCE = ParseSource(API_SOURCE_NAME, _API_SCHEME_VAR_NAME, _API_CONTEXT) _API_LOGGING = init_log('ccpp_suite') set_log_to_null(_API_LOGGING) _API_DUMMY_RUN_ENV = CCPPFrameworkEnv(_API_LOGGING, @@ -299,7 +298,7 @@ def find_variable(self, standard_name=None, source_var=None, # Remove this entry to avoid looping back here del self.__gvar_stdnames[standard_name] # Let everyone know this is now a Suite variable - var.source = ParseSource(_API_SOURCE_NAME, + var.source = ParseSource(API_SOURCE_NAME, _API_SUITE_VAR_NAME, var.context) self.add_variable(var, self.__run_env) @@ -730,9 +729,9 @@ def write_var_set_loop(ofile, varlist_name, var_list, indent, def write_suite_part_list_sub(self, ofile, errmsg_name, errcode_name): """Write the suite-part list subroutine""" - oline = "suite_name, part_list, {errmsg}, {errcode}" - inargs = oline.format(errmsg=errmsg_name, errcode=errcode_name) - ofile.write("\nsubroutine {}({})".format(API.__part_fname, inargs), 1) + ofile.blank_line() + inargs = f"suite_name, part_list, {errmsg_name}, {errcode_name}" + ofile.write(f"subroutine {API.__part_fname}({inargs})", 1) oline = "character(len=*), intent(in) :: suite_name" ofile.write(oline, 2) oline = "character(len=*), allocatable, intent(out) :: part_list(:)" @@ -741,9 +740,9 @@ def write_suite_part_list_sub(self, ofile, errmsg_name, errcode_name): self._errcode_var.write_def(ofile, 2, self) else_str = '' ename = self._errcode_var.get_prop_value('local_name') - ofile.write("{} = 0".format(ename), 2) + ofile.write(f"{ename} = 0", 2) ename = self._errmsg_var.get_prop_value('local_name') - ofile.write("{} = ''".format(ename), 2) + ofile.write(f"{ename} = ''", 2) for suite in self.suites: oline = "{}if(trim(suite_name) == '{}') then" ofile.write(oline.format(else_str, suite.name), 2) @@ -751,12 +750,12 @@ def write_suite_part_list_sub(self, ofile, errmsg_name, errcode_name): else_str = 'else ' # end for ofile.write("else", 2) - emsg = "write({errmsg}, '(3a)')".format(errmsg=errmsg_name) + emsg = f"write({errmsg_name}, '(3a)')" emsg += "'No suite named ', trim(suite_name), ' found'" ofile.write(emsg, 3) - ofile.write("{errcode} = 1".format(errcode=errcode_name), 3) + ofile.write(f"{errcode_name} = 1", 3) ofile.write("end if", 2) - ofile.write("end subroutine {}".format(API.__part_fname), 1) + ofile.write(f"end subroutine {API.__part_fname}", 1) def write_req_vars_sub(self, ofile, errmsg_name, errcode_name): """Write the required variables subroutine""" @@ -807,9 +806,9 @@ def write_req_vars_sub(self, ofile, errmsg_name, errcode_name): parent = suite.parent # Collect all the suite variables oline = "{}if(trim(suite_name) == '{}') then" - 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 + input_vars = [set(), set(), set()] # leaves, arrays, leaf elements + inout_vars = [set(), set(), set()] # leaves, arrays, leaf elements + output_vars = [set(), set(), set()] # leaves, arrays, leaf elements for part in suite.groups: for var in part.call_list.variable_list(): stdname = var.get_prop_value("standard_name") @@ -821,7 +820,8 @@ def write_req_vars_sub(self, ofile, errmsg_name, errcode_name): protected = pvar.get_prop_value("protected") # end if # end if - elements = var.intrinsic_elements(check_dict=self.parent) + elements = var.intrinsic_elements(check_dict=self.parent, + ddt_lib=self.__ddt_lib) if (intent == 'in') and (not protected): if isinstance(elements, list): input_vars[1].add(stdname) diff --git a/scripts/constituents.py b/scripts/constituents.py index be8d4774..34ddf52d 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, @@ -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 @@ -301,6 +296,7 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): 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() if 'vertical_layer_dimension' in dims: vertical_dim = 'vertical_layer_dimension' @@ -310,10 +306,14 @@ 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"]}'] + stmt = 'call {}(index)%initialize({})' outfile.write(stmt.format(self.constituent_prop_array_name(), - std_name, long_name, vertical_dim, - advect_str, errvar_call2), indent+1) + ", ".join(init_args)), indent+1) # end for for evar in err_vars: self.__init_err_var(evar, outfile, indent+1) @@ -363,9 +363,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 +380,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 +401,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 @@ -450,7 +453,8 @@ def write_constituent_use_statements(cap, suite_list, indent): def write_host_routines(cap, host, reg_funcname, num_const_funcname, copy_in_funcname, copy_out_funcname, const_obj_name, const_names_name, const_indices_name, - suite_list, err_vars): + 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. @@ -476,121 +480,139 @@ 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, ncols, num_layers, 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("integer, intent(in) :: ncols", 2) + cap.write("integer, intent(in) :: num_layers", 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) cap.write("end if", 2) + # Register host model constituents + cap.comment("Add host model constituent metadata", 3) + cap.write("do index = 1, size(host_constituents, 1)", 2) + cap.write(f"if ({herrcode} == 0) then", 3) + cap.write("const_prop => host_constituents(index)", 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("", 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(f"if ({herrcode} == 0) then", 3) + cap.write(f"allocate(const_prop, stat={herrcode})", 4) + cap.write("end if", 3) + cap.write(f"if ({herrcode} /= 0) then", 3) + cap.write(f'{herrmsg} = "ERROR allocating const_prop"', 4) + cap.write("exit", 4) cap.write("end if", 3) - cap.write("if ({} == 0) then".format(herrcode), 3) + cap.write(f"if ({herrcode} == 0) then", 3) 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(f"if ({herrcode} == 0) then", 3) 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(f"if ({herrcode} /= 0) then", 3) cap.write("exit", 4) cap.write("end if", 3) cap.write("end do", 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(ncols, num_layers, {})" 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.comment("Set the index for each active constituent", 2) + cap.write(f"do index = 1, SIZE({const_indices_name})", 2) + 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(f"{const_indices_name}(index) = field_ind", 4) cap.write("else", 3) - cap.write("{} = 1".format(herrcode), 4) + cap.write(f"{herrcode} = 1", 4) 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(f"if ({herrcode} /= 0) then", 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(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 +621,66 @@ 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 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 +704,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/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.py b/scripts/fortran_tools/parse_fortran.py index 624f02cb..2310e13f 100644 --- a/scripts/fortran_tools/parse_fortran.py +++ b/scripts/fortran_tools/parse_fortran.py @@ -532,7 +532,8 @@ def class_match(cls, line): @classmethod def type_def_line(cls, line): """Return a type information if represents the start - of a type definition""" + of a type definition. + Otherwise, return None""" type_def = None if not cls.type_match(line): if '!' in line: @@ -629,7 +630,8 @@ def ftype_factory(line, context): def fortran_type_definition(line): ######################################################################## """Return a type information if represents the start - of a type definition""" + of a type definition. + Otherwise, return None.""" return FtypeTypeDecl.type_def_line(line) ######################################################################## @@ -720,8 +722,8 @@ def parse_fortran_var_decl(line, source, run_env): varprops = Ftype.parse_attr_specs(elements[0].strip(), context) for prop in varprops: if prop[0:6] == 'intent': - if source.type != 'scheme': - typ = source.type + if source.ptype != 'scheme': + typ = source.ptype errmsg = 'Invalid variable declaration, {}, intent' errmsg = errmsg + ' not allowed in {} variable' if run_env.logger is not None: 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/framework_env.py b/scripts/framework_env.py index 8ee553f4..313dbb38 100644 --- a/scripts/framework_env.py +++ b/scripts/framework_env.py @@ -10,6 +10,8 @@ # Python library imports import argparse import os +# CCPP framework imports +from parse_tools import type_name _EPILOG = ''' ''' @@ -103,8 +105,8 @@ def __init__(self, logger, ndict=None, verbose=0, clean=False, # String of definitions, separated by spaces preproc_list = [x.strip() for x in preproc_defs.split(' ') if x] else: - wmsg = "Error: Bad preproc list type, '{}'" - emsg += esep + wmsg.format(type(preproc_defs)) + wmsg = f"Error: Bad preproc list type, '{type_name(preproc_defs)}'" + emsg += esep + wmsg esep = '\n' # end if # Turn the list into a dictionary diff --git a/scripts/host_cap.py b/scripts/host_cap.py index 9c88cf34..001063d3 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): @@ -129,10 +128,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 +150,29 @@ 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_advected_consts(host_model): +############################################################################### + """Return the name of the function that will return a pointer to the + array of advected constituents""" + hstr = "{}_advected_constituents".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 +186,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), + f" standard_name = {stdname_layer}", " units = count", " dimensions = ()", " type = integer", - "[ num_2d_vars ]", - " standard_name = {}".format(stdname_2d), - " units = count", " dimensions = ()", " type = integer", - "[ {} ]".format(array_layer), - " standard_name = ccpp_constituents_array_of_layer_consts", - " 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", + f"[ {array_layer} ]", + " standard_name = ccpp_constituent_array", " 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 +237,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 +244,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 +380,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 +406,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}" @@ -427,14 +428,20 @@ def write_host_cap(host_model, api, output_dir, run_env): cap.write("public :: {}".format(copyin_name), 1) copyout_name = constituent_copyout_subname(host_model) cap.write("public :: {}".format(copyout_name), 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 +482,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) @@ -568,6 +575,9 @@ def write_host_cap(host_model, api, output_dir, run_env): copyout_name, const_obj_name, const_names_name, const_indices_name, + 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/metadata_table.py b/scripts/metadata_table.py index 17e8dfdb..5e9820d7 100644 --- a/scripts/metadata_table.py +++ b/scripts/metadata_table.py @@ -158,7 +158,7 @@ def blank_metadata_line(line): def _parse_config_line(line, context): """Parse a config line and return a list of keyword value pairs.""" - parse_items = list() + parse_items = [] if line is None: pass # No properties on this line elif blank_metadata_line(line): @@ -182,8 +182,8 @@ def _parse_config_line(line, context): def parse_metadata_file(filename, known_ddts, run_env): """Parse and return list of parsed metadata tables""" # Read all lines of the file at once - meta_tables = list() - table_titles = list() # Keep track of names in file + meta_tables = [] + table_titles = [] # Keep track of names in file with open(filename, 'r') as infile: fin_lines = infile.readlines() for index, fin_line in enumerate(fin_lines): @@ -225,7 +225,7 @@ def find_scheme_names(filename): """Find and return a list of all the physics scheme names in . A scheme is identified by its ccpp-table-properties name. """ - scheme_names = list() + scheme_names = [] with open(filename, 'r') as infile: fin_lines = infile.readlines() # end with @@ -283,7 +283,7 @@ def __init__(self, run_env, table_name_in=None, table_type_in=None, self.__pobj = parse_object self.__dependencies = dependencies self.__relative_path = relative_path - self.__sections = list() + self.__sections = [] self.__run_env = run_env if parse_object is None: if table_name_in is not None: @@ -339,7 +339,7 @@ def __init__(self, run_env, table_name_in=None, table_type_in=None, raise ParseInternalError(perr) # end if if known_ddts is None: - known_ddts = list() + known_ddts = [] # end if self.__start_context = ParseContext(context=self.__pobj) self.__init_from_file(known_ddts, self.__run_env) @@ -351,7 +351,7 @@ def __init_from_file(self, known_ddts, run_env): curr_line, _ = self.__pobj.next_line() in_properties_header = True skip_rest_of_section = False - self.__dependencies = list() # Default is no dependencies + self.__dependencies = [] # Default is no dependencies # Process lines until the end of the file or start of the next table. while ((curr_line is not None) and (not MetadataTable.table_start(curr_line))): @@ -440,7 +440,7 @@ def __init_from_file(self, known_ddts, run_env): known_ddts.append(self.table_name) # end if if self.__dependencies is None: - self.__dependencies = list() + self.__dependencies = [] # end if def start_context(self, with_comma=True, nodir=True): @@ -673,7 +673,7 @@ def __init__(self, table_name, table_type, run_env, parse_object=None, self.__start_context = None else: if known_ddts is None: - known_ddts = list() + known_ddts = [] # end if self.__start_context = ParseContext(context=self.__pobj) self.__init_from_file(table_name, table_type, known_ddts, run_env) @@ -683,7 +683,7 @@ def __init__(self, table_name, table_type, run_env, parse_object=None, register_fortran_ddt_name(self.title) # end if # Categorize the variables - self._var_intents = {'in' : list(), 'out' : list(), 'inout' : list()} + self._var_intents = {'in' : [], 'out' : [], 'inout' : []} for var in self.variable_list(): intent = var.get_prop_value('intent') if intent is not None: @@ -693,7 +693,7 @@ def __init__(self, table_name, table_type, run_env, parse_object=None, def _default_module(self): """Set a default module for this header""" - mfile = self.__pobj.file_name + mfile = self.__pobj.filename if mfile[-5:] == '.meta': # Default value is a Fortran module that matches the filename def_mod = os.path.basename(mfile)[:-5] @@ -888,7 +888,7 @@ def parse_variable(self, curr_line, known_ddts): # Special case for dimensions, turn them into ranges if pname == 'dimensions': porig = pval - pval = list() + pval = [] for dim in porig: if ':' in dim: pval.append(dim) @@ -975,7 +975,7 @@ def check_array_reference(local_name, var_dict, context): local_name, colon_rank, ctx)) # end if - sub_dims = list() + sub_dims = [] sindex = 0 for rind in rdims: if rind == ':': @@ -1010,7 +1010,7 @@ def convert_dims_to_standard_names(self, var, logger=None, context=None): """Convert the dimension elements in to standard names by by using other variables in this header. """ - std_dims = list() + std_dims = [] vdims = var.get_dimensions() # Check for bad dimensions if vdims is None: @@ -1036,7 +1036,7 @@ def convert_dims_to_standard_names(self, var, logger=None, context=None): raise CCPPError("{}".format(errmsg)) # end if for dim in vdims: - std_dim = list() + std_dim = [] if ':' not in dim: # Metadata dimensions always have an explicit start var_one = CCPP_CONSTANT_VARS.find_local_name('1') @@ -1056,7 +1056,7 @@ def convert_dims_to_standard_names(self, var, logger=None, context=None): # Some non-standard integer value dname = item # end if - except ValueError: + except ValueError as verr: # Not an integer, try to find the standard_name if not item: # Naked colons are okay @@ -1070,15 +1070,15 @@ def convert_dims_to_standard_names(self, var, logger=None, context=None): # end if # end if if dname is None: - errmsg = "Unknown dimension element, {}, in {}{}" std = var.get_prop_value('local_name') - ctx = context_string(context) + errmsg = f"Unknown dimension element, {item}, in {std}" + errmsg += context_string(context) if logger is not None: errmsg = "ERROR: " + errmsg logger.error(errmsg.format(item, std, ctx)) dname = unique_standard_name() else: - raise CCPPError(errmsg.format(item, std, ctx)) + raise CCPPError(errmsg) from verr # end if # end if # end try diff --git a/scripts/metavar.py b/scripts/metavar.py index 71609580..7aa12dab 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -20,7 +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 ParseContext, ParseSource +from parse_tools import ParseContext, ParseSource, type_name from parse_tools import ParseInternalError, ParseSyntaxError, CCPPError from var_props import CCPP_LOOP_DIM_SUBSTS, VariableProperty, VarCompatObj from var_props import find_horizontal_dimension, find_vertical_dimension @@ -196,6 +196,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 @@ -261,7 +263,7 @@ def __init__(self, prop_dict, source, run_env, context=None, if isinstance(prop_dict, Var): prop_dict = prop_dict.copy_prop_dict() # end if - if source.type == 'scheme': + if source.ptype == 'scheme': self.__required_props = Var.__required_var_props # XXgoldyXX: v don't fill in default properties? # mstr_propdict = Var.__var_propdict @@ -272,7 +274,7 @@ def __init__(self, prop_dict, source, run_env, context=None, mstr_propdict = Var.__spec_propdict # XXgoldyXX: ^ don't fill in default properties? # end if - self._source = source + self.__source = source # Grab a frozen copy of the context if context is None: self._context = ParseContext(context=source.context) @@ -476,7 +478,7 @@ def clone(self, subst_dict=None, remove_intent=False, source_name = self.source.name # end if if source_type is None: - source_type = self.source.type + source_type = self.source.ptype # end if if context is None: context = self._context @@ -610,7 +612,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 +690,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 +758,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 +774,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: @@ -822,7 +845,7 @@ def parent(self, parent_var): else: emsg = 'Attempting to set parent for {}, bad parent type, {}' lname = self.get_prop_value('local_name') - raise ParseInternalError(emsg.format(lname, type(parent_var))) + raise ParseInternalError(emsg.format(lname, type_name(parent_var))) # end if def add_child(self, cvar): @@ -844,6 +867,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""" @@ -852,13 +880,13 @@ def context(self): @property def source(self): """Return the source object for this variable""" - return self._source + return self.__source @source.setter def source(self, new_source): """Reset this Var's source if seems legit""" if isinstance(new_source, ParseSource): - self._source = new_source + self.__source = new_source else: errmsg = 'Attemping to set source of {} ({}) to "{}"' stdname = self.get_prop_value('standard_name') @@ -874,7 +902,7 @@ def clone_source(self): @property def host_interface_var(self): """True iff self is included in the host model interface calls""" - return self.source.type == 'host' + return self.source.ptype == 'host' @property def run_env(self): @@ -932,7 +960,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 +1022,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 +1036,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}" @@ -1427,7 +1461,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_name(variables), + self.name)) # end if @property @@ -1608,7 +1644,7 @@ def add_variable_dimensions(self, var, ignore_sources, to_dict=None, # end if if not present: dvar = self.find_variable(standard_name=dimname, any_scope=True) - if dvar and (dvar.source.type not in ignore_sources): + if dvar and (dvar.source.ptype not in ignore_sources): if to_dict: to_dict.add_variable(dvar, self.__run_env, exists_ok=True, @@ -1631,7 +1667,7 @@ def add_variable_dimensions(self, var, ignore_sources, to_dict=None, err_ret += "\nFound {} from excluded source, '{}'{}" lname = dvar.get_prop_value('local_name') dctx = context_string(dvar.context) - err_ret = err_ret.format(lname, dvar.source.type, dctx) + err_ret = err_ret.format(lname, dvar.source.ptype, dctx) # end if # end if # end if diff --git a/scripts/parse_tools/__init__.py b/scripts/parse_tools/__init__.py index 03dd0429..4f888fb1 100644 --- a/scripts/parse_tools/__init__.py +++ b/scripts/parse_tools/__init__.py @@ -8,7 +8,7 @@ # pylint: disable=wrong-import-position from parse_source import ParseContext, ParseSource from parse_source import ParseSyntaxError, ParseInternalError -from parse_source import CCPPError, context_string +from parse_source import CCPPError, context_string, type_name from parse_source import unique_standard_name, reset_standard_name_counter from parse_object import ParseObject from parse_checkers import check_fortran_id, FORTRAN_ID @@ -71,6 +71,7 @@ 'set_log_to_file', 'set_log_to_null', 'set_log_to_stdout', + 'type_name', 'unique_standard_name', 'validate_xml_file' ] diff --git a/scripts/parse_tools/parse_checkers.py b/scripts/parse_tools/parse_checkers.py index 487478e6..ca5f1f51 100755 --- a/scripts/parse_tools/parse_checkers.py +++ b/scripts/parse_tools/parse_checkers.py @@ -12,7 +12,7 @@ ######################################################################## -_UNITS_RE = re.compile(r"^[^/@#$%^&*()\|<>\[\]{}?,.]+$") +_UNITS_RE = re.compile(r"^[^/!@#$%^&*=()\|<>\[\]{}?,.]+$") def check_units(test_val, prop_dict, error): """Return if a valid unit, otherwise, None diff --git a/scripts/parse_tools/parse_object.py b/scripts/parse_tools/parse_object.py index 4518d75c..f141b298 100644 --- a/scripts/parse_tools/parse_object.py +++ b/scripts/parse_tools/parse_object.py @@ -35,7 +35,6 @@ class ParseObject(ParseContext): def __init__(self, filename, lines_in, line_start=0): """Initialize this ParseObject""" - self.__filename = filename self.__lines = lines_in self.__line_start = line_start self.__line_end = line_start @@ -43,7 +42,7 @@ def __init__(self, filename, lines_in, line_start=0): self.__num_lines = len(self.__lines) self.__error_message = "" self.__num_errors = 0 - super(ParseObject, self).__init__(linenum=line_start, filename=filename) + super().__init__(linenum=line_start, filename=filename) @property def first_line_num(self): @@ -59,11 +58,6 @@ def valid_line(self): """Return True if the current line is valid""" return (self.line_num >= 0) and (self.line_num < self.__num_lines) - @property - def file_name(self): - """Return this object's filename""" - return self.__filename - @property def error_message(self): """Return this object's error message""" diff --git a/scripts/parse_tools/parse_source.py b/scripts/parse_tools/parse_source.py index 6d28b694..0447225a 100644 --- a/scripts/parse_tools/parse_source.py +++ b/scripts/parse_tools/parse_source.py @@ -19,7 +19,7 @@ # CCPP framework imports # pylint: enable=wrong-import-position -class _StdNameCounter(object): +class _StdNameCounter: """Class to hold a global counter to avoid using global keyword""" __SNAME_NUM = 0 # Counter for unique standard names @@ -117,6 +117,12 @@ def context_string(context=None, with_comma=True, nodir=False): # End if return cstr.format(comma=comma, where_str=where_str, ctx=context) +############################################################################### +def type_name(obj): +############################################################################### + """Return the name of the type of """ + return type(obj).__name__ + ############################################################################### class CCPPError(ValueError): """Class so programs can log user errors without backtrace""" @@ -198,7 +204,7 @@ def __getitem__(self, index): ######################################################################## -class ParseContext(object): +class ParseContext: """A class for keeping track of a parsing position >>> ParseContext(32, "source.F90") #doctest: +ELLIPSIS <__main__.ParseContext object at 0x...> @@ -216,13 +222,6 @@ class ParseContext(object): """ - # python 2/3 difference - try: - __pstr_types = (str, unicode) - except NameError: - __pstr_types = (str,) - # End try - def __init__(self, linenum=None, filename=None, context=None): """Initialize this ParseContext""" # Set regions first in case of exception @@ -245,27 +244,27 @@ def __init__(self, linenum=None, filename=None, context=None): filename = context.filename elif filename is None: filename = "" - elif not isinstance(filename, ParseContext.__pstr_types): + elif not isinstance(filename, str): raise CCPPError('ParseContext filename must be a string') # No else, everything is okay # End if - self._linenum = linenum - self._filename = filename + self.__linenum = linenum + self.__filename = filename @property def line_num(self): """Return the current line""" - return self._linenum + return self.__linenum @line_num.setter def line_num(self, newnum): """Set a new line number for this context""" - self._linenum = newnum + self.__linenum = newnum @property def filename(self): """Return the object's filename""" - return self._filename + return self.__filename @property def regions(self): @@ -274,19 +273,19 @@ def regions(self): def __format__(self, spec): """Return a string representing the location in a file - Note that self._linenum is zero based. + Note that self.__linenum is zero based. can be 'dir' (show filename directory) or 'nodir' filename only. Any other spec entry is ignored. """ if spec == 'dir': - fname = self._filename + fname = self.__filename elif spec == 'nodir': - fname = os.path.basename(self._filename) + fname = os.path.basename(self.__filename) else: - fname = self._filename + fname = self.__filename # End if - if self._linenum >= 0: - fmt_str = "{}:{}".format(fname, self._linenum+1) + if self.__linenum >= 0: + fmt_str = "{}:{}".format(fname, self.__linenum+1) else: fmt_str = "{}".format(fname) # End if @@ -294,21 +293,21 @@ def __format__(self, spec): def __str__(self): """Return a string representing the location in a file - Note that self._linenum is zero based. + Note that self.__linenum is zero based. """ - if self._linenum >= 0: - retstr = "{}:{}".format(self._filename, self._linenum+1) + if self.__linenum >= 0: + retstr = "{}:{}".format(self.__filename, self.__linenum+1) else: - retstr = "{}".format(self._filename) + retstr = "{}".format(self.__filename) # End if return retstr def increment(self, inc=1): """Increment the location within a file""" - if self._linenum < 0: - self._linenum = 0 + if self.__linenum < 0: + self.__linenum = 0 # End if - self._linenum = self._linenum + inc + self.__linenum = self.__linenum + inc def enter_region(self, region_type, region_name=None, nested_ok=True): """Mark the entry of a region (e.g., DDT, module, function). @@ -378,12 +377,12 @@ def region_str(self): ######################################################################## -class ParseSource(object): +class ParseSource: """ A simple object for providing source information >>> ParseSource("myname", "mytype", ParseContext(13, "foo.F90")) #doctest: +ELLIPSIS <__main__.ParseSource object at 0x...> - >>> ParseSource("myname", "mytype", ParseContext(13, "foo.F90")).type + >>> ParseSource("myname", "mytype", ParseContext(13, "foo.F90")).ptype 'mytype' >>> ParseSource("myname", "mytype", ParseContext(13, "foo.F90")).name 'myname' @@ -393,24 +392,24 @@ class ParseSource(object): def __init__(self, name_in, type_in, context_in): """Initialize this ParseSource object.""" - self._name = name_in - self._type = type_in - self._context = context_in + self.__name = name_in + self.__type = type_in + self.__context = context_in @property - def type(self): + def ptype(self): """Return this source's type""" - return self._type + return self.__type @property def name(self): """Return this source's name""" - return self._name + return self.__name @property def context(self): """Return this source's context""" - return self._context + return self.__context ######################################################################## diff --git a/scripts/parse_tools/xml_tools.py b/scripts/parse_tools/xml_tools.py index 414ffc5a..d5a767b5 100644 --- a/scripts/parse_tools/xml_tools.py +++ b/scripts/parse_tools/xml_tools.py @@ -7,26 +7,19 @@ # 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"([<][^/][^<>/]+[/][>])") @@ -36,6 +29,14 @@ 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): ############################################################################### @@ -59,7 +60,7 @@ def call_command(commands, logger, silent=False): capture_output=True) if not silent: logger.debug(cproc.stdout) - # End if + # end if result = cproc.returncode == 0 elif PYSUBVER >= 5: cproc = subprocess.run(commands, check=True, @@ -67,11 +68,11 @@ def call_command(commands, logger, silent=False): stderr=subprocess.PIPE) if not silent: logger.debug(cproc.stdout) - # End if + # end if result = cproc.returncode == 0 else: raise ValueError("Python 3 must be at least version 3.5") - # End if + # end if else: pproc = subprocess.Popen(commands, stdin=None, stdout=subprocess.PIPE, @@ -79,9 +80,9 @@ def call_command(commands, logger, silent=False): output, _ = pproc.communicate() if not silent: logger.debug(output) - # End if + # end if result = pproc.returncode == 0 - # End if + # end if except (OSError, CCPPError, subprocess.CalledProcessError) as err: if silent: result = False @@ -90,9 +91,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 +103,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 +121,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 +164,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 +181,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 @@ -222,23 +230,23 @@ def read_xml_file(filename, logger=None): file_open = (lambda x: open(x, 'r', encoding='utf-8')) else: file_open = (lambda x: open(x, 'r')) - # End if + # end if 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 +256,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,7 +276,7 @@ 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", @@ -276,26 +284,26 @@ def write(self, file, encoding="us-ascii", xml_declaration=None, """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) + 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: - input = ET.tostring(self.getroot(), - encoding=encoding, method=method, - short_empty_elements=short_empty_elements) + et_str = 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) + et_str = ET.tostring(self.getroot(), + encoding=encoding, method=method) # end if if PY3: fmode = 'wt' - root = str(input, encoding="utf-8") + root = str(et_str, encoding="utf-8") else: fmode = 'w' - root = input + root = et_str # end if indent = 0 last_write_text = False diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 472556cb..5a710548 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 @@ -361,7 +377,7 @@ def register_action(self, vaction): @classmethod def is_suite_variable(cls, var): """Return True iff belongs to our Suite""" - return var and (var.source.type == _API_SUITE_VAR_NAME) + return var and (var.source.ptype == _API_SUITE_VAR_NAME) def is_local_variable(self, var): """Return the local variable matching if one is found belonging @@ -433,7 +449,7 @@ def add_call_list_variable(self, newvar, exists_ok=False, if dvar is None: emsg = "{}: Could not find dimension {} in {}" raise ParseInternalError(emsg.format(self.name, - stdname, vardim)) + vardim, stdname)) # end if elif self.parent is None: errmsg = 'No call_list found for {}'.format(newvar) @@ -831,7 +847,7 @@ def match_variable(self, var, vstdname=None, vdims=None): found_var = self.parent.add_variable_to_call_tree(dict_var, vmatch=vmatch) new_vdims = vdims - elif dict_var.source.type in _API_LOCAL_VAR_TYPES: + elif dict_var.source.ptype in _API_LOCAL_VAR_TYPES: # We cannot change the dimensions of locally-declared variables # Using a loop substitution is invalid because the loop variable # value has not yet been set. @@ -1716,7 +1732,7 @@ def find_variable(self, standard_name=None, source_var=None, search_call_list=search_call_list, loop_subst=loop_subst) if fvar and fvar.is_constituent(): - if fvar.source.type == ConstituentVarDict.constitutent_source_type(): + if fvar.source.ptype == ConstituentVarDict.constitutent_source_type(): # We found this variable in the constituent dictionary, # add it to our call list self.add_call_list_variable(fvar, exists_ok=True) diff --git a/scripts/var_props.py b/scripts/var_props.py index 4e2cadb5..a8a5e29d 100755 --- a/scripts/var_props.py +++ b/scripts/var_props.py @@ -133,18 +133,22 @@ def standard_name_to_long_name(prop_dict, context=None): """Translate a standard_name to its default long_name >>> standard_name_to_long_name({'standard_name':'cloud_optical_depth_layers_from_0p55mu_to_0p99mu'}) 'Cloud optical depth layers from 0.55mu to 0.99mu' - >>> standard_name_to_long_name({'local_name':'foo'}) #doctest: +IGNORE_EXCEPTION_DETAIL + >>> standard_name_to_long_name({'local_name':'foo'}) #doctest: +ELLIPSIS Traceback (most recent call last): - CCPPError: No standard name to convert foo to long name - >>> standard_name_to_long_name({}) #doctest: +IGNORE_EXCEPTION_DETAIL + ... + parse_source.CCPPError: No standard name to convert foo to long name + >>> standard_name_to_long_name({}) #doctest: +ELLIPSIS Traceback (most recent call last): - CCPPError: No standard name to convert to long name - >>> standard_name_to_long_name({'local_name':'foo'}, context=ParseContext(linenum=3, filename='foo.F90')) #doctest: +IGNORE_EXCEPTION_DETAIL + ... + parse_source.CCPPError: No standard name to convert to long name + >>> standard_name_to_long_name({'local_name':'foo'}, context=ParseContext(linenum=3, filename='foo.F90')) #doctest: +ELLIPSIS Traceback (most recent call last): - CCPPError: No standard name to convert foo to long name at foo.F90:3 - >>> standard_name_to_long_name({}, context=ParseContext(linenum=3, filename='foo.F90')) #doctest: +IGNORE_EXCEPTION_DETAIL + ... + parse_source.CCPPError: No standard name to convert foo to long name, at foo.F90:4 + >>> standard_name_to_long_name({}, context=ParseContext(linenum=3, filename='foo.F90')) #doctest: +ELLIPSIS Traceback (most recent call last): - CCPPError: No standard name to convert to long name at foo.F90:3 + ... + parse_source.CCPPError: No standard name to convert to long name, at foo.F90:4 """ # We assume that standar_name has been checked for validity # Make the first char uppercase and replace each underscore with a space @@ -191,18 +195,22 @@ def default_kind_val(prop_dict, context=None): '' >>> default_kind_val({'type':'logical'}) '' - >>> default_kind_val({'local_name':'foo'}) #doctest: +IGNORE_EXCEPTION_DETAIL + >>> default_kind_val({'local_name':'foo'}) #doctest: +ELLIPSIS Traceback (most recent call last): - CCPPError: No type to find default kind for foo - >>> default_kind_val({}) #doctest: +IGNORE_EXCEPTION_DETAIL + ... + parse_source.CCPPError: No type to find default kind for foo + >>> default_kind_val({}) #doctest: +ELLIPSIS Traceback (most recent call last): - CCPPError: No type to find default kind - >>> default_kind_val({'local_name':'foo'}, context=ParseContext(linenum=3, filename='foo.F90')) #doctest: +IGNORE_EXCEPTION_DETAIL + ... + parse_source.CCPPError: No type to find default kind + >>> default_kind_val({'local_name':'foo'}, context=ParseContext(linenum=3, filename='foo.F90')) #doctest: +ELLIPSIS Traceback (most recent call last): - CCPPError: No type to find default kind for foo at foo.F90:3 - >>> default_kind_val({}, context=ParseContext(linenum=3, filename='foo.F90')) #doctest: +IGNORE_EXCEPTION_DETAIL + ... + parse_source.CCPPError: No type to find default kind for foo, at foo.F90:4 + >>> default_kind_val({}, context=ParseContext(linenum=3, filename='foo.F90')) #doctest: +ELLIPSIS Traceback (most recent call last): - CCPPError: No type to find default kind at foo.F90:3 + ... + parse_source.CCPPError: No type to find default kind, at foo.F90:4 """ if 'type' in prop_dict: vtype = prop_dict['type'].lower() @@ -277,29 +285,34 @@ def __init__(self, forward_permutation, reverse_permutation, # Test that bad inputs are trapped: >>> DimTransform((0, 1, 2), (2, 1), 'horizontal_dimension', 0, 1, \ 'horizontal_dimension', \ - 1, 0) #doctest: +IGNORE_EXCEPTION_DETAIL + 1, 0) #doctest: +ELLIPSIS Traceback (most recent call last): + ... parse_source.ParseInternalError: Permutation mismatch, '(0, 1, 2)' and '(2, 1)' - >>> DimTransform((2, 0, 1), (1, 2, 0), 'horizontal_dimension', 3, 4, \ + >>> DimTransform((2, 0, 1), (1, 2, 0), 'horizontal_dimension', 3, 2, \ 'horizontal_dimension', \ - 4, 3) #doctest: +IGNORE_EXCEPTION_DETAIL + 4, 3) #doctest: +ELLIPSIS Traceback (most recent call last): + ... parse_source.ParseInternalError: forward_hdim_index (3) out of range [0, 2] >>> DimTransform((2, 0, 1), (1, 2, 0), 'horizontal_dimension', 0, 4, \ 'horizontal_dimension', \ - 4, 3) #doctest: +IGNORE_EXCEPTION_DETAIL + 4, 3) #doctest: +ELLIPSIS Traceback (most recent call last): - parse_source.ParseInternalError: forward_vdim_index (4) out of range [0, 2 + ... + parse_source.ParseInternalError: forward_vdim_index (4) out of range [0, 2] >>> DimTransform((2, 0, 1), (1, 2, 0), 'horizontal_dimension', 0, 2, \ 'horizontal_dimension', \ - 4, 3) #doctest: +IGNORE_EXCEPTION_DETAIL + 4, 3) #doctest: +ELLIPSIS Traceback (most recent call last): + ... parse_source.ParseInternalError: reverse_hdim_index (4) out of range [0, 2] - >>> DimTransform((2, 0, 1), (1, 2, 0), 'horizontal_dimension', 3, 4, \ + >>> DimTransform((2, 0, 1), (1, 2, 0), 'horizontal_dimension', 1, 2, \ 'horizontal_dimension', \ - 0, 3) #doctest: +IGNORE_EXCEPTION_DETAIL + 0, 3) #doctest: +ELLIPSIS Traceback (most recent call last): - parse_source.ParseInternalError: forward_hdim_index (3) out of range [0, 2] + ... + parse_source.ParseInternalError: reverse_vdim_index (3) out of range [0, 2] """ # Store inputs if len(forward_permutation) != len(reverse_permutation): @@ -523,7 +536,7 @@ class VariableProperty: <__main__.VariableProperty object at ...> >>> VariableProperty('local_name', str).name 'local_name' - >>> VariableProperty('standard_name', str).type == str + >>> VariableProperty('standard_name', str).ptype == str True >>> VariableProperty('units', str).is_match('units') True @@ -535,16 +548,18 @@ class VariableProperty: 2 >>> VariableProperty('value', int, valid_values_in=[1, 2 ]).valid_value('3') - >>> VariableProperty('value', int, valid_values_in=[1, 2 ]).valid_value('3', error=True) #doctest: +IGNORE_EXCEPTION_DETAIL + >>> VariableProperty('value', int, valid_values_in=[1, 2 ]).valid_value('3', error=True) #doctest: +ELLIPSIS Traceback (most recent call last): - CCPPError: Invalid value variable property, '3' + ... + parse_source.CCPPError: Invalid value variable property, '3' >>> VariableProperty('units', str, check_fn_in=check_units).valid_value('m s-1') 'm s-1' >>> VariableProperty('units', str, check_fn_in=check_units).valid_value(' ') - >>> VariableProperty('units', str, check_fn_in=check_units).valid_value(' ', error=True) #doctest: +IGNORE_EXCEPTION_DETAIL + >>> VariableProperty('units', str, check_fn_in=check_units).valid_value(' ', error=True) #doctest: +ELLIPSIS Traceback (most recent call last): - CCPPError: ' ' is not a valid unit + ... + parse_source.CCPPError: ' ' is not a valid unit >>> VariableProperty('dimensions', list, check_fn_in=check_dimensions).valid_value('()') [] >>> VariableProperty('dimensions', list, check_fn_in=check_dimensions).valid_value('(x)') @@ -557,12 +572,14 @@ class VariableProperty: ['w:x', 'y:z'] >>> VariableProperty('dimensions', list, check_fn_in=check_dimensions).valid_value(['size(foo)']) ['size(foo)'] - >>> VariableProperty('dimensions', list, check_fn_in=check_dimensions).valid_value('(w:x,x:y:z:q)', error=True) #doctest: +IGNORE_EXCEPTION_DETAIL + >>> VariableProperty('dimensions', list, check_fn_in=check_dimensions).valid_value('(w:x,x:y:z:q)', error=True) #doctest: +ELLIPSIS Traceback (most recent call last): - CCPPError: 'x:y:z:q' is an invalid dimension range - >>> VariableProperty('dimensions', list, check_fn_in=check_dimensions).valid_value('(x:3y)', error=True) #doctest: +IGNORE_EXCEPTION_DETAIL + ... + parse_source.CCPPError: 'x:y:z:q' is an invalid dimension range + >>> VariableProperty('dimensions', list, check_fn_in=check_dimensions).valid_value('(x:3y)', error=True) #doctest: +ELLIPSIS Traceback (most recent call last): - CCPPError: '3y' is not a valid Fortran identifier + ... + parse_source.CCPPError: '3y' is not a valid Fortran identifier >>> VariableProperty('local_name', str, check_fn_in=check_local_name).valid_value('foo') 'foo' >>> VariableProperty('local_name', str, check_fn_in=check_local_name).valid_value('foo(bar)') @@ -612,7 +629,7 @@ def name(self): return self._name @property - def type(self): + def ptype(self): """Return the type of the property""" return self._type @@ -659,7 +676,7 @@ def valid_value(self, test_value, prop_dict=None, error=False): If is not None, it may be used in value validation. """ valid_val = None - if self.type is int: + if self.ptype is int: try: tval = int(test_value) if self._valid_values is not None: @@ -671,7 +688,7 @@ 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.type is list: + elif self.ptype is list: if isinstance(test_value, str): tval = fortran_list_match(test_value) if tval and (len(tval) == 1) and (not tval[0]): @@ -698,7 +715,7 @@ def valid_value(self, test_value, prop_dict=None, error=False): # end for else: pass - elif self.type is bool: + elif self.ptype is bool: if isinstance(test_value, str): if test_value.lower() in VariableProperty.__true_vals + VariableProperty.__false_vals: valid_val = test_value.lower() in VariableProperty.__true_vals @@ -707,7 +724,7 @@ def valid_value(self, test_value, prop_dict=None, error=False): # end if else: valid_val = not not test_value # pylint: disable=unneeded-not - elif self.type is str: + elif self.ptype is str: if isinstance(test_value, str): if self._valid_values is not None: if test_value in self._valid_values: @@ -1037,9 +1054,16 @@ def _get_unit_convstrs(self, var1_units, var2_units): >>> _DOCTEST_VCOMPAT._get_unit_convstrs('C', 'K') ('{var}+273.15{kind}', '{var}-273.15{kind}') + # Try an invalid conversion + >>> _DOCTEST_VCOMPAT._get_unit_convstrs('1', 'none') #doctest: +ELLIPSIS + Traceback (most recent call last): + ... + parse_source.ParseSyntaxError: Unsupported units entry for var_stdname, '1', at foo.F90:4 + # Try an unsupported conversion - >>> _DOCTEST_VCOMPAT._get_unit_convstrs('C', 'm') #doctest: +IGNORE_EXCEPTION_DETAIL + >>> _DOCTEST_VCOMPAT._get_unit_convstrs('C', 'm') #doctest: +ELLIPSIS Traceback (most recent call last): + ... parse_source.ParseSyntaxError: Unsupported unit conversion, 'C' to 'm' for 'var_stdname' """ u1_str = self.units_to_string(var1_units, self.__v1_context) @@ -1183,8 +1207,7 @@ def char_kind_check(kind_str): # end if (no else, kind_ok already False) return kind_ok - @staticmethod - def units_to_string(units, context=None): + def units_to_string(self, units, context=None): """Replace variable unit description with string that is a legal Python identifier. If the resulting string is a Python keyword, raise an exception.""" @@ -1196,9 +1219,9 @@ def units_to_string(units, context=None): string = string.replace("+","_plus_") # Test that the resulting string is a valid Python identifier if not string.isidentifier(): - emsg = "Unsupported units entry, '{}'{}" + emsg = "Unsupported units entry for {}, '{}'{}" ctx = context_string(context) - raise ParseSyntaxError(emsg.format(units ,ctx)) + raise ParseSyntaxError(emsg.format(self.__stdname, units ,ctx)) # end if # Test that the resulting string is NOT a Python keyword if keyword.iskeyword(string): @@ -1311,10 +1334,15 @@ def __bool__(self): kind_types=["kind_phys=REAL64", "kind_dyn=REAL32", "kind_host=REAL64"]) + _DOCTEST_CONTEXT1 = ParseContext(linenum=3, filename='foo.F90') + _DOCTEST_CONTEXT2 = ParseContext(linenum=5, filename='bar.F90') _DOCTEST_VCOMPAT = VarCompatObj("var_stdname", "real", "kind_phys", "m", [], "var1_lname", "var_stdname", "real", "kind_phys", "m", [], - "var2_lname", _DOCTEST_RUNENV) - fail, _ = doctest.testmod() + "var2_lname", _DOCTEST_RUNENV, + v1_context=_DOCTEST_CONTEXT1, + v2_context=_DOCTEST_CONTEXT2) + OPTIONS = doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE + fail, _ = doctest.testmod(optionflags=OPTIONS) sys.exit(fail) # end if diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index 419af297..d3ca323e 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -10,64 +10,119 @@ 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 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_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 :: 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 ! Copy method (be sure to update this anytime fields are added) procedure :: copyConstituent generic :: assignment(=) => copyConstituent - ! Methods that change state + ! Methods that change state (XXgoldyXX: make private?) procedure :: initialize => ccp_initialize 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 + ! 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. ! 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 @@ -87,22 +142,28 @@ module ccpp_constituent_prop_mod procedure :: reset => ccp_model_const_reset ! Query number of constituents matching pattern procedure :: num_constituents => ccp_model_const_num_match - ! 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 + ! Gather constituent fields matching pattern + procedure :: copy_in => ccp_model_const_copy_in_3d + ! Update constituent fields matching pattern + procedure :: copy_out => ccp_model_const_copy_out_3d + ! 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 +182,90 @@ 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 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 +275,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) @@ -184,28 +332,17 @@ logical function ccp_is_initialized(this, errcode, errmsg) 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 + call initialize_errvars(errcode, errmsg) + if (.not. ccp_is_initialized) 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 !####################################################################### - subroutine ccp_initialize(this, std_name, long_name, vertical_dim, & + subroutine ccp_initialize(this, std_name, long_name, units, vertical_dim, & advected, errcode, errmsg) ! Initialize all fields in @@ -213,6 +350,7 @@ subroutine ccp_initialize(this, std_name, long_name, vertical_dim, & 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 integer, intent(out) :: errcode @@ -231,6 +369,7 @@ 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 @@ -238,6 +377,28 @@ subroutine ccp_initialize(this, std_name, long_name, vertical_dim, & this%advected = .false. 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 @@ -260,8 +421,10 @@ 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 end subroutine ccp_deallocate @@ -278,7 +441,10 @@ subroutine ccp_get_standard_name(this, std_name, errcode, errmsg) if (this%is_initialized(errcode, errmsg)) then std_name = this%var_std_name + else + std_name = '' end if + end subroutine ccp_get_standard_name !####################################################################### @@ -294,7 +460,10 @@ subroutine ccp_get_long_name(this, long_name, errcode, errmsg) if (this%is_initialized(errcode, errmsg)) then long_name = this%var_long_name + else + long_name = '' end if + end subroutine ccp_get_long_name !####################################################################### @@ -310,7 +479,10 @@ subroutine ccp_get_vertical_dimension(this, vert_dim, errcode, errmsg) if (this%is_initialized(errcode, errmsg)) then vert_dim = this%vert_dim + else + vert_dim = '' end if + end subroutine ccp_get_vertical_dimension !####################################################################### @@ -321,7 +493,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 +508,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 +523,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,7 +533,7 @@ 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 @@ -370,24 +542,11 @@ integer function ccp_const_index(this, errcode, errmsg) if (this%is_initialized(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 !####################################################################### @@ -402,16 +561,12 @@ subroutine ccp_set_const_index(this, index, errcode, errmsg) character(len=*), optional, intent(out) :: errmsg if (this%is_initialized(errcode, errmsg)) then - if (this%const_ind /= int_unassigned) 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,53 +574,29 @@ 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 + 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 @@ -479,7 +610,130 @@ logical function ccp_is_equivalent(this, oconst, & equiv = .false. end if - end function ccp_is_equivalent + end subroutine ccp_is_equivalent + + !######################################################################## + + subroutine ccp_is_mass_mixing_ratio(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_initialized(errcode, errmsg)) then + val_out = this%const_type == mass_mixing_ratio + else + val_out = .false. + end if + end subroutine ccp_is_mass_mixing_ratio + + !######################################################################## + + subroutine ccp_is_volume_mixing_ratio(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_initialized(errcode, errmsg)) then + val_out = this%const_type == volume_mixing_ratio + else + val_out = .false. + end if + end subroutine ccp_is_volume_mixing_ratio + + !######################################################################## + + 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_initialized(errcode, errmsg)) then + val_out = this%const_type == number_concentration + else + val_out = .false. + end if + end subroutine ccp_is_number_concentration + + !######################################################################## + + subroutine ccp_is_dry(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_initialized(errcode, errmsg)) then + val_out = this%const_water == dry_mixing_ratio + else + val_out = .false. + end if + + end subroutine ccp_is_dry + + !######################################################################## + + subroutine ccp_is_moist(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_initialized(errcode, errmsg)) then + val_out = this%const_water == moist_mixing_ratio + else + val_out = .false. + end if + + end subroutine ccp_is_moist + + !######################################################################## + + subroutine ccp_is_wet(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_initialized(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_initialized(errcode, errmsg)) then + val_out = this%min_val + else + val_out = kphys_unassigned + end if + + end subroutine ccp_min_val !######################################################################## ! @@ -499,12 +753,7 @@ logical function ccp_model_const_locked(this, errcode, errmsg, warn_func) ! Local variable character(len=*), parameter :: subname = 'ccp_model_const_locked' - if (present(errcode)) then - errcode = 0 - end if - if (present(errmsg)) then - errmsg = '' - end if + 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 @@ -517,17 +766,14 @@ logical function ccp_model_const_locked(this, errcode, errmsg, warn_func) ' WARNING: Model constituents not ready to use' end if 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 + 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 @@ -538,7 +784,8 @@ end function ccp_model_const_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. + ! Optionally fill out and if the conditions + ! are not met. ! Dummy arguments class(ccpp_model_constituents_t), intent(inout) :: this @@ -553,30 +800,26 @@ logical function ccp_model_const_okay_to_add(this, errcode, errmsg, & 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 - 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' + call set_errvars(1, trim(warn_func), & + errcode=errcode, errmsg=errmsg, & + errmsg2=" WARNING: Model constituents are locked") else - errmsg = subname//' WARNING: Model constituents not initialized' + 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 @@ -592,49 +835,37 @@ subroutine ccp_model_const_add_metadata(this, field_data, errcode, errmsg) integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg ! Local variables - character(len=256) :: error - character(len=*), parameter :: subnam = 'ccp_model_const_add_metadata' + 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=subnam)) then + 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 - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - errmsg = trim(error) - end if + 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 (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), "'" + 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 - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - errmsg = 'ERROR: Model contituents are locked' - end if + call set_errvars(1, subname, & + errcode=errcode, errmsg=errmsg, & + errmsg2=" WARNING: Model constituents are locked") end if end subroutine ccp_model_const_add_metadata @@ -651,21 +882,7 @@ subroutine ccp_model_const_initialize(this, num_elements) integer :: tbl_size ! 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) - end if + 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)) + & @@ -678,7 +895,7 @@ end subroutine ccp_model_const_initialize !######################################################################## - function ccp_model_const_find_const(this, standard_name, errcode, errmsg) & + 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 @@ -693,30 +910,22 @@ function ccp_model_const_find_const(this, standard_name, errcode, 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,119 +933,124 @@ end function ccp_model_const_find_const !######################################################################## - subroutine ccp_model_const_lock(this, ncols, num_layers, num_interfaces, & - errcode, errmsg) + subroutine ccp_model_const_lock(this, ncols, num_layers, errcode, errmsg) ! Freeze hash table and initialize constituent field arrays ! 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=dimname_len) :: dimname character(len=*), parameter :: subname = 'ccp_model_const_lock' + astat = 0 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 + call set_errvars(1, subname, errcode=errcode, errmsg=errmsg, & + errmsg2=" WARNING: Model constituents 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 + call cprop%is_advected(check) + if (check) then + this%num_advected_vars = this%num_advected_vars + 1 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' - end if - exit + 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 + 1 + ! 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() else @@ -845,63 +1059,32 @@ 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 + 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 ! Everything looks okay, allocate field arrays - allocate(this%vars_layer(ncols, num_layers, index_layer), & + allocate(this%vars_layer(ncols, num_layers, index_const), & 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', & + allocate(this%vars_minvalue(index_const), stat=astat) + call handle_allocate_error(astat, 'vars_minvalue', & errcode=errcode, errmsg=errmsg) end if if (astat == 0) then - this%vars_2d = kphys_unassigned + this%num_layers = num_layers + this%vars_layer = kphys_unassigned + this%vars_minvalue = 0.0_kind_phys end if if (present(errcode)) then if (errcode /= 0) then @@ -918,25 +1101,42 @@ end subroutine ccp_model_const_lock !######################################################################## - subroutine ccp_model_const_reset(this) + 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 argument + ! 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_interface)) then - deallocate(this%vars_interface) - end if - if (allocated(this%vars_2d)) then - deallocate(this%vars_2d) + 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 - call this%hash_table%clear() + 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 @@ -954,11 +1154,14 @@ 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 + ! Local variable + logical :: check ! By default, every constituent is a match is_match = .true. if (present(advected)) then - if (advected .neqv. this%const_metadata(index)%is_advected()) then + call this%const_metadata(index)%is_advected(check) + if (advected .neqv. check) then is_match = .false. end if end if @@ -967,8 +1170,7 @@ end function ccp_model_const_is_match !######################################################################## - integer function ccp_model_const_num_match(this, advected, & - errcode, errmsg) result(nmatch) + 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. @@ -976,6 +1178,7 @@ integer function ccp_model_const_num_match(this, advected, & ! 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 @@ -992,7 +1195,62 @@ integer function ccp_model_const_num_match(this, advected, & end do end if - end function ccp_model_const_num_match + 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%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%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 !######################################################################## @@ -1015,7 +1273,7 @@ subroutine ccp_model_const_copy_in_3d(this, const_array, advected, & 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=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 @@ -1027,58 +1285,41 @@ 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 - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - write(errmsg, *) subname, & - ": Too many constituents for " - end if + call set_errvars(1, subname, & + errcode=errcode, errmsg=errmsg, & + errmsg2=": Too many constituents for ") 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 + 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 - 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 + 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 @@ -1107,7 +1348,7 @@ subroutine ccp_model_const_copy_out_3d(this, const_array, advected, & 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=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 @@ -1119,58 +1360,41 @@ 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 - if (present(errcode)) then - errcode = 1 - end if - if (present(errmsg)) then - write(errmsg, *) subname, & - ": Too many constituents for " - end if + 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 - 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 + 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 - 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 + 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 @@ -1180,85 +1404,483 @@ end subroutine ccp_model_const_copy_out_3d !######################################################################## - integer function ccp_model_const_index(this, standard_name, errcode, errmsg) - ! Return index of metadata matching . - ! must be locked to execute this function + 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), intent(in) :: this - character(len=*), intent(in) :: standard_name - integer, optional, intent(out) :: errcode - character(len=*), optional, intent(out) :: errmsg + class(ccpp_model_constituents_t), target, intent(inout) :: this + real(kind_phys), pointer :: const_ptr(:,:,:) ! Local variables - type(ccpp_constituent_properties_t), pointer :: cprop - character(len=*), parameter :: subname = "ccp_model_const_index" + 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 - 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 + const_ptr => this%vars_layer else - ccp_model_const_index = int_unassigned + ! 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_model_const_index + end function ccp_field_data_ptr !######################################################################## - integer function ccp_model_const_field_index(this, standard_name, & - errcode, errmsg) - ! Return index of field matching . - ! must be locked to execute this function + 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), intent(in) :: this - character(len=*), intent(in) :: standard_name - integer, optional, intent(out) :: errcode - character(len=*), optional, intent(out) :: errmsg + class(ccpp_model_constituents_t), target, intent(inout) :: this + real(kind_phys), pointer :: const_ptr(:,:,:) ! Local variables - type(ccpp_constituent_properties_t), pointer :: cprop - character(len=*), parameter :: subname = "ccp_model_field_index" + 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 - 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 + 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%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_initialized(errcode, errmsg)) then + vert_dim = this%prop%vert_dim end if else - ccp_model_const_field_index = int_unassigned + 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 function ccp_model_const_field_index + end subroutine ccpt_is_advected !######################################################################## - 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_is_mass_mixing_ratio(this, val_out, errcode, errmsg) ! 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 + 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 ccpt_is_number_concentration(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_number_concentration' + + 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 + + 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 + + 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 + + end subroutine ccpt_is_moist + + !######################################################################## + + subroutine ccpt_is_wet(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_wet' + + 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 subroutine ccpt_is_wet + + !######################################################################## + + subroutine ccpt_min_val(this, val_out, errcode, errmsg) + + ! 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_min_val' + + 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 subroutine ccpt_min_val + + !######################################################################## + + 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_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_const_metadata" + 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 + this%prop => const_ptr + end if - 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 + end subroutine ccpt_set + + !######################################################################## + + 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_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 (associated(this%prop)) then + if (this%prop%is_initialized(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..9da89cbf 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 () @@ -185,52 +201,6 @@ 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: twist_array @@ -248,17 +218,21 @@ 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_number_constituents + use test_host_ccpp_cap, only: test_host_advected_constituents 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,125 @@ 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)%initialize(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(:), & + ncols, pver, 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 our 'data' + if (errflg == 0) then + const_ptr => test_host_advected_constituents() + 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 +417,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 +441,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 +460,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 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..f553953b 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 diff --git a/test/advection_test/test_reports.py b/test/advection_test/test_reports.py index c28fe38a..49bbce7f 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] < 6)): + raise Exception("Python 3.6 or greater required") # end if sys.path.append(_SCRIPTS_DIR) 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/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..3b1b15ba --- /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, ccpp_num_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..78031b2a --- /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, ccpp_num_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..7412daf3 --- /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, ccpp_num_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..49256b2e --- /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, ccpp_num_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..c3b14874 --- /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, ccpp_num_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_fortran_write.py b/test/unit_tests/test_fortran_write.py new file mode 100644 index 00000000..fdc00085 --- /dev/null +++ b/test/unit_tests/test_fortran_write.py @@ -0,0 +1,126 @@ +#! /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_host_file.py b/test/unit_tests/test_metadata_host_file.py new file mode 100644 index 00000000..cc5ba7b5 --- /dev/null +++ b/test/unit_tests/test_metadata_host_file.py @@ -0,0 +1,260 @@ +#! /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() diff --git a/test/unit_tests/test_metadata_scheme_file.py b/test/unit_tests/test_metadata_scheme_file.py index 52f62aa1..9598406d 100644 --- a/test/unit_tests/test_metadata_scheme_file.py +++ b/test/unit_tests/test_metadata_scheme_file.py @@ -40,7 +40,7 @@ Command line arguments: none - Usage: python test_metadata_scheme_file.py # run the unit tests + Usage: python3 test_metadata_scheme_file.py # run the unit tests ----------------------------------------------------------------------- """ import sys @@ -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,262 @@ 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__': +if __name__ == "__main__": unittest.main() diff --git a/test/unit_tests/test_metadata_table.py b/test/unit_tests/test_metadata_table.py index d9791879..544d3013 100755 --- a/test/unit_tests/test_metadata_table.py +++ b/test/unit_tests/test_metadata_table.py @@ -8,7 +8,7 @@ Command line arguments: none - Usage: python test_metadata_table.py # run the unit tests + Usage: python3 test_metadata_table.py # run the unit tests ----------------------------------------------------------------------- """ import sys From bce4d22bb6a0403a6a5dbc2c10820390ec1c996a Mon Sep 17 00:00:00 2001 From: Steve Goldhaber Date: Fri, 12 Aug 2022 14:20:57 -0600 Subject: [PATCH 002/159] Avoid RE deprecation warning by moving case fold to state_machine.py --- scripts/ccpp_state_machine.py | 10 +++++----- scripts/state_machine.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/ccpp_state_machine.py b/scripts/ccpp_state_machine.py index 30540fc9..0de2c7bd 100644 --- a/scripts/ccpp_state_machine.py +++ b/scripts/ccpp_state_machine.py @@ -3,11 +3,11 @@ # CCPP framework imports from state_machine import StateMachine -_INIT_ST = r"(?:(?i)init(?:ial(?:ize)?)?)" -_FINAL_ST = r"(?:(?i)final(?:ize)?)" -_RUN_ST = r"(?:(?i)run)" -_TS_INIT_ST = r"(?:(?i)timestep_init(?:ial(?:ize)?)?)" -_TS_FINAL_ST = r"(?:(?i)timestep_final(?:ize)?)" +_INIT_ST = r"(?:init(?:ial(?:ize)?)?)" +_FINAL_ST = r"(?:final(?:ize)?)" +_RUN_ST = r"(?:run)" +_TS_INIT_ST = r"(?:timestep_init(?:ial(?:ize)?)?)" +_TS_FINAL_ST = r"(?:timestep_final(?:ize)?)" # Allowed CCPP transitions # pylint: disable=bad-whitespace diff --git a/scripts/state_machine.py b/scripts/state_machine.py index 966ad04f..902b4d4d 100644 --- a/scripts/state_machine.py +++ b/scripts/state_machine.py @@ -162,8 +162,8 @@ def __setitem__(self, key, value): if len(value) != 3: raise ValueError("Invalid transition ({}), should be of the form (inital_state, final_state, regex).".format(value)) # end if - regex = re.compile(value[2] + r"$") - function = re.compile(FORTRAN_ID + r"_(" + value[2] + r")$") + regex = re.compile(r"(?i)" + value[2] + r"$") + function = re.compile("(?i)" + FORTRAN_ID + r"_(" + value[2] + r")$") self.__stt__[key] = (value[0], value[1], regex, function) def __delitem__(self, key): From 279c1809910f06ce25e2f922da56517632ae1407 Mon Sep 17 00:00:00 2001 From: Steve Goldhaber Date: Fri, 12 Aug 2022 14:28:01 -0600 Subject: [PATCH 003/159] Fix doctest --- scripts/state_machine.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/state_machine.py b/scripts/state_machine.py index 902b4d4d..692da7a9 100644 --- a/scripts/state_machine.py +++ b/scripts/state_machine.py @@ -30,7 +30,7 @@ class StateMachine: >>> StateMachine([('ab','a','b','a')]).final_state('ab') 'b' >>> StateMachine([('ab','a','b','a')]).transition_regex('ab') - re.compile('a$') + re.compile('a$', re.IGNORECASE) >>> StateMachine([('ab','a','b','a')]).function_match('foo_a', transition='ab') ('foo', 'a', 'ab') >>> StateMachine([('ab','a','b',r'ax?')]).function_match('foo_a', transition='ab') @@ -162,8 +162,9 @@ def __setitem__(self, key, value): if len(value) != 3: raise ValueError("Invalid transition ({}), should be of the form (inital_state, final_state, regex).".format(value)) # end if - regex = re.compile(r"(?i)" + value[2] + r"$") - function = re.compile("(?i)" + FORTRAN_ID + r"_(" + value[2] + r")$") + regex = re.compile(value[2] + r"$", re.IGNORECASE) + function = re.compile(FORTRAN_ID + r"_(" + value[2] + r")$", + re.IGNORECASE) self.__stt__[key] = (value[0], value[1], regex, function) def __delitem__(self, key): From 057588e61a2cfb2d33fffa9d24253647621a76ac Mon Sep 17 00:00:00 2001 From: Steve Goldhaber Date: Mon, 12 Dec 2022 16:33:01 +0100 Subject: [PATCH 004/159] Allow '1' to be a valid unit --- scripts/var_props.py | 4 ++++ test/unit_tests/test_var_transforms.py | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/scripts/var_props.py b/scripts/var_props.py index a8a5e29d..0bb51eaf 100755 --- a/scripts/var_props.py +++ b/scripts/var_props.py @@ -1217,6 +1217,10 @@ def units_to_string(self, units, context=None): string = string.replace("-","_minus_") # Replace each plus sign with '_plus_' string = string.replace("+","_plus_") + # "1" is a valid unit + if string == "1": + string = "one" + # end if # Test that the resulting string is a valid Python identifier if not string.isidentifier(): emsg = "Unsupported units entry for {}, '{}'{}" diff --git a/test/unit_tests/test_var_transforms.py b/test/unit_tests/test_var_transforms.py index 5178ce9b..70f31d1e 100644 --- a/test/unit_tests/test_var_transforms.py +++ b/test/unit_tests/test_var_transforms.py @@ -163,12 +163,24 @@ def test_unsupported_unit_change(self): 'real', vkind='kind_phys') real_scalar2 = self._new_var('real_stdname1', 'd', [], 'real', vkind='kind_phys') + char_nounit1 = self._new_var('char_stdname1', 'none', [], + 'character', vkind='len=256') + char_nounit2 = self._new_var('char_stdname1', '1', [], + 'character', vkind='len=256') with self.assertRaises(ParseSyntaxError) as context: compat = real_scalar1.compatible(real_scalar2, self.__run_env) # end with + #Test bad conversion for real time variables #Verify correct error message returned emsg = "Unsupported unit conversion, 'min' to 'd' for 'real_stdname1'" self.assertTrue(emsg in str(context.exception)) + #Test bad conversion for unitless variables + with self.assertRaises(ParseSyntaxError) as context: + compat = char_nounit1.compatible(char_nounit2, self.__run_env) + # end with + #Verify correct error message returned + emsg = "Unsupported unit conversion, 'none' to '1' for 'char_stdname1'" + self.assertTrue(emsg in str(context.exception)) def test_valid_kind_change(self): """Test that valid kind changes are detected""" From 678621e3fcce4c58bf2df94db006b1a4342ece33 Mon Sep 17 00:00:00 2001 From: Steve Goldhaber Date: Mon, 2 Jan 2023 21:36:09 +0100 Subject: [PATCH 005/159] Added molar_mass (molecular weight) value to constituent property type --- src/ccpp_constituent_prop_mod.F90 | 44 +++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index d3ca323e..2043659f 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -43,6 +43,8 @@ module ccpp_constituent_prop_mod integer, private :: const_water = int_unassigned ! minimum_mr is the minimum allowed value (default zero) real(kind_phys), private :: min_val = 0.0_kind_phys + ! molar_mass is the molecular weight of the constituent (g mol-1) + real(kind_phys), private :: molar_mass = kphys_unassigned contains ! Required hashable method procedure :: key => ccp_properties_get_key @@ -64,6 +66,7 @@ module ccpp_constituent_prop_mod procedure :: is_moist => ccp_is_moist procedure :: is_wet => ccp_is_wet procedure :: minimum => ccp_min_val + procedure :: molec_weight => ccp_molec_weight ! Copy method (be sure to update this anytime fields are added) procedure :: copyConstituent generic :: assignment(=) => copyConstituent @@ -95,6 +98,7 @@ module ccpp_constituent_prop_mod procedure :: is_moist => ccpt_is_moist procedure :: is_wet => ccpt_is_wet procedure :: minimum => ccpt_min_val + procedure :: molec_weight => ccpt_molec_weight ! ccpt_set: Set the internal pointer procedure :: set => ccpt_set ! Methods that change state (XXgoldyXX: make private?) @@ -735,6 +739,24 @@ subroutine ccp_min_val(this, val_out, errcode, errmsg) 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_initialized(errcode, errmsg)) then + val_out = this%molar_mass + else + val_out = kphys_unassigned + end if + + end subroutine ccp_molec_weight + !######################################################################## ! ! CCPP_MODEL_CONSTITUENTS_T (constituent field data) methods @@ -1807,6 +1829,28 @@ end subroutine ccpt_min_val !######################################################################## + subroutine ccpt_molec_weight(this, val_out, errcode, errmsg) + + ! 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_molec_weight' + + 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 ccpt_molec_weight + + !######################################################################## + subroutine ccpt_set(this, const_ptr, errcode, errmsg) ! Set the pointer to , however, an error is recorded if ! the pointer is already set. From 27f68518a51c3fbf02bc3821c3a538e376a8de73 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Wed, 4 Jan 2023 23:01:57 -0700 Subject: [PATCH 006/159] initial changes --- scripts/ccpp_datafile.py | 2 +- scripts/metavar.py | 5 +++ scripts/parse_tools/parse_checkers.py | 44 +++++++++++++++++++++++++++ scripts/var_props.py | 2 +- 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/scripts/ccpp_datafile.py b/scripts/ccpp_datafile.py index 23db0887..d197eb1b 100755 --- a/scripts/ccpp_datafile.py +++ b/scripts/ccpp_datafile.py @@ -657,7 +657,7 @@ 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"]) prop_list.extend(Var.constituent_property_names()) # end if ventry = ET.SubElement(parent, "var") diff --git a/scripts/metavar.py b/scripts/metavar.py index 7aa12dab..7dda7354 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, type_name from parse_tools import ParseInternalError, ParseSyntaxError, CCPPError from var_props import CCPP_LOOP_DIM_SUBSTS, VariableProperty, VarCompatObj @@ -199,6 +200,9 @@ class Var: default_in='.false.'), VariableProperty('target', bool, optional_in=True, default_in='.false.')] + VariableProperty('molar_mass', float, + optional_in=True, default_in=0.0, + check_fn_in=check_molar_mass) # XXgoldyXX: v debug only __to_add = VariableProperty('valid_values', str, @@ -1060,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) diff --git a/scripts/parse_tools/parse_checkers.py b/scripts/parse_tools/parse_checkers.py index ca5f1f51..74140006 100755 --- a/scripts/parse_tools/parse_checkers.py +++ b/scripts/parse_tools/parse_checkers.py @@ -936,6 +936,50 @@ 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 + >>> 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('-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 + """ + if isinstance(test_val, float) or isinstance(test_val, int): + if test_val < 0.0: + if error: + raise CCPPError("{} is not a valid molar mass".format(test_val)) + else: + test_val = None + # end if + # end if + else: + if error: + raise CCPPError("{} is invalid; not a float or int".format(test_val)) + else: + test_val = None + # end if + # end if + 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/var_props.py b/scripts/var_props.py index 0bb51eaf..5beb654f 100755 --- a/scripts/var_props.py +++ b/scripts/var_props.py @@ -597,7 +597,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 From ca653683e3b17499901a7e7ee76311eddeb58962 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Fri, 6 Jan 2023 15:31:53 -0700 Subject: [PATCH 007/159] fixes --- scripts/metavar.py | 4 +-- scripts/parse_tools/__init__.py | 2 +- scripts/parse_tools/parse_checkers.py | 35 +++++++++++++++------------ scripts/var_props.py | 12 +++++++++ 4 files changed, 35 insertions(+), 18 deletions(-) diff --git a/scripts/metavar.py b/scripts/metavar.py index 7dda7354..80110189 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -199,10 +199,10 @@ class Var: VariableProperty('polymorphic', bool, optional_in=True, default_in='.false.'), VariableProperty('target', bool, optional_in=True, - default_in='.false.')] + default_in='.false.'), VariableProperty('molar_mass', float, optional_in=True, default_in=0.0, - check_fn_in=check_molar_mass) + check_fn_in=check_molar_mass)] # XXgoldyXX: v debug only __to_add = VariableProperty('valid_values', str, diff --git a/scripts/parse_tools/__init__.py b/scripts/parse_tools/__init__.py index 4f888fb1..689c5f63 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 diff --git a/scripts/parse_tools/parse_checkers.py b/scripts/parse_tools/parse_checkers.py index 74140006..4c2f7546 100755 --- a/scripts/parse_tools/parse_checkers.py +++ b/scripts/parse_tools/parse_checkers.py @@ -939,11 +939,11 @@ 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 - >>> check_molar_mass(1.0, None, True) + >>> check_molar_mass('1', None, True) 1.0 - >>> check_molar_mass(1.0, None, False) + >>> check_molar_mass('1.0', None, True) + 1.0 + >>> check_molar_mass('1.0', None, False) 1.0 >>> check_molar_mass('-1', None, False) @@ -961,19 +961,24 @@ def check_molar_mass(test_val, prop_dict, error): Traceback (most recent call last): CCPPError: '-1.0' is not a valid molar mass """ - if isinstance(test_val, float) or isinstance(test_val, int): - if test_val < 0.0: - if error: - raise CCPPError("{} is not a valid molar mass".format(test_val)) - else: - test_val = None - # end if - # end if - else: + # Check if input value is an int or float + try: + valid = isinstance(float(test_val), float) + except: + # not an int or float, conditionally throw error + if error: + raise CCPPError("{} is invalid; not a float or int".format(test_val)) + else: + return None + # end if + # end try + # we have an int or float, convert to float and see if it's positive + test_val = float(test_val) + if test_val < 0.0: if error: - raise CCPPError("{} is invalid; not a float or int".format(test_val)) + raise CCPPError("{} is not a valid molar mass".format(test_val)) else: - test_val = None + return None # end if # end if return test_val diff --git a/scripts/var_props.py b/scripts/var_props.py index 5beb654f..1c5c0fb6 100755 --- a/scripts/var_props.py +++ b/scripts/var_props.py @@ -688,6 +688,18 @@ 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_value is not Note: + if tval in self._valid_values: + valid_val = tval + else: + valid_val = None # i.e. pass + else: + valid_val = tval + except CCPPError: + valid_val = None elif self.ptype is list: if isinstance(test_value, str): tval = fortran_list_match(test_value) From 10416a957eeba1196b9a8eb3b885bc51334c5def Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 9 Jan 2023 14:17:35 -0700 Subject: [PATCH 008/159] address review comments --- scripts/parse_tools/parse_checkers.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/scripts/parse_tools/parse_checkers.py b/scripts/parse_tools/parse_checkers.py index 4c2f7546..75157b35 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 @@ -951,6 +952,8 @@ def check_molar_mass(test_val, prop_dict, error): >>> 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 @@ -960,27 +963,28 @@ def check_molar_mass(test_val, prop_dict, error): >>> 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: - valid = isinstance(float(test_val), float) + 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: - return None + test_val=None # end if # end try - # we have an int or float, convert to float and see if it's positive - test_val = float(test_val) - if test_val < 0.0: - if error: - raise CCPPError("{} is not a valid molar mass".format(test_val)) - else: - return None - # end if - # end if return test_val ######################################################################## From 5e8e33aa3f006a6ae31e8440c0a540eac0ce39a3 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 9 Jan 2023 14:32:51 -0700 Subject: [PATCH 009/159] hopefully final fixes --- scripts/ccpp_datafile.py | 3 ++- scripts/metavar.py | 10 +++++----- scripts/var_props.py | 7 +++++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/scripts/ccpp_datafile.py b/scripts/ccpp_datafile.py index d197eb1b..31c3bfca 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", "molar_mass"]) + "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/metavar.py b/scripts/metavar.py index 80110189..e2a22e24 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -199,10 +199,7 @@ class Var: VariableProperty('polymorphic', bool, optional_in=True, default_in='.false.'), VariableProperty('target', bool, optional_in=True, - default_in='.false.'), - VariableProperty('molar_mass', float, - optional_in=True, default_in=0.0, - check_fn_in=check_molar_mass)] + default_in='.false.')] # XXgoldyXX: v debug only __to_add = VariableProperty('valid_values', str, @@ -218,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} diff --git a/scripts/var_props.py b/scripts/var_props.py index 1c5c0fb6..208fba89 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.'] @@ -691,7 +694,7 @@ def valid_value(self, test_value, prop_dict=None, error=False): elif self.ptype is float: try: tval = float(test_value) - if self._valid_value is not Note: + if self._valid_values is not None: if tval in self._valid_values: valid_val = tval else: @@ -1070,7 +1073,7 @@ def _get_unit_convstrs(self, var1_units, var2_units): >>> _DOCTEST_VCOMPAT._get_unit_convstrs('1', 'none') #doctest: +ELLIPSIS Traceback (most recent call last): ... - parse_source.ParseSyntaxError: Unsupported units entry for var_stdname, '1', at foo.F90:4 + parse_source.ParseSyntaxError: Unsupported unit conversion, '1' to 'none' for 'var_stdname' # Try an unsupported conversion >>> _DOCTEST_VCOMPAT._get_unit_convstrs('C', 'm') #doctest: +ELLIPSIS From a2386d6b64bad20c7d2a1cbde2629ec94acd1720 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Sun, 5 Feb 2023 13:06:45 -0700 Subject: [PATCH 010/159] initial commit --- scripts/constituents.py | 17 ++++++++++++++++- scripts/host_cap.py | 11 +++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/scripts/constituents.py b/scripts/constituents.py index 34ddf52d..d64929ea 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -452,7 +452,7 @@ def write_constituent_use_statements(cap, suite_list, indent): @staticmethod def write_host_routines(cap, host, reg_funcname, num_const_funcname, copy_in_funcname, copy_out_funcname, const_obj_name, - const_names_name, const_indices_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 @@ -462,6 +462,10 @@ def write_host_routines(cap, host, reg_funcname, num_const_funcname, : 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 @@ -637,6 +641,17 @@ def write_host_routines(cap, host, reg_funcname, num_const_funcname, 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 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}%field_data_ptr()", 2) + cap.write("end function {const_array_func}", 1) # Write advected constituents routine cap.write("", 0) cap.write(f"function {advect_array_func}() result(const_ptr)", 1) diff --git a/scripts/host_cap.py b/scripts/host_cap.py index 001063d3..2d93b473 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -150,6 +150,14 @@ 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_const_indices(host_model): +############################################################################### + """Return the name of the function that will return a pointer to the + array of all constituents""" + hstr = "{}_constituents".format(host_model.name) + return unique_local_name(hstr, host_model) + ############################################################################### def constituent_model_advected_consts(host_model): ############################################################################### @@ -428,6 +436,8 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): 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) @@ -575,6 +585,7 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): copyout_name, const_obj_name, const_names_name, const_indices_name, + const_array_func, advect_array_func, prop_array_func, const_index_func, From 7657f33a01a5b62589ff7241c5bc508b7dc933bb Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Sun, 5 Feb 2023 13:16:59 -0700 Subject: [PATCH 011/159] fix function name --- scripts/host_cap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/host_cap.py b/scripts/host_cap.py index 2d93b473..92d8ce22 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -144,7 +144,7 @@ def constituent_model_const_stdnames(host_model): return unique_local_name(hstr, host_model) ############################################################################### -def constituent_model_const_indices(host_model): +def constituent_model_consts(host_model): ############################################################################### """Return the name of the array of constituent field array indices""" hstr = "{}_model_const_indices".format(host_model.name) From 53155003a7e15aa8ff513fdc509e7900bf16221f Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Sun, 5 Feb 2023 21:26:12 -0700 Subject: [PATCH 012/159] fix fstring --- scripts/constituents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/constituents.py b/scripts/constituents.py index d64929ea..6e2d56d0 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -651,7 +651,7 @@ def write_host_routines(cap, host, reg_funcname, num_const_funcname, 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("end function {const_array_func}", 1) + 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) From 84bade66a6e5f4d1400b9e1de922d2c48b552965 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Sun, 5 Feb 2023 21:50:55 -0700 Subject: [PATCH 013/159] fix mistakes in routine names and comments --- scripts/constituents.py | 2 +- scripts/host_cap.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/constituents.py b/scripts/constituents.py index 6e2d56d0..c5e92301 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -645,7 +645,7 @@ def write_host_routines(cap, host, reg_funcname, num_const_funcname, cap.write("", 0) cap.write(f"function {const_array_func}() result(const_ptr)", 1) cap.write("", 0) - cap.comment("Return pointer to advected constituent array", 2) + 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) diff --git a/scripts/host_cap.py b/scripts/host_cap.py index 92d8ce22..ba82c31d 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -144,14 +144,14 @@ def constituent_model_const_stdnames(host_model): return unique_local_name(hstr, host_model) ############################################################################### -def constituent_model_consts(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) return unique_local_name(hstr, host_model) ############################################################################### -def constituent_model_const_indices(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""" From 349431128d879690fa6b73fcd95351cb3df4221c Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Tue, 28 Feb 2023 17:14:15 -0700 Subject: [PATCH 014/159] split up register and initialize --- scripts/constituents.py | 53 +++++--- scripts/host_cap.py | 23 +++- src/ccpp_constituent_prop_mod.F90 | 201 ++++++++++++++++++++++++------ 3 files changed, 216 insertions(+), 61 deletions(-) diff --git a/scripts/constituents.py b/scripts/constituents.py index c5e92301..bf4cce2d 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -450,7 +450,7 @@ 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, const_array_func, advect_array_func, prop_array_func, @@ -459,6 +459,7 @@ def write_host_routines(cap, host, reg_funcname, num_const_funcname, 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 @@ -485,7 +486,7 @@ def write_host_routines(cap, host, reg_funcname, num_const_funcname, # XXgoldyXX: ^ need to generalize host model error var type support # First up, the registration routine substmt = f"subroutine {reg_funcname}" - args = "suite_list, ncols, num_layers, host_constituents " + 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) @@ -494,8 +495,6 @@ def write_host_routines(cap, host, reg_funcname, num_const_funcname, cap.write("", 0) cap.comment("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(f"type({CONST_PROP_TYPE}), target, intent(in) :: " + \ "host_constituents(:)", 2) for evar in err_vars: @@ -574,25 +573,43 @@ def write_host_routines(cap, host, reg_funcname, num_const_funcname, cap.write("", 0) # end for cap.write(f"if ({herrcode} == 0) then", 2) - stmt = "call {}%lock_table(ncols, num_layers, {})" + stmt = "call {}%lock_table({})" cap.write(stmt.format(const_obj_name, obj_err_callstr), 3) cap.write("end if", 2) - cap.comment("Set the index for each active constituent", 2) - cap.write(f"do index = 1, SIZE({const_indices_name})", 2) + 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(f"{const_indices_name}(index) = field_ind", 4) - cap.write("else", 3) - cap.write(f"{herrcode} = 1", 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(f"if ({herrcode} /= 0) then", 3) - cap.write("exit", 4) - cap.write("end if", 3) - cap.write("end do", 2) + 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}" diff --git a/scripts/host_cap.py b/scripts/host_cap.py index ba82c31d..7485b205 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -78,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): ############################################################################### @@ -430,6 +445,8 @@ def write_host_cap(host_model, api, module_name, 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) @@ -580,7 +597,7 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): cap.write("", 0) const_names_name = constituent_model_const_stdnames(host_model) const_indices_name = constituent_model_const_indices(host_model) - 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, diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index 2043659f..8fd3cca9 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -1,4 +1,5 @@ module ccpp_constituent_prop_mod + use vert_coord, only: pver ! ccpp_contituent_prop_mod contains types and procedures for storing ! and retrieving constituent properties @@ -6,6 +7,7 @@ module ccpp_constituent_prop_mod use ccpp_hashable, only: ccpp_hashable_t, ccpp_hashable_char_t use ccpp_hash_table, only: ccpp_hash_table_t, ccpp_hash_iterator_t use ccpp_kinds, only: kind_phys + use cam_logfile, only: iulog implicit none private @@ -120,6 +122,7 @@ module ccpp_constituent_prop_mod integer, private :: num_layers = 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(:,:,:) @@ -132,16 +135,22 @@ module ccpp_constituent_prop_mod 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 @@ -779,8 +788,8 @@ logical function ccp_model_const_locked(this, errcode, errmsg, warn_func) 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. & + 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). @@ -803,6 +812,84 @@ 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 @@ -819,8 +906,9 @@ logical function ccp_model_const_okay_to_add(this, errcode, 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) + 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), & @@ -955,13 +1043,11 @@ end function ccp_model_const_find_const !######################################################################## - subroutine ccp_model_const_lock(this, ncols, num_layers, 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, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg ! Local variables @@ -974,12 +1060,12 @@ subroutine ccp_model_const_lock(this, ncols, num_layers, errcode, errmsg) class(ccpp_hashable_t), pointer :: hval type(ccpp_constituent_properties_t), pointer :: cprop character(len=dimname_len) :: dimname - character(len=*), parameter :: subname = 'ccp_model_const_lock' + character(len=*), parameter :: subname = 'ccp_model_const_table_lock' astat = 0 - if (this%locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then + 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 constituents already locked, ignoring") + errmsg2=" WARNING: Model constituent properites already locked, ignoring") astat = astat + 1 else ! Make sure everything is really initialized @@ -1020,7 +1106,7 @@ subroutine ccp_model_const_lock(this, ncols, num_layers, errcode, errmsg) end if end if index_advect = 0 - index_const = this%num_advected_vars + 1 + index_const = this%num_advected_vars ! Iterate through the hash table to find entries if (astat == 0) then call hiter%initialize(this%hash_table) @@ -1080,6 +1166,10 @@ subroutine ccp_model_const_lock(this, ncols, num_layers, errcode, errmsg) end if end do ! Some size sanity checks + write(iulog,*) 'PEVERWHEE nums' + write(iulog,*) this%hash_table%num_values() + write(iulog,*) this%num_advected_vars + write(iulog,*) index_const if (index_const /= this%hash_table%num_values()) then call set_errvars(errcode + 1, subname, & errcode=errcode, errmsg=errmsg, & @@ -1093,33 +1183,64 @@ subroutine ccp_model_const_lock(this, ncols, num_layers, errcode, errmsg) errmsg3="in hash table") astat = astat + 1 end if - ! Everything looks okay, allocate field arrays - allocate(this%vars_layer(ncols, num_layers, index_const), & - stat=astat) - call handle_allocate_error(astat, 'vars_layer', & - errcode=errcode, errmsg=errmsg) - if (astat == 0) then - allocate(this%vars_minvalue(index_const), stat=astat) - call handle_allocate_error(astat, 'vars_minvalue', & - errcode=errcode, errmsg=errmsg) - end if - if (astat == 0) then - this%num_layers = num_layers - this%vars_layer = kphys_unassigned - 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%table_locked = .true. end if 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 + 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 + this%vars_layer = kphys_unassigned + 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 !######################################################################## @@ -1209,7 +1330,7 @@ subroutine ccp_model_const_num_match(this, nmatch, advected, errcode, errmsg) character(len=*), parameter :: subname = "ccp_model_const_num_match" nmatch = 0 - if (this%locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then + 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 @@ -1235,7 +1356,7 @@ subroutine ccp_model_const_index(this, index, standard_name, errcode, errmsg) type(ccpp_constituent_properties_t), pointer :: cprop character(len=*), parameter :: subname = "ccp_model_const_index" - if (this%locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then + 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() @@ -1265,7 +1386,7 @@ subroutine ccp_model_const_metadata(this, standard_name, const_data, & type(ccpp_constituent_properties_t), pointer :: cprop character(len=*), parameter :: subname = "ccp_model_const_metadata" - if (this%locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then + 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 @@ -1481,7 +1602,7 @@ function ccp_constituent_props_ptr(this) result(const_ptr) character(len=errmsg_len) :: errmsg character(len=*), parameter :: subname = 'ccp_constituent_props_ptr' - if (this%locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then + 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 From d9871c02ffd7a68e9cea5e99193a3d7efd5c6315 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Tue, 28 Feb 2023 20:48:12 -0700 Subject: [PATCH 015/159] cleanup --- src/ccpp_constituent_prop_mod.F90 | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index 8fd3cca9..f7b5f5db 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -1166,10 +1166,6 @@ subroutine ccp_model_const_table_lock(this, errcode, errmsg) end if end do ! Some size sanity checks - write(iulog,*) 'PEVERWHEE nums' - write(iulog,*) this%hash_table%num_values() - write(iulog,*) this%num_advected_vars - write(iulog,*) index_const if (index_const /= this%hash_table%num_values()) then call set_errvars(errcode + 1, subname, & errcode=errcode, errmsg=errmsg, & From a71e1b531632aaf82b77a7327901c7b70e110ed4 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Wed, 1 Mar 2023 02:16:10 -0700 Subject: [PATCH 016/159] cleanup --- src/ccpp_constituent_prop_mod.F90 | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index f7b5f5db..25f4dfa5 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -7,7 +7,6 @@ module ccpp_constituent_prop_mod use ccpp_hashable, only: ccpp_hashable_t, ccpp_hashable_char_t use ccpp_hash_table, only: ccpp_hash_table_t, ccpp_hash_iterator_t use ccpp_kinds, only: kind_phys - use cam_logfile, only: iulog implicit none private From 6c7c82a84cf62cda052cfed3f7de351ffa2e4be4 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Wed, 1 Mar 2023 16:03:18 -0700 Subject: [PATCH 017/159] add temporary hash_table variable to avoid dangling pointer --- src/ccpp_hash_table.F90 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ccpp_hash_table.F90 b/src/ccpp_hash_table.F90 index 147ca5f0..5c92ac00 100644 --- a/src/ccpp_hash_table.F90 +++ b/src/ccpp_hash_table.F90 @@ -398,7 +398,11 @@ subroutine hash_iterator_initialize(this, hash_table) class(ccpp_hash_iterator_t) :: this class(ccpp_hash_table_t), target :: hash_table - this%hash_table => hash_table + class(ccpp_hash_table_t), target, allocatable, save :: temp_hash_table + + temp_hash_table = hash_table + + this%hash_table => temp_hash_table this%index = 0 nullify(this%table_entry) do From d4ad4aa6f5545637da389ac1785e14598e276bae Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Thu, 2 Mar 2023 11:43:53 -0700 Subject: [PATCH 018/159] deallocate hash_table --- src/ccpp_constituent_prop_mod.F90 | 7 +++++++ src/ccpp_hash_table.F90 | 11 +++++++++++ test/hash_table_tests/test_hash.F90 | 1 + 3 files changed, 19 insertions(+) diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index 25f4dfa5..8ab2f061 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -1095,6 +1095,7 @@ subroutine ccp_model_const_table_lock(this, errcode, errmsg) exit end if end do + call hiter%finalize() ! Sanity check on num_advect if (this%num_advected_vars > num_vars) then astat = 1 @@ -1164,6 +1165,7 @@ subroutine ccp_model_const_table_lock(this, errcode, errmsg) exit end if end do + call hiter%finalize() ! Some size sanity checks if (index_const /= this%hash_table%num_values()) then call set_errvars(errcode + 1, subname, & @@ -1178,6 +1180,11 @@ subroutine ccp_model_const_table_lock(this, errcode, errmsg) errmsg3="in hash table") astat = astat + 1 end if + if (present(errcode)) then + if (errcode /= 0) then + astat = 1 + end if + end if if (astat == 0) then this%table_locked = .true. end if diff --git a/src/ccpp_hash_table.F90 b/src/ccpp_hash_table.F90 index 5c92ac00..4c9c1028 100644 --- a/src/ccpp_hash_table.F90 +++ b/src/ccpp_hash_table.F90 @@ -59,6 +59,7 @@ module ccpp_hash_table procedure :: next => hash_iterator_next_entry procedure :: valid => hash_iterator_is_valid procedure :: value => hash_iterator_value + procedure :: finalize => hash_iterator_finalize end type ccpp_hash_iterator_t !! Private interfaces @@ -521,4 +522,14 @@ function hash_iterator_value(this) result(val) end function hash_iterator_value + subroutine hash_iterator_finalize(this) + ! Deallocate hash_table object + + ! Dummy arguments + class(ccpp_hash_iterator_t) :: this + + nullify(this%hash_table) + + end subroutine hash_iterator_finalize + end module ccpp_hash_table diff --git a/test/hash_table_tests/test_hash.F90 b/test/hash_table_tests/test_hash.F90 index 883ab17b..35536cdd 100644 --- a/test/hash_table_tests/test_hash.F90 +++ b/test/hash_table_tests/test_hash.F90 @@ -151,6 +151,7 @@ subroutine test_table(hash_table, table_size, num_tests, num_errs, errors) exit end if end do + call hash_iter%finalize() if (ANY(.not. hash_found)) then write(errmsg, '(a,i0,a)') "ERROR: ", & COUNT(.not. hash_found), " test keys not found in table." From f549d64db06a3120f582c689a8445514c56dccc9 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Tue, 7 Mar 2023 12:06:50 -0700 Subject: [PATCH 019/159] cleaner way to deallocate hash table in iterator object --- src/ccpp_hash_table.F90 | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/ccpp_hash_table.F90 b/src/ccpp_hash_table.F90 index 4c9c1028..8445e9db 100644 --- a/src/ccpp_hash_table.F90 +++ b/src/ccpp_hash_table.F90 @@ -62,6 +62,8 @@ module ccpp_hash_table procedure :: finalize => hash_iterator_finalize end type ccpp_hash_iterator_t + !! Module-level hash table variable + class(ccpp_hash_table_t), private, allocatable, target :: hash_table_module !! Private interfaces private :: have_error ! Has a called routine detected an error? private :: clear_optstring ! Clear a string, if present @@ -397,13 +399,11 @@ subroutine hash_iterator_initialize(this, hash_table) ! Dummy arguments class(ccpp_hash_iterator_t) :: this - class(ccpp_hash_table_t), target :: hash_table + class(ccpp_hash_table_t), intent(in), target :: hash_table - class(ccpp_hash_table_t), target, allocatable, save :: temp_hash_table + hash_table_module = hash_table - temp_hash_table = hash_table - - this%hash_table => temp_hash_table + this%hash_table => hash_table_module this%index = 0 nullify(this%table_entry) do @@ -529,6 +529,7 @@ subroutine hash_iterator_finalize(this) class(ccpp_hash_iterator_t) :: this nullify(this%hash_table) + deallocate(hash_table_module) end subroutine hash_iterator_finalize From 5813d191b3c75077c83bac24acc9b391c800f629 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Tue, 21 Mar 2023 20:26:28 -0600 Subject: [PATCH 020/159] change name of const array interfaces --- scripts/host_cap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/host_cap.py b/scripts/host_cap.py index 7485b205..d833c649 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -170,7 +170,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".format(host_model.name) + hstr = "{}_constituents_array".format(host_model.name) return unique_local_name(hstr, host_model) ############################################################################### @@ -178,7 +178,7 @@ 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".format(host_model.name) + hstr = "{}_advected_constituents_array".format(host_model.name) return unique_local_name(hstr, host_model) ############################################################################### From 8ff8d5a85519b1845843026f8718d5623819045a Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 10 Apr 2023 11:13:17 -0600 Subject: [PATCH 021/159] add intialized_in_physics property --- scripts/constituents.py | 11 +- scripts/metavar.py | 11 ++ src/ccpp_constituent_prop_mod.F90 | 178 +++++++++++++++++++----------- 3 files changed, 130 insertions(+), 70 deletions(-) diff --git a/scripts/constituents.py b/scripts/constituents.py index bf4cce2d..e53eed86 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -293,6 +293,9 @@ 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') @@ -306,18 +309,16 @@ 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_in_phys_str = self.TF_string(var.is_initialized_in_physics()) 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'advected={advect_str}', f'initialized_in_physics={init_in_phys_str}', f'errcode={errvar_names["ccpp_error_code"]}', f'errmsg={errvar_names["ccpp_error_message"]}'] - stmt = 'call {}(index)%initialize({})' + stmt = 'call {}(index)%instantiate({})' outfile.write(stmt.format(self.constituent_prop_array_name(), ", ".join(init_args)), indent+1) # end for - for evar in err_vars: - self.__init_err_var(evar, outfile, indent+1) - # end for outfile.write("{} = .true.".format(self.constituent_prop_init_name()), indent+1) stmt = "end subroutine {}".format(self.constituent_prop_init_consts()) diff --git a/scripts/metavar.py b/scripts/metavar.py index e2a22e24..5f08292c 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -332,6 +332,13 @@ def __init__(self, prop_dict, source, run_env, context=None, break # end if # end for + self.__initialized_in_physics = False + # Check for variables that are initialized in physics scheme(s) + if source.name[-4:] == 'init' and source.name[-14:] != 'timestep_init' and 'intent' in prop_dict: + if prop_dict['intent'] == 'out': + self.__initialized_in_physics = True + # end if + # end if # Steal dict from caller self._prop_dict = prop_dict # XXgoldyXX: v don't fill in default properties? @@ -1077,6 +1084,10 @@ def is_constituent(self): """Return True iff is a constituent variable.""" return self.__is_constituent + def is_initialized_in_physics(self): + """Return True iff is initialized in a physics init routine""" + return self.__initialized_in_physics + def __str__(self): """Print representation or string for Var objects""" return "".format(**self._prop_dict) diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index 8ab2f061..919e0ca3 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -36,6 +36,7 @@ module ccpp_constituent_prop_mod character(len=:), private, allocatable :: vert_dim integer, private :: const_ind = int_unassigned logical, private :: advected = .false. + logical, private :: initialized_in_physics = .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 @@ -50,29 +51,30 @@ module ccpp_constituent_prop_mod ! 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 :: 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 :: is_instantiated => ccp_is_instantiated + procedure :: is_initialized_in_physics => ccp_is_initialized_in_physics + 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 ! Copy method (be sure to update this anytime fields are added) procedure :: copyConstituent generic :: assignment(=) => copyConstituent ! Methods that change state (XXgoldyXX: make private?) - procedure :: initialize => ccp_initialize + procedure :: instantiate => ccp_instantiate procedure :: deallocate => ccp_deallocate procedure :: set_const_index => ccp_set_const_index end type ccpp_constituent_properties_t @@ -84,22 +86,23 @@ module ccpp_constituent_prop_mod 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 :: 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 :: is_initialized_in_physics => ccpt_is_initialized_in_physics + procedure :: minimum => ccpt_min_val + procedure :: molec_weight => ccpt_molec_weight ! ccpt_set: Set the internal pointer procedure :: set => ccpt_set ! Methods that change state (XXgoldyXX: make private?) @@ -157,7 +160,7 @@ module ccpp_constituent_prop_mod ! Return index of constituent matching standard name procedure :: const_index => ccp_model_const_index ! Return metadata matching standard name - procedure :: field_metada => ccp_model_const_metadata + procedure :: field_metadata => ccp_model_const_metadata ! Gather constituent fields matching pattern procedure :: copy_in => ccp_model_const_copy_in_3d ! Update constituent fields matching pattern @@ -331,11 +334,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 @@ -343,19 +346,19 @@ 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) + ccp_is_instantiated = allocated(this%var_std_name) call initialize_errvars(errcode, errmsg) - if (.not. ccp_is_initialized) then + 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, units, vertical_dim, & - advected, errcode, errmsg) + subroutine ccp_instantiate(this, std_name, long_name, units, vertical_dim, & + advected, initialized_in_physics, errcode, errmsg) ! Initialize all fields in ! Dummy arguments @@ -365,12 +368,13 @@ subroutine ccp_initialize(this, std_name, long_name, units, vertical_dim, & character(len=*), intent(in) :: units character(len=*), intent(in) :: vertical_dim logical, optional, intent(in) :: advected + logical, optional, intent(in) :: initialized_in_physics 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 @@ -388,6 +392,11 @@ subroutine ccp_initialize(this, std_name, long_name, units, vertical_dim, & else this%advected = .false. end if + if (present(initialized_in_physics)) then + this%initialized_in_physics = initialized_in_physics + else + this%initialized_in_physics = .false. + end if end if if (errcode == 0) then if (index(this%var_std_name, "volume_mixing_ratio") > 0) then @@ -414,7 +423,7 @@ subroutine ccp_initialize(this, std_name, long_name, units, vertical_dim, & if (errcode /= 0) then call this%deallocate() end if - end subroutine ccp_initialize + end subroutine ccp_instantiate !####################################################################### @@ -451,7 +460,7 @@ 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 = '' @@ -470,7 +479,7 @@ 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 = '' @@ -489,7 +498,7 @@ 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 = '' @@ -552,7 +561,7 @@ integer function ccp_const_index(this, 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 ccp_const_index = this%const_ind else ccp_const_index = int_unassigned @@ -572,7 +581,7 @@ 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%is_instantiated(errcode, errmsg)) then if (this%const_ind == int_unassigned) then this%const_ind = index else @@ -594,7 +603,7 @@ subroutine ccp_is_advected(this, val_out, 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 val_out = this%advected else val_out = .false. @@ -603,6 +612,23 @@ end subroutine ccp_is_advected !####################################################################### + subroutine ccp_is_initialized_in_physics(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_instantiated(errcode, errmsg)) then + val_out = this%initialized_in_physics + else + val_out = .false. + end if + end subroutine ccp_is_initialized_in_physics + + !####################################################################### + subroutine ccp_is_equivalent(this, oconst, equiv, errcode, errmsg) ! Dummy arguments @@ -612,8 +638,8 @@ subroutine ccp_is_equivalent(this, oconst, equiv, errcode, errmsg) 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. & @@ -634,7 +660,7 @@ subroutine ccp_is_mass_mixing_ratio(this, val_out, errcode, errmsg) integer, intent(out) :: errcode character(len=*), intent(out) :: errmsg - if (this%is_initialized(errcode, errmsg)) then + if (this%is_instantiated(errcode, errmsg)) then val_out = this%const_type == mass_mixing_ratio else val_out = .false. @@ -651,7 +677,7 @@ subroutine ccp_is_volume_mixing_ratio(this, val_out, errcode, errmsg) integer, intent(out) :: errcode character(len=*), intent(out) :: errmsg - if (this%is_initialized(errcode, errmsg)) then + if (this%is_instantiated(errcode, errmsg)) then val_out = this%const_type == volume_mixing_ratio else val_out = .false. @@ -668,7 +694,7 @@ subroutine ccp_is_number_concentration(this, val_out, errcode, errmsg) integer, intent(out) :: errcode character(len=*), intent(out) :: errmsg - if (this%is_initialized(errcode, errmsg)) then + if (this%is_instantiated(errcode, errmsg)) then val_out = this%const_type == number_concentration else val_out = .false. @@ -685,7 +711,7 @@ subroutine ccp_is_dry(this, val_out, errcode, errmsg) integer, intent(out) :: errcode character(len=*), intent(out) :: errmsg - if (this%is_initialized(errcode, errmsg)) then + if (this%is_instantiated(errcode, errmsg)) then val_out = this%const_water == dry_mixing_ratio else val_out = .false. @@ -703,7 +729,7 @@ subroutine ccp_is_moist(this, val_out, errcode, errmsg) integer, intent(out) :: errcode character(len=*), intent(out) :: errmsg - if (this%is_initialized(errcode, errmsg)) then + if (this%is_instantiated(errcode, errmsg)) then val_out = this%const_water == moist_mixing_ratio else val_out = .false. @@ -721,7 +747,7 @@ subroutine ccp_is_wet(this, val_out, errcode, errmsg) integer, intent(out) :: errcode character(len=*), intent(out) :: errmsg - if (this%is_initialized(errcode, errmsg)) then + if (this%is_instantiated(errcode, errmsg)) then val_out = this%const_water == wet_mixing_ratio else val_out = .false. @@ -739,7 +765,7 @@ subroutine ccp_min_val(this, val_out, errcode, errmsg) integer, intent(out) :: errcode character(len=*), intent(out) :: errmsg - if (this%is_initialized(errcode, errmsg)) then + if (this%is_instantiated(errcode, errmsg)) then val_out = this%min_val else val_out = kphys_unassigned @@ -757,7 +783,7 @@ subroutine ccp_molec_weight(this, val_out, errcode, errmsg) integer, intent(out) :: errcode character(len=*), intent(out) :: errmsg - if (this%is_initialized(errcode, errmsg)) then + if (this%is_instantiated(errcode, errmsg)) then val_out = this%molar_mass else val_out = kphys_unassigned @@ -1680,7 +1706,7 @@ subroutine ccpt_get_vertical_dimension(this, vert_dim, errcode, errmsg) character(len=*), parameter :: subname = 'ccpt_get_vertical_dimension' if (associated(this%prop)) then - if (this%prop%is_initialized(errcode, errmsg)) then + if (this%prop%is_instantiated(errcode, errmsg)) then vert_dim = this%prop%vert_dim end if else @@ -1930,6 +1956,28 @@ end subroutine ccpt_is_wet !######################################################################## + subroutine ccpt_is_initialized_in_physics(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_initialized_in_physics' + + if (associated(this%prop)) then + call this%prop%is_initialized_in_physics(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_initialized_in_physics + + !######################################################################## + subroutine ccpt_min_val(this, val_out, errcode, errmsg) ! Dummy arguments @@ -2034,7 +2082,7 @@ subroutine ccpt_set_const_index(this, index, errcode, errmsg) character(len=*), parameter :: subname = 'ccpt_set_const_index' if (associated(this%prop)) then - if (this%prop%is_initialized(errcode, errmsg)) then + if (this%prop%is_instantiated(errcode, errmsg)) then if (this%prop%const_ind == int_unassigned) then this%prop%const_ind = index else From f074f1c528bbaee7030160b5f489f64b42d5503d Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 10 Apr 2023 22:48:43 -0600 Subject: [PATCH 022/159] better error handling; fix for copy constituent --- scripts/constituents.py | 68 ++++++++++++++++--------------- src/ccpp_constituent_prop_mod.F90 | 1 + 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/scripts/constituents.py b/scripts/constituents.py index e53eed86..88e5ed97 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -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=True, + var = source_var.clone({'dimensions' : newdims}, remove_intent=False, source_type=self.__constituent_type) self.add_variable(var, self.__run_env) return var @@ -316,8 +316,10 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): f'errcode={errvar_names["ccpp_error_code"]}', f'errmsg={errvar_names["ccpp_error_message"]}'] 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(), - ", ".join(init_args)), 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) @@ -524,20 +526,20 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.comment("Initialize constituent data and field object", 3) stmt = "call {}%initialize_table(num_consts)" cap.write(stmt.format(const_obj_name), 3) - cap.write("end if", 2) # Register host model constituents cap.comment("Add host model constituent metadata", 3) - cap.write("do index = 1, size(host_constituents, 1)", 2) - cap.write(f"if ({herrcode} == 0) then", 3) - cap.write("const_prop => host_constituents(index)", 4) + 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), 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("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: @@ -550,27 +552,29 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna 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(f"if ({herrcode} == 0) then", 3) - cap.write(f"allocate(const_prop, stat={herrcode})", 4) - cap.write("end if", 3) - cap.write(f"if ({herrcode} /= 0) then", 3) - cap.write(f'{herrmsg} = "ERROR allocating const_prop"', 4) - cap.write("exit", 4) - cap.write("end if", 3) - cap.write(f"if ({herrcode} == 0) then", 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(f"if ({herrcode} == 0) then", 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(f"if ({herrcode} /= 0) then", 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(f"if ({herrcode} == 0) then", 2) diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index 919e0ca3..878b43b0 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -198,6 +198,7 @@ subroutine copyConstituent(outConst, inConst) outConst%vert_dim = inConst%vert_dim outConst%const_ind = inConst%const_ind outConst%advected = inConst%advected + outConst%initialized_in_physics = inConst%initialized_in_physics outConst%const_type = inConst%const_type outConst%const_water = inConst%const_water outConst%min_val = inConst%min_val From 5e839bfc81bc9b9e45f75ce0d7185d5f4b0e64ca Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Wed, 12 Apr 2023 12:04:54 -0600 Subject: [PATCH 023/159] remove unnecessary use statment --- src/ccpp_constituent_prop_mod.F90 | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index 878b43b0..c4b8c261 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -1,5 +1,4 @@ module ccpp_constituent_prop_mod - use vert_coord, only: pver ! ccpp_contituent_prop_mod contains types and procedures for storing ! and retrieving constituent properties From feb8bcb2657fb14edf26c941961df0f6f5fda3fc Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Tue, 18 Apr 2023 19:03:04 -0600 Subject: [PATCH 024/159] fix unit tests --- scripts/metavar.py | 2 +- src/ccpp_constituent_prop_mod.F90 | 2 - src/ccpp_hash_table.F90 | 4 +- test/advection_test/cld_ice.F90 | 12 +++--- test/advection_test/cld_ice.meta | 4 +- test/advection_test/cld_liq.F90 | 12 +++--- test/advection_test/cld_liq.meta | 4 +- test/advection_test/test_host.F90 | 56 ++++++++++++++++++++------ test/advection_test/test_host_mod.F90 | 2 - test/var_action_test/effr_calc.F90 | 8 ++-- test/var_action_test/test_host.F90 | 2 +- test/var_action_test/test_host_mod.F90 | 6 ++- 12 files changed, 71 insertions(+), 43 deletions(-) diff --git a/scripts/metavar.py b/scripts/metavar.py index 5f08292c..66f93dca 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -334,7 +334,7 @@ def __init__(self, prop_dict, source, run_env, context=None, # end for self.__initialized_in_physics = False # Check for variables that are initialized in physics scheme(s) - if source.name[-4:] == 'init' and source.name[-14:] != 'timestep_init' and 'intent' in prop_dict: + if source.name and source.name[-4:] == 'init' and source.name[-14:] != 'timestep_init' and 'intent' in prop_dict: if prop_dict['intent'] == 'out': self.__initialized_in_physics = True # end if diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index c4b8c261..b7cf1749 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -371,8 +371,6 @@ subroutine ccp_instantiate(this, std_name, long_name, units, vertical_dim, & logical, optional, intent(in) :: initialized_in_physics integer, intent(out) :: errcode character(len=*), intent(out) :: errmsg - ! Local variable - integer :: astat if (this%is_instantiated()) then errcode = 1 diff --git a/src/ccpp_hash_table.F90 b/src/ccpp_hash_table.F90 index 8445e9db..34e0e286 100644 --- a/src/ccpp_hash_table.F90 +++ b/src/ccpp_hash_table.F90 @@ -63,7 +63,7 @@ module ccpp_hash_table end type ccpp_hash_iterator_t !! Module-level hash table variable - class(ccpp_hash_table_t), private, allocatable, target :: hash_table_module + type(ccpp_hash_table_t), private, allocatable, target :: hash_table_module !! Private interfaces private :: have_error ! Has a called routine detected an error? private :: clear_optstring ! Clear a string, if present @@ -399,7 +399,7 @@ subroutine hash_iterator_initialize(this, hash_table) ! Dummy arguments class(ccpp_hash_iterator_t) :: this - class(ccpp_hash_table_t), intent(in), target :: hash_table + type(ccpp_hash_table_t), intent(in), target :: hash_table hash_table_module = hash_table 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..f66888e0 100644 --- a/test/advection_test/cld_ice.meta +++ b/test/advection_test/cld_ice.meta @@ -41,7 +41,7 @@ units = Pa dimensions = (horizontal_loop_extent) intent = in -[ cld_ice ] +[ cld_ice_array ] standard_name = cloud_ice_dry_mixing_ratio advected = .true. units = kg kg-1 @@ -73,7 +73,7 @@ dimensions = () type = real | kind = kind_phys intent = in -[ cld_ice ] +[ cld_ice_array ] standard_name = cloud_ice_dry_mixing_ratio advected = .true. units = kg kg-1 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 9da89cbf..604b6cb0 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -136,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 @@ -202,7 +201,7 @@ logical function check_suite(test_suite) end function check_suite 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 @@ -223,8 +222,9 @@ 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_initialize_constituents use test_host_ccpp_cap, only: test_host_ccpp_number_constituents - use test_host_ccpp_cap, only: test_host_advected_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 @@ -281,14 +281,14 @@ subroutine test_host(retval, test_suites) end if ! Register the constituents to find out what needs advecting - call host_constituents(1)%initialize(std_name="specific_humidity", & + 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(:), & - ncols, pver, host_constituents, errmsg=errmsg, errflg=errflg) + host_constituents, errmsg=errmsg, errflg=errflg) end if if (errflg /= 0) then write(6, '(2a)') 'ERROR register_constituents: ', trim(errmsg) @@ -303,10 +303,12 @@ subroutine test_host(retval, test_suites) 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_advected_constituents() + 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) @@ -487,8 +489,38 @@ 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(7) +! '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) +! '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) +! '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 + + test_parts1 = (/ 'physics '/) + test_invars1 = (/ & 'cloud_ice_dry_mixing_ratio ', & 'cloud_liquid_dry_mixing_ratio ', & 'surface_air_pressure ', & @@ -496,14 +528,14 @@ program test '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 ', & @@ -514,12 +546,10 @@ program test '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_mod.F90 b/test/advection_test/test_host_mod.F90 index f553953b..560b7619 100644 --- a/test/advection_test/test_host_mod.F90 +++ b/test/advection_test/test_host_mod.F90 @@ -92,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 @@ -121,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 diff --git a/test/var_action_test/effr_calc.F90 b/test/var_action_test/effr_calc.F90 index 50804a24..d3c1b205 100644 --- a/test/var_action_test/effr_calc.F90 +++ b/test/var_action_test/effr_calc.F90 @@ -28,9 +28,9 @@ subroutine effr_calc_run(ncol, nlev, effrr_in, effrl_inout, & integer, intent(out) :: errflg !---------------------------------------------------------------- - real(kind_phys), parameter :: re_qc_min = 2.5 ! microns - real(kind_phys), parameter :: re_qc_max = 50. ! microns - real(kind_phys), parameter :: re_qi_avg = 75. ! microns + real(kind_phys), parameter :: re_qc_min = 2.5E-6 ! 2.5 microns, in meters + real(kind_phys), parameter :: re_qc_max = 5.0E-5 ! 50 microns, in meters + real(kind_phys), parameter :: re_qi_avg = 7.5E-5 ! 75 microns, in meters real(kind_phys) :: effrr_local(ncol,nlev) errmsg = '' @@ -39,7 +39,7 @@ subroutine effr_calc_run(ncol, nlev, effrr_in, effrl_inout, & effrr_local = effrr_in effrl_inout = min(max(effrl_inout,re_qc_min),re_qc_max) effri_out = re_qi_avg - effrs_inout = effrs_inout + 10.0 ! in micrometer + effrs_inout = effrs_inout + 1.0E-5 ! 10 microns in meters end subroutine effr_calc_run diff --git a/test/var_action_test/test_host.F90 b/test/var_action_test/test_host.F90 index 885c2e62..c8260f11 100644 --- a/test/var_action_test/test_host.F90 +++ b/test/var_action_test/test_host.F90 @@ -329,7 +329,7 @@ subroutine test_host(retval, test_suites) if (errflg == 0) then ! Run finished without error, check answers - if (compare_data()) then + if (compare_data(col_start, col_end)) then write(6, *) 'Answers are correct!' errflg = 0 else diff --git a/test/var_action_test/test_host_mod.F90 b/test/var_action_test/test_host_mod.F90 index d6191814..64ca5e08 100644 --- a/test/var_action_test/test_host_mod.F90 +++ b/test/var_action_test/test_host_mod.F90 @@ -30,7 +30,9 @@ subroutine init_data() end subroutine init_data - logical function compare_data() + logical function compare_data(col_start, col_end) + integer, intent(in) :: col_start + integer, intent(in) :: col_end real(kind_phys), parameter :: effrr_expected = 1.0E-3 ! 1000 microns, in meter real(kind_phys), parameter :: effrl_expected = 5.0E-5 ! 50 microns, in meter @@ -58,7 +60,7 @@ logical function compare_data() compare_data = .false. end if - if (maxval(abs( effrs - effrs_expected)) > tolerance*effrs_expected) then + if (maxval(abs(effrs(col_start:col_end,:) - effrs_expected)) > tolerance*effrs_expected) then write(6, '(a,e16.7,a,e16.7)') 'Error: max diff of effrs from expected value exceeds tolerance: ', & maxval(abs( effrs - effrs_expected)), ' > ', tolerance*effrs_expected compare_data = .false. From 15f52c6baee97ecc43c9dc036546dad35ea9e65b Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Thu, 11 May 2023 02:48:54 -0600 Subject: [PATCH 025/159] revert changes for unit conversion test --- test/var_action_test/effr_calc.F90 | 8 ++++---- test/var_action_test/test_host.F90 | 2 +- test/var_action_test/test_host_mod.F90 | 6 ++---- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/test/var_action_test/effr_calc.F90 b/test/var_action_test/effr_calc.F90 index d3c1b205..d0b0d87f 100644 --- a/test/var_action_test/effr_calc.F90 +++ b/test/var_action_test/effr_calc.F90 @@ -28,9 +28,9 @@ subroutine effr_calc_run(ncol, nlev, effrr_in, effrl_inout, & integer, intent(out) :: errflg !---------------------------------------------------------------- - real(kind_phys), parameter :: re_qc_min = 2.5E-6 ! 2.5 microns, in meters - real(kind_phys), parameter :: re_qc_max = 5.0E-5 ! 50 microns, in meters - real(kind_phys), parameter :: re_qi_avg = 7.5E-5 ! 75 microns, in meters + real(kind_phys), parameter :: re_qc_min = 2.5 ! microns + real(kind_phys), parameter :: re_qc_max = 50 ! microns + real(kind_phys), parameter :: re_qi_avg = 75 ! microns real(kind_phys) :: effrr_local(ncol,nlev) errmsg = '' @@ -39,7 +39,7 @@ subroutine effr_calc_run(ncol, nlev, effrr_in, effrl_inout, & effrr_local = effrr_in effrl_inout = min(max(effrl_inout,re_qc_min),re_qc_max) effri_out = re_qi_avg - effrs_inout = effrs_inout + 1.0E-5 ! 10 microns in meters + effrs_inout = effrs_inout + 10.0 ! in micrometer end subroutine effr_calc_run diff --git a/test/var_action_test/test_host.F90 b/test/var_action_test/test_host.F90 index c8260f11..885c2e62 100644 --- a/test/var_action_test/test_host.F90 +++ b/test/var_action_test/test_host.F90 @@ -329,7 +329,7 @@ subroutine test_host(retval, test_suites) if (errflg == 0) then ! Run finished without error, check answers - if (compare_data(col_start, col_end)) then + if (compare_data()) then write(6, *) 'Answers are correct!' errflg = 0 else diff --git a/test/var_action_test/test_host_mod.F90 b/test/var_action_test/test_host_mod.F90 index 64ca5e08..d6191814 100644 --- a/test/var_action_test/test_host_mod.F90 +++ b/test/var_action_test/test_host_mod.F90 @@ -30,9 +30,7 @@ subroutine init_data() end subroutine init_data - logical function compare_data(col_start, col_end) - integer, intent(in) :: col_start - integer, intent(in) :: col_end + logical function compare_data() real(kind_phys), parameter :: effrr_expected = 1.0E-3 ! 1000 microns, in meter real(kind_phys), parameter :: effrl_expected = 5.0E-5 ! 50 microns, in meter @@ -60,7 +58,7 @@ logical function compare_data(col_start, col_end) compare_data = .false. end if - if (maxval(abs(effrs(col_start:col_end,:) - effrs_expected)) > tolerance*effrs_expected) then + if (maxval(abs( effrs - effrs_expected)) > tolerance*effrs_expected) then write(6, '(a,e16.7,a,e16.7)') 'Error: max diff of effrs from expected value exceeds tolerance: ', & maxval(abs( effrs - effrs_expected)), ' > ', tolerance*effrs_expected compare_data = .false. From 3703645c876d5a09f325016778e731b1f914be41 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 22 May 2023 13:32:12 -0600 Subject: [PATCH 026/159] handle constituent edge cases better and differently; revert module variable in hash_table --- scripts/ccpp_database_obj.py | 2 +- scripts/ccpp_suite.py | 15 +++++++-- scripts/constituents.py | 3 +- scripts/metavar.py | 11 ------- src/ccpp_constituent_prop_mod.F90 | 53 +------------------------------ src/ccpp_hash_table.F90 | 18 +---------- 6 files changed, 17 insertions(+), 85 deletions(-) diff --git a/scripts/ccpp_database_obj.py b/scripts/ccpp_database_obj.py index f18516b2..31aa450c 100644 --- a/scripts/ccpp_database_obj.py +++ b/scripts/ccpp_database_obj.py @@ -2,7 +2,7 @@ """ Define the CCPPDatabaseObj object -Ojbect definition and methods to provide information from a run of capgen. +Object definition and methods to provide information from a run of capgen. """ from host_model import HostModel diff --git a/scripts/ccpp_suite.py b/scripts/ccpp_suite.py index 6e25510c..7fa5e88a 100644 --- a/scripts/ccpp_suite.py +++ b/scripts/ccpp_suite.py @@ -809,11 +809,16 @@ def write_req_vars_sub(self, ofile, errmsg_name, errcode_name): input_vars = [set(), set(), set()] # leaves, arrays, leaf elements inout_vars = [set(), set(), set()] # leaves, arrays, leaf elements output_vars = [set(), set(), set()] # leaves, arrays, 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 if (parent is not None) and (not protected): pvar = parent.find_variable(standard_name=stdname) if pvar is not None: @@ -822,20 +827,26 @@ def write_req_vars_sub(self, ofile, errmsg_name, errcode_name): # end if elements = var.intrinsic_elements(check_dict=self.parent, ddt_lib=self.__ddt_lib) - if (intent == 'in') and (not protected): + 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 88e5ed97..b4c9c0bb 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -309,10 +309,9 @@ 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_in_phys_str = self.TF_string(var.is_initialized_in_physics()) 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'initialized_in_physics={init_in_phys_str}', + f'advected={advect_str}', f'errcode={errvar_names["ccpp_error_code"]}', f'errmsg={errvar_names["ccpp_error_message"]}'] stmt = 'call {}(index)%instantiate({})' diff --git a/scripts/metavar.py b/scripts/metavar.py index 66f93dca..e2a22e24 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -332,13 +332,6 @@ def __init__(self, prop_dict, source, run_env, context=None, break # end if # end for - self.__initialized_in_physics = False - # Check for variables that are initialized in physics scheme(s) - if source.name and source.name[-4:] == 'init' and source.name[-14:] != 'timestep_init' and 'intent' in prop_dict: - if prop_dict['intent'] == 'out': - self.__initialized_in_physics = True - # end if - # end if # Steal dict from caller self._prop_dict = prop_dict # XXgoldyXX: v don't fill in default properties? @@ -1084,10 +1077,6 @@ def is_constituent(self): """Return True iff is a constituent variable.""" return self.__is_constituent - def is_initialized_in_physics(self): - """Return True iff is initialized in a physics init routine""" - return self.__initialized_in_physics - def __str__(self): """Print representation or string for Var objects""" return "".format(**self._prop_dict) diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index b7cf1749..7a26f126 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -35,7 +35,6 @@ module ccpp_constituent_prop_mod character(len=:), private, allocatable :: vert_dim integer, private :: const_ind = int_unassigned logical, private :: advected = .false. - logical, private :: initialized_in_physics = .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 @@ -51,7 +50,6 @@ module ccpp_constituent_prop_mod procedure :: key => ccp_properties_get_key ! Informational methods procedure :: is_instantiated => ccp_is_instantiated - procedure :: is_initialized_in_physics => ccp_is_initialized_in_physics procedure :: standard_name => ccp_get_standard_name procedure :: long_name => ccp_get_long_name procedure :: is_layer_var => ccp_is_layer_var @@ -99,7 +97,6 @@ module ccpp_constituent_prop_mod procedure :: is_dry => ccpt_is_dry procedure :: is_moist => ccpt_is_moist procedure :: is_wet => ccpt_is_wet - procedure :: is_initialized_in_physics => ccpt_is_initialized_in_physics procedure :: minimum => ccpt_min_val procedure :: molec_weight => ccpt_molec_weight ! ccpt_set: Set the internal pointer @@ -197,7 +194,6 @@ subroutine copyConstituent(outConst, inConst) outConst%vert_dim = inConst%vert_dim outConst%const_ind = inConst%const_ind outConst%advected = inConst%advected - outConst%initialized_in_physics = inConst%initialized_in_physics outConst%const_type = inConst%const_type outConst%const_water = inConst%const_water outConst%min_val = inConst%min_val @@ -358,7 +354,7 @@ end function ccp_is_instantiated !####################################################################### subroutine ccp_instantiate(this, std_name, long_name, units, vertical_dim, & - advected, initialized_in_physics, errcode, errmsg) + advected, errcode, errmsg) ! Initialize all fields in ! Dummy arguments @@ -368,7 +364,6 @@ subroutine ccp_instantiate(this, std_name, long_name, units, vertical_dim, & character(len=*), intent(in) :: units character(len=*), intent(in) :: vertical_dim logical, optional, intent(in) :: advected - logical, optional, intent(in) :: initialized_in_physics integer, intent(out) :: errcode character(len=*), intent(out) :: errmsg @@ -390,11 +385,6 @@ subroutine ccp_instantiate(this, std_name, long_name, units, vertical_dim, & else this%advected = .false. end if - if (present(initialized_in_physics)) then - this%initialized_in_physics = initialized_in_physics - else - this%initialized_in_physics = .false. - end if end if if (errcode == 0) then if (index(this%var_std_name, "volume_mixing_ratio") > 0) then @@ -610,23 +600,6 @@ end subroutine ccp_is_advected !####################################################################### - subroutine ccp_is_initialized_in_physics(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_instantiated(errcode, errmsg)) then - val_out = this%initialized_in_physics - else - val_out = .false. - end if - end subroutine ccp_is_initialized_in_physics - - !####################################################################### - subroutine ccp_is_equivalent(this, oconst, equiv, errcode, errmsg) ! Dummy arguments @@ -1119,7 +1092,6 @@ subroutine ccp_model_const_table_lock(this, errcode, errmsg) exit end if end do - call hiter%finalize() ! Sanity check on num_advect if (this%num_advected_vars > num_vars) then astat = 1 @@ -1189,7 +1161,6 @@ subroutine ccp_model_const_table_lock(this, errcode, errmsg) exit end if end do - call hiter%finalize() ! Some size sanity checks if (index_const /= this%hash_table%num_values()) then call set_errvars(errcode + 1, subname, & @@ -1954,28 +1925,6 @@ end subroutine ccpt_is_wet !######################################################################## - subroutine ccpt_is_initialized_in_physics(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_initialized_in_physics' - - if (associated(this%prop)) then - call this%prop%is_initialized_in_physics(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_initialized_in_physics - - !######################################################################## - subroutine ccpt_min_val(this, val_out, errcode, errmsg) ! Dummy arguments diff --git a/src/ccpp_hash_table.F90 b/src/ccpp_hash_table.F90 index 34e0e286..57ef3106 100644 --- a/src/ccpp_hash_table.F90 +++ b/src/ccpp_hash_table.F90 @@ -59,11 +59,8 @@ module ccpp_hash_table procedure :: next => hash_iterator_next_entry procedure :: valid => hash_iterator_is_valid procedure :: value => hash_iterator_value - procedure :: finalize => hash_iterator_finalize end type ccpp_hash_iterator_t - !! Module-level hash table variable - type(ccpp_hash_table_t), private, allocatable, target :: hash_table_module !! Private interfaces private :: have_error ! Has a called routine detected an error? private :: clear_optstring ! Clear a string, if present @@ -401,9 +398,7 @@ subroutine hash_iterator_initialize(this, hash_table) class(ccpp_hash_iterator_t) :: this type(ccpp_hash_table_t), intent(in), target :: hash_table - hash_table_module = hash_table - - this%hash_table => hash_table_module + this%hash_table => hash_table this%index = 0 nullify(this%table_entry) do @@ -522,15 +517,4 @@ function hash_iterator_value(this) result(val) end function hash_iterator_value - subroutine hash_iterator_finalize(this) - ! Deallocate hash_table object - - ! Dummy arguments - class(ccpp_hash_iterator_t) :: this - - nullify(this%hash_table) - deallocate(hash_table_module) - - end subroutine hash_iterator_finalize - end module ccpp_hash_table From 1e83c4cb9a2e412791481aee65354c7841ebf869 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 22 May 2023 14:03:50 -0600 Subject: [PATCH 027/159] fix decimal --- test/var_action_test/effr_calc.F90 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/var_action_test/effr_calc.F90 b/test/var_action_test/effr_calc.F90 index d0b0d87f..50804a24 100644 --- a/test/var_action_test/effr_calc.F90 +++ b/test/var_action_test/effr_calc.F90 @@ -29,8 +29,8 @@ subroutine effr_calc_run(ncol, nlev, effrr_in, effrl_inout, & !---------------------------------------------------------------- real(kind_phys), parameter :: re_qc_min = 2.5 ! microns - real(kind_phys), parameter :: re_qc_max = 50 ! microns - real(kind_phys), parameter :: re_qi_avg = 75 ! microns + real(kind_phys), parameter :: re_qc_max = 50. ! microns + real(kind_phys), parameter :: re_qi_avg = 75. ! microns real(kind_phys) :: effrr_local(ncol,nlev) errmsg = '' From 31919df0239f01411614cfd36d9aeb1ae0cad12c Mon Sep 17 00:00:00 2001 From: Steve Goldhaber Date: Thu, 8 Jun 2023 20:11:47 +0200 Subject: [PATCH 028/159] Merged changes from new_variables_input Rename dir for var_action test to distinguish from ct_build --- scripts/ccpp_suite.py | 694 ++++++++++++++++++++-------------- scripts/constituents.py | 2 +- scripts/metavar.py | 15 +- test/var_action_test/run_test | 2 +- 4 files changed, 414 insertions(+), 299 deletions(-) diff --git a/scripts/ccpp_suite.py b/scripts/ccpp_suite.py index 7fa5e88a..130a828c 100644 --- a/scripts/ccpp_suite.py +++ b/scripts/ccpp_suite.py @@ -78,6 +78,20 @@ class Suite(VarDictionary): __scheme_template = '{}' + # The var_type_vals are bits to describe the status of a variable in + # each phase with a single number. + # Each phase is 2 bits with the following table: + # 0: Variable not present in this phase + # 1: Variable is intent(in) in this phase + # 2: Variable is intent(out) in this phase + # 3: Variable is intent(inout) in this phase + __var_type_vals = [f"{phase}_{bit}_bitpos" + for phase in CCPP_STATE_MACH.transitions() + for bit in ("input", "output")] + # __struct_element is a bit for variables which are part of a DDT + __struct_element = "struct_element" + __var_type_vals.append(__struct_element) + def __init__(self, filename, api, run_env): """Initialize this Suite object from the SDF, . serves as the Suite's parent.""" @@ -111,6 +125,11 @@ def __init__(self, filename, api, run_env): # end if # Parse the SDF self.parse(run_env) + # For completeness, we initilize these here, however, + # they will receive correct values at the end of analyze + self.__regular_vars = None + self.__parent_vars = None + self.__elem_vars = None @property def name(self): @@ -261,6 +280,78 @@ def groups(self): """Get the list of groups in this suite.""" return self.__groups + def _interface_vars_dict(self, check_dict, ddt_lib): + """Collect the input, output, and inout variables for each phase + of this suite.""" + parent = self.parent + # Collect all the suite variables, by type + regular_vars = {} # Fortran intrinsics + parent_vars = {} # E.g., DDT + elem_vars = {} # Intrinsic vars inside a DDT + for part in self.groups: + phase = part.phase() + for var in part.call_list.variable_list(): + stdname = var.get_prop_value("standard_name") + intent = var.get_prop_value("intent") + protected = var.get_prop_value("protected") + 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=check_dict, + ddt_lib=ddt_lib) + if (intent == 'in') and (not protected): + if isinstance(elements, list): + self._add_vars_to_dict(parent_vars, stdname, + phase, "input") + self._add_vars_to_dict(elem_vars, elements, + phase, "input") + else: + self._add_vars_to_dict(regular_vars, stdname, + phase, "input") + # end if + elif intent == 'inout': + if isinstance(elements, list): + self._add_vars_to_dict(parent_vars, stdname, + phase, "input") + self._add_vars_to_dict(elem_vars, elements, + phase, "input") + self._add_vars_to_dict(parent_vars, stdname, + phase, "output") + self._add_vars_to_dict(elem_vars, elements, + phase, "output") + else: + self._add_vars_to_dict(regular_vars, stdname, + phase, "input") + self._add_vars_to_dict(regular_vars, stdname, + phase, "output") + # end if + elif intent == 'out': + if isinstance(elements, list): + self._add_vars_to_dict(parent_vars, stdname, + phase, "output") + self._add_vars_to_dict(elem_vars, elements, + phase, "output") + else: + self._add_vars_to_dict(regular_vars, stdname, + phase, "output") + # end if + # end if + # end for + # end for + # Check for overlap (there should not be any) + reg_set = set(regular_vars.keys()) + par_set = set(parent_vars.keys()) + if not reg_set.isdisjoint(par_set): + overlap = reg_set.intersection(par_set) + emsg = (f"Suite {self.name}: Standard name overlap with: " + + f"{', '.join(overlap)}.") + raise CCPPError(emsg) + # end if + return regular_vars, parent_vars, elem_vars + def find_variable(self, standard_name=None, source_var=None, any_scope=True, clone=None, search_call_list=False, loop_subst=False): @@ -446,6 +537,9 @@ def analyze(self, host_model, scheme_library, ddt_library, run_env): # end if # end for # end for + # Finally, collect usage dictionaries of Suite variables + args = self._interface_vars_dict(host_model, ddt_library) + self.__regular_vars, self.__parent_vars, self.__elem_vars = args def is_run_group(self, group): """Method to separate out run-loop groups from special initial @@ -485,7 +579,208 @@ def constituent_dictionary(self): """Return the constituent dictionary for this suite""" return self.parent - def write(self, output_dir, run_env): + @staticmethod + def _add_vars_to_dict(vdict, stdname, phase, inout): + # Add a presence flag to the set in + # Add a new set if is not in + # If is a list, apply this function to each element + if isinstance(stdname, list): + for var in stdname: + self._add_vars_to_dict(vdict, var, phase, inout) + # end for + else: + if not stdname in vdict: + vdict[stdname] = set() + # end if + vdict[stdname].add(f"{phase}_{inout}_bitpos") + # end if + + def req_vars_subname(self): + """Return the name of the required variables subroutine for this suite""" + return f"suite_{self.name}_variables" + + def write_req_vars_sub(self, ofile, errvars): + """Write the required variables subroutine""" + bitfield_funcname = "find_bitfld_val" + varmatch_funcname = "var_match" + # Find error variables (only support old style for now) + errmsg_var = None + errcode_var= None + for evar in errvars: + if evar.get_prop_value('standard_name') == 'ccpp_error_code': + errcode_var = evar + elif evar.get_prop_value('standard_name') == 'ccpp_error_message': + errmsg_var = evar + else: + raise ParseInternalError("Unsupported error variable") + # end if + # end for + if (errmsg_var == None) or (errcode_var == None): + emsg = "Could not find error variable names: errvars = " + emsg += f"{', '.join([str(x) for x in errvars])}" + raise ParseInternalError(emsg) + # end if + errmsg = errmsg_var.get_prop_value('local_name') + errcode = errcode_var.get_prop_value('local_name') + inargs = f"variable_list, {errmsg}, {errcode}" + inargs += ", input_vars, output_vars, phases, struct_elements" + ofile.blank_line() + ofile.write(f"subroutine {self.req_vars_subname()}({inargs})", 1) + ofile.write("! Dummy arguments", 2) + vtype = "character(len=*)" + oline = f"{vtype}, allocatable, intent(out) :: variable_list(:)" + ofile.write(oline, 2) + errmsg_var.write_def(ofile, 2, self, + extra_space=11, dummy=True, add_intent="out") + errcode_var.write_def(ofile, 2, self, + extra_space=11, dummy=True, add_intent="out") + oline = "logical, optional, intent(in) :: input_vars" + ofile.write(oline, 2) + oline = "logical, optional, intent(in) :: output_vars" + ofile.write(oline, 2) + oline = "character(len=*), optional, intent(in) :: phases(:)" + ofile.write(oline, 2) + oline = f"logical,{' '*9} optional, intent(in) :: struct_elements" + ofile.write(oline, 2) + ofile.write("! Local variables", 2) + spc = 37 + ofile.write(f"logical {' '*spc}:: input_vars_use", 2) + ofile.write(f"logical {' '*spc}:: output_vars_use", 2) + ofile.write(f"logical {' '*spc}:: struct_elements_use", 2) + mlen = max([len(x.phase()) for x in self.groups]) + mspc = ' '*15 + ofile.write(f"character(len={mlen}), allocatable{mspc}:: phases_use(:)", + 2) + ofile.write(f"integer {' '*spc}:: num_vars", 2) + ofile.write(f"integer {' '*spc}:: var_index", 2) + ofile.write(f"integer(int_kind) {' '*(spc - 10)}:: var_mask", 2) + ofile.write(f"integer(int_kind) {' '*(spc - 10)}:: ptype_val", 2) + ofile.write(f"integer {' '*spc}:: out_index", 2) + ofile.write(f"integer {' '*spc}:: ierr", 2) + ofile.blank_line() + ofile.write(f"{errcode} = 0", 2) + ofile.write(f"{errmsg} = ''", 2) + ofile.write("if (present(input_vars)) then", 2) + ofile.write("input_vars_use = input_vars", 3) + ofile.write("else", 2) + ofile.write("input_vars_use = .true.", 3) + ofile.write("end if", 2) + ofile.write("if (present(output_vars)) then", 2) + ofile.write("output_vars_use = output_vars", 3) + ofile.write("else", 2) + ofile.write("output_vars_use = .true.", 3) + ofile.write("end if", 2) + ofile.write("if (present(phases)) then", 2) + ofile.write("allocate(phases_use(size(phases, 1)), stat=ierr)", 3) + ofile.write("if (ierr /= 0) then", 3) + ofile.write(f"{errcode} = ierr", 4) + ofile.write(f"{errmsg} = 'Unable to allocate phases_use'", 4) + ofile.write("return", 4) + ofile.write("end if", 3) + ofile.write("phases_use(:) = phases(:)", 3) + ofile.write("else", 2) + pnames = [f"\"{x.phase()}{' '*(mlen - len(x.phase()))}\"" + for x in self.groups] + ofile.write(f"allocate(phases_use({len(pnames)}), stat=ierr)", 3) + ofile.write("if (ierr /= 0) then", 3) + ofile.write(f"{errcode} = ierr", 4) + ofile.write(f"{errmsg} = 'Unable to allocate phases_use'", 4) + ofile.write("return", 4) + ofile.write("end if", 3) + ofile.write(f"phases_use(:) = (/ {', '.join(pnames)} /)", 3) + ofile.write("end if", 2) + ofile.write("if (present(struct_elements)) then", 2) + ofile.write("struct_elements_use = struct_elements", 3) + ofile.write("else", 2) + ofile.write("struct_elements_use = .true.", 3) + ofile.write("end if", 2) + ofile.comment("Find the correct variable mask based on phase and type", + 2) + ofile.write("var_mask = 0_int_kind", 2) + ofile.write("do var_index = 1, size(phases_use, 1)", 2) + ofile.write("if (input_vars_use) then", 3) + args = f"(phases_use(var_index), 'input')" + ofile.write(f"ptype_val = {bitfield_funcname}{args}", 4) + ofile.write("if (ptype_val == 0_int_kind) then", 4) + ofile.write(f"{errcode} = 1", 5) + emsg = "'No bitval for input phase = '//trim(phases_use(var_index))" + ofile.write(f"{errmsg} = {emsg}", 5) + ofile.write("return", 5) + ofile.write("end if", 4) + ofile.write("var_mask = var_mask + ptype_val", 4) + ofile.write("end if", 3) + ofile.write("if (output_vars_use) then", 3) + args = f"(phases_use(var_index), 'output')" + ofile.write(f"ptype_val = {bitfield_funcname}{args}", 4) + ofile.write("if (ptype_val == 0_int_kind) then", 4) + ofile.write(f"{errcode} = 1", 5) + emsg = "'No bitval for output phase = '//trim(phases_use(var_index))" + ofile.write(f"{errmsg} = {emsg}", 5) + ofile.write("return", 5) + ofile.write("end if", 4) + ofile.write("var_mask = var_mask + ptype_val", 4) + ofile.write("end if", 3) + ofile.write("end do", 2) + ofile.comment("Find the number of variables that match the pattern", 2) + ofile.write("num_vars = 0", 2) + ofile.write("do var_index = 1, size(suite_bitvals, 1)", 2) + ofile.write(f"if ({varmatch_funcname}(suite_bitvals(var_index))) then", + 3) + ofile.write("num_vars = num_vars + 1", 4) + ofile.write("end if", 3) + ofile.write("end do", 2) + ofile.write("allocate(variable_list(num_vars), stat=ierr)", 2) + ofile.write("if (ierr /= 0) then", 2) + ofile.write(f"{errcode} = ierr", 3) + ofile.write(f"{errmsg} = 'Unable to allocate variable_list'", 3) + ofile.write("return", 3) + ofile.write("end if", 2) + ofile.write("out_index = 0", 2) + ofile.write("do var_index = 1, size(suite_allvars, 1)", 2) + ofile.write(f"if ({varmatch_funcname}(suite_bitvals(var_index))) then", + 3) + ofile.write("out_index = out_index + 1", 4) + ofile.write("variable_list(out_index) = suite_allvars(var_index)", 4) + ofile.write("end if", 3) + ofile.write("end do", 2) + ofile.blank_line() + ofile.write("contains", 1) + ofile.blank_line() + ofile.comment("Find the bit value for this phase and inout type", 2) + int_type = "integer(int_kind)" + ofile.write(f"{int_type} function {bitfield_funcname}(phase, iotype)", 2) + ofile.write("character(len=*), intent(in) :: phase", 3) + ofile.write("character(len=*), intent(in) :: iotype", 3) + ofile.write("character(len=:), allocatable :: ptype_name", 3) + ofile.write("ptype_name = trim(phase)//'_'//trim(iotype)//'_bitpos'", 3) + ofile.write("select case(trim(ptype_name))", 3) + for vtype in self.__var_type_vals: + if vtype == self.__struct_element: + continue + # end if + ofile.write(f"case ('{vtype}')", 3) + ofile.write(f"{bitfield_funcname} = {vtype}", 4) + # end for + ofile.write("case default", 3) + ofile.comment("Error, return 0: Caller must trap", 4) + ofile.write(f"{bitfield_funcname} = 0", 4) + ofile.write("end select", 3) + ofile.write(f"end function {bitfield_funcname}", 2) + ofile.blank_line() + ofile.write(f"logical function {varmatch_funcname}(var_bfield)", 2) + ofile.write("integer(int_kind), intent(in) :: var_bfield", 3) + ofile.write(f"{varmatch_funcname} = .false.", 3) + etest = "if (struct_elements_use .or. " + etest += "(iand(struct_element, var_bfield) == 0_int_kind)) then" + ofile.write(etest, 3) + ofile.write(f"{varmatch_funcname} = iand(var_bfield, var_mask) /= 0", 4) + ofile.write("end if", 3) + ofile.write(f"end function {varmatch_funcname}", 2) + ofile.blank_line() + ofile.write(f"end subroutine {self.req_vars_subname()}", 1) + ofile.blank_line() + + def write(self, output_dir, run_env, check_dict, ddt_lib): """Create caps for all groups in the suite and for the entire suite (calling the group caps one after another)""" # Set name of module and filename of cap @@ -501,6 +796,18 @@ def write(self, output_dir, run_env): "CCPP Suite Cap for {}".format(self.name), self.module) as outfile: # Write module 'use' statements here + # For proper conversion of binary literals + bitmax = len(self.__var_type_vals) + 1 + if bitmax >= 64: + raise ParseInternalError("Not enough bits for Suite var types") + # end if + if bitmax >= 32: + int_str = "INT64" + else: + int_str = "INT32" + # end if + outfile.write(f"use ISO_FORTRAN_ENV, only: int_kind => {int_str}", + 1) outfile.write('use {}'.format(KINDS_MODULE), 1) # Look for any DDT types self.__ddt_library.write_ddt_use_statements(self.values(), @@ -516,8 +823,9 @@ def write(self, output_dir, run_env): outfile.write(line.format(css_var_name=var_name, state=var_state), 1) for group in self.__groups: - outfile.write('public :: {}'.format(group.name), 1) + outfile.write(f"public :: {group.name}", 1) # end for + outfile.write(f"public :: {self.req_vars_subname()}", 1) # Declare constituent public interfaces const_dict.declare_public_interfaces(outfile, 1) # Declare constituent private suite interfaces and data @@ -526,6 +834,66 @@ def write(self, output_dir, run_env): for svar in self.keys(): self[svar].write_def(outfile, 1, self, allocatable=True) # end for + # Declare suite variable variables and parameters + int_type = f"integer(int_kind)" + bitpos = 1 # Position in value where 1 is least significant + bitfld = 1 # Value of binary digit at bitpos + valbits = {} # Remember bitfld of each entry type + mspc = max([len(x) for x in self.__var_type_vals]) + vtype_names = [] + # First, the bitpos parameters + for vtype in self.__var_type_vals: + vfld = f"{vtype}{' '*(mspc - len(vtype))}" + bitstr = f"int(b'{bitfld:0{bitmax}b}', int_kind)" + outfile.write(f"{int_type}, parameter :: {vfld} = {bitstr}", 1) + valbits[f"{vtype}"] = bitfld + vtype_names.append(f'"{vfld}"') + bitpos += 1 + bitfld *= 2 + # end for + # Now some strings so we can find the right pos + oline = f"character(len={mspc}), private :: vartype_names" + oline += f"({len(valbits)}) = (/ {', '.join(vtype_names)} /)" + outfile.write(oline, 1) + # Collect all the Suite variables in one list + allvars = set() + allvars.update(self.__regular_vars.keys()) + allvars.update(self.__parent_vars.keys()) + allvars.update(self.__elem_vars.keys()) + # Change allvars to a list for a consistent ordering + allvars = sorted(allvars) + mspc = max([len(x) for x in allvars]) + nvars = len(allvars) + # Declare an array of all this Suite's variable standard names + decl = f"character(len={mspc}), private :: suite_allvars({nvars}) = " + vlist = ', '.join([f"\"{x}{' '*(mspc - len(x))}\"" for x in allvars]) + decl += f"(/ {vlist} /)" + outfile.write(decl, 1) + + # Declare a bitfield for each variable with phase and type info + decl = f"{int_type}, private :: suite_bitvals({nvars}) = " + vlist = [] + for stdname in allvars: + if stdname in self.__regular_vars: + var = self.__regular_vars[stdname] + varval = 0 + elif stdname in self.__parent_vars: + var = self.__parent_vars[stdname] + varval = 0 + elif stdname in self.__elem_vars: + var = self.__elem_vars[stdname] + varval = valbits[self.__struct_element] + else: + raise ParseInternalError(f"No var for '{stdname}'?") + # end if + # Compute value of variable + for entry in var: + varval += valbits[entry] + # end for + vlist.append(f"int(b'{varval:0{bitmax}b}', int_kind)") + # end for + decl += f"(/ {', '.join(vlist)} /)" + outfile.write(decl, 1) outfile.end_module_header() for group in self.__groups: if group.name in self._beg_groups: @@ -541,6 +909,8 @@ def write(self, output_dir, run_env): # end for err_vars = self.find_error_variables(any_scope=True, clone_as_out=True) + # Write the required variables subroutine for this suite + self.write_req_vars_sub(outfile, err_vars) # Write the constituent properties interface const_dict.write_constituent_routines(outfile, 1, self.name, err_vars) @@ -687,7 +1057,8 @@ def write(self, output_dir, run_env): api_filenames = list() # Write out the suite files for suite in self.suites: - out_file_name = suite.write(output_dir, run_env) + out_file_name = suite.write(output_dir, run_env, + self.parent, self.__ddt_lib) api_filenames.append(out_file_name) # end for return api_filenames @@ -760,308 +1131,47 @@ def write_suite_part_list_sub(self, ofile, errmsg_name, errcode_name): def write_req_vars_sub(self, ofile, errmsg_name, errcode_name): """Write the required variables subroutine""" oline = "suite_name, variable_list, {errmsg}, {errcode}" - oline += ", input_vars, output_vars, struct_elements" + oline += ", input_vars, output_vars, phases, struct_elements" inargs = oline.format(errmsg=errmsg_name, errcode=errcode_name) ofile.write("\nsubroutine {}({})".format(API.__vars_fname, inargs), 1) + # Declare use statements for suite varlist routines + mlen = max([len(x.module) for x in self.suites]) + for suite in self.suites: + mod = f"{suite.module}{' '*(mlen - len(suite.module))}" + ofile.write(f"use {mod}, only: {suite.req_vars_subname()}", 2) + # end for ofile.write("! Dummy arguments", 2) oline = "character(len=*), intent(in) :: suite_name" ofile.write(oline, 2) oline = "character(len=*), allocatable, intent(out) :: variable_list(:)" ofile.write(oline, 2) - self._errmsg_var.write_def(ofile, 2, self, extra_space=22) - self._errcode_var.write_def(ofile, 2, self, extra_space=22) - oline = "logical, optional, intent(in) :: input_vars" + self._errmsg_var.write_def(ofile, 2, self, extra_space=11, + dummy=True, add_intent="out") + self._errcode_var.write_def(ofile, 2, self, extra_space=11, + dummy=True, add_intent="out") + oline = "logical, optional, intent(in) :: input_vars" ofile.write(oline, 2) - oline = "logical, optional, intent(in) :: output_vars" + oline = "logical, optional, intent(in) :: output_vars" ofile.write(oline, 2) - oline = "logical, optional, intent(in) :: struct_elements" + oline = "character(len=*), optional, intent(in) :: phases(:)" ofile.write(oline, 2) - ofile.write("! Local variables", 2) - ofile.write("logical {}:: input_vars_use".format(' '*34), 2) - ofile.write("logical {}:: output_vars_use".format(' '*34), 2) - ofile.write("logical {}:: struct_elements_use".format(' '*34), 2) - ofile.write("integer {}:: num_vars".format(' '*34), 2) - ofile.write("", 0) - ename = self._errcode_var.get_prop_value('local_name') - ofile.write("{} = 0".format(ename), 2) - ename = self._errmsg_var.get_prop_value('local_name') - ofile.write("{} = ''".format(ename), 2) - ofile.write("if (present(input_vars)) then", 2) - ofile.write("input_vars_use = input_vars", 3) - ofile.write("else", 2) - ofile.write("input_vars_use = .true.", 3) - ofile.write("end if", 2) - ofile.write("if (present(output_vars)) then", 2) - ofile.write("output_vars_use = output_vars", 3) - ofile.write("else", 2) - ofile.write("output_vars_use = .true.", 3) - ofile.write("end if", 2) - ofile.write("if (present(struct_elements)) then", 2) - ofile.write("struct_elements_use = struct_elements", 3) - ofile.write("else", 2) - ofile.write("struct_elements_use = .true.", 3) - ofile.write("end if", 2) + oline = "logical, optional, intent(in) :: struct_elements" + ofile.write(oline, 2) + ofile.blank_line() + ecname = self._errcode_var.get_prop_value('local_name') + ofile.write(f"{ecname} = 0", 2) + emname = self._errmsg_var.get_prop_value('local_name') + ofile.write(f"{emname} = ''", 2) else_str = '' for suite in self.suites: - parent = suite.parent - # Collect all the suite variables - oline = "{}if(trim(suite_name) == '{}') then" - input_vars = [set(), set(), set()] # leaves, arrays, leaf elements - inout_vars = [set(), set(), set()] # leaves, arrays, leaf elements - output_vars = [set(), set(), set()] # leaves, arrays, 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 - 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, - 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' 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) - output_vars[2].update(elements) - else: - output_vars[0].add(stdname) - # end if - # end if - # end for - # end for - # Figure out how many total variables to return and allocate - # variable_list to that size - ofile.write(oline.format(else_str, suite.name), 2) - ofile.write("if (input_vars_use .and. output_vars_use) then", 3) - have_elems = input_vars[2] or inout_vars[2] or output_vars[2] - if have_elems: - ofile.write("if (struct_elements_use) then", 4) - numvars = len(input_vars[0] | input_vars[2] | inout_vars[0] | - inout_vars[2] | output_vars[0] | output_vars[2]) - ofile.write("num_vars = {}".format(numvars), 5) - ofile.write("else", 4) - # end if - numvars = len(input_vars[0] | input_vars[1] | inout_vars[0] | - inout_vars[1] | output_vars[0] | output_vars[1]) - ofile.write("num_vars = {}".format(numvars), 5 if have_elems else 4) - if have_elems: - ofile.write("end if", 4) - # end if - ofile.write("else if (input_vars_use) then", 3) - have_elems = input_vars[2] or inout_vars[2] - if have_elems: - ofile.write("if (struct_elements_use) then", 4) - numvars = len(input_vars[0] | input_vars[2] | - inout_vars[0] | inout_vars[2]) - ofile.write("num_vars = {}".format(numvars), 5) - ofile.write("else", 4) - # end if - numvars = len(input_vars[0] | input_vars[1] | - inout_vars[0] | inout_vars[1]) - ofile.write("num_vars = {}".format(numvars), 5 if have_elems else 4) - if have_elems: - ofile.write("end if", 4) - # end if - ofile.write("else if (output_vars_use) then", 3) - have_elems = inout_vars[2] or output_vars[2] - if have_elems: - ofile.write("if (struct_elements_use) then", 4) - numvars = len(inout_vars[0] | inout_vars[2] | - output_vars[0] | output_vars[2]) - ofile.write("num_vars = {}".format(numvars), 5) - ofile.write("else", 4) - # end if - numvars = len(inout_vars[0] | inout_vars[1] | - output_vars[0] | output_vars[1]) - ofile.write("num_vars = {}".format(numvars), 5 if have_elems else 4) - if have_elems: - ofile.write("end if", 4) - # end if - ofile.write("else", 3) - ofile.write("num_vars = 0", 4) - ofile.write("end if", 3) - ofile.write("allocate(variable_list(num_vars))", 3) - # Now, fill in the variable_list array - # Start with inout variables - elem_start = 1 - leaf_start = 1 - leaf_written_set = inout_vars[0].copy() - elem_written_set = inout_vars[0].copy() - leaf_list = sorted(inout_vars[0]) - if inout_vars[0] or inout_vars[1] or inout_vars[2]: - ofile.write("if (input_vars_use .or. output_vars_use) then", 3) - API.write_var_set_loop(ofile, 'variable_list', leaf_list, 4, - add_allocate=False, - start_index=leaf_start) - # end if - leaf_start += len(leaf_list) - elem_start += len(leaf_list) - # elements which have not been written out - elem_list = sorted(inout_vars[2] - elem_written_set) - elem_written_set = elem_written_set | inout_vars[2] - leaf_list = sorted(inout_vars[1] - leaf_written_set) - leaf_written_set = leaf_written_set | inout_vars[1] - if elem_list or leaf_list: - ofile.write("if (struct_elements_use) then", 4) - API.write_var_set_loop(ofile, 'variable_list', elem_list, 5, - add_allocate=False, - start_index=elem_start) - elem_start += len(elem_list) - ofile.write("num_vars = {}".format(elem_start - 1), 5) - ofile.write("else", 4) - API.write_var_set_loop(ofile, 'variable_list', leaf_list, 5, - add_allocate=False, - start_index=leaf_start) - leaf_start += len(leaf_list) - ofile.write("num_vars = {}".format(leaf_start - 1), 5) - ofile.write("end if", 4) - else: - ofile.write("num_vars = {}".format(len(leaf_written_set)), - 4 if leaf_written_set else 3) - # end if - if inout_vars[0] or inout_vars[1] or inout_vars[2]: - ofile.write("end if", 3) - # end if - # Write input variables - leaf_list = sorted(input_vars[0] - leaf_written_set) - # Are there any output variables which are also input variables - # (e.g., for a different part (group) of the suite)? - # We need to collect them now in case is selected - # but not . - leaf_cross_set = output_vars[0] & input_vars[0] - simp_cross_set = (output_vars[1] & input_vars[1]) - leaf_cross_set - elem_cross_set = (output_vars[2] & input_vars[2]) - leaf_cross_set - # Subtract the variables which have already been written out - leaf_cross_list = sorted(leaf_cross_set - leaf_written_set) - simp_cross_list = sorted(simp_cross_set - leaf_written_set) - elem_cross_list = sorted(elem_cross_set - elem_written_set) - # Next move back to processing the input variables - leaf_written_set = leaf_written_set | input_vars[0] - elem_list = sorted(input_vars[2] - elem_written_set) - elem_written_set = elem_written_set | input_vars[0] | input_vars[2] - have_inputs = elem_list or leaf_list - if have_inputs: - ofile.write("if (input_vars_use) then", 3) - # elements which have not been written out - # end if - API.write_var_set_loop(ofile, 'variable_list', leaf_list, 4, - add_allocate=False, start_var="num_vars", - start_index=1) - if leaf_list: - ofile.write("num_vars = num_vars + {}".format(len(leaf_list)), - 4) - # end if - leaf_start += len(leaf_list) - elem_start += len(leaf_list) - leaf_list = input_vars[1].difference(leaf_written_set) - leaf_written_set.union(input_vars[1]) - if elem_list or leaf_list: - ofile.write("if (struct_elements_use) then", 4) - API.write_var_set_loop(ofile, 'variable_list', elem_list, 5, - add_allocate=False, - start_index=elem_start) - elem_start += len(elem_list) - 1 - ofile.write("num_vars = {}".format(elem_start), 5) - ofile.write("else", 4) - API.write_var_set_loop(ofile, 'variable_list', leaf_list, 5, - add_allocate=False, - start_index=leaf_start) - leaf_start += len(leaf_list) - 1 - ofile.write("num_vars = {}".format(leaf_start), 5) - ofile.write("end if", 4) - # end if - if have_inputs: - ofile.write("end if", 3) - # end if - # Write output variables - leaf_list = sorted(output_vars[0].difference(leaf_written_set)) - leaf_written_set = leaf_written_set.union(output_vars[0]) - elem_written_set = elem_written_set.union(output_vars[0]) - elem_list = sorted(output_vars[2].difference(elem_written_set)) - elem_written_set = elem_written_set.union(output_vars[2]) - have_outputs = elem_list or leaf_list - if have_outputs: - ofile.write("if (output_vars_use) then", 3) - # end if - leaf_start = 1 - API.write_var_set_loop(ofile, 'variable_list', leaf_list, 4, - add_allocate=False, start_var="num_vars", - start_index=leaf_start) - leaf_start += len(leaf_list) - elem_start = leaf_start - leaf_list = output_vars[1].difference(leaf_written_set) - leaf_written_set.union(output_vars[1]) - if elem_list or leaf_list: - ofile.write("if (struct_elements_use) then", 4) - API.write_var_set_loop(ofile, 'variable_list', elem_list, 5, - add_allocate=False, start_var="num_vars", - start_index=elem_start) - elem_start += len(elem_list) - ofile.write("else", 4) - API.write_var_set_loop(ofile, 'variable_list', leaf_list, 5, - add_allocate=False, start_var="num_vars", - start_index=leaf_start) - leaf_start += len(leaf_list) - ofile.write("end if", 4) - # end if - if leaf_cross_list or elem_cross_list: - ofile.write("if (.not. input_vars_use) then", 4) - API.write_var_set_loop(ofile, 'variable_list', leaf_cross_list, - 5, add_allocate=False, - start_var="num_vars", - start_index=leaf_start) - leaf_start += len(leaf_cross_list) - elem_start += len(leaf_cross_list) - if elem_cross_list or simp_cross_list: - ofile.write("if (struct_elements_use) then", 5) - API.write_var_set_loop(ofile, 'variable_list', - elem_cross_list, 6, - add_allocate=False, - start_var="num_vars", - start_index=elem_start) - elem_start += len(elem_list) - ofile.write("else", 5) - API.write_var_set_loop(ofile, 'variable_list', - leaf_cross_list, 6, - add_allocate=False, - start_var="num_vars", - start_index=leaf_start) - leaf_start += len(leaf_list) - ofile.write("end if", 5) - # end if - ofile.write("end if", 4) - if have_outputs: - ofile.write("end if", 3) - # end if + oline = f"{else_str}if(trim(suite_name) == '{suite.name}') then" + ofile.write(oline, 2) else_str = 'else ' + args = ["variable_list", emname, ecname, "input_vars=input_vars", + "output_vars=output_vars", "phases=phases", + "struct_elements=struct_elements"] + ofile.write(f"call {suite.req_vars_subname()}({', '.join(args)})", + 3) # end for ofile.write("else", 2) emsg = "write({errmsg}, '(3a)')".format(errmsg=errmsg_name) diff --git a/scripts/constituents.py b/scripts/constituents.py index b4c9c0bb..d8209c53 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -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 diff --git a/scripts/metavar.py b/scripts/metavar.py index e2a22e24..7549f9ad 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -1004,15 +1004,15 @@ def write_def(self, outfile, indent, wdict, allocatable=False, intent = None # end if if protected and allocatable: - errmsg = 'Cannot create allocatable variable from protected, {}' + errmsg = "Cannot create allocatable variable from protected, {}" raise CCPPError(errmsg.format(name)) # end if if dummy and (intent is None): if add_intent is not None: intent = add_intent else: - errmsg = " is missing for dummy argument, {}" - raise CCPPError(errmsg.format(name)) + errmsg = f" is missing for dummy argument, {name}" + raise CCPPError(errmsg) # end if # end if if protected and dummy: @@ -1025,7 +1025,12 @@ def write_def(self, outfile, indent, wdict, allocatable=False, # end if elif intent is not None: alloval = self.get_prop_value('allocatable') - if (intent.lower()[-3:] == 'out') and alloval: + if alloval: + if intent.lower() == 'in': + # We should not have allocatable, intent(in), makes no sense + errmsg = f"{name} ({stdname}) is allocatable and intent(in)" + raise CCPPError(errmsg) + # end if intent_str = f"allocatable, intent({intent})" else: intent_str = f"intent({intent}){' '*(5 - len(intent))}" @@ -1064,7 +1069,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) diff --git a/test/var_action_test/run_test b/test/var_action_test/run_test index 2b4db0ac..d26cee1d 100755 --- a/test/var_action_test/run_test +++ b/test/var_action_test/run_test @@ -6,7 +6,7 @@ scriptdir="$( cd $( dirname $0 ); pwd -P )" ## ## Option default values ## -defdir="ct_build" +defdir="va_build" build_dir="${currdir}/${defdir}" cleanup="PASS" # Other supported options are ALWAYS and NEVER verbosity=0 From dad0dbdb1a5f5c529f2a893f7db6368a54c3cbf8 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Fri, 9 Jun 2023 11:48:58 -0600 Subject: [PATCH 029/159] remove initialized-in-physics constituent from advection test required variable arrays --- test/advection_test/test_host.F90 | 31 +++---------------------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index 604b6cb0..9c7abd42 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -490,31 +490,9 @@ program test implicit none character(len=cs), target :: test_parts1(1) - character(len=cm), target :: test_invars1(7) -! '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) -! '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) -! '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 ' /) + 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 @@ -522,7 +500,6 @@ 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 ', & @@ -533,13 +510,11 @@ 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 c477eaf866dfba99adc37d43fad3af2d15c35b0d Mon Sep 17 00:00:00 2001 From: testuser Date: Tue, 27 Jun 2023 15:47:46 -0400 Subject: [PATCH 030/159] Initial commit of containerized GitHub actions to run unit tests. --- .github/workflows/test.yaml | 13 +++++++++++++ Dockerfile | 9 +++++++++ 2 files changed, 22 insertions(+) create mode 100644 .github/workflows/test.yaml create mode 100644 Dockerfile diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 00000000..a65a5a50 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,13 @@ +name test + +on: [push] + +jobs: + unit_tests: + runs_on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: build Docker image + run: docker build -t ccpp . + - name: run tsts in container + run: docker run --name test-container -t ccpp bash -c 'cd ccpp/test && ./run_tests.sh' \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..838f32f1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM fedora:37 + +RUN dnf -y update && \ + dnf -y install cmake gcc-g++ gcc-fortran make git && \ + dnf clean all + +COPY . /ccpp/ + +ENTRYPOINT [ '/bin/bash' ] \ No newline at end of file From 43a47e42780a064a7e218c16154f063bf3497fe5 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 27 Jun 2023 15:51:30 -0400 Subject: [PATCH 031/159] Fixing typo from previous commit. --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index a65a5a50..2a9b3f80 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,4 +1,4 @@ -name test +name: test on: [push] From 5cc29b59688e4567cd014de05476a6cc130616c6 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 27 Jun 2023 15:52:28 -0400 Subject: [PATCH 032/159] Fixing typo from previous commit. --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2a9b3f80..182cb56c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -4,7 +4,7 @@ on: [push] jobs: unit_tests: - runs_on: ubuntu-latest + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: build Docker image From aaa12aa824fc1695f5290bb9dfd1cadae644b789 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 27 Jun 2023 15:58:24 -0400 Subject: [PATCH 033/159] Addming missing python dependency. --- .github/workflows/test.yaml | 2 +- Dockerfile | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 182cb56c..d4c8f497 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -10,4 +10,4 @@ jobs: - name: build Docker image run: docker build -t ccpp . - name: run tsts in container - run: docker run --name test-container -t ccpp bash -c 'cd ccpp/test && ./run_tests.sh' \ No newline at end of file + run: docker run --name test-container -t ccpp bash -c 'source ccpp-env/bin/activate && cd ccpp/test && ./run_tests.sh' \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 838f32f1..454612e1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,13 @@ FROM fedora:37 RUN dnf -y update && \ - dnf -y install cmake gcc-g++ gcc-fortran make git && \ + dnf -y install cmake gcc-g++ gcc-fortran make git python3 && \ dnf clean all +RUN python3 -m venv ./ccpp-env && \ + source ./ccpp-env/bin/activate && \ + pip3 install -y pytest black flake8 pylint + COPY . /ccpp/ ENTRYPOINT [ '/bin/bash' ] \ No newline at end of file From 92680b79119d11fd83dc847530006927600ca34e Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 27 Jun 2023 16:14:49 -0400 Subject: [PATCH 034/159] Removing incorrect flag from pip install. --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 454612e1..dfb7a3d2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,12 @@ FROM fedora:37 RUN dnf -y update && \ - dnf -y install cmake gcc-g++ gcc-fortran make git python3 && \ + dnf -y install cmake gcc-g++ gcc-fortran make git && \ dnf clean all RUN python3 -m venv ./ccpp-env && \ source ./ccpp-env/bin/activate && \ - pip3 install -y pytest black flake8 pylint + pip3 install pytest black flake8 pylint COPY . /ccpp/ From 3ff2d09a84383b734d113259d0ad9d991dba682f Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 27 Jun 2023 16:39:51 -0400 Subject: [PATCH 035/159] Replacing incorrect Dockerfile instruction. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index dfb7a3d2..04b6dbc9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,4 +10,4 @@ RUN python3 -m venv ./ccpp-env && \ COPY . /ccpp/ -ENTRYPOINT [ '/bin/bash' ] \ No newline at end of file +CMD ["/bin/bash"] \ No newline at end of file From 7cce8c5cf868956805d34c57735089293b2139de Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 27 Jun 2023 17:01:33 -0400 Subject: [PATCH 036/159] Returning non-zero if test failures detected. --- test/run_tests.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/test/run_tests.sh b/test/run_tests.sh index 97001d30..81ffdb00 100755 --- a/test/run_tests.sh +++ b/test/run_tests.sh @@ -67,4 +67,5 @@ if [ $errcnt -eq 0 ]; then echo "All tests PASSed!" else echo "${errcnt} tests FAILed" + return 1 fi From 8ac4c7ee43e35136ab3a9d0421c5027f6d7b651f Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Wed, 28 Jun 2023 13:56:21 -0400 Subject: [PATCH 037/159] Removing test failing due to not being fully implemented yet. --- test/run_tests.sh | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/run_tests.sh b/test/run_tests.sh index 81ffdb00..a7f07456 100755 --- a/test/run_tests.sh +++ b/test/run_tests.sh @@ -38,12 +38,13 @@ if [ $res -ne 0 ]; then fi # Run var_action test -./var_action_test/run_test -res=$? -errcnt=$((errcnt + res)) -if [ $res -ne 0 ]; then - echo "Failure running var_action test" -fi +# TODO: Re-enable after feature fully implemented. +# ./var_action_test/run_test +# res=$? +# errcnt=$((errcnt + res)) +# if [ $res -ne 0 ]; then +# echo "Failure running var_action test" +# fi # Run doctests ./run_doctest.sh From 00edf9a6f87dfed8dc5d685f08414be25a0e055b Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Wed, 28 Jun 2023 14:18:19 -0400 Subject: [PATCH 038/159] Addming missing newlines. --- .github/workflows/test.yaml | 3 ++- Dockerfile | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d4c8f497..af9778ba 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -10,4 +10,5 @@ jobs: - name: build Docker image run: docker build -t ccpp . - name: run tsts in container - run: docker run --name test-container -t ccpp bash -c 'source ccpp-env/bin/activate && cd ccpp/test && ./run_tests.sh' \ No newline at end of file + run: docker run --name test-container -t ccpp bash -c 'source ccpp-env/bin/activate && cd ccpp/test && ./run_tests.sh' + diff --git a/Dockerfile b/Dockerfile index 04b6dbc9..17a11a9c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,4 +10,5 @@ RUN python3 -m venv ./ccpp-env && \ COPY . /ccpp/ -CMD ["/bin/bash"] \ No newline at end of file +CMD ["/bin/bash"] + From f44f37763921a70dacf841607a2dcfca38762125 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 3 Jul 2023 00:33:54 -0600 Subject: [PATCH 039/159] add default_value to constituent properties object --- scripts/constituents.py | 3 ++ src/ccpp_constituent_prop_mod.F90 | 65 +++++++++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/scripts/constituents.py b/scripts/constituents.py index b4c9c0bb..7a60af04 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -301,6 +301,7 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): 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: @@ -314,6 +315,8 @@ 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: + 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(), diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index 7a26f126..03c87491 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -45,6 +45,9 @@ module ccpp_constituent_prop_mod 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 @@ -67,6 +70,7 @@ module ccpp_constituent_prop_mod procedure :: is_wet => ccp_is_wet procedure :: minimum => ccp_min_val procedure :: molec_weight => ccp_molec_weight + procedure :: default_value => ccp_default_value ! Copy method (be sure to update this anytime fields are added) procedure :: copyConstituent generic :: assignment(=) => copyConstituent @@ -99,6 +103,7 @@ module ccpp_constituent_prop_mod procedure :: is_wet => ccpt_is_wet procedure :: minimum => ccpt_min_val procedure :: molec_weight => ccpt_molec_weight + procedure :: default_value => ccpt_default_value ! ccpt_set: Set the internal pointer procedure :: set => ccpt_set ! Methods that change state (XXgoldyXX: make private?) @@ -197,6 +202,7 @@ subroutine copyConstituent(outConst, inConst) 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 !####################################################################### @@ -354,7 +360,7 @@ end function ccp_is_instantiated !####################################################################### subroutine ccp_instantiate(this, std_name, long_name, units, vertical_dim, & - advected, errcode, errmsg) + advected, default_value, errcode, errmsg) ! Initialize all fields in ! Dummy arguments @@ -364,6 +370,7 @@ subroutine ccp_instantiate(this, std_name, long_name, units, vertical_dim, & 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 @@ -385,6 +392,9 @@ subroutine ccp_instantiate(this, std_name, long_name, units, vertical_dim, & 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 @@ -434,6 +444,7 @@ subroutine ccp_deallocate(this) this%advected = .false. this%const_type = int_unassigned this%const_water = int_unassigned + this%const_default_value = kphys_unassigned end subroutine ccp_deallocate @@ -614,7 +625,8 @@ subroutine ccp_is_equivalent(this, oconst, equiv, errcode, errmsg) 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 @@ -762,6 +774,24 @@ subroutine ccp_molec_weight(this, val_out, errcode, errmsg) 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 + !######################################################################## ! ! CCPP_MODEL_CONSTITUENTS_T (constituent field data) methods @@ -1200,7 +1230,8 @@ 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 + 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 @@ -1224,7 +1255,11 @@ subroutine ccp_model_const_data_lock(this, ncols, num_layers, errcode, errmsg) end if if (astat == 0) then this%num_layers = num_layers - this%vars_layer = kphys_unassigned + 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 @@ -1969,6 +2004,28 @@ end subroutine ccpt_molec_weight !######################################################################## + subroutine ccpt_default_value(this, val_out, errcode, errmsg) + + ! 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 (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 ccpt_default_value + + !######################################################################## + subroutine ccpt_set(this, const_ptr, errcode, errmsg) ! Set the pointer to , however, an error is recorded if ! the pointer is already set. From d3413c15bea41132597beebe87b88e98fae5bb74 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Mon, 3 Jul 2023 10:03:00 -0400 Subject: [PATCH 040/159] Adding testing CI action to pull requests and all branches. --- .github/workflows/test.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index af9778ba..9354d2f5 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,6 +1,12 @@ name: test -on: [push] +on: + pull_request: + types: [opened, synchronize, reopened] + push: + branches: + #Trigger workflow on push to any branch or branch heirarchy: + - '**' jobs: unit_tests: From f3fa550ceb87c63b7777cea5f4c709596b1d7550 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Mon, 3 Jul 2023 10:04:54 -0400 Subject: [PATCH 041/159] Updating workflow name with more accurate description. --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 9354d2f5..4e33996e 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,4 +1,4 @@ -name: test +name: Capgen Unit Tests on: pull_request: From 7c5c3fb54d2e85ee3759a86e1d2ec8982918c9ec Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Mon, 3 Jul 2023 10:05:56 -0400 Subject: [PATCH 042/159] Renaming capgen unit test workflow file. --- .github/workflows/{test.yaml => capgen_unit_tests.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{test.yaml => capgen_unit_tests.yaml} (100%) diff --git a/.github/workflows/test.yaml b/.github/workflows/capgen_unit_tests.yaml similarity index 100% rename from .github/workflows/test.yaml rename to .github/workflows/capgen_unit_tests.yaml From 97cd70ae3520a9823f44601fcaeb92f750830488 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Mon, 3 Jul 2023 10:07:03 -0400 Subject: [PATCH 043/159] Fixing typo. --- .github/workflows/capgen_unit_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index 4e33996e..f3402a4d 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -15,6 +15,6 @@ jobs: - uses: actions/checkout@v3 - name: build Docker image run: docker build -t ccpp . - - name: run tsts in container + - name: run tests in container run: docker run --name test-container -t ccpp bash -c 'source ccpp-env/bin/activate && cd ccpp/test && ./run_tests.sh' From e5fdab9153eb84e3e7aaa3e7c50c955f0d072571 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Mon, 3 Jul 2023 10:09:50 -0400 Subject: [PATCH 044/159] Moving capgen Dockerfile into test directory. --- .github/workflows/capgen_unit_tests.yaml | 2 +- Dockerfile => test/Dockerfile | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename Dockerfile => test/Dockerfile (100%) diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index f3402a4d..4cc813ec 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: build Docker image - run: docker build -t ccpp . + run: docker build -f test/Dockerfile -t ccpp . - name: run tests in container run: docker run --name test-container -t ccpp bash -c 'source ccpp-env/bin/activate && cd ccpp/test && ./run_tests.sh' diff --git a/Dockerfile b/test/Dockerfile similarity index 100% rename from Dockerfile rename to test/Dockerfile From d5cc5f51afb8c28dce243447d648e867c1356f11 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Mon, 3 Jul 2023 10:40:00 -0400 Subject: [PATCH 045/159] Adding print statement to test output for reasong for skipping var_action_test. --- test/run_tests.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/test/run_tests.sh b/test/run_tests.sh index a7f07456..04ce5e8c 100755 --- a/test/run_tests.sh +++ b/test/run_tests.sh @@ -45,6 +45,7 @@ fi # if [ $res -ne 0 ]; then # echo "Failure running var_action test" # fi +echo "Skipping var_action_test/run_test until feature is fully implemented" # Run doctests ./run_doctest.sh From a10648a0848b664c315c7636df20a0001354f445 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Wed, 5 Jul 2023 09:54:41 -0400 Subject: [PATCH 046/159] Moving tests to running in github container directly. --- .github/workflows/capgen_unit_tests.yaml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index 4cc813ec..6b9da125 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -13,8 +13,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: build Docker image - run: docker build -f test/Dockerfile -t ccpp . - - name: run tests in container - run: docker run --name test-container -t ccpp bash -c 'source ccpp-env/bin/activate && cd ccpp/test && ./run_tests.sh' + - name: update repos and install dependencies + run: apt update && apt install -y cmake gcc-g++ gcc-fortran make python3 git + - name: create python venv + run: python3 -m venv ./ccpp-env && source ./ccpp-env/bin/activate && pip3 install -r requirements.txt + - name: Run unit tests + run: source ccpp-env/bin/activate && cd ccpp/test && ./run_tests.sh From 374a47302e53f7494cd60aabccaf4d88de11bb36 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Wed, 5 Jul 2023 09:55:57 -0400 Subject: [PATCH 047/159] Replacing apt calls with apt-get. --- .github/workflows/capgen_unit_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index 6b9da125..ed8fb950 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: update repos and install dependencies - run: apt update && apt install -y cmake gcc-g++ gcc-fortran make python3 git + run: apt-get update && apt-get install -y cmake gcc-g++ gcc-fortran make python3 git - name: create python venv run: python3 -m venv ./ccpp-env && source ./ccpp-env/bin/activate && pip3 install -r requirements.txt - name: Run unit tests From 7030d43c9cfab9f57dcef281efa7ea0103cd6d3b Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Wed, 5 Jul 2023 09:57:35 -0400 Subject: [PATCH 048/159] Adding sudo to apt-get commands. --- .github/workflows/capgen_unit_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index ed8fb950..0cfb18eb 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: update repos and install dependencies - run: apt-get update && apt-get install -y cmake gcc-g++ gcc-fortran make python3 git + run: sudo apt-get update && sudo apt-get install -y cmake gcc-g++ gcc-fortran make python3 git - name: create python venv run: python3 -m venv ./ccpp-env && source ./ccpp-env/bin/activate && pip3 install -r requirements.txt - name: Run unit tests From d0e2e22708ea01f0605fce52b3d391c67fdd8cb3 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Wed, 5 Jul 2023 10:01:33 -0400 Subject: [PATCH 049/159] Updating package names to ubuntu packages. --- .github/workflows/capgen_unit_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index 0cfb18eb..a0482f9f 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: update repos and install dependencies - run: sudo apt-get update && sudo apt-get install -y cmake gcc-g++ gcc-fortran make python3 git + run: sudo apt-get update && sudo apt-get install -y build-essential gfortran cmake python3 git - name: create python venv run: python3 -m venv ./ccpp-env && source ./ccpp-env/bin/activate && pip3 install -r requirements.txt - name: Run unit tests From 5e700515d166bea3fd2a648d400f0499ca2824e5 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Wed, 5 Jul 2023 10:05:54 -0400 Subject: [PATCH 050/159] Updating path to cd into. --- .github/workflows/capgen_unit_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index a0482f9f..17dba199 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -18,5 +18,5 @@ jobs: - name: create python venv run: python3 -m venv ./ccpp-env && source ./ccpp-env/bin/activate && pip3 install -r requirements.txt - name: Run unit tests - run: source ccpp-env/bin/activate && cd ccpp/test && ./run_tests.sh + run: source ccpp-env/bin/activate && cd test && ./run_tests.sh From e76bb45b7af94c993624243015873e13e95de9b7 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Wed, 5 Jul 2023 10:13:35 -0400 Subject: [PATCH 051/159] Removing test/Dockerfile as no longer needed. --- test/Dockerfile | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 test/Dockerfile diff --git a/test/Dockerfile b/test/Dockerfile deleted file mode 100644 index 17a11a9c..00000000 --- a/test/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM fedora:37 - -RUN dnf -y update && \ - dnf -y install cmake gcc-g++ gcc-fortran make git && \ - dnf clean all - -RUN python3 -m venv ./ccpp-env && \ - source ./ccpp-env/bin/activate && \ - pip3 install pytest black flake8 pylint - -COPY . /ccpp/ - -CMD ["/bin/bash"] - From 752f1f1f212348354baaeb5f4d445aca353df5e8 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Wed, 5 Jul 2023 11:24:03 -0400 Subject: [PATCH 052/159] Removing un-needed python dependencies install step. --- .github/workflows/capgen_unit_tests.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index 17dba199..4c2d6cdc 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -15,8 +15,6 @@ jobs: - uses: actions/checkout@v3 - name: update repos and install dependencies run: sudo apt-get update && sudo apt-get install -y build-essential gfortran cmake python3 git - - name: create python venv - run: python3 -m venv ./ccpp-env && source ./ccpp-env/bin/activate && pip3 install -r requirements.txt - name: Run unit tests run: source ccpp-env/bin/activate && cd test && ./run_tests.sh From 62a0d0376e2ba7951940e52b825f7da0d9b3f8d9 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Wed, 5 Jul 2023 11:25:26 -0400 Subject: [PATCH 053/159] Removing sourcing python venv. --- .github/workflows/capgen_unit_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index 4c2d6cdc..3dde695b 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -16,5 +16,5 @@ jobs: - name: update repos and install dependencies run: sudo apt-get update && sudo apt-get install -y build-essential gfortran cmake python3 git - name: Run unit tests - run: source ccpp-env/bin/activate && cd test && ./run_tests.sh + run: cd test && ./run_tests.sh From 2552b69c8741ab708c5a1c1500a9fe2155655dbe Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Thu, 6 Jul 2023 23:13:53 -0400 Subject: [PATCH 054/159] Filtering CI action to only auto run if in a PR or pushing to the main repo. --- .github/workflows/capgen_unit_tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index 3dde695b..02c60ced 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -10,6 +10,7 @@ on: jobs: unit_tests: + if: github.event_name == 'pull_request' || github.repository == 'NCAR/ccpp-framework' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 From 731a0cb66d53f1bb6c41b1b774268d815c476576 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Thu, 6 Jul 2023 23:20:42 -0400 Subject: [PATCH 055/159] Testing allowing run of actions manually. --- .github/workflows/capgen_unit_tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index 02c60ced..32bb77e2 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -7,6 +7,7 @@ on: branches: #Trigger workflow on push to any branch or branch heirarchy: - '**' + workflow_dispatch: jobs: unit_tests: From b4ba84c54db2c6e7b93499d5d4d099ab67471db9 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Thu, 6 Jul 2023 23:38:50 -0400 Subject: [PATCH 056/159] Adding proper filter to job to run unit test action manually. --- .github/workflows/capgen_unit_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index 32bb77e2..3fce1de9 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -11,7 +11,7 @@ on: jobs: unit_tests: - if: github.event_name == 'pull_request' || github.repository == 'NCAR/ccpp-framework' + if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.repository == 'NCAR/ccpp-framework' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 From af5119bba76fe90bf25388faa5c49512342238d1 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Fri, 7 Jul 2023 14:10:29 -0400 Subject: [PATCH 057/159] Fixing format of workflow_dispatch declaration. --- .github/workflows/capgen_unit_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index 3fce1de9..74347ff4 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -7,7 +7,7 @@ on: branches: #Trigger workflow on push to any branch or branch heirarchy: - '**' - workflow_dispatch: + workflow_dispatch jobs: unit_tests: From 813e46a632b6201b861197d0e97236b7ec22c46a Mon Sep 17 00:00:00 2001 From: mwaxmonsky <137746677+mwaxmonsky@users.noreply.github.com> Date: Fri, 7 Jul 2023 14:38:22 -0400 Subject: [PATCH 058/159] Changing ordering of workflow triggers. --- .github/workflows/capgen_unit_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index 74347ff4..eb574f2d 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -1,13 +1,13 @@ name: Capgen Unit Tests on: + workflow_dispatch: pull_request: types: [opened, synchronize, reopened] push: branches: #Trigger workflow on push to any branch or branch heirarchy: - '**' - workflow_dispatch jobs: unit_tests: From 08c5503c41f47883e29ae7529c030663f9f2fb6f Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Fri, 7 Jul 2023 15:43:26 -0400 Subject: [PATCH 059/159] Adding initial pylint configuration. --- .github/workflows/capgen_python_linting.yaml | 31 ++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/capgen_python_linting.yaml diff --git a/.github/workflows/capgen_python_linting.yaml b/.github/workflows/capgen_python_linting.yaml new file mode 100644 index 00000000..cb0db1b3 --- /dev/null +++ b/.github/workflows/capgen_python_linting.yaml @@ -0,0 +1,31 @@ +name: Capgen Python Linting + +on: + workflow_dispatch: + pull_request: + types: [opened, synchronize, reopened] + push: + branches: + #Trigger workflow on push to any branch or branch heirarchy: + - '**' + +jobs: + capgen_python_linting: + strategy: + matrix: + python-version: [3.7] + if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.repository == 'NCAR/ccpp-framework' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + pip3 install pylint + - name: Run pylint against capgen files + run: | + cd test && pylint From e28d5d504c11f5c7a57fe280dc07909eba2f1c62 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Fri, 7 Jul 2023 17:50:04 -0400 Subject: [PATCH 060/159] Removing pylint job to evaluate in a new PR. --- .github/workflows/capgen_python_linting.yaml | 31 -------------------- 1 file changed, 31 deletions(-) delete mode 100644 .github/workflows/capgen_python_linting.yaml diff --git a/.github/workflows/capgen_python_linting.yaml b/.github/workflows/capgen_python_linting.yaml deleted file mode 100644 index cb0db1b3..00000000 --- a/.github/workflows/capgen_python_linting.yaml +++ /dev/null @@ -1,31 +0,0 @@ -name: Capgen Python Linting - -on: - workflow_dispatch: - pull_request: - types: [opened, synchronize, reopened] - push: - branches: - #Trigger workflow on push to any branch or branch heirarchy: - - '**' - -jobs: - capgen_python_linting: - strategy: - matrix: - python-version: [3.7] - if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.repository == 'NCAR/ccpp-framework' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python3 -m pip install --upgrade pip - pip3 install pylint - - name: Run pylint against capgen files - run: | - cd test && pylint From b87a1f8994ee4dbcf4355d73fcd7e61c38f48317 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Tue, 11 Jul 2023 22:55:50 -0600 Subject: [PATCH 061/159] add has_default method --- src/ccpp_constituent_prop_mod.F90 | 44 +++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index 03c87491..df80ec0d 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -71,6 +71,7 @@ module ccpp_constituent_prop_mod 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 @@ -104,6 +105,7 @@ module ccpp_constituent_prop_mod 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?) @@ -792,6 +794,26 @@ subroutine ccp_default_value(this, val_out, errcode, errmsg) 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 @@ -2026,6 +2048,28 @@ end subroutine ccpt_default_value !######################################################################## + subroutine ccpt_has_default(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_has_default' + + if (associated(this%prop)) then + call this%prop%has_default(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_has_default + + !######################################################################## + subroutine ccpt_set(this, const_ptr, errcode, errmsg) ! Set the pointer to , however, an error is recorded if ! the pointer is already set. From 06c9d5d29abfea97e5dedcf41dee2e42a223d34a Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Tue, 30 May 2023 15:07:28 -0600 Subject: [PATCH 062/159] Add 'thermo_active' property to constituent properties DDT. --- src/ccpp_constituent_prop_mod.F90 | 142 ++++++++++++++++++++++++++---- 1 file changed, 125 insertions(+), 17 deletions(-) diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index df80ec0d..b151e7ff 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. @@ -1363,11 +1408,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 +1431,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 +1441,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 +1507,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 +1517,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 +1534,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 +1584,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 +1594,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 +1611,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 +1887,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 +2204,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 +2227,31 @@ 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%is_instantiated(errcode, errmsg)) then + this%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_const_index + + end module ccpp_constituent_prop_mod From dfe8cb1af970df9fb63e9938d2f13913545e7765 Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Wed, 31 May 2023 09:19:32 -0600 Subject: [PATCH 063/159] Update CCPP constituent standard names. --- scripts/constituents.py | 2 +- scripts/host_cap.py | 4 ++-- src/ccpp_constituent_prop_mod.meta | 16 ++++++++-------- test/unit_tests/sample_host_files/ddt1.meta | 2 +- test/unit_tests/sample_host_files/ddt1_plus.meta | 2 +- test/unit_tests/sample_host_files/ddt2.meta | 2 +- .../sample_host_files/ddt2_extra_var.meta | 2 +- .../sample_host_files/ddt_data1_mod.meta | 2 +- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/scripts/constituents.py b/scripts/constituents.py index 7a60af04..6a471cbb 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -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" diff --git a/scripts/host_cap.py b/scripts/host_cap.py index d833c649..70051f2a 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -209,7 +209,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" @@ -224,7 +224,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"] 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/ddt1.meta b/test/unit_tests/sample_host_files/ddt1.meta index 3b1b15ba..e1a0f1ac 100644 --- a/test/unit_tests/sample_host_files/ddt1.meta +++ b/test/unit_tests/sample_host_files/ddt1.meta @@ -16,5 +16,5 @@ standard_name = vars_array long_name = Array of vars managed by ddt1 units = none - dimensions = (horizontal_dimension, vertical_layer_dimension, ccpp_num_constituents) + 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.meta b/test/unit_tests/sample_host_files/ddt1_plus.meta index 78031b2a..ca3a92ab 100644 --- a/test/unit_tests/sample_host_files/ddt1_plus.meta +++ b/test/unit_tests/sample_host_files/ddt1_plus.meta @@ -16,5 +16,5 @@ standard_name = vars_array long_name = Array of vars managed by ddt2 units = none - dimensions = (horizontal_dimension, vertical_layer_dimension, ccpp_num_constituents) + 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.meta b/test/unit_tests/sample_host_files/ddt2.meta index 7412daf3..159f08b0 100644 --- a/test/unit_tests/sample_host_files/ddt2.meta +++ b/test/unit_tests/sample_host_files/ddt2.meta @@ -25,5 +25,5 @@ standard_name = vars_array long_name = Array of vars managed by ddt2 units = none - dimensions = (horizontal_dimension, vertical_layer_dimension, ccpp_num_constituents) + 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.meta b/test/unit_tests/sample_host_files/ddt2_extra_var.meta index 49256b2e..867720e5 100644 --- a/test/unit_tests/sample_host_files/ddt2_extra_var.meta +++ b/test/unit_tests/sample_host_files/ddt2_extra_var.meta @@ -25,7 +25,7 @@ standard_name = vars_array long_name = Array of vars managed by ddt2 units = none - dimensions = (horizontal_dimension, vertical_layer_dimension, ccpp_num_constituents) + dimensions = (horizontal_dimension, vertical_layer_dimension, number_of_ccpp_constituents) type = real | kind = kind_phys [ bogus ] standard_name = misplaced_variable diff --git a/test/unit_tests/sample_host_files/ddt_data1_mod.meta b/test/unit_tests/sample_host_files/ddt_data1_mod.meta index c3b14874..e149c07b 100644 --- a/test/unit_tests/sample_host_files/ddt_data1_mod.meta +++ b/test/unit_tests/sample_host_files/ddt_data1_mod.meta @@ -25,7 +25,7 @@ standard_name = vars_array long_name = Array of vars managed by ddt2 units = none - dimensions = (horizontal_dimension, vertical_layer_dimension, ccpp_num_constituents) + dimensions = (horizontal_dimension, vertical_layer_dimension, number_of_ccpp_constituents) type = real | kind = kind_phys ######################################################################## From 9fb2b40097d3a939e46610195fa03945d595f386 Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Mon, 26 Jun 2023 12:17:08 -0600 Subject: [PATCH 064/159] Fix thermo_active syntax errors caught by unit tests. --- src/ccpp_constituent_prop_mod.F90 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index b151e7ff..c9f5322b 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -1396,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 @@ -2243,15 +2244,14 @@ subroutine ccpt_set_thermo_active(this, thermo_flag, errcode, errmsg) character(len=*), parameter :: subname = 'ccpt_set_thermo_active' if (associated(this%prop)) then - if (this%is_instantiated(errcode, errmsg)) then - this%thermo_active = thermo_flag + 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_const_index - + end subroutine ccpt_set_thermo_active end module ccpp_constituent_prop_mod From 93eb6b3a2b86d2031022df208a55084271a9973e Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Mon, 26 Jun 2023 14:09:43 -0600 Subject: [PATCH 065/159] Add 'thermo_active' property tests to 'advection_test' unit test collection. --- test/advection_test/test_host.F90 | 40 ++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index 9c7abd42..d8ee7778 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -384,8 +384,46 @@ subroutine test_host(retval, test_suites) errflg = -1 end if end if + !Check that being thermodynamically active defaults to False: + if (errflg == 0) then + call const_props(index_ice)%is_thermo_active(check, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to get thermo_active prop for cld_ice index = ", index_ice, trim(errmsg) + end if + 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 = -1 + end if + end if + !Check that setting a constituent to be thermodynamiaclly active works + !as expected: + if (errflg == 0) then + call const_props(index_ice)%set_thermo_active(.true., errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to set thermo_active prop for cld_ice index = ", index_ice, trim(errmsg) + end if + end if + if (errflg == 0) then + call const_props(index_ice)%is_thermo_active(check, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to get thermo_active prop for cld_ice index = ", index_ice, trim(errmsg) + end if + end if + if (errflg == 0) then + if (.not.check) then !Should now be True + write(6, *) "ERROR: 'set_thermo_active' did not set thermo_active ", & + "constituent property correctly." + errflg = -1 + end if + end if - ! Use the suite information to setup the run + ! Use the suite information to setup the run do sind = 1, num_suites if (errflg == 0) then call test_host_ccpp_physics_initialize( & From fab788a794ca0d9b6b8c468057ef62f4000d12b0 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 18 Jul 2023 14:41:32 -0400 Subject: [PATCH 066/159] Attempt to add more python versions to python tests. --- .github/workflows/python.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index c630c438..f3d143e1 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7] + python-version: [3.7, 3.9, 3.11] steps: - uses: actions/checkout@v2 From fcd966c3a55aa691c1a35c5f3ad828be182af810 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 18 Jul 2023 14:45:45 -0400 Subject: [PATCH 067/159] Updating action versions to no longer use deprecated node12. --- .github/workflows/python.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index f3d143e1..468271f2 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -11,9 +11,9 @@ jobs: python-version: [3.7, 3.9, 3.11] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - name: Install dependencies From c9af06c42d9c36f0cbfc07ca6d98cba7e72bb334 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 18 Jul 2023 15:14:34 -0400 Subject: [PATCH 068/159] Updating python.yaml to run on demand. --- .github/workflows/python.yaml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index 468271f2..2b22c74e 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -1,10 +1,17 @@ name: Python package -on: [push] +on: + workflow_dispatch: + pull_request: + types: [opened, synchronize, reopened] + push: + branches: + #Trigger workflow on push to any branch or branch heirarchy: + - '**' jobs: build: - + if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.repository == 'NCAR/ccpp-framework' runs-on: ubuntu-latest strategy: matrix: @@ -22,7 +29,6 @@ jobs: pip install flake8 pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Test with pytest - if: github.repository == 'NCAR/ccpp-framework' # Only run on main repo run: | export PYTHONPATH=$(pwd)/scripts:$(pwd)/scripts/parse_tools pytest From 7417224aa17f2d70077b9617419f92a63c4503b6 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 18 Jul 2023 15:25:04 -0400 Subject: [PATCH 069/159] Temporarily adding push events back. --- .github/workflows/python.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index 2b22c74e..3513cb4e 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -11,7 +11,7 @@ on: jobs: build: - if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.repository == 'NCAR/ccpp-framework' + if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.repository == 'NCAR/ccpp-framework' || github.event_name == 'push' runs-on: ubuntu-latest strategy: matrix: From 861af3e2933ae85bc712258abe7a25b2ef6b29a3 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 18 Jul 2023 15:49:15 -0400 Subject: [PATCH 070/159] Updating metadata tests to check updated property name. --- tests/test_metadata_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_metadata_parser.py b/tests/test_metadata_parser.py index 0f7d18d6..0637adb0 100644 --- a/tests/test_metadata_parser.py +++ b/tests/test_metadata_parser.py @@ -51,7 +51,7 @@ def test_MetadataTable_parse_table(tmpdir): assert len(metadata_header.sections()) == 1 metadata_section = metadata_header.sections()[0] assert metadata_section.name == "" - assert metadata_section.type == "scheme" + assert metadata_section.header_type == "scheme" (im_data,) = metadata_section.variable_list() assert isinstance(im_data, Var) assert im_data.get_dimensions() == [] From c4df7a2a82d7b7a6724e430f6d921cc8ad677446 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Wed, 26 Jul 2023 11:59:19 -0400 Subject: [PATCH 071/159] Initial doctest action and associated fixes. --- .github/workflows/python.yaml | 23 +++++ pytest.ini | 2 +- scripts/code_block.py | 11 +- scripts/fortran_tools/parse_fortran.py | 4 + scripts/metadata_table.py | 18 +--- scripts/metavar.py | 12 +-- scripts/parse_tools/parse_object.py | 11 +- scripts/parse_tools/parse_source.py | 14 +-- scripts/parse_tools/xml_tools.py | 18 +--- scripts/var_props.py | 135 ++++++++++++++++--------- 10 files changed, 139 insertions(+), 109 deletions(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index 3513cb4e..3074eb7e 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -32,3 +32,26 @@ jobs: run: | export PYTHONPATH=$(pwd)/scripts:$(pwd)/scripts/parse_tools pytest + + doctest: + if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.repository == 'NCAR/ccpp-framework' || github.event_name == 'push' + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.7, 3.9, 3.11] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Doctest + run :| + export PYTHONPATH=$(pwd)/scripts:$(pwd)/scripts/parse_tools + pytest scripts -m --doctest-modules \ No newline at end of file diff --git a/pytest.ini b/pytest.ini index 83323d6a..9973f6f2 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,4 @@ [pytest] -addopts = -ra -q --ignore=tests/test_capgen.py +addopts = -ra --ignore=scripts/metadata2html.py testpaths = tests \ No newline at end of file diff --git a/scripts/code_block.py b/scripts/code_block.py index 96dc30e9..f649d70b 100644 --- a/scripts/code_block.py +++ b/scripts/code_block.py @@ -13,7 +13,7 @@ class CodeBlock(object): """Class to store a block of code and a method to write it to a file >>> CodeBlock([]) #doctest: +ELLIPSIS - <__main__.CodeBlock object at 0x...> + >>> CodeBlock(['hi mom']) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ParseInternalError: Each element of must contain exactly two items, a code string and a relative indent @@ -24,7 +24,10 @@ class CodeBlock(object): Traceback (most recent call last): ParseInternalError: Each element of must contain exactly two items, a code string and a relative indent >>> CodeBlock([('hi mom', 1)]) #doctest: +ELLIPSIS - <__main__.CodeBlock object at 0x...> + + >>> from fortran_tools import FortranWriter + >>> outfile_name = "__code_block_temp.F90" + >>> outfile = FortranWriter(outfile_name, 'w', 'test file', 'test_mod') >>> CodeBlock([('hi mom', 1)]).write(outfile, 1, {}) >>> CodeBlock([('hi {greet} mom', 1)]).write(outfile, 1, {}) #doctest: +IGNORE_EXCEPTION_DETAIL @@ -32,6 +35,10 @@ class CodeBlock(object): ParseInternalError: 'greet' missing from >>> CodeBlock([('hi {{greet}} mom', 1)]).write(outfile, 1, {}) >>> CodeBlock([('{greet} there mom', 1)]).write(outfile, 1, {'greet':'hi'}) + >>> outfile.__exit__() + False + >>> import os + >>> os.remove(outfile_name) """ __var_re = re.compile(r"[{][ ]*([A-Za-z][A-Za-z0-9_]*)[ ]*[}]") diff --git a/scripts/fortran_tools/parse_fortran.py b/scripts/fortran_tools/parse_fortran.py index 2310e13f..c1a6eda6 100644 --- a/scripts/fortran_tools/parse_fortran.py +++ b/scripts/fortran_tools/parse_fortran.py @@ -667,6 +667,10 @@ def parse_fortran_var_decl(line, source, run_env): '(8)' >>> _VAR_ID_RE.match("foo(::,a:b,a:,:b)").group(2) '(::,a:b,a:,:b)' + >>> from framework_env import CCPPFrameworkEnv + >>> _DUMMY_RUN_ENV = CCPPFrameworkEnv(None, ndict={'host_files':'', \ + 'scheme_files':'', \ + 'suites':''}) >>> parse_fortran_var_decl("integer :: foo", ParseSource('foo.F90', 'module', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('local_name') 'foo' >>> parse_fortran_var_decl("integer :: foo = 0", ParseSource('foo.F90', 'module', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('local_name') diff --git a/scripts/metadata_table.py b/scripts/metadata_table.py index 5e9820d7..7b2da3f7 100644 --- a/scripts/metadata_table.py +++ b/scripts/metadata_table.py @@ -504,6 +504,10 @@ def table_start(cls, line): class MetadataSection(ParseSource): """Class to hold all information from a metadata header + >>> from framework_env import CCPPFrameworkEnv + >>> _DUMMY_RUN_ENV = CCPPFrameworkEnv(None, {'host_files':'', \ + 'scheme_files':'', \ + 'suites':''}) >>> MetadataSection("footable", "scheme", _DUMMY_RUN_ENV, \ parse_object=ParseObject("foobar.txt", \ ["name = footable", "type = scheme", "module = foo", \ @@ -511,7 +515,7 @@ class MetadataSection(ParseSource): "long_name = horizontal loop extent, start at 1", \ "units = index | type = integer", \ "dimensions = () | intent = in"])) #doctest: +ELLIPSIS - <__main__.MetadataSection foo / footable at 0x...> + >>> MetadataSection("footable", "scheme", _DUMMY_RUN_ENV, \ parse_object=ParseObject("foobar.txt", \ ["name = footable", "type = scheme", "module = foobar", \ @@ -1267,15 +1271,3 @@ def is_scalar_reference(test_val): return check_fortran_ref(test_val, None, False) is not None ######################################################################## - -if __name__ == "__main__": -# pylint: enable=ungrouped-imports - import doctest - import sys -# pylint: disable=ungrouped-imports - from framework_env import CCPPFrameworkEnv - _DUMMY_RUN_ENV = CCPPFrameworkEnv(None, {'host_files':'', - 'scheme_files':'', - 'suites':''}) - fail, _ = doctest.testmod() - sys.exit(fail) diff --git a/scripts/metavar.py b/scripts/metavar.py index e2a22e24..de8be65e 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -1418,11 +1418,11 @@ class VarDictionary(OrderedDict): >>> VarDictionary('bar', _MVAR_DUMMY_RUN_ENV, variables={}) VarDictionary(bar) >>> VarDictionary('baz', _MVAR_DUMMY_RUN_ENV, variables=Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV)) #doctest: +ELLIPSIS - VarDictionary(baz, [('hi_mom', <__main__.Var hi_mom: foo at 0x...>)]) + VarDictionary(baz, [('hi_mom', )]) >>> print("{}".format(VarDictionary('baz', _MVAR_DUMMY_RUN_ENV, variables=Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV)))) VarDictionary(baz, ['hi_mom']) >>> VarDictionary('qux', _MVAR_DUMMY_RUN_ENV, variables=[Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV)]) #doctest: +ELLIPSIS - VarDictionary(qux, [('hi_mom', <__main__.Var hi_mom: foo at 0x...>)]) + VarDictionary(qux, [('hi_mom', )]) >>> VarDictionary('boo', _MVAR_DUMMY_RUN_ENV).add_variable(Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV), _MVAR_DUMMY_RUN_ENV) >>> VarDictionary('who', _MVAR_DUMMY_RUN_ENV, variables=[Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV)]).prop_list('local_name') @@ -2023,11 +2023,3 @@ def new_internal_variable_name(self, prefix=None, max_len=63): _MVAR_DUMMY_RUN_ENV)]) ############################################################################### -if __name__ == "__main__": - # pylint: disable=ungrouped-imports - import doctest - import sys - # pylint: enable=ungrouped-imports - fail, _ = doctest.testmod() - sys.exit(fail) -# end if diff --git a/scripts/parse_tools/parse_object.py b/scripts/parse_tools/parse_object.py index f141b298..2c5f72f5 100644 --- a/scripts/parse_tools/parse_object.py +++ b/scripts/parse_tools/parse_object.py @@ -10,7 +10,7 @@ class ParseObject(ParseContext): """ParseObject is a simple class that keeps track of an object's place in a file and safely produces lines from an array of lines >>> ParseObject('foobar.F90', []) #doctest: +ELLIPSIS - <__main__.ParseObject object at 0x...> + >>> ParseObject('foobar.F90', []).filename 'foobar.F90' >>> ParseObject('foobar.F90', ["##hi mom",], line_start=1).curr_line() @@ -164,12 +164,3 @@ def __del__(self): # end try ######################################################################## - -if __name__ == "__main__": - # pylint: disable=ungrouped-imports - import doctest - import sys - # pylint: enable=ungrouped-imports - fail, _ = doctest.testmod() - sys.exit(fail) -# end if diff --git a/scripts/parse_tools/parse_source.py b/scripts/parse_tools/parse_source.py index 0447225a..a27f04f3 100644 --- a/scripts/parse_tools/parse_source.py +++ b/scripts/parse_tools/parse_source.py @@ -207,10 +207,10 @@ def __getitem__(self, index): class ParseContext: """A class for keeping track of a parsing position >>> ParseContext(32, "source.F90") #doctest: +ELLIPSIS - <__main__.ParseContext object at 0x...> + >>> ParseContext("source.F90", 32) Traceback (most recent call last): - CCPPError: ParseContext linenum must be an int + parse_tools.parse_source.CCPPError: ParseContext linenum must be an int >>> ParseContext(32, 90) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: ParseContext filenum must be a string @@ -381,7 +381,7 @@ class ParseSource: """ A simple object for providing source information >>> ParseSource("myname", "mytype", ParseContext(13, "foo.F90")) #doctest: +ELLIPSIS - <__main__.ParseSource object at 0x...> + >>> ParseSource("myname", "mytype", ParseContext(13, "foo.F90")).ptype 'mytype' >>> ParseSource("myname", "mytype", ParseContext(13, "foo.F90")).name @@ -412,11 +412,3 @@ def context(self): return self.__context ######################################################################## - -if __name__ == "__main__": - # pylint: disable=ungrouped-imports - import doctest - # pylint: enable=ungrouped-imports - fail, _ = doctest.testmod() - sys.exit(fail) -# end if diff --git a/scripts/parse_tools/xml_tools.py b/scripts/parse_tools/xml_tools.py index d5a767b5..80d96b88 100644 --- a/scripts/parse_tools/xml_tools.py +++ b/scripts/parse_tools/xml_tools.py @@ -42,6 +42,8 @@ def call_command(commands, logger, silent=False): ############################################################################### """ Try a command line and return the output on success (None on failure) + >>> _LOGGER = init_log('xml_tools') + >>> set_log_to_null(_LOGGER) >>> call_command(['ls', 'really__improbable_fffilename.foo'], _LOGGER) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: Execution of 'ls really__improbable_fffilename.foo' failed: @@ -358,19 +360,3 @@ def write(self, file, encoding="us-ascii", xml_declaration=None, # end with ############################################################################## - -if __name__ == "__main__": - _LOGGER = init_log('xml_tools') - set_log_to_null(_LOGGER) - try: - # First, run doctest - # pylint: disable=ungrouped-imports - import doctest - # pylint: enable=ungrouped-imports - fail, _ = doctest.testmod() - sys.exit(fail) - except CCPPError as cerr: - print("{}".format(cerr)) - sys.exit(fail) - # end try -# end if diff --git a/scripts/var_props.py b/scripts/var_props.py index 208fba89..c683b059 100755 --- a/scripts/var_props.py +++ b/scripts/var_props.py @@ -199,7 +199,7 @@ def default_kind_val(prop_dict, context=None): >>> default_kind_val({'local_name':'foo'}) #doctest: +ELLIPSIS Traceback (most recent call last): ... - parse_source.CCPPError: No type to find default kind for foo + parse_source.CCPPError: No type to find default kind for foo >>> default_kind_val({}) #doctest: +ELLIPSIS Traceback (most recent call last): ... @@ -207,7 +207,7 @@ def default_kind_val(prop_dict, context=None): >>> default_kind_val({'local_name':'foo'}, context=ParseContext(linenum=3, filename='foo.F90')) #doctest: +ELLIPSIS Traceback (most recent call last): ... - parse_source.CCPPError: No type to find default kind for foo, at foo.F90:4 + parse_source.CCPPError: No type to find default kind for foo, at foo.F90:4 >>> default_kind_val({}, context=ParseContext(linenum=3, filename='foo.F90')) #doctest: +ELLIPSIS Traceback (most recent call last): ... @@ -516,25 +516,25 @@ def __is_horizontal_loop_dimension(hdim): class VariableProperty: """Class to represent a single property of a metadata header entry >>> VariableProperty('local_name', str) #doctest: +ELLIPSIS - <__main__.VariableProperty object at ...> + >>> VariableProperty('standard_name', str) #doctest: +ELLIPSIS - <__main__.VariableProperty object at ...> + >>> VariableProperty('long_name', str) #doctest: +ELLIPSIS - <__main__.VariableProperty object at ...> + >>> VariableProperty('units', str) #doctest: +ELLIPSIS - <__main__.VariableProperty object at ...> + >>> VariableProperty('dimensions', list) #doctest: +ELLIPSIS - <__main__.VariableProperty object at ...> + >>> VariableProperty('type', str) #doctest: +ELLIPSIS - <__main__.VariableProperty object at ...> + >>> VariableProperty('kind', str) #doctest: +ELLIPSIS - <__main__.VariableProperty object at ...> + >>> VariableProperty('state_variable', str, valid_values_in=['True', 'False', '.true.', '.false.' ], optional_in=True, default_in=False) #doctest: +ELLIPSIS - <__main__.VariableProperty object at ...> + >>> VariableProperty('intent', str, valid_values_in=['in', 'out', 'inout']) #doctest: +ELLIPSIS - <__main__.VariableProperty object at ...> + >>> VariableProperty('optional', str, valid_values_in=['True', 'False', '.true.', '.false.' ], optional_in=True, default_in=False) #doctest: +ELLIPSIS - <__main__.VariableProperty object at ...> + >>> VariableProperty('local_name', str).name 'local_name' >>> VariableProperty('standard_name', str).ptype == str @@ -778,31 +778,41 @@ class VarCompatObj: character(len=) # Test that we can create a standard VarCompatObj object + >>> from parse_tools import init_log, set_log_to_null + >>> _DOCTEST_LOGGING = init_log('var_props') + >>> set_log_to_null(_DOCTEST_LOGGING) + >>> _DOCTEST_RUNENV = CCPPFrameworkEnv(_DOCTEST_LOGGING, \ + ndict={'host_files':'', \ + 'scheme_files':'', \ + 'suites':''}, \ + kind_types=["kind_phys=REAL64", \ + "kind_dyn=REAL32", \ + "kind_host=REAL64"]) >>> VarCompatObj("var_stdname", "real", "kind_phys", "m", [], \ "var1_lname", "var_stdname", "real", "kind_phys", \ "m", [], "var2_lname", _DOCTEST_RUNENV) #doctest: +ELLIPSIS - <__main__.VarCompatObj object at 0x...> + # Test that a 2-D var with no horizontal transform works >>> VarCompatObj("var_stdname", "real", "kind_phys", "m", \ ['horizontal_dimension'], "var1_lname", "var_stdname", \ "real", "kind_phys", "m", ['horizontal_dimension'], \ "var2_lname", _DOCTEST_RUNENV) #doctest: +ELLIPSIS - <__main__.VarCompatObj object at 0x...> + # Test that a 2-D var with a horizontal transform works >>> VarCompatObj("var_stdname", "real", "kind_phys", "m", \ ['horizontal_dimension'], "var1_lname", "var_stdname", \ "real", "kind_phys", "m", ['horizontal_loop_extent'], \ "var2_lname", _DOCTEST_RUNENV) #doctest: +ELLIPSIS - <__main__.VarCompatObj object at 0x...> + # Test that a 2-D var with unit conversion m->km works >>> VarCompatObj("var_stdname", "real", "kind_phys", "m", \ ['horizontal_dimension'], "var1_lname", "var_stdname", \ "real", "kind_phys", "km", ['horizontal_dimension'], \ "var2_lname", _DOCTEST_RUNENV) #doctest: +ELLIPSIS - <__main__.VarCompatObj object at 0x...> + # Test that a 2-D var with unit conversion m->km works and that it # produces the correct forward transformation @@ -1027,6 +1037,26 @@ def _get_kind_convstrs(self, var1_kind, var2_kind, run_env): If a conversion is required, return a tuple with the two kinds, i.e., (var1_kind, var2_kind). + # Initial setup + >>> from parse_tools import init_log, set_log_to_null + >>> _DOCTEST_LOGGING = init_log('var_props') + >>> set_log_to_null(_DOCTEST_LOGGING) + >>> _DOCTEST_RUNENV = CCPPFrameworkEnv(_DOCTEST_LOGGING, \ + ndict={'host_files':'', \ + 'scheme_files':'', \ + 'suites':''}, \ + kind_types=["kind_phys=REAL64", \ + "kind_dyn=REAL32", \ + "kind_host=REAL64"]) + >>> _DOCTEST_CONTEXT1 = ParseContext(linenum=3, filename='foo.F90') + >>> _DOCTEST_CONTEXT2 = ParseContext(linenum=5, filename='bar.F90') + >>> _DOCTEST_VCOMPAT = VarCompatObj("var_stdname", "real", "kind_phys", \ + "m", [], "var1_lname", "var_stdname", \ + "real", "kind_phys", "m", [], \ + "var2_lname", _DOCTEST_RUNENV, \ + v1_context=_DOCTEST_CONTEXT1, \ + v2_context=_DOCTEST_CONTEXT2) + # Try some kind conversions >>> _DOCTEST_VCOMPAT._get_kind_convstrs('kind_phys', 'kind_dyn', \ _DOCTEST_RUNENV) @@ -1061,6 +1091,26 @@ def _get_unit_convstrs(self, var1_units, var2_units): for transforming a variable in to / from a variable in . + # Initial setup + >>> from parse_tools import init_log, set_log_to_null + >>> _DOCTEST_LOGGING = init_log('var_props') + >>> set_log_to_null(_DOCTEST_LOGGING) + >>> _DOCTEST_RUNENV = CCPPFrameworkEnv(_DOCTEST_LOGGING, \ + ndict={'host_files':'', \ + 'scheme_files':'', \ + 'suites':''}, \ + kind_types=["kind_phys=REAL64", \ + "kind_dyn=REAL32", \ + "kind_host=REAL64"]) + >>> _DOCTEST_CONTEXT1 = ParseContext(linenum=3, filename='foo.F90') + >>> _DOCTEST_CONTEXT2 = ParseContext(linenum=5, filename='bar.F90') + >>> _DOCTEST_VCOMPAT = VarCompatObj("var_stdname", "real", "kind_phys", \ + "m", [], "var1_lname", "var_stdname", \ + "real", "kind_phys", "m", [], \ + "var2_lname", _DOCTEST_RUNENV, \ + v1_context=_DOCTEST_CONTEXT1, \ + v2_context=_DOCTEST_CONTEXT2) + # Try some working unit transforms >>> _DOCTEST_VCOMPAT._get_unit_convstrs('m', 'mm') ('1.0E+3{kind}*{var}', '1.0E-3{kind}*{var}') @@ -1113,20 +1163,40 @@ def _get_dim_transforms(self, var1_dims, var2_dims): The reverse dimension transformation is a permutation of the indices of the second variable to the first. + # Initial setup + >>> from parse_tools import init_log, set_log_to_null + >>> _DOCTEST_LOGGING = init_log('var_props') + >>> set_log_to_null(_DOCTEST_LOGGING) + >>> _DOCTEST_RUNENV = CCPPFrameworkEnv(_DOCTEST_LOGGING, \ + ndict={'host_files':'', \ + 'scheme_files':'', \ + 'suites':''}, \ + kind_types=["kind_phys=REAL64", \ + "kind_dyn=REAL32", \ + "kind_host=REAL64"]) + >>> _DOCTEST_CONTEXT1 = ParseContext(linenum=3, filename='foo.F90') + >>> _DOCTEST_CONTEXT2 = ParseContext(linenum=5, filename='bar.F90') + >>> _DOCTEST_VCOMPAT = VarCompatObj("var_stdname", "real", "kind_phys", \ + "m", [], "var1_lname", "var_stdname", \ + "real", "kind_phys", "m", [], \ + "var2_lname", _DOCTEST_RUNENV, \ + v1_context=_DOCTEST_CONTEXT1, \ + v2_context=_DOCTEST_CONTEXT2) + # Test simple permutations >>> _DOCTEST_VCOMPAT._get_dim_transforms(['horizontal_dimension', \ 'vertical_layer_dimension'], \ ['vertical_layer_dimension', \ 'horizontal_dimension']) \ #doctest: +ELLIPSIS - <__main__.DimTransform object at 0x...> + >>> _DOCTEST_VCOMPAT._get_dim_transforms(['horizontal_dimension', \ 'vertical_layer_dimension', \ 'xdim'], \ ['vertical_layer_dimension', \ 'horizontal_dimension', \ 'xdim']) #doctest: +ELLIPSIS - <__main__.DimTransform object at 0x...> + >>> _DOCTEST_VCOMPAT._get_dim_transforms(['horizontal_dimension', \ 'vertical_layer_dimension', \ 'xdim'], \ @@ -1134,7 +1204,7 @@ def _get_dim_transforms(self, var1_dims, var2_dims): 'horizontal_dimension', \ 'vertical_layer_dimension']) \ #doctest: +ELLIPSIS - <__main__.DimTransform object at 0x...> + # Test some mismatch sets >>> _DOCTEST_VCOMPAT._get_dim_transforms(['horizontal_dimension', \ @@ -1338,30 +1408,3 @@ def __bool__(self): return self.equiv ############################################################################### -if __name__ == "__main__": - # pylint: disable=ungrouped-imports - import doctest - import sys - from parse_tools import init_log, set_log_to_null - # pylint: enable=ungrouped-imports - _DOCTEST_LOGGING = init_log('var_props') - set_log_to_null(_DOCTEST_LOGGING) - _DOCTEST_RUNENV = CCPPFrameworkEnv(_DOCTEST_LOGGING, - ndict={'host_files':'', - 'scheme_files':'', - 'suites':''}, - kind_types=["kind_phys=REAL64", - "kind_dyn=REAL32", - "kind_host=REAL64"]) - _DOCTEST_CONTEXT1 = ParseContext(linenum=3, filename='foo.F90') - _DOCTEST_CONTEXT2 = ParseContext(linenum=5, filename='bar.F90') - _DOCTEST_VCOMPAT = VarCompatObj("var_stdname", "real", "kind_phys", - "m", [], "var1_lname", "var_stdname", - "real", "kind_phys", "m", [], - "var2_lname", _DOCTEST_RUNENV, - v1_context=_DOCTEST_CONTEXT1, - v2_context=_DOCTEST_CONTEXT2) - OPTIONS = doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE - fail, _ = doctest.testmod(optionflags=OPTIONS) - sys.exit(fail) -# end if From 1e760fa81617bc52d1469ecb94ec3aadd2f5f1d1 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Wed, 26 Jul 2023 12:01:55 -0400 Subject: [PATCH 072/159] Fixing actions file syntax error. --- .github/workflows/python.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index 3074eb7e..8f888735 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -52,6 +52,6 @@ jobs: pip install pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Doctest - run :| + run : | export PYTHONPATH=$(pwd)/scripts:$(pwd)/scripts/parse_tools pytest scripts -m --doctest-modules \ No newline at end of file From 20d20441a6c4ddc169fa9904d6f40612a4027df8 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Wed, 26 Jul 2023 12:03:14 -0400 Subject: [PATCH 073/159] Fixing doctest call. --- .github/workflows/python.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index 8f888735..1989aef7 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -54,4 +54,4 @@ jobs: - name: Doctest run : | export PYTHONPATH=$(pwd)/scripts:$(pwd)/scripts/parse_tools - pytest scripts -m --doctest-modules \ No newline at end of file + pytest scripts/ --doctest-modules \ No newline at end of file From 7b42f73030e217fad2d8b956b028478aa82ef775 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 1 Aug 2023 10:40:24 -0400 Subject: [PATCH 074/159] Fixing shell script syntax error. --- test/run_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/run_tests.sh b/test/run_tests.sh index 04ce5e8c..6d259ebe 100755 --- a/test/run_tests.sh +++ b/test/run_tests.sh @@ -69,5 +69,5 @@ if [ $errcnt -eq 0 ]; then echo "All tests PASSed!" else echo "${errcnt} tests FAILed" - return 1 + exit 1 fi From 6696865bb6bea5d5c405b45c7aa5c5bc7ac86de7 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 1 Aug 2023 10:49:53 -0400 Subject: [PATCH 075/159] Removing doctest from main unit test script as no longer needed with new doctest action. --- test/run_tests.sh | 8 -------- 1 file changed, 8 deletions(-) diff --git a/test/run_tests.sh b/test/run_tests.sh index 6d259ebe..7b7b0585 100755 --- a/test/run_tests.sh +++ b/test/run_tests.sh @@ -47,14 +47,6 @@ fi # fi echo "Skipping var_action_test/run_test until feature is fully implemented" -# Run doctests -./run_doctest.sh -res=$? -errcnt=$((errcnt + res)) -if [ $res -ne 0 ]; then - echo "${errcnt} doctest failures" -fi - for test in `ls unit_tests/test_*.py`; do echo "Running unit test, ${test}" python3 ${test} From 57b2e0d6ca71f988dda598f1d4edd569d3e471e7 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 1 Aug 2023 10:59:17 -0400 Subject: [PATCH 076/159] Fixing no newline in some files. --- .github/workflows/python.yaml | 2 +- pytest.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index 1989aef7..fc432afa 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -54,4 +54,4 @@ jobs: - name: Doctest run : | export PYTHONPATH=$(pwd)/scripts:$(pwd)/scripts/parse_tools - pytest scripts/ --doctest-modules \ No newline at end of file + pytest scripts/ --doctest-modules diff --git a/pytest.ini b/pytest.ini index 9973f6f2..cc493590 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,4 @@ [pytest] addopts = -ra --ignore=scripts/metadata2html.py testpaths = - tests \ No newline at end of file + tests From 8065f821fd63b2e19d91d37df0dab855f425ec4a Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 1 Aug 2023 11:17:10 -0400 Subject: [PATCH 077/159] Removing push events. --- .github/workflows/python.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index fc432afa..b9ca0eba 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -11,7 +11,7 @@ on: jobs: build: - if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.repository == 'NCAR/ccpp-framework' || github.event_name == 'push' + if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.repository == 'NCAR/ccpp-framework' runs-on: ubuntu-latest strategy: matrix: @@ -34,7 +34,7 @@ jobs: pytest doctest: - if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.repository == 'NCAR/ccpp-framework' || github.event_name == 'push' + if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.repository == 'NCAR/ccpp-framework' runs-on: ubuntu-latest strategy: matrix: From 9f1573f629d46e142fc33cee7c28a57f00f96526 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 1 Aug 2023 15:10:28 -0400 Subject: [PATCH 078/159] Initial attempt of removing python 2 code checks. --- scripts/ccpp_datafile.py | 4 -- scripts/file_utils.py | 12 +----- scripts/parse_tools/parse_source.py | 8 +--- scripts/parse_tools/xml_tools.py | 57 +++++++++-------------------- 4 files changed, 20 insertions(+), 61 deletions(-) diff --git a/scripts/ccpp_datafile.py b/scripts/ccpp_datafile.py index 31c3bfca..7a4c3d8d 100755 --- a/scripts/ccpp_datafile.py +++ b/scripts/ccpp_datafile.py @@ -27,10 +27,6 @@ from parse_tools import read_xml_file, PrettyElementTree from suite_objects import VerticalLoop, Subcycle -# Find python version -PY3 = sys.version_info[0] > 2 -PYSUBVER = sys.version_info[1] - # Global data _INDENT_STR = " " diff --git a/scripts/file_utils.py b/scripts/file_utils.py index 8cdfd023..283cf9ec 100644 --- a/scripts/file_utils.py +++ b/scripts/file_utils.py @@ -13,11 +13,7 @@ import os # CCPP framework imports from parse_tools import CCPPError, ParseInternalError -#XXgoldyXX: v Crap required to support python 2 import sys -# Find python version -PY3 = sys.version_info[0] > 2 -#XXgoldyXX: ^ Crap required to support python 2 # Standardize name of generated kinds file and module KINDS_MODULE = 'ccpp_kinds' @@ -300,13 +296,7 @@ def move_modified_files(src_dir, dest_dir, overwrite=False, remove_src=False): fmove = True # end if if fmove: -#XXgoldyXX: v Crap required to support python 2 - if PY3: - os.replace(src_path, dest_path) - else: - os.rename(src_path, dest_path) - # end if -#XXgoldyXX: ^ Crap required to support python 2 + os.replace(src_path, dest_path) else: os.remove(src_path) # end if diff --git a/scripts/parse_tools/parse_source.py b/scripts/parse_tools/parse_source.py index 0447225a..c45a3b75 100644 --- a/scripts/parse_tools/parse_source.py +++ b/scripts/parse_tools/parse_source.py @@ -3,16 +3,10 @@ """Classes to aid the parsing process""" import sys -# Find python version -PY3 = sys.version_info[0] > 2 # pylint: disable=wrong-import-position # Python library imports -if PY3: - from collections.abc import Iterable -else: - from collections import Iterable -# end if +from collections.abc import Iterable import copy import os.path import logging diff --git a/scripts/parse_tools/xml_tools.py b/scripts/parse_tools/xml_tools.py index d5a767b5..2b55d1f6 100644 --- a/scripts/parse_tools/xml_tools.py +++ b/scripts/parse_tools/xml_tools.py @@ -25,7 +25,6 @@ simple_tag_re = re.compile(r"([<][^/][^<>/]+[/][>])") # Find python version -PY3 = sys.version_info[0] > 2 PYSUBVER = sys.version_info[1] _LOGGER = None @@ -54,34 +53,23 @@ 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") + if PYSUBVER > 6: + cproc = subprocess.run(commands, check=True, + capture_output=True) + if not silent: + logger.debug(cproc.stdout) # end if - else: - pproc = subprocess.Popen(commands, stdin=None, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - output, _ = pproc.communicate() + result = cproc.returncode == 0 + elif PYSUBVER >= 5: + cproc = subprocess.run(commands, check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) if not silent: - logger.debug(output) + logger.debug(cproc.stdout) # end if - result = pproc.returncode == 0 + result = cproc.returncode == 0 + else: + raise ValueError("Python 3 must be at least version 3.5") # end if except (OSError, CCPPError, subprocess.CalledProcessError) as err: if silent: @@ -226,11 +214,7 @@ 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_) @@ -282,7 +266,7 @@ 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 >= 4: if PYSUBVER >= 8: et_str = ET.tostring(self.getroot(), encoding=encoding, method=method, @@ -298,13 +282,8 @@ def write(self, file, encoding="us-ascii", xml_declaration=None, et_str = ET.tostring(self.getroot(), encoding=encoding, method=method) # end if - if PY3: - fmode = 'wt' - root = str(et_str, encoding="utf-8") - else: - fmode = 'w' - root = et_str - # end if + fmode = 'wt' + root = str(et_str, encoding="utf-8") indent = 0 last_write_text = False with open(file, fmode) as outfile: From 50fc53c978fe36e46dc966d231a658ca99cd3413 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 1 Aug 2023 22:07:45 -0400 Subject: [PATCH 079/159] Removing python code older than 2.7 --- scripts/parse_tools/xml_tools.py | 43 ++++++++++---------------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/scripts/parse_tools/xml_tools.py b/scripts/parse_tools/xml_tools.py index 2b55d1f6..6a12bb34 100644 --- a/scripts/parse_tools/xml_tools.py +++ b/scripts/parse_tools/xml_tools.py @@ -53,24 +53,12 @@ def call_command(commands, logger, silent=False): result = False outstr = '' try: - 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") + 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 @@ -266,21 +254,16 @@ 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 >= 4: - 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) - # end if + 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) + encoding=encoding, method=method, + short_empty_elements=short_empty_elements) # end if fmode = 'wt' root = str(et_str, encoding="utf-8") From 6803e90f830999155f28603bc3415564ac6ccb1e Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Thu, 3 Aug 2023 10:37:51 -0400 Subject: [PATCH 080/159] Chaning python unit tests to run in pytest harness. --- test/run_tests.sh | 10 +--------- test/unit_tests/test_fortran_write.py | 3 --- test/unit_tests/test_metadata_host_file.py | 3 --- test/unit_tests/test_metadata_scheme_file.py | 3 --- test/unit_tests/test_metadata_table.py | 3 --- test/unit_tests/test_var_transforms.py | 3 --- 6 files changed, 1 insertion(+), 24 deletions(-) diff --git a/test/run_tests.sh b/test/run_tests.sh index 7b7b0585..e177739a 100755 --- a/test/run_tests.sh +++ b/test/run_tests.sh @@ -47,15 +47,7 @@ fi # fi echo "Skipping var_action_test/run_test until feature is fully implemented" -for test in `ls unit_tests/test_*.py`; do - echo "Running unit test, ${test}" - python3 ${test} - res=$? - errcnt=$((errcnt + res)) - if [ $res -ne 0 ]; then - echo "Failure, '${res}', running unit test, ${test}" - fi -done +pytest -v unit_tests/ if [ $errcnt -eq 0 ]; then echo "All tests PASSed!" diff --git a/test/unit_tests/test_fortran_write.py b/test/unit_tests/test_fortran_write.py index fdc00085..d5d538d8 100644 --- a/test/unit_tests/test_fortran_write.py +++ b/test/unit_tests/test_fortran_write.py @@ -121,6 +121,3 @@ def test_good_comments(self): 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_host_file.py b/test/unit_tests/test_metadata_host_file.py index cc5ba7b5..6ddaa873 100644 --- a/test/unit_tests/test_metadata_host_file.py +++ b/test/unit_tests/test_metadata_host_file.py @@ -255,6 +255,3 @@ def test_module_with_two_ddts_and_extra_var(self): for emsg in emsgs: self.assertTrue(emsg in except_str) # end for - -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 9598406d..8347c56e 100644 --- a/test/unit_tests/test_metadata_scheme_file.py +++ b/test/unit_tests/test_metadata_scheme_file.py @@ -343,6 +343,3 @@ def test_scheme_ddt_only(self): # 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/unit_tests/test_metadata_table.py b/test/unit_tests/test_metadata_table.py index 544d3013..c2cd99ef 100755 --- a/test/unit_tests/test_metadata_table.py +++ b/test/unit_tests/test_metadata_table.py @@ -393,6 +393,3 @@ def test_invalid_table_properties_type(self): #print("The exception is", context.exception) emsg = "Invalid metadata table type, 'banana', at " self.assertTrue(emsg in str(context.exception)) - -if __name__ == '__main__': - unittest.main() diff --git a/test/unit_tests/test_var_transforms.py b/test/unit_tests/test_var_transforms.py index 70f31d1e..9ed2f4f8 100644 --- a/test/unit_tests/test_var_transforms.py +++ b/test/unit_tests/test_var_transforms.py @@ -431,6 +431,3 @@ def test_valid_dim_transforms(self): rkind = real_array4.get_prop_value('kind') expected = f"{v5_lname}({lind_str}) = {v4_lname}({rind_str})" self.assertEqual(rev_stmt, expected) - -if __name__ == '__main__': - unittest.main() From b48abe50db854723716796fcd08a9f7dc66bc67d Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Thu, 3 Aug 2023 12:10:45 -0600 Subject: [PATCH 081/159] Removing un-needed pylint statements and fixing imports. --- scripts/file_utils.py | 1 - scripts/parse_tools/parse_source.py | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/file_utils.py b/scripts/file_utils.py index 283cf9ec..669922b9 100644 --- a/scripts/file_utils.py +++ b/scripts/file_utils.py @@ -13,7 +13,6 @@ import os # CCPP framework imports from parse_tools import CCPPError, ParseInternalError -import sys # Standardize name of generated kinds file and module KINDS_MODULE = 'ccpp_kinds' diff --git a/scripts/parse_tools/parse_source.py b/scripts/parse_tools/parse_source.py index c45a3b75..06680070 100644 --- a/scripts/parse_tools/parse_source.py +++ b/scripts/parse_tools/parse_source.py @@ -2,16 +2,14 @@ """Classes to aid the parsing process""" -import sys -# pylint: disable=wrong-import-position # Python library imports from collections.abc import Iterable import copy +import sys import os.path import logging # CCPP framework imports -# pylint: enable=wrong-import-position class _StdNameCounter: """Class to hold a global counter to avoid using global keyword""" From 8780abc0f72343e99c43a2107f71cfda6eb43b78 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Fri, 4 Aug 2023 14:53:41 -0400 Subject: [PATCH 082/159] Fixing formatting. --- scripts/parse_tools/xml_tools.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/parse_tools/xml_tools.py b/scripts/parse_tools/xml_tools.py index 6a12bb34..07074935 100644 --- a/scripts/parse_tools/xml_tools.py +++ b/scripts/parse_tools/xml_tools.py @@ -256,14 +256,14 @@ def write(self, file, encoding="us-ascii", xml_declaration=None, """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) + 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) + encoding=encoding, method=method, + short_empty_elements=short_empty_elements) # end if fmode = 'wt' root = str(et_str, encoding="utf-8") From 820acfc8339a84af59540b01897690afa105136f Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Fri, 4 Aug 2023 15:04:40 -0400 Subject: [PATCH 083/159] Removing python2 style is string check. --- scripts/common.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/scripts/common.py b/scripts/common.py index 0ac2c74d..7f4e2f43 100755 --- a/scripts/common.py +++ b/scripts/common.py @@ -165,14 +165,7 @@ def escape_tex(text): def isstring(s): """Return true if a variable is a string""" - # We use Python 3 - if (sys.version_info.major == 3): - return isinstance(s, str) - # We use Python 2 - elif (sys.version_info.major == 2): - return isinstance(s, basestring) - else: - raise Exception('Unknown Python version') + return isinstance(s, str) def string_to_python_identifier(string): """Replaces forbidden characters in strings with standard substitutions From 0b65ba5d0cdf9c8f279ebfd701e53267a12babde Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Fri, 4 Aug 2023 15:26:09 -0400 Subject: [PATCH 084/159] Update minimum version to 3.7 --- test/advection_test/test_reports.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/advection_test/test_reports.py b/test/advection_test/test_reports.py index 49bbce7f..c28fe38a 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] < 6)): - raise Exception("Python 3.6 or greater required") + (sys.version_info[0] == 3) and (sys.version_info[1] < 7)): + raise Exception("Python 3.7 or greater required") # end if sys.path.append(_SCRIPTS_DIR) From ffcd5826ad8d9ffed61dd45e94fe3d47040ba1fd Mon Sep 17 00:00:00 2001 From: mwaxmonsky <137746677+mwaxmonsky@users.noreply.github.com> Date: Mon, 7 Aug 2023 10:12:42 -0400 Subject: [PATCH 085/159] Update .github/workflows/python.yaml Updating github actions to v4. Co-authored-by: Jesse Nusbaumer --- .github/workflows/python.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index b9ca0eba..3b32d6d3 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -20,7 +20,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies From 9f6c9a276053e272047eb0e326acb6636893c100 Mon Sep 17 00:00:00 2001 From: mwaxmonsky <137746677+mwaxmonsky@users.noreply.github.com> Date: Mon, 7 Aug 2023 10:13:34 -0400 Subject: [PATCH 086/159] Update .github/workflows/python.yaml Update setup-python github action to v4. Co-authored-by: Jesse Nusbaumer --- .github/workflows/python.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index 3b32d6d3..c5de887a 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -43,7 +43,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies From b46f0426f910dc99646c2c35dd70d00b37d12d20 Mon Sep 17 00:00:00 2001 From: mwaxmonsky <137746677+mwaxmonsky@users.noreply.github.com> Date: Mon, 7 Aug 2023 10:15:08 -0400 Subject: [PATCH 087/159] Update .github/workflows/python.yaml Removing un-needed whitespace. Co-authored-by: Jesse Nusbaumer --- .github/workflows/python.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index c5de887a..683d0bc2 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -52,6 +52,6 @@ jobs: pip install pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Doctest - run : | + run: | export PYTHONPATH=$(pwd)/scripts:$(pwd)/scripts/parse_tools pytest scripts/ --doctest-modules From 85d5e50fec614cea91adf2f8f0200f8eafa3520a Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Mon, 7 Aug 2023 08:24:57 -0600 Subject: [PATCH 088/159] Removing un-needed main function that runs doc test. --- scripts/fortran_tools/parse_fortran.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/scripts/fortran_tools/parse_fortran.py b/scripts/fortran_tools/parse_fortran.py index c1a6eda6..9aa4de7a 100644 --- a/scripts/fortran_tools/parse_fortran.py +++ b/scripts/fortran_tools/parse_fortran.py @@ -832,15 +832,3 @@ def parse_fortran_var_decl(line, source, run_env): ######################################################################## ######################################################################## - -if __name__ == "__main__": - # pylint: disable=ungrouped-imports - import doctest - # pylint: enable=ungrouped-imports - from framework_env import CCPPFrameworkEnv - _DUMMY_RUN_ENV = CCPPFrameworkEnv(None, ndict={'host_files':'', - 'scheme_files':'', - 'suites':''}) - fail, _ = doctest.testmod() - sys.exit(fail) -# end if From 971bf1b77954284376456aae90afb681644e4337 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Mon, 7 Aug 2023 08:28:49 -0600 Subject: [PATCH 089/159] Removing un-needed main function that runs doctest. --- scripts/code_block.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/scripts/code_block.py b/scripts/code_block.py index f649d70b..ccd3f209 100644 --- a/scripts/code_block.py +++ b/scripts/code_block.py @@ -117,19 +117,3 @@ def write(self, outfile, indent_level, var_dict): # end for ############################################################################### -if __name__ == "__main__": - # pylint: disable=ungrouped-imports - import doctest - import os - import sys - from fortran_tools import FortranWriter - # pylint: enable=ungrouped-imports - outfile_name = "__code_block_temp.F90" - with FortranWriter(outfile_name, 'w', 'test file', 'test_mod') as outfile: - fail, _ = doctest.testmod() - # end with - if os.path.exists(outfile_name): - os.remove(outfile_name) - # end if - sys.exit(fail) -# end if From b568453e1378b518d47018b35cf3086dd4dd45bf Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 8 Aug 2023 12:00:19 -0600 Subject: [PATCH 090/159] Adding more python versions to test against. --- .github/workflows/python.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index 683d0bc2..7549b249 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7, 3.9, 3.11] + python-version: [3.7, 3.8, 3.9, 3.10, 3.11] steps: - uses: actions/checkout@v3 @@ -38,7 +38,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7, 3.9, 3.11] + python-version: [3.7, 3.8, 3.9, 3.10, 3.11] steps: - uses: actions/checkout@v3 From b82c0308659ecfa978d7bb4fa593cae55cec40ae Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 8 Aug 2023 12:04:27 -0600 Subject: [PATCH 091/159] Testing version syntax fix. --- .github/workflows/python.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index 7549b249..a566e79f 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7, 3.8, 3.9, 3.10, 3.11] + python-version: [3.7, 3.8, 3.9, '3.10', 3.11] steps: - uses: actions/checkout@v3 @@ -38,7 +38,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7, 3.8, 3.9, 3.10, 3.11] + python-version: [3.7, 3.8, 3.9, '3.10', 3.11] steps: - uses: actions/checkout@v3 From ba8d351638895c37f423fda64fb700d5c9f1bebd Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 8 Aug 2023 12:07:14 -0600 Subject: [PATCH 092/159] Testing updated version syntax fix. --- .github/workflows/python.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index a566e79f..66d7e668 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7, 3.8, 3.9, '3.10', 3.11] + python-version: '>= 3.7' steps: - uses: actions/checkout@v3 @@ -38,7 +38,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7, 3.8, 3.9, '3.10', 3.11] + python-version: '>= 3.7' steps: - uses: actions/checkout@v3 From 8b08bfcacc4c3a963ca513722b1fe80cdaa91c10 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 8 Aug 2023 12:10:00 -0600 Subject: [PATCH 093/159] Fixing version syntax. --- .github/workflows/python.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index 66d7e668..669c4557 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -15,7 +15,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: '>= 3.7' + python-version: + - '>= 3.7' steps: - uses: actions/checkout@v3 @@ -38,7 +39,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: '>= 3.7' + python-version: + - '>= 3.7' steps: - uses: actions/checkout@v3 From ae08563c5b382159f140ec05051a9795103792d1 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 8 Aug 2023 12:14:58 -0600 Subject: [PATCH 094/159] Reverting version support syntax. --- .github/workflows/python.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index 669c4557..4e425e5e 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -15,8 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: - - '>= 3.7' + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v3 @@ -39,8 +38,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: - - '>= 3.7' + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v3 From 419b4dfb078b505af7a6750ea1fb3e17773591fb Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 8 Aug 2023 12:38:33 -0600 Subject: [PATCH 095/159] Removing pytest from testing of generated fortran script and removing doctest script as no longer usable. --- test/run_doctest.sh | 35 ----------------------------------- test/run_tests.sh | 2 -- 2 files changed, 37 deletions(-) delete mode 100755 test/run_doctest.sh diff --git a/test/run_doctest.sh b/test/run_doctest.sh deleted file mode 100755 index aeecb133..00000000 --- a/test/run_doctest.sh +++ /dev/null @@ -1,35 +0,0 @@ -#! /bin/bash - -root=$( dirname $( cd $( dirname ${0}); pwd -P ) ) -scripts=${root}/scripts - -perr() { - # Print error message ($2) on error ($1) - if [ ${1} -ne 0 ]; then - echo "ERROR: ${2}" - if [ $# -gt 2 ]; then - exit ${3} - else - exit 1 - fi - fi -} - -cd ${scripts} -perr $? "Cannot cd to scripts directory, '${scripts}'" - -errcnt=0 - -export PYTHONPATH="${scripts}:${PYTHONPATH}" -# Find all python scripts that have doctest -for pyfile in $(find . -name \*.py); do - if [ -f "${pyfile}" ]; then - if [ $(grep -c doctest ${pyfile}) -ne 0 ]; then - python3 ${pyfile} - res=$? - errcnt=$((errcnt + res)) - fi - fi -done - -exit ${errcnt} diff --git a/test/run_tests.sh b/test/run_tests.sh index e177739a..3b3512c2 100755 --- a/test/run_tests.sh +++ b/test/run_tests.sh @@ -47,8 +47,6 @@ fi # fi echo "Skipping var_action_test/run_test until feature is fully implemented" -pytest -v unit_tests/ - if [ $errcnt -eq 0 ]; then echo "All tests PASSed!" else From 0693ebd1b687adf67d5bdbfd5f6ce40a90049097 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 8 Aug 2023 13:50:54 -0600 Subject: [PATCH 096/159] Updating pytest.ini to ignore non-unit test files. --- pytest.ini | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pytest.ini b/pytest.ini index cc493590..d08180f1 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,2 @@ [pytest] -addopts = -ra --ignore=scripts/metadata2html.py -testpaths = - tests +addopts = -ra --ignore=scripts/metadata2html.py --ignore-glob=test/**/test_reports.py \ No newline at end of file From 4d1ac61d1f04cd0c775f9652c394563a8e8f74d6 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 8 Aug 2023 13:53:18 -0600 Subject: [PATCH 097/159] Adding verbose pytest output. --- .github/workflows/python.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index 4e425e5e..68397c34 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -31,7 +31,7 @@ jobs: - name: Test with pytest run: | export PYTHONPATH=$(pwd)/scripts:$(pwd)/scripts/parse_tools - pytest + pytest -v doctest: if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' || github.repository == 'NCAR/ccpp-framework' @@ -54,4 +54,4 @@ jobs: - name: Doctest run: | export PYTHONPATH=$(pwd)/scripts:$(pwd)/scripts/parse_tools - pytest scripts/ --doctest-modules + pytest -v scripts/ --doctest-modules From 8a6a69b40424ac73e94315ca36ca67d18dd5a659 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Tue, 8 Aug 2023 13:59:08 -0600 Subject: [PATCH 098/159] Re-adding unit test main check to unit tests. --- test/unit_tests/test_fortran_write.py | 4 ++++ test/unit_tests/test_metadata_host_file.py | 4 ++++ test/unit_tests/test_metadata_scheme_file.py | 4 ++++ test/unit_tests/test_metadata_table.py | 4 ++++ test/unit_tests/test_var_transforms.py | 4 ++++ 5 files changed, 20 insertions(+) diff --git a/test/unit_tests/test_fortran_write.py b/test/unit_tests/test_fortran_write.py index d5d538d8..87e64baa 100644 --- a/test/unit_tests/test_fortran_write.py +++ b/test/unit_tests/test_fortran_write.py @@ -121,3 +121,7 @@ def test_good_comments(self): 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_host_file.py b/test/unit_tests/test_metadata_host_file.py index 6ddaa873..d01fda97 100644 --- a/test/unit_tests/test_metadata_host_file.py +++ b/test/unit_tests/test_metadata_host_file.py @@ -255,3 +255,7 @@ def test_module_with_two_ddts_and_extra_var(self): for emsg in emsgs: self.assertTrue(emsg in except_str) # end for + +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 8347c56e..6651c2de 100644 --- a/test/unit_tests/test_metadata_scheme_file.py +++ b/test/unit_tests/test_metadata_scheme_file.py @@ -343,3 +343,7 @@ def test_scheme_ddt_only(self): # 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/unit_tests/test_metadata_table.py b/test/unit_tests/test_metadata_table.py index c2cd99ef..3a01f98b 100755 --- a/test/unit_tests/test_metadata_table.py +++ b/test/unit_tests/test_metadata_table.py @@ -393,3 +393,7 @@ def test_invalid_table_properties_type(self): #print("The exception is", context.exception) emsg = "Invalid metadata table type, 'banana', at " self.assertTrue(emsg in str(context.exception)) + +if __name__ == "__main__": + unittest.main() + diff --git a/test/unit_tests/test_var_transforms.py b/test/unit_tests/test_var_transforms.py index 9ed2f4f8..12cffcdb 100644 --- a/test/unit_tests/test_var_transforms.py +++ b/test/unit_tests/test_var_transforms.py @@ -431,3 +431,7 @@ def test_valid_dim_transforms(self): rkind = real_array4.get_prop_value('kind') expected = f"{v5_lname}({lind_str}) = {v4_lname}({rind_str})" self.assertEqual(rev_stmt, expected) + +if __name__ == "__main__": + unittest.main() + From 1076f7162902de220beeff8e037774bffaa93125 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Wed, 9 Aug 2023 10:13:59 -0600 Subject: [PATCH 099/159] Renaming test script to more appropriate name. --- test/{run_tests.sh => run_fortran_tests.sh} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{run_tests.sh => run_fortran_tests.sh} (100%) diff --git a/test/run_tests.sh b/test/run_fortran_tests.sh similarity index 100% rename from test/run_tests.sh rename to test/run_fortran_tests.sh From 8f1c3ad513dcfe8d2709b2fe196a04ab587de71a Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Wed, 9 Aug 2023 10:16:49 -0600 Subject: [PATCH 100/159] removing un-needed python requirements.txt install step. --- .github/workflows/python.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index 68397c34..74836a9b 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -26,8 +26,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install flake8 pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + pip install pytest - name: Test with pytest run: | export PYTHONPATH=$(pwd)/scripts:$(pwd)/scripts/parse_tools @@ -50,7 +49,6 @@ jobs: run: | python -m pip install --upgrade pip pip install pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Doctest run: | export PYTHONPATH=$(pwd)/scripts:$(pwd)/scripts/parse_tools From 5c6f91e7ea8c7c8fa95ab15897cb475c80180452 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Wed, 9 Aug 2023 10:17:53 -0600 Subject: [PATCH 101/159] Updating test script name missing from previous commit. --- .github/workflows/capgen_unit_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index eb574f2d..7626a7dc 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -18,5 +18,5 @@ jobs: - name: update repos and install dependencies run: sudo apt-get update && sudo apt-get install -y build-essential gfortran cmake python3 git - name: Run unit tests - run: cd test && ./run_tests.sh + run: cd test && ./run_fortran_tests.sh From add29178246210a08a55383b9cd65ab95a73ec42 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Wed, 9 Aug 2023 10:26:15 -0600 Subject: [PATCH 102/159] Removing requirements.txt as not needed currently. --- requirements.txt | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index be9f272b..00000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -black -flake8 -pytest From b8fae25bac1586015a5009f23ef1dce0f12c8034 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Thu, 24 Aug 2023 17:42:06 -0400 Subject: [PATCH 103/159] Fixing typo --- .github/workflows/python.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index 74836a9b..3ab5347c 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -6,7 +6,7 @@ on: types: [opened, synchronize, reopened] push: branches: - #Trigger workflow on push to any branch or branch heirarchy: + #Trigger workflow on push to any branch or branch hierarchy: - '**' jobs: From 3207b59ed56016efde51d4c592da93ae1fef674c Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Thu, 24 Aug 2023 16:37:26 -0600 Subject: [PATCH 104/159] fix advection test --- test/advection_test/test_host.F90 | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index 9c7abd42..a93b59f4 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -490,9 +490,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 +500,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 +511,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 3c987d5e768bc4cb1369eedb8ce4ec42d59d6014 Mon Sep 17 00:00:00 2001 From: Michael Waxmonsky Date: Thu, 24 Aug 2023 19:12:27 -0400 Subject: [PATCH 105/159] Typo fix. --- .github/workflows/capgen_unit_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index 7626a7dc..4406ae77 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -6,7 +6,7 @@ on: types: [opened, synchronize, reopened] push: branches: - #Trigger workflow on push to any branch or branch heirarchy: + #Trigger workflow on push to any branch or branch hierarchy: - '**' jobs: From 04a1d7ef80950264fbe8f4dedc1ef896f79b55eb Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Fri, 25 Aug 2023 11:07:52 -0600 Subject: [PATCH 106/159] add new host cap interface to check if a variable is already a scheme constituent --- scripts/constituents.py | 24 +++++++++++++++++++++--- scripts/host_cap.py | 15 +++++++++++++-- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/scripts/constituents.py b/scripts/constituents.py index 7a60af04..7aba9e61 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -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,23 @@ 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.write("", 0) + 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.write("", 0) + 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.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.write("", 0) + 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 d833c649..4cebb517 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -109,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 "{}_ccpp_is_scheme_constituent".format(host_model.name) + ############################################################################### def constituent_copyin_subname(host_model): ############################################################################### @@ -449,6 +457,8 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): cap.write("public :: {}".format(init_name), 1) numconsts_name = constituent_num_consts_funcname(host_model) cap.write("public :: {}".format(numconsts_name), 1) + queryconsts_name = query_scheme_constituents_funcname(host_model) + cap.write("public :: {}".format(queryconsts_name), 1) copyin_name = constituent_copyin_subname(host_model) cap.write("public :: {}".format(copyin_name), 1) copyout_name = constituent_copyout_subname(host_model) @@ -598,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 48041d88f688167653fff593dba31eb4e8bb083e Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Fri, 25 Aug 2023 12:55:08 -0600 Subject: [PATCH 107/159] add testing --- scripts/constituents.py | 4 +- test/advection_test/cld_ice.meta | 2 + test/advection_test/test_host.F90 | 69 +++++++++++++++++++++++++++++-- 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/scripts/constituents.py b/scripts/constituents.py index 5be80721..4fdebee4 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -641,8 +641,8 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna 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.write("", 0) - cap.write("character(len=*), intent(in) :: var_name", 2) - cap.write("logical, intent(out) :: constituent_exists", 2) + 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 diff --git a/test/advection_test/cld_ice.meta b/test/advection_test/cld_ice.meta index f66888e0..010fb419 100644 --- a/test/advection_test/cld_ice.meta +++ b/test/advection_test/cld_ice.meta @@ -44,6 +44,7 @@ [ 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 @@ -76,6 +77,7 @@ [ 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/test_host.F90 b/test/advection_test/test_host.F90 index a93b59f4..86de7cd7 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -222,6 +222,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,11 +246,14 @@ 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 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' @@ -280,8 +284,29 @@ 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) + ! specific_humidity should not be an existing constituent + if (is_constituent) then + write(6, *) "ERROR: specific humidity is already a constituent" + 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) + ! 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" + 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) @@ -385,7 +410,45 @@ subroutine test_host(retval, test_suites) end if end if - ! Use the suite information to setup the run + if (errflg == 0) then + call const_props(index_liq)%has_default(has_default, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to check for default for cld_liq index = ", index_liq, trim(errmsg) + end if + end if + if (errflg == 0) then + if (has_default) then + write(6, *) "ERROR: cloud liquid mass_mixing_ratio should not have default but does" + end if + end if + if (errflg == 0) then + call const_props(index_ice)%has_default(has_default, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to check for default for cld_ice index = ", index_ice, trim(errmsg) + end if + 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" + end if + end if + if (errflg == 0) then + call const_props(index_ice)%default_value(default_value, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to grab default for cld_ice index = ", index_ice, trim(errmsg) + end if + 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" + end if + end if + + ! Use the suite information to setup the run do sind = 1, num_suites if (errflg == 0) then call test_host_ccpp_physics_initialize( & From 11191fe6d826698b9100c07e43e5ebc1103d13df Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Fri, 25 Aug 2023 13:08:35 -0600 Subject: [PATCH 108/159] Make capgen unit tests less inter-dependent, and perform some cleanup. --- test/advection_test/test_host.F90 | 223 ++++++++++++++++++++---------- test/run_tests.sh | 9 +- 2 files changed, 155 insertions(+), 77 deletions(-) diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index d8ee7778..376c82a2 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(inout) :: 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 @@ -249,10 +253,15 @@ subroutine test_host(retval, test_suites) 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(:,:,:) 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) @@ -285,144 +294,201 @@ subroutine test_host(retval, test_suites) 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 - 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 + + !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 if (.not. const_log) then write(6, *) "ERROR: cloud ice is not a mass 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 - 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 + + !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 occured end if if (errflg == 0) then if (.not. const_log) then write(6, *) "ERROR: cloud ice mass_mixing_ratio is not dry" - errflg = -1 + 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: - if (errflg == 0) then - call const_props(index_ice)%is_thermo_active(check, errflg, errmsg) - if (errflg /= 0) then - write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & - "to get thermo_active prop for cld_ice index = ", index_ice, trim(errmsg) - end if + 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 occured 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 = -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 - !Check that setting a constituent to be thermodynamiaclly active works + + !Check that setting a constituent to be thermodynamically active works !as expected: - if (errflg == 0) then - call const_props(index_ice)%set_thermo_active(.true., errflg, errmsg) - if (errflg /= 0) then - write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & - "to set thermo_active prop for cld_ice index = ", index_ice, trim(errmsg) - end if + 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 occured end if if (errflg == 0) then call const_props(index_ice)%is_thermo_active(check, errflg, errmsg) if (errflg /= 0) then - write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & - "to get thermo_active 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 occured end if end if if (errflg == 0) then if (.not.check) then !Should now be True - write(6, *) "ERROR: 'set_thermo_active' did not set thermo_active ", & - "constituent property correctly." - errflg = -1 + write(6, *) "ERROR: 'set_thermo_active' did not set", & + " thermo_active constituent property correctly." + 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 + !++++++++++++++++++++++++++++++++++ + + !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 @@ -435,6 +501,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 @@ -516,7 +583,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 diff --git a/test/run_tests.sh b/test/run_tests.sh index 04ce5e8c..c3f9932e 100755 --- a/test/run_tests.sh +++ b/test/run_tests.sh @@ -68,6 +68,11 @@ done if [ $errcnt -eq 0 ]; then echo "All tests PASSed!" else - echo "${errcnt} tests FAILed" - return 1 + if [ $errcnt -eq 1 ]; then + echo "${errcnt} test FAILed" + else + echo "${errcnt} tests FAILed" + fi + #Exit with non-zero exit code + exit 1 fi From a0d5d5a69fff0a8dfce0373740dd366e0e96dc50 Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Sun, 27 Aug 2023 12:19:01 -0600 Subject: [PATCH 109/159] Fix variable intent. --- test/advection_test/test_host.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index bc1d0492..6d8771d8 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -39,7 +39,7 @@ subroutine check_errflg(subname, errflg, errmsg, errflg_final) integer, intent(in) :: errflg character(len=*), intent(in) :: errmsg - integer, intent(inout) :: errflg_final + integer, intent(out) :: errflg_final if (errflg /= 0) then write(6, '(a,i0,4a)') "Error ", errflg, " from ", trim(subname), & From 6819f2e18022f30be962d90f19a47e3506a017ec Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 28 Aug 2023 15:19:30 -0600 Subject: [PATCH 110/159] fix check_errflg call --- test/advection_test/test_host.F90 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index a1fb9147..d28422c7 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -299,14 +299,16 @@ subroutine test_host(retval, test_suites) ! 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) + 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" 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) + 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 ", & From 87f74cbebfeec4012ee763d42ffedd821648763f Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Tue, 29 Aug 2023 10:58:44 -0600 Subject: [PATCH 111/159] address reviewer comments --- scripts/constituents.py | 6 +++--- scripts/host_cap.py | 14 +++++++------- test/advection_test/test_host.F90 | 10 +++++++--- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/scripts/constituents.py b/scripts/constituents.py index 9a7489a5..c89dc415 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -637,10 +637,10 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.write("end {}".format(substmt), 1) # Write query_consts routine substmt = f"subroutine {query_const_funcname}" - cap.write("", 0) + 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.write("", 0) + blank_line() cap.write("character(len=*), intent(in) :: var_name", 2) cap.write("logical, intent(out) :: constituent_exists", 2) for evar in err_vars: @@ -650,7 +650,7 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna 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.write("", 0) + blank_line() cap.write(f"end {substmt}", 1) # Write copy_in routine substmt = "subroutine {}".format(copy_in_funcname) diff --git a/scripts/host_cap.py b/scripts/host_cap.py index 35f7fae1..d2b3066f 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -115,7 +115,7 @@ 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 "{}_ccpp_is_scheme_constituent".format(host_model.name) + return f"{host_model.name}_ccpp_is_scheme_constituent" ############################################################################### def constituent_copyin_subname(host_model): @@ -452,17 +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("public :: {}".format(queryconsts_name), 1) + 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) diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index d28422c7..49a3c602 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -304,6 +304,7 @@ subroutine test_host(retval, test_suites) ! specific_humidity should not be an existing constituent if (is_constituent) then write(6, *) "ERROR: specific humidity is already a constituent" + errflg_final = -1 !Notify test script that a failure occurred end if call test_host_ccpp_is_scheme_constituent('cloud_ice_dry_mixing_ratio', & is_constituent, errflg, errmsg) @@ -313,6 +314,7 @@ subroutine test_host(retval, test_suites) if (.not. is_constituent) then write(6, *) "ERROR: cloud_ice_dry_mixing ratio not found in ", & "host cap constituent list" + errflg_final = -1 !Notify test script that a failure occurred end if @@ -512,7 +514,7 @@ subroutine test_host(retval, test_suites) !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,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + 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 @@ -527,13 +529,14 @@ subroutine test_host(retval, test_suites) end if call const_props(index_ice)%has_default(has_default, errflg, errmsg) if (errflg /= 0) then - write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + 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: @@ -541,7 +544,7 @@ subroutine test_host(retval, test_suites) end if call const_props(index_ice)%default_value(default_value, errflg, errmsg) if (errflg /= 0) then - write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + 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 @@ -549,6 +552,7 @@ subroutine test_host(retval, test_suites) 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: From 65824a5cab6a6f0c3d977d0bb086ae47bacb02fb Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Tue, 29 Aug 2023 11:01:32 -0600 Subject: [PATCH 112/159] fix function calls --- scripts/constituents.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/constituents.py b/scripts/constituents.py index c89dc415..cd0fa2cd 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -637,10 +637,10 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.write("end {}".format(substmt), 1) # Write query_consts routine substmt = f"subroutine {query_const_funcname}" - blank_line() + 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) - blank_line() + 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: @@ -650,7 +650,7 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna 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) - blank_line() + cap.blank_line() cap.write(f"end {substmt}", 1) # Write copy_in routine substmt = "subroutine {}".format(copy_in_funcname) From 45f0859e3d8f18b08cadba46b51964249d02c53a Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Tue, 29 Aug 2023 16:20:30 -0600 Subject: [PATCH 113/159] Set values for error flag and error message in new is_scheme_const subroutine. --- scripts/constituents.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/constituents.py b/scripts/constituents.py index cd0fa2cd..e01d2eae 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -646,6 +646,10 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna 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) From 0bf3117bca859b3d4a1502887f40cfa06d76d2df Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Wed, 6 Sep 2023 15:37:20 -0600 Subject: [PATCH 114/159] Add new 'set_minimum' method which can change min constituent value --- src/ccpp_constituent_prop_mod.F90 | 55 ++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index c9f5322b..d096970d 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -82,6 +82,7 @@ module ccpp_constituent_prop_mod procedure :: deallocate => ccp_deallocate procedure :: set_const_index => ccp_set_const_index procedure :: set_thermo_active => ccp_set_thermo_active + procedure :: set_minimum => ccp_set_min_val end type ccpp_constituent_properties_t !! \section arg_table_ccpp_constituent_prop_ptr_t @@ -116,6 +117,7 @@ module ccpp_constituent_prop_mod procedure :: deallocate => ccpt_deallocate procedure :: set_const_index => ccpt_set_const_index procedure :: set_thermo_active => ccpt_set_thermo_active + procedure :: set_minimum => ccpt_set_min_val end type ccpp_constituent_prop_ptr_t !! \section arg_table_ccpp_model_constituents_t @@ -805,6 +807,26 @@ end subroutine ccp_min_val !######################################################################## + subroutine ccp_set_min_val(this, min_value, errcode, errmsg) + ! Set the minimum value of this particular constituent. + ! If this subroutine is never used then the minimum + ! value defaults to zero. + + ! Dummy arguments + class(ccpp_constituent_properties_t), intent(inout) :: this + real(kind_phys), intent(in) :: min_value + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + + !Set minimum allowed value for this constituent: + if (this%is_instantiated(errcode, errmsg)) then + this%min_val = min_value + end if + + end subroutine ccp_set_min_val + + !######################################################################## + subroutine ccp_molec_weight(this, val_out, errcode, errmsg) ! Dummy arguments @@ -1299,6 +1321,7 @@ subroutine ccp_model_const_data_lock(this, ncols, num_layers, errcode, errmsg) ! Local variables integer :: astat, index real(kind=kind_phys) :: default_value + real(kind=kind_phys) :: minvalue character(len=*), parameter :: subname = 'ccp_model_const_data_lock' if (this%const_data_locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then @@ -1323,11 +1346,16 @@ subroutine ccp_model_const_data_lock(this, ncols, num_layers, errcode, errmsg) if (astat == 0) then this%num_layers = num_layers do index = 1, this%hash_table%num_values() + !Set all constituents to their default values: call this%const_metadata(index)%default_value(default_value, & errcode, errmsg) this%vars_layer(:,:,index) = default_value + + !Also set the minimum allowed value array: + call this%const_metadata(index)%minimum(minvalue, errcode, & + errmsg) + this%vars_minvalue(index) = minvalue end do - this%vars_minvalue = 0.0_kind_phys end if if (present(errcode)) then if (errcode /= 0) then @@ -2086,6 +2114,31 @@ end subroutine ccpt_min_val !######################################################################## + subroutine ccpt_set_min_val(this, min_value, errcode, errmsg) + ! Set the minimum value of this particular constituent. + ! If this subroutine is never used then the minimum + ! value defaults to zero. + + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(inout) :: this + real(kind_phys), intent(in) :: min_value + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_set_min_val' + + !Set minimum value for this constituent: + if (associated(this%prop)) then + call this%prop%set_minimum(min_value, errcode, errmsg) + else + call set_errvars(1, subname//": invalid constituent pointer", & + errcode=errcode, errmsg=errmsg) + end if + + end subroutine ccpt_set_min_val + + !######################################################################## + subroutine ccpt_molec_weight(this, val_out, errcode, errmsg) ! Dummy arguments From cc7fe1ac1cde163ac4e95d761677c054a09c8605 Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Thu, 7 Sep 2023 09:42:28 -0600 Subject: [PATCH 115/159] Add unit tests for new set_minimum method. --- test/advection_test/test_host.F90 | 58 +++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index 49a3c602..773fdb58 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -259,6 +259,7 @@ subroutine test_host(retval, test_suites) integer :: errflg_final ! Used to notify testing script of test failure real(kind_phys), pointer :: const_ptr(:,:,:) real(kind_phys) :: default_value + real(kind_phys) :: check_value type(ccpp_constituent_prop_ptr_t), pointer :: const_props(:) character(len=*), parameter :: subname = 'test_host' @@ -463,6 +464,62 @@ subroutine test_host(retval, test_suites) errflg = 0 end if + !------------------- + !minimum value tests: + !------------------- + + !Check that a constituent's minimum value defaults to zero: + call const_props(index_ice)%minimum(check_value, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to get minimum value for 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_value /= 0._kind_phys) then !Should be zero + write(6, *) "ERROR: 'minimum' should default to zero for all ", & + "constituents unless set by host model or scheme metadata." + errflg_final = -1 !Notify test script that a failure occured + end if + else + !Reset error flag to continue testing other properties: + errflg = 0 + end if + + !Check that setting a constituent's minimum value works + !as expected: + call const_props(index_ice)%set_minimum(1._kind_phys, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to set minimum value for 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)%minimum(check_value, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, & + " tryingto get minimum value for cld_ice index = ", & + index_ice, trim(errmsg) + errflg_final = -1 !Notify test script that a failure occurred + end if + end if + if (errflg == 0) then + if (check_value /= 1._kind_phys) then !Should now be one + write(6, *) "ERROR: 'set_minimum' did not set constituent", & + " minimum value correctly." + errflg_final = -1 !Notify test script that a failure occurred + end if + else + !Reset error flag to continue testing other properties: + errflg = 0 + end if + + !------------------- + !thermo-active tests: + !------------------- + !Check that being thermodynamically active defaults to False: call const_props(index_ice)%is_thermo_active(check, errflg, errmsg) if (errflg /= 0) then @@ -510,6 +567,7 @@ subroutine test_host(retval, test_suites) !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) From f635213dccdd335114d42ac40c1a7751560776b3 Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Wed, 20 Sep 2023 08:14:25 -0600 Subject: [PATCH 116/159] Fixed typos in test_host messages. --- test/advection_test/test_host.F90 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index 773fdb58..bdc1068f 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -500,7 +500,7 @@ subroutine test_host(retval, test_suites) call const_props(index_ice)%minimum(check_value, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, & - " tryingto get minimum value for cld_ice index = ", & + " trying to get minimum value for cld_ice index = ", & index_ice, trim(errmsg) errflg_final = -1 !Notify test script that a failure occurred end if @@ -552,7 +552,7 @@ subroutine test_host(retval, test_suites) call const_props(index_ice)%is_thermo_active(check, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, & - " tryingto get thermo_active prop for cld_ice index = ", & + " trying to get thermo_active prop for cld_ice index = ", & index_ice, trim(errmsg) errflg_final = -1 !Notify test script that a failure occurred end if From f9d31a5fe073e83d3ffc06ee1e41bb19baca84fb Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Wed, 27 Sep 2023 14:03:28 -0600 Subject: [PATCH 117/159] Add new 'water_species' property and associated methods. --- src/ccpp_constituent_prop_mod.F90 | 125 +++++++++++++++++++++++++++--- 1 file changed, 116 insertions(+), 9 deletions(-) diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index d096970d..25ec5e6c 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -36,6 +36,7 @@ module ccpp_constituent_prop_mod integer, private :: const_ind = int_unassigned logical, private :: advected = .false. logical, private :: thermo_active = .false. + logical, private :: water_species = .false. ! While the quantities below can be derived from the standard name, ! this implementation avoids string searching in parameterizations ! const_type distinguishes mass, volume, and number conc. mixing ratios @@ -63,6 +64,7 @@ module ccpp_constituent_prop_mod procedure :: const_index => ccp_const_index procedure :: is_advected => ccp_is_advected procedure :: is_thermo_active => ccp_is_thermo_active + procedure :: is_water_species => ccp_is_water_species procedure :: equivalent => ccp_is_equivalent procedure :: is_mass_mixing_ratio => ccp_is_mass_mixing_ratio procedure :: is_volume_mixing_ratio => ccp_is_volume_mixing_ratio @@ -82,6 +84,7 @@ module ccpp_constituent_prop_mod procedure :: deallocate => ccp_deallocate procedure :: set_const_index => ccp_set_const_index procedure :: set_thermo_active => ccp_set_thermo_active + procedure :: set_water_species => ccp_set_water_species procedure :: set_minimum => ccp_set_min_val end type ccpp_constituent_properties_t @@ -101,6 +104,7 @@ module ccpp_constituent_prop_mod procedure :: const_index => ccpt_const_index procedure :: is_advected => ccpt_is_advected procedure :: is_thermo_active => ccpt_is_thermo_active + procedure :: is_water_species => ccpt_is_water_species procedure :: is_mass_mixing_ratio => ccpt_is_mass_mixing_ratio procedure :: is_volume_mixing_ratio => ccpt_is_volume_mixing_ratio procedure :: is_number_concentration => ccpt_is_number_concentration @@ -117,6 +121,7 @@ module ccpp_constituent_prop_mod procedure :: deallocate => ccpt_deallocate procedure :: set_const_index => ccpt_set_const_index procedure :: set_thermo_active => ccpt_set_thermo_active + procedure :: set_water_species => ccpt_set_water_species procedure :: set_minimum => ccpt_set_min_val end type ccpp_constituent_prop_ptr_t @@ -623,6 +628,26 @@ end subroutine ccp_set_thermo_active !####################################################################### + subroutine ccp_set_water_species(this, water_flag, errcode, errmsg) + ! Set whether this constituent is a water species, which means + ! that this constituent represents a particular phase or type + ! of water in the atmosphere. + + ! Dummy arguments + class(ccpp_constituent_properties_t), intent(inout) :: this + logical, intent(in) :: water_flag + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + + !Set thermodynamically active flag for this constituent: + if (this%is_instantiated(errcode, errmsg)) then + this%water_species = water_flag + end if + + end subroutine ccp_set_water_species + + !####################################################################### + subroutine ccp_is_thermo_active(this, val_out, errcode, errmsg) ! Dummy arguments @@ -642,6 +667,25 @@ end subroutine ccp_is_thermo_active !####################################################################### + subroutine ccp_is_water_species(this, val_out, errcode, errmsg) + + ! Dummy arguments + class(ccpp_constituent_properties_t), intent(in) :: this + logical, intent(out) :: val_out + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + + !If instantiated then check if constituent is + !thermodynamically active, otherwise return false: + if (this%is_instantiated(errcode, errmsg)) then + val_out = this%water_species + else + val_out = .false. + end if + end subroutine ccp_is_water_species + + !####################################################################### + subroutine ccp_is_advected(this, val_out, errcode, errmsg) ! Dummy arguments @@ -668,14 +712,15 @@ subroutine ccp_is_equivalent(this, oconst, equiv, errcode, errmsg) integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg - if (this%is_instantiated(errcode, errmsg) .and. & + if (this%is_instantiated(errcode, errmsg) .and. & oconst%is_instantiated(errcode, errmsg)) then equiv = (trim(this%var_std_name) == trim(oconst%var_std_name)) .and. & (trim(this%var_long_name) == trim(oconst%var_long_name)) .and. & (trim(this%vert_dim) == trim(oconst%vert_dim)) .and. & (this%advected .eqv. oconst%advected) .and. & (this%const_default_value == oconst%const_default_value) .and. & - (this%thermo_active .eqv. oconst%thermo_active) + (this%thermo_active .eqv. oconst%thermo_active) .and. & + (this%water_species .eqv. oconst%water_species) else equiv = .false. end if @@ -1413,7 +1458,7 @@ end subroutine ccp_model_const_reset !######################################################################## logical function ccp_model_const_is_match(this, index, advected, & - thermo_active) result(is_match) + thermo_active, water_species) result(is_match) ! Return .true. iff the constituent at matches a pattern ! Each (optional) property which is present represents something ! which is required as part of a match. @@ -1425,6 +1470,7 @@ logical function ccp_model_const_is_match(this, index, advected, & integer, intent(in) :: index logical, optional, intent(in) :: advected logical, optional, intent(in) :: thermo_active + logical, optional, intent(in) :: water_species ! Local variable logical :: check @@ -1444,13 +1490,20 @@ logical function ccp_model_const_is_match(this, index, advected, & end if end if + if (present(water_species)) then + call this%const_metadata(index)%is_water_species(check) + if (water_species .neqv. check) then + is_match = .false. + end if + end if + end function ccp_model_const_is_match !######################################################################## subroutine ccp_model_const_num_match(this, nmatch, advected, thermo_active, & - errcode, errmsg) + water_species, errcode, errmsg) ! Query number of constituents matching pattern ! Each (optional) property which is present represents something ! which is required as part of a match. @@ -1461,6 +1514,7 @@ subroutine ccp_model_const_num_match(this, nmatch, advected, thermo_active, & integer, intent(out) :: nmatch logical, optional, intent(in) :: advected logical, optional, intent(in) :: thermo_active + logical, optional, intent(in) :: water_species integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg ! Local variables @@ -1470,7 +1524,8 @@ subroutine ccp_model_const_num_match(this, nmatch, advected, thermo_active, & nmatch = 0 if (this%const_props_locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then do index = 1, SIZE(this%const_metadata) - if (this%is_match(index, advected=advected, thermo_active=thermo_active)) then + if (this%is_match(index, advected=advected, thermo_active=thermo_active, & + water_species=water_species)) then nmatch = nmatch + 1 end if end do @@ -1536,7 +1591,7 @@ end subroutine ccp_model_const_metadata !######################################################################## subroutine ccp_model_const_copy_in_3d(this, const_array, advected, & - thermo_active, errcode, errmsg) + thermo_active, water_species, errcode, errmsg) ! Gather constituent fields matching pattern ! Each (optional) property which is present represents something ! which is required as part of a match. @@ -1547,6 +1602,7 @@ subroutine ccp_model_const_copy_in_3d(this, const_array, advected, & real(kind_phys), intent(out) :: const_array(:,:,:) logical, optional, intent(in) :: advected logical, optional, intent(in) :: thermo_active + logical, optional, intent(in) :: water_species integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg ! Local variables @@ -1564,7 +1620,8 @@ subroutine ccp_model_const_copy_in_3d(this, const_array, advected, & num_levels = SIZE(const_array, 2) do index = 1, SIZE(this%const_metadata) if (this%is_match(index, advected=advected, & - thermo_active=thermo_active)) then + thermo_active=thermo_active, & + water_species=water_species)) then ! See if we have room for another constituent cindex = cindex + 1 if (cindex > max_cind) then @@ -1613,7 +1670,7 @@ end subroutine ccp_model_const_copy_in_3d !######################################################################## subroutine ccp_model_const_copy_out_3d(this, const_array, advected, & - thermo_active, errcode, errmsg) + thermo_active, water_species, errcode, errmsg) ! Update constituent fields matching pattern ! Each (optional) property which is present represents something ! which is required as part of a match. @@ -1624,6 +1681,7 @@ subroutine ccp_model_const_copy_out_3d(this, const_array, advected, & real(kind_phys), intent(in) :: const_array(:,:,:) logical, optional, intent(in) :: advected logical, optional, intent(in) :: thermo_active + logical, optional, intent(in) :: water_species integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg ! Local variables @@ -1641,7 +1699,8 @@ subroutine ccp_model_const_copy_out_3d(this, const_array, advected, & num_levels = SIZE(const_array, 2) do index = 1, SIZE(this%const_metadata) if (this%is_match(index, advected=advected, & - thermo_active=thermo_active)) then + thermo_active=thermo_active, & + water_species=water_species)) then ! See if we have room for another constituent cindex = cindex + 1 if (cindex > max_cind) then @@ -1938,6 +1997,28 @@ end subroutine ccpt_is_thermo_active !####################################################################### + subroutine ccpt_is_water_species(this, val_out, errcode, errmsg) + + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(in) :: this + logical, intent(out) :: val_out + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_is_thermo_active' + + if (associated(this%prop)) then + call this%prop%is_water_species(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_water_species + + !####################################################################### + subroutine ccpt_is_advected(this, val_out, errcode, errmsg) ! Dummy arguments @@ -2307,4 +2388,30 @@ subroutine ccpt_set_thermo_active(this, thermo_flag, errcode, errmsg) end subroutine ccpt_set_thermo_active + !####################################################################### + + subroutine ccpt_set_water_species(this, water_flag, errcode, errmsg) + ! Set whether this constituent is a water species, which means + ! that this constituent represents a particular phase or type + ! of water in the atmosphere. + + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(inout) :: this + logical, intent(in) :: water_flag + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_set_water_species' + + if (associated(this%prop)) then + if (this%prop%is_instantiated(errcode, errmsg)) then + this%prop%water_species = water_flag + end if + else + call set_errvars(1, subname//": invalid constituent pointer", & + errcode=errcode, errmsg=errmsg) + end if + + end subroutine ccpt_set_water_species + end module ccpp_constituent_prop_mod From 456741000ba56a52848163ce4bcd839fe1468153 Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Fri, 29 Sep 2023 09:10:42 -0600 Subject: [PATCH 118/159] Add 'water_species' property unit tests. --- test/advection_test/test_host.F90 | 53 +++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index bdc1068f..9a86b1b3 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -569,6 +569,59 @@ subroutine test_host(retval, test_suites) end if !------------------- + !------------------- + !water-species tests: + !------------------- + + !Check that being thermodynamically active defaults to False: + call const_props(index_liq)%is_water_species(check, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to get water_species prop for cld_liq index = ", index_liq, & + trim(errmsg) + errflg_final = -1 !Notify test script that a failure occurred + end if + if (errflg == 0) then + if (check) then !Should be False + write(6, *) "ERROR: 'is_water_species' should default to False ", & + "for all constituents unless set by host model." + errflg_final = -1 !Notify test script that a failure occured + end if + else + !Reset error flag to continue testing other properties: + errflg = 0 + end if + + !Check that setting a constituent to be a water species works + !as expected: + call const_props(index_liq)%set_water_species(.true., errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to set water_species prop for cld_liq index = ", index_liq, & + trim(errmsg) + errflg_final = -1 !Notify test script that a failure occurred + end if + if (errflg == 0) then + call const_props(index_liq)%is_water_species(check, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, & + " trying to get water_species prop for cld_liq index = ", & + index_liq, trim(errmsg) + errflg_final = -1 !Notify test script that a failure occurred + end if + end if + if (errflg == 0) then + if (.not.check) then !Should now be True + write(6, *) "ERROR: 'set_water_species' did not set", & + " water_species constituent property correctly." + errflg_final = -1 !Notify test script that a failure occurred + end if + else + !Reset error flag to continue testing other properties: + 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 From 900b4fcd4636dd8770f0e030033d1ef7799aecc5 Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Tue, 3 Oct 2023 09:17:37 -0600 Subject: [PATCH 119/159] Fix comments in water species routines. --- src/ccpp_constituent_prop_mod.F90 | 4 ++-- test/advection_test/test_host.F90 | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index 25ec5e6c..60eab202 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -639,7 +639,7 @@ subroutine ccp_set_water_species(this, water_flag, errcode, errmsg) integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg - !Set thermodynamically active flag for this constituent: + !Set water species flag for this constituent: if (this%is_instantiated(errcode, errmsg)) then this%water_species = water_flag end if @@ -676,7 +676,7 @@ subroutine ccp_is_water_species(this, val_out, errcode, errmsg) character(len=*), optional, intent(out) :: errmsg !If instantiated then check if constituent is - !thermodynamically active, otherwise return false: + !a water species, otherwise return false: if (this%is_instantiated(errcode, errmsg)) then val_out = this%water_species else diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index 9a86b1b3..b8b7e3a9 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -573,7 +573,7 @@ subroutine test_host(retval, test_suites) !water-species tests: !------------------- - !Check that being thermodynamically active defaults to False: + !Check that being a water species defaults to False: call const_props(index_liq)%is_water_species(check, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & From 8b0d1f6b00f072b19a1da8a3647bfa8e087ee387 Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Tue, 10 Oct 2023 16:01:42 -0600 Subject: [PATCH 120/159] Add space between '.not.' and logical to improve readability. --- test/advection_test/test_host.F90 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index b8b7e3a9..7ec3f24e 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -558,7 +558,7 @@ subroutine test_host(retval, test_suites) end if end if if (errflg == 0) then - if (.not.check) then !Should now be True + if (.not. check) then !Should now be True write(6, *) "ERROR: 'set_thermo_active' did not set", & " thermo_active constituent property correctly." errflg_final = -1 !Notify test script that a failure occurred @@ -611,7 +611,7 @@ subroutine test_host(retval, test_suites) end if end if if (errflg == 0) then - if (.not.check) then !Should now be True + if (.not. check) then !Should now be True write(6, *) "ERROR: 'set_water_species' did not set", & " water_species constituent property correctly." errflg_final = -1 !Notify test script that a failure occurred From a722da899bb0c18994b8cfff436772bc8a30fafb Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Tue, 7 Nov 2023 15:21:48 -0700 Subject: [PATCH 121/159] sort sets before writing to files to ease comparison between runs --- scripts/ccpp_suite.py | 4 ++-- scripts/host_cap.py | 4 ++-- scripts/suite_objects.py | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/ccpp_suite.py b/scripts/ccpp_suite.py index 130a828c..fc8cf1b6 100644 --- a/scripts/ccpp_suite.py +++ b/scripts/ccpp_suite.py @@ -1088,7 +1088,7 @@ def write_var_set_loop(ofile, varlist_name, var_list, indent, ofile.write("allocate({}({}))".format(varlist_name, len(var_list)), indent) # end if - for ind, var in enumerate(var_list): + for ind, var in enumerate(sorted(var_list)): if start_var: ind_str = "{} + {}".format(start_var, ind + start_index) else: @@ -1136,7 +1136,7 @@ def write_req_vars_sub(self, ofile, errmsg_name, errcode_name): ofile.write("\nsubroutine {}({})".format(API.__vars_fname, inargs), 1) # Declare use statements for suite varlist routines mlen = max([len(x.module) for x in self.suites]) - for suite in self.suites: + for suite in sorted(self.suites): mod = f"{suite.module}{' '*(mlen - len(suite.module))}" ofile.write(f"use {mod}, only: {suite.req_vars_subname()}", 2) # end for diff --git a/scripts/host_cap.py b/scripts/host_cap.py index d2b3066f..4809b158 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -432,7 +432,7 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): mlen = max([len(x.module) for x in api.suites]) maxmod = max(maxmod, mlen) maxmod = max(maxmod, len(CONST_DDT_MOD)) - for mod in modules: + for mod in sorted(modules): mspc = (maxmod - len(mod[0]))*' ' cap.write("use {}, {}only: {}".format(mod[0], mspc, mod[1]), 1) # End for @@ -530,7 +530,7 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): for suite in api.suites: mspc = (max_suite_len - len(suite.module))*' ' spart_list = suite_part_list(suite, stage) - for spart in spart_list: + for spart in sorted(spart_list): stmt = "use {}, {}only: {}" cap.write(stmt.format(suite.module, mspc, spart.name), 2) # End for diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 5a710548..f6ced28a 100644 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1787,7 +1787,7 @@ def write(self, outfile, host_arglist, indent, const_mod, # end if # Write out the scheme use statements scheme_use = 'use {},{} only: {}' - for scheme in self._local_schemes: + for scheme in sorted(self._local_schemes): smod = scheme[0] sname = scheme[1] slen = ' '*(modmax - len(smod)) @@ -1853,7 +1853,7 @@ def write(self, outfile, host_arglist, indent, const_mod, 'funcname' : self.name}) # Allocate local arrays alloc_stmt = "allocate({}({}))" - for lname in allocatable_var_set: + for lname in sorted(allocatable_var_set): var = subpart_vars[lname][0] dims = var.get_dimensions() alloc_str = self.allocate_dim_str(dims, var.context) @@ -1886,7 +1886,7 @@ def write(self, outfile, host_arglist, indent, const_mod, item.write(outfile, errcode, indent + 1) # end for # Deallocate local arrays - for lname in allocatable_var_set: + for lname in sorted(allocatable_var_set): outfile.write('deallocate({})'.format(lname), indent+1) # end for # Deallocate suite vars From 935fb7248b65f61c3534b37d37ba9c663988b134 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Tue, 7 Nov 2023 15:34:17 -0700 Subject: [PATCH 122/159] fix attempt to sort list --- scripts/ccpp_suite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ccpp_suite.py b/scripts/ccpp_suite.py index fc8cf1b6..ade2c234 100644 --- a/scripts/ccpp_suite.py +++ b/scripts/ccpp_suite.py @@ -1136,7 +1136,7 @@ def write_req_vars_sub(self, ofile, errmsg_name, errcode_name): ofile.write("\nsubroutine {}({})".format(API.__vars_fname, inargs), 1) # Declare use statements for suite varlist routines mlen = max([len(x.module) for x in self.suites]) - for suite in sorted(self.suites): + for suite in self.suites: mod = f"{suite.module}{' '*(mlen - len(suite.module))}" ofile.write(f"use {mod}, only: {suite.req_vars_subname()}", 2) # end for From 0e5562605dbbacc6bf0caf2975ccc24089e402fa Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Thu, 11 Jan 2024 10:56:00 -0700 Subject: [PATCH 123/159] Add setter methods for molecular weight, and add some additional property checks. --- src/ccpp_constituent_prop_mod.F90 | 72 ++++++++++++++++++++++++++----- 1 file changed, 62 insertions(+), 10 deletions(-) diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index 60eab202..921aa84e 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -45,7 +45,7 @@ module ccpp_constituent_prop_mod integer, private :: const_water = int_unassigned ! minimum_mr is the minimum allowed value (default zero) real(kind_phys), private :: min_val = 0.0_kind_phys - ! molar_mass is the molecular weight of the constituent (g mol-1) + ! molar_mass is the molecular weight of the constituent (kg mol-1) real(kind_phys), private :: molar_mass = kphys_unassigned ! default_value is the default value that the constituent array will be ! initialized to @@ -86,6 +86,7 @@ module ccpp_constituent_prop_mod procedure :: set_thermo_active => ccp_set_thermo_active procedure :: set_water_species => ccp_set_water_species procedure :: set_minimum => ccp_set_min_val + procedure :: set_molec_weight => ccp_set_molec_weight end type ccpp_constituent_properties_t !! \section arg_table_ccpp_constituent_prop_ptr_t @@ -123,6 +124,7 @@ module ccpp_constituent_prop_mod procedure :: set_thermo_active => ccpt_set_thermo_active procedure :: set_water_species => ccpt_set_water_species procedure :: set_minimum => ccpt_set_min_val + procedure :: set_molec_weight => ccpt_set_molec_weight end type ccpp_constituent_prop_ptr_t !! \section arg_table_ccpp_model_constituents_t @@ -208,15 +210,18 @@ subroutine copyConstituent(outConst, inConst) class(ccpp_constituent_properties_t), intent(inout) :: outConst type(ccpp_constituent_properties_t), intent(in) :: inConst - outConst%var_std_name = inConst%var_std_name - outConst%var_long_name = inConst%var_long_name - outConst%vert_dim = inConst%vert_dim - outConst%const_ind = inConst%const_ind - outConst%advected = inConst%advected - outConst%const_type = inConst%const_type - outConst%const_water = inConst%const_water - outConst%min_val = inConst%min_val + outConst%var_std_name = inConst%var_std_name + outConst%var_long_name = inConst%var_long_name + outConst%vert_dim = inConst%vert_dim + outConst%const_ind = inConst%const_ind + outConst%advected = inConst%advected + outConst%const_type = inConst%const_type + outConst%const_water = inConst%const_water + outConst%min_val = inConst%min_val outConst%const_default_value = inConst%const_default_value + outConst%molar_mass = inConst%molar_mass + outConst%thermo_active = inConst%thermo_active + outConst%water_species = inConst%water_species end subroutine copyConstituent !####################################################################### @@ -374,7 +379,7 @@ end function ccp_is_instantiated !####################################################################### subroutine ccp_instantiate(this, std_name, long_name, units, vertical_dim, & - advected, default_value, errcode, errmsg) + advected, default_value, min_value, molec_weight, errcode, errmsg) ! Initialize all fields in ! Dummy arguments @@ -385,6 +390,8 @@ subroutine ccp_instantiate(this, std_name, long_name, units, vertical_dim, & character(len=*), intent(in) :: vertical_dim logical, optional, intent(in) :: advected real(kind_phys), optional, intent(in) :: default_value + real(kind_phys), optional, intent(in) :: min_value + real(kind_phys), optional, intent(in) :: molec_weight integer, intent(out) :: errcode character(len=*), intent(out) :: errmsg @@ -409,6 +416,12 @@ subroutine ccp_instantiate(this, std_name, long_name, units, vertical_dim, & if (present(default_value)) then this%const_default_value = default_value end if + if (present(min_value)) then + this%min_val = min_value + end if + if (present(molec_weight)) then + this%molar_mass = molec_weight + end if end if if (errcode == 0) then if (index(this%var_std_name, "volume_mixing_ratio") > 0) then @@ -719,6 +732,8 @@ subroutine ccp_is_equivalent(this, oconst, equiv, errcode, errmsg) (trim(this%vert_dim) == trim(oconst%vert_dim)) .and. & (this%advected .eqv. oconst%advected) .and. & (this%const_default_value == oconst%const_default_value) .and. & + (this%min_val == oconst%min_val) .and. & + (this%molar_mass = oconst%molar_mass) .and. & (this%thermo_active .eqv. oconst%thermo_active) .and. & (this%water_species .eqv. oconst%water_species) else @@ -890,6 +905,22 @@ end subroutine ccp_molec_weight !######################################################################## + subroutine ccp_set_molec_weight(this, molec_weight, errcode, errmsg) + + ! Dummy arguments + class(ccpp_constituent_properties_t), intent(in) :: this + real(kind_phys), intent(in) :: molec_weight + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg + + if (this%is_instantiated(errcode, errmsg)) then + this%molar_mass = molec_weight + end if + + end subroutine ccp_set_molec_weight + + !######################################################################## + subroutine ccp_default_value(this, val_out, errcode, errmsg) ! Dummy arguments @@ -2242,6 +2273,27 @@ end subroutine ccpt_molec_weight !######################################################################## + subroutine ccpt_set_molec_weight(this, molec_weight, errcode, errmsg) + + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(in) :: this + real(kind_phys), intent(in) :: molec_weight + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_set_molec_weight' + + if (associated(this%prop)) then + call this%prop%set_molec_weight(molec_weight, errcode, errmsg) + else + call set_errvars(1, subname//": invalid constituent pointer", & + errcode=errcode, errmsg=errmsg) + end if + + end subroutine ccpt_set_molec_weight + + !######################################################################## + subroutine ccpt_default_value(this, val_out, errcode, errmsg) ! Dummy arguments From 7f3e7da7135b176c5f55e1dc47875bf02948ad0b Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Thu, 11 Jan 2024 15:34:03 -0700 Subject: [PATCH 124/159] Add new unit tests, and fix bugs found during testing. --- src/ccpp_constituent_prop_mod.F90 | 18 ++++---- test/advection_test/test_host.F90 | 75 +++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 9 deletions(-) diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index 921aa84e..cc623596 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -733,7 +733,7 @@ subroutine ccp_is_equivalent(this, oconst, equiv, errcode, errmsg) (this%advected .eqv. oconst%advected) .and. & (this%const_default_value == oconst%const_default_value) .and. & (this%min_val == oconst%min_val) .and. & - (this%molar_mass = oconst%molar_mass) .and. & + (this%molar_mass == oconst%molar_mass) .and. & (this%thermo_active .eqv. oconst%thermo_active) .and. & (this%water_species .eqv. oconst%water_species) else @@ -908,10 +908,10 @@ end subroutine ccp_molec_weight subroutine ccp_set_molec_weight(this, molec_weight, errcode, errmsg) ! Dummy arguments - class(ccpp_constituent_properties_t), intent(in) :: this - real(kind_phys), intent(in) :: molec_weight - integer, intent(out) :: errcode - character(len=*), intent(out) :: errmsg + class(ccpp_constituent_properties_t), intent(inout) :: this + real(kind_phys), intent(in) :: molec_weight + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg if (this%is_instantiated(errcode, errmsg)) then this%molar_mass = molec_weight @@ -2276,10 +2276,10 @@ end subroutine ccpt_molec_weight subroutine ccpt_set_molec_weight(this, molec_weight, errcode, errmsg) ! Dummy arguments - class(ccpp_constituent_prop_ptr_t), intent(in) :: this - real(kind_phys), intent(in) :: molec_weight - integer, intent(out) :: errcode - character(len=*), intent(out) :: errmsg + class(ccpp_constituent_prop_ptr_t), intent(inout) :: this + real(kind_phys), intent(in) :: molec_weight + integer, intent(out) :: errcode + character(len=*), intent(out) :: errmsg ! Local variable character(len=*), parameter :: subname = 'ccpt_set_molec_weight' diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index 7ec3f24e..08411a41 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -323,6 +323,7 @@ subroutine test_host(retval, test_suites) call host_constituents(1)%instantiate(std_name="specific_humidity", & long_name="Specific humidity", units="kg kg-1", & vertical_dim="vertical_layer_dimension", advected=.true., & + min_value=1000._kind_phys, molec_weight=2000._kind_phys, & errcode=errflg, errmsg=errmsg) call check_errflg(subname//'.initialize', errflg, errmsg, errflg_final) if (errflg == 0) then @@ -487,6 +488,26 @@ subroutine test_host(retval, test_suites) errflg = 0 end if + !Check that a constituent instantiated with a specified minimum value + !actually contains that minimum value property: + call const_props(index)%minimum(check_value, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to get minimum value for specific humidity index = ", index, & + trim(errmsg) + errflg_final = -1 !Notify test script that a failure occurred + end if + if (errflg == 0) then + if (check_value /= 1000._kind_phys) then !Should be 1000 + write(6, *) "ERROR: 'minimum' should give a value of 1000 ", & + "for specific humidity, as was set during instantiation." + errflg_final = -1 !Notify test script that a failure occured + end if + else + !Reset error flag to continue testing other properties: + errflg = 0 + end if + !Check that setting a constituent's minimum value works !as expected: call const_props(index_ice)%set_minimum(1._kind_phys, errflg, errmsg) @@ -516,6 +537,60 @@ subroutine test_host(retval, test_suites) errflg = 0 end if + !---------------------- + !molecular weight tests: + !---------------------- + + !Check that a constituent instantiated with a specified molecular + !weight actually contains that molecular weight property value: + call const_props(index)%molec_weight(check_value, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to get molecular weight for specific humidity index = ", & + index, trim(errmsg) + errflg_final = -1 !Notify test script that a failure occurred + end if + if (errflg == 0) then + if (check_value /= 2000._kind_phys) then !Should be 2000 + write(6, *) "ERROR: 'molec_weight' should give a value of 2000 ", & + "for specific humidity, as was set during instantiation." + errflg_final = -1 !Notify test script that a failure occured + end if + else + !Reset error flag to continue testing other properties: + errflg = 0 + end if + + !Check that setting a constituent's molecular weight works + !as expected: + call const_props(index_ice)%set_molec_weight(1._kind_phys, errflg, & + errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to set molecular weight for cld_ice index = ", index_ice, & + trim(errmsg) + errflg_final = -1 !Notify test script that a failure occurred + end if + if (errflg == 0) then + call const_props(index_ice)%molec_weight(check_value, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, & + " trying to get molecular weight for cld_ice index = ", & + index_ice, trim(errmsg) + errflg_final = -1 !Notify test script that a failure occurred + end if + end if + if (errflg == 0) then + if (check_value /= 1._kind_phys) then !Should be equal to one + write(6, *) "ERROR: 'set_molec_weight' did not set constituent", & + " molecular weight value correctly." + errflg_final = -1 !Notify test script that a failure occurred + end if + else + !Reset error flag to continue testing other properties: + errflg = 0 + end if + !------------------- !thermo-active tests: !------------------- From 76fb68243c6fc063e94f6dbd808aceb1e831c6b2 Mon Sep 17 00:00:00 2001 From: Jesse Nusbaumer Date: Fri, 12 Jan 2024 09:46:50 -0700 Subject: [PATCH 125/159] Replace 'molec_weight' with 'molar_mass', as it is likely a more accurate term. --- src/ccpp_constituent_prop_mod.F90 | 58 +++++++++++++++---------------- test/advection_test/test_host.F90 | 12 +++---- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index cc623596..599a9d3f 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -45,8 +45,8 @@ module ccpp_constituent_prop_mod integer, private :: const_water = int_unassigned ! minimum_mr is the minimum allowed value (default zero) real(kind_phys), private :: min_val = 0.0_kind_phys - ! molar_mass is the molecular weight of the constituent (kg mol-1) - real(kind_phys), private :: molar_mass = kphys_unassigned + ! molar_mass_val is the molar mass of the constituent (kg mol-1) + real(kind_phys), private :: molar_mass_val = kphys_unassigned ! default_value is the default value that the constituent array will be ! initialized to real(kind_phys), private :: const_default_value = kphys_unassigned @@ -73,7 +73,7 @@ module ccpp_constituent_prop_mod procedure :: is_moist => ccp_is_moist procedure :: is_wet => ccp_is_wet procedure :: minimum => ccp_min_val - procedure :: molec_weight => ccp_molec_weight + procedure :: molar_mass => ccp_molar_mass procedure :: default_value => ccp_default_value procedure :: has_default => ccp_has_default ! Copy method (be sure to update this anytime fields are added) @@ -86,7 +86,7 @@ module ccpp_constituent_prop_mod procedure :: set_thermo_active => ccp_set_thermo_active procedure :: set_water_species => ccp_set_water_species procedure :: set_minimum => ccp_set_min_val - procedure :: set_molec_weight => ccp_set_molec_weight + procedure :: set_molar_mass => ccp_set_molar_mass end type ccpp_constituent_properties_t !! \section arg_table_ccpp_constituent_prop_ptr_t @@ -113,18 +113,18 @@ module ccpp_constituent_prop_mod procedure :: is_moist => ccpt_is_moist procedure :: is_wet => ccpt_is_wet procedure :: minimum => ccpt_min_val - procedure :: molec_weight => ccpt_molec_weight + procedure :: molar_mass => ccpt_molar_mass procedure :: default_value => ccpt_default_value procedure :: has_default => ccpt_has_default ! ccpt_set: Set the internal pointer - procedure :: set => ccpt_set + procedure :: set => ccpt_set ! Methods that change state (XXgoldyXX: make private?) procedure :: deallocate => ccpt_deallocate procedure :: set_const_index => ccpt_set_const_index procedure :: set_thermo_active => ccpt_set_thermo_active procedure :: set_water_species => ccpt_set_water_species procedure :: set_minimum => ccpt_set_min_val - procedure :: set_molec_weight => ccpt_set_molec_weight + procedure :: set_molar_mass => ccpt_set_molar_mass end type ccpp_constituent_prop_ptr_t !! \section arg_table_ccpp_model_constituents_t @@ -219,7 +219,7 @@ subroutine copyConstituent(outConst, inConst) outConst%const_water = inConst%const_water outConst%min_val = inConst%min_val outConst%const_default_value = inConst%const_default_value - outConst%molar_mass = inConst%molar_mass + outConst%molar_mass_val = inConst%molar_mass_val outConst%thermo_active = inConst%thermo_active outConst%water_species = inConst%water_species end subroutine copyConstituent @@ -379,7 +379,7 @@ end function ccp_is_instantiated !####################################################################### subroutine ccp_instantiate(this, std_name, long_name, units, vertical_dim, & - advected, default_value, min_value, molec_weight, errcode, errmsg) + advected, default_value, min_value, molar_mass, errcode, errmsg) ! Initialize all fields in ! Dummy arguments @@ -391,7 +391,7 @@ subroutine ccp_instantiate(this, std_name, long_name, units, vertical_dim, & logical, optional, intent(in) :: advected real(kind_phys), optional, intent(in) :: default_value real(kind_phys), optional, intent(in) :: min_value - real(kind_phys), optional, intent(in) :: molec_weight + real(kind_phys), optional, intent(in) :: molar_mass integer, intent(out) :: errcode character(len=*), intent(out) :: errmsg @@ -419,8 +419,8 @@ subroutine ccp_instantiate(this, std_name, long_name, units, vertical_dim, & if (present(min_value)) then this%min_val = min_value end if - if (present(molec_weight)) then - this%molar_mass = molec_weight + if (present(molar_mass)) then + this%molar_mass_val = molar_mass end if end if if (errcode == 0) then @@ -733,7 +733,7 @@ subroutine ccp_is_equivalent(this, oconst, equiv, errcode, errmsg) (this%advected .eqv. oconst%advected) .and. & (this%const_default_value == oconst%const_default_value) .and. & (this%min_val == oconst%min_val) .and. & - (this%molar_mass == oconst%molar_mass) .and. & + (this%molar_mass_val == oconst%molar_mass_val) .and. & (this%thermo_active .eqv. oconst%thermo_active) .and. & (this%water_species .eqv. oconst%water_species) else @@ -887,7 +887,7 @@ end subroutine ccp_set_min_val !######################################################################## - subroutine ccp_molec_weight(this, val_out, errcode, errmsg) + subroutine ccp_molar_mass(this, val_out, errcode, errmsg) ! Dummy arguments class(ccpp_constituent_properties_t), intent(in) :: this @@ -896,28 +896,28 @@ subroutine ccp_molec_weight(this, val_out, errcode, errmsg) character(len=*), intent(out) :: errmsg if (this%is_instantiated(errcode, errmsg)) then - val_out = this%molar_mass + val_out = this%molar_mass_val else val_out = kphys_unassigned end if - end subroutine ccp_molec_weight + end subroutine ccp_molar_mass !######################################################################## - subroutine ccp_set_molec_weight(this, molec_weight, errcode, errmsg) + subroutine ccp_set_molar_mass(this, molar_mass, errcode, errmsg) ! Dummy arguments class(ccpp_constituent_properties_t), intent(inout) :: this - real(kind_phys), intent(in) :: molec_weight + real(kind_phys), intent(in) :: molar_mass integer, intent(out) :: errcode character(len=*), intent(out) :: errmsg if (this%is_instantiated(errcode, errmsg)) then - this%molar_mass = molec_weight + this%molar_mass_val = molar_mass end if - end subroutine ccp_set_molec_weight + end subroutine ccp_set_molar_mass !######################################################################## @@ -2251,7 +2251,7 @@ end subroutine ccpt_set_min_val !######################################################################## - subroutine ccpt_molec_weight(this, val_out, errcode, errmsg) + subroutine ccpt_molar_mass(this, val_out, errcode, errmsg) ! Dummy arguments class(ccpp_constituent_prop_ptr_t), intent(in) :: this @@ -2259,38 +2259,38 @@ subroutine ccpt_molec_weight(this, val_out, errcode, errmsg) integer, intent(out) :: errcode character(len=*), intent(out) :: errmsg ! Local variable - character(len=*), parameter :: subname = 'ccpt_molec_weight' + character(len=*), parameter :: subname = 'ccpt_molar_mass' if (associated(this%prop)) then - call this%prop%molec_weight(val_out, errcode, errmsg) + call this%prop%molar_mass(val_out, errcode, errmsg) else val_out = kphys_unassigned call set_errvars(1, subname//": invalid constituent pointer", & errcode=errcode, errmsg=errmsg) end if - end subroutine ccpt_molec_weight + end subroutine ccpt_molar_mass !######################################################################## - subroutine ccpt_set_molec_weight(this, molec_weight, errcode, errmsg) + subroutine ccpt_set_molar_mass(this, molar_mass, errcode, errmsg) ! Dummy arguments class(ccpp_constituent_prop_ptr_t), intent(inout) :: this - real(kind_phys), intent(in) :: molec_weight + real(kind_phys), intent(in) :: molar_mass integer, intent(out) :: errcode character(len=*), intent(out) :: errmsg ! Local variable - character(len=*), parameter :: subname = 'ccpt_set_molec_weight' + character(len=*), parameter :: subname = 'ccpt_set_molar_mass' if (associated(this%prop)) then - call this%prop%set_molec_weight(molec_weight, errcode, errmsg) + call this%prop%set_molar_mass(molar_mass, errcode, errmsg) else call set_errvars(1, subname//": invalid constituent pointer", & errcode=errcode, errmsg=errmsg) end if - end subroutine ccpt_set_molec_weight + end subroutine ccpt_set_molar_mass !######################################################################## diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index 08411a41..de77535f 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -323,7 +323,7 @@ subroutine test_host(retval, test_suites) call host_constituents(1)%instantiate(std_name="specific_humidity", & long_name="Specific humidity", units="kg kg-1", & vertical_dim="vertical_layer_dimension", advected=.true., & - min_value=1000._kind_phys, molec_weight=2000._kind_phys, & + min_value=1000._kind_phys, molar_mass=2000._kind_phys, & errcode=errflg, errmsg=errmsg) call check_errflg(subname//'.initialize', errflg, errmsg, errflg_final) if (errflg == 0) then @@ -543,7 +543,7 @@ subroutine test_host(retval, test_suites) !Check that a constituent instantiated with a specified molecular !weight actually contains that molecular weight property value: - call const_props(index)%molec_weight(check_value, errflg, errmsg) + call const_props(index)%molar_mass(check_value, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to get molecular weight for specific humidity index = ", & @@ -552,7 +552,7 @@ subroutine test_host(retval, test_suites) end if if (errflg == 0) then if (check_value /= 2000._kind_phys) then !Should be 2000 - write(6, *) "ERROR: 'molec_weight' should give a value of 2000 ", & + write(6, *) "ERROR: 'molar_mass' should give a value of 2000 ", & "for specific humidity, as was set during instantiation." errflg_final = -1 !Notify test script that a failure occured end if @@ -563,7 +563,7 @@ subroutine test_host(retval, test_suites) !Check that setting a constituent's molecular weight works !as expected: - call const_props(index_ice)%set_molec_weight(1._kind_phys, errflg, & + call const_props(index_ice)%set_molar_mass(1._kind_phys, errflg, & errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & @@ -572,7 +572,7 @@ subroutine test_host(retval, test_suites) errflg_final = -1 !Notify test script that a failure occurred end if if (errflg == 0) then - call const_props(index_ice)%molec_weight(check_value, errflg, errmsg) + call const_props(index_ice)%molar_mass(check_value, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, & " trying to get molecular weight for cld_ice index = ", & @@ -582,7 +582,7 @@ subroutine test_host(retval, test_suites) end if if (errflg == 0) then if (check_value /= 1._kind_phys) then !Should be equal to one - write(6, *) "ERROR: 'set_molec_weight' did not set constituent", & + write(6, *) "ERROR: 'set_molar_mass' did not set constituent", & " molecular weight value correctly." errflg_final = -1 !Notify test script that a failure occurred end if From d89b65eefb85212bfec1b885c3a07f3dcfda9b41 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Sun, 4 Feb 2024 17:08:39 -0700 Subject: [PATCH 126/159] initial commit; metadata table and parsing working --- scripts/ccpp_datafile.py | 39 ++++++++++++++++++++++++++++++++ scripts/metadata_table.py | 13 +++++++++-- test/advection_test/cld_ice.F90 | 27 ++++++++++++++++++++++ test/advection_test/cld_ice.meta | 1 + 4 files changed, 78 insertions(+), 2 deletions(-) diff --git a/scripts/ccpp_datafile.py b/scripts/ccpp_datafile.py index 1fd5f830..00f43986 100755 --- a/scripts/ccpp_datafile.py +++ b/scripts/ccpp_datafile.py @@ -51,6 +51,8 @@ {"report" : "dependencies", "type" : bool, "help" : ("Return a list of scheme and host " "dependency module names")}, + {"report" : "dyn_const_routine", "type" : bool, + "help" : ("Return the constituent routines for a suite")}, {"report" : "suite_list", "type" : bool, "help" : "Return a list of configured suite names"}, {"report" : "required_variables", "type" : str, @@ -288,6 +290,23 @@ def _retrieve_dependencies(table): # end for return sorted(result) +############################################################################### +def _retrieve_dyn_const_routines(table): +############################################################################### + """Find and return a list of all scheme constituent routines.""" + result = set() + routines = table.find("dyn_const_routine") + if routines is None: + raise CCPPDatatableError("Could not find 'dyn_const_routine' element") + # end if + for routine in routines: + routine_name = routine.text + if routine_name is not none: + result.add(routine_name) + # end if + # end for + return sorted(result) + ############################################################################### def _find_var_dictionary(table, dict_name=None, dict_type=None): ############################################################################### @@ -489,6 +508,8 @@ def datatable_report(datatable, action, sep, excl_prot=False): result = _retrieve_module_list(table) elif action.action_is("dependencies"): result = _retrieve_dependencies(table) + elif action.action_is("dyn_const_routine"): + result = _retrieve_const_num_routines(table) elif action.action_is("suite_list"): result = _retrieve_suite_list(table) elif action.action_is("required_variables"): @@ -795,6 +816,17 @@ def _add_dependencies(parent, scheme_depends, host_depends): entry.text = sfile # end for +############################################################################### +def _add_dyn_const_routines(parent, routines): +############################################################################### + """Add a section to that lists all the constituent routines + for the suite""" + file_entry = ET.SubElement(parent, "dyn_const_routines") + for routine in routines: + entry = ET.SubElement(file_entry, "dyn_const_routine") + entry.text = routine + # end for + ############################################################################### def _add_generated_files(parent, host_files, suite_files, ccpp_kinds, src_dir): ############################################################################### @@ -921,6 +953,13 @@ def generate_ccpp_datatable(run_env, host_model, api, scheme_headers, # end for # end for _add_dependencies(datatable, scheme_depends, host_depends) + # Add in all constituent routines + dyn_const_routines = set() + for table in scheme_tdict: + if scheme_tdict[table].dyn_const_routine is not None: + dyn_const_routines.add(scheme_tdict[table].dyn_const_routine) + # end if + _add_dyn_const_routines(datatable, dyn_const_routines) # Write tree datatable_tree = PrettyElementTree(datatable) datatable_tree.write(run_env.datatable_file) diff --git a/scripts/metadata_table.py b/scripts/metadata_table.py index 946e9782..6840f6be 100755 --- a/scripts/metadata_table.py +++ b/scripts/metadata_table.py @@ -270,8 +270,8 @@ class MetadataTable(): __table_start = re.compile(r"(?i)\s*\[\s*ccpp-table-properties\s*\]") def __init__(self, run_env, table_name_in=None, table_type_in=None, - dependencies=None, relative_path=None, known_ddts=None, - var_dict=None, module=None, parse_object=None): + dependencies=None, relative_path=None, dyn_const_routine=None, + known_ddts=None, var_dict=None, module=None, parse_object=None): """Initialize a MetadataTable, either with a name, , and type, , or with information from a file (). if is None, and are @@ -283,6 +283,7 @@ def __init__(self, run_env, table_name_in=None, table_type_in=None, self.__pobj = parse_object self.__dependencies = dependencies self.__relative_path = relative_path + self.__dyn_const_routine = dyn_const_routine self.__sections = [] self.__run_env = run_env if parse_object is None: @@ -395,6 +396,8 @@ def __init_from_file(self, known_ddts, run_env): # end if elif key == 'relative_path': self.__relative_path = value + elif key == 'dynamic_constituent_routine': + self.__dyn_const_routine = value else: tok_type = "metadata table start property" self.__pobj.add_syntax_err(tok_type, token=value) @@ -475,6 +478,12 @@ def relative_path(self): """Return the relative path for the table's dependencies""" return self.__relative_path + @property + def dyn_const_routine(self): + """Return the name of the routine that will dynamically return + an array of constituent properties""" + return self.__dyn_const_routine + @property def run_env(self): """Return this table's CCPPFrameworkEnv object""" diff --git a/test/advection_test/cld_ice.F90 b/test/advection_test/cld_ice.F90 index 9c1e769a..b59991f2 100644 --- a/test/advection_test/cld_ice.F90 +++ b/test/advection_test/cld_ice.F90 @@ -91,6 +91,33 @@ subroutine cld_ice_final(errmsg, errflg) errflg = 0 end subroutine cld_ice_final + + subroutine cld_ice_dynamic_constituents(dyn_const, errmsg, errflg) + use ccpp_constituent_prop_mod, only: ccpp_constituent_properties_t + type(ccpp_constituent_properties_t), allocatable, intent(out) :: dyn_const(:) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + ! Local variables + integer :: num_const + errmsg = '' + errflg = 0 + num_const = 2 + allocate(dyn_const(2)) + ! check allocate? + call dyn_const(1)%instantiate(std_name='dyn_const1', long_name='dyn const1', & + units='kg kg-1', default_value=0._kind_phys, & + vertical_dim='vertical_layer_dimension', advected=.true., & + errcode=errflg, errmsg=errmsg) + if (errflg /= 0) then + return + end if + call dyn_const(1)%instantiate(std_name='dyn_const2', long_name='dyn const2', & + units='kg kg-1', default_value=0._kind_phys, & + vertical_dim='vertical_layer_dimension', advected=.true., & + errcode=errflg, errmsg=errmsg) + + end subroutine cld_ice_dynamic_constituents !! @} !! @} diff --git a/test/advection_test/cld_ice.meta b/test/advection_test/cld_ice.meta index 010fb419..c27c3c6d 100644 --- a/test/advection_test/cld_ice.meta +++ b/test/advection_test/cld_ice.meta @@ -2,6 +2,7 @@ [ccpp-table-properties] name = cld_ice type = scheme + dynamic_constituent_routine = cld_ice_dynamic_constituents [ccpp-arg-table] name = cld_ice_run type = scheme From 443c37e2dae6a5c8182413ffcfe08d7db0d54025 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Fri, 9 Feb 2024 15:48:36 -0700 Subject: [PATCH 127/159] code works; may need cleanup --- scripts/ccpp_capgen.py | 9 +++- scripts/ccpp_datafile.py | 27 +++++------ scripts/ccpp_suite.py | 10 ++++- scripts/constituents.py | 69 ++++++++++++++++++++++++++--- scripts/host_cap.py | 4 +- test/advection_test/cld_ice.F90 | 24 +++++----- test/advection_test/cld_liq.F90 | 21 +++++++++ test/advection_test/cld_liq.meta | 1 + test/advection_test/run_test | 2 + test/advection_test/test_host.F90 | 20 +++++++-- test/advection_test/test_reports.py | 3 ++ 11 files changed, 153 insertions(+), 37 deletions(-) diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index cbd46599..fa8de660 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -619,6 +619,13 @@ 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) + # Pull out the dynamic constituent routines, if any + dyn_const_dict = {} + for table in scheme_tdict: + if scheme_tdict[table].dyn_const_routine is not None: + dyn_const_dict[table] = scheme_tdict[table].dyn_const_routine + # end if + # end for if run_env.verbose: ddts = host_model.ddt_lib.keys() if ddts: @@ -649,7 +656,7 @@ def capgen(run_env, return_db=False): # end if os.makedirs(outtemp_dir) # end if - ccpp_api = API(sdfs, host_model, scheme_headers, run_env) + ccpp_api = API(sdfs, host_model, scheme_headers, run_env, dyn_const_dict) cap_filenames = ccpp_api.write(outtemp_dir, run_env) if run_env.generate_host_cap: # Create a cap file diff --git a/scripts/ccpp_datafile.py b/scripts/ccpp_datafile.py index 00f43986..a6cef6e6 100755 --- a/scripts/ccpp_datafile.py +++ b/scripts/ccpp_datafile.py @@ -51,7 +51,7 @@ {"report" : "dependencies", "type" : bool, "help" : ("Return a list of scheme and host " "dependency module names")}, - {"report" : "dyn_const_routine", "type" : bool, + {"report" : "dyn_const_routines", "type" : bool, "help" : ("Return the constituent routines for a suite")}, {"report" : "suite_list", "type" : bool, "help" : "Return a list of configured suite names"}, @@ -295,13 +295,13 @@ def _retrieve_dyn_const_routines(table): ############################################################################### """Find and return a list of all scheme constituent routines.""" result = set() - routines = table.find("dyn_const_routine") + routines = table.find("dyn_const_routines") if routines is None: raise CCPPDatatableError("Could not find 'dyn_const_routine' element") # end if for routine in routines: routine_name = routine.text - if routine_name is not none: + if routine_name is not None: result.add(routine_name) # end if # end for @@ -508,8 +508,8 @@ def datatable_report(datatable, action, sep, excl_prot=False): result = _retrieve_module_list(table) elif action.action_is("dependencies"): result = _retrieve_dependencies(table) - elif action.action_is("dyn_const_routine"): - result = _retrieve_const_num_routines(table) + elif action.action_is("dyn_const_routines"): + result = _retrieve_dyn_const_routines(table) elif action.action_is("suite_list"): result = _retrieve_suite_list(table) elif action.action_is("required_variables"): @@ -817,14 +817,13 @@ def _add_dependencies(parent, scheme_depends, host_depends): # end for ############################################################################### -def _add_dyn_const_routines(parent, routines): +def _add_dyn_const_routine(file_entry, routine, scheme): ############################################################################### """Add a section to that lists all the constituent routines for the suite""" - file_entry = ET.SubElement(parent, "dyn_const_routines") - for routine in routines: - entry = ET.SubElement(file_entry, "dyn_const_routine") - entry.text = routine + entry = ET.SubElement(file_entry, "dyn_const_routine") + entry.text = routine + entry.set("parent", scheme) # end for ############################################################################### @@ -954,12 +953,14 @@ def generate_ccpp_datatable(run_env, host_model, api, scheme_headers, # end for _add_dependencies(datatable, scheme_depends, host_depends) # Add in all constituent routines - dyn_const_routines = set() + first_const_routine = True for table in scheme_tdict: if scheme_tdict[table].dyn_const_routine is not None: - dyn_const_routines.add(scheme_tdict[table].dyn_const_routine) + if first_const_routine: + file_entry = ET.SubElement(datatable, "dyn_const_routines") + first_const_routine = False + _add_dyn_const_routine(file_entry, scheme_tdict[table].dyn_const_routine, table) # end if - _add_dyn_const_routines(datatable, dyn_const_routines) # Write tree datatable_tree = PrettyElementTree(datatable) datatable_tree.write(run_env.datatable_file) diff --git a/scripts/ccpp_suite.py b/scripts/ccpp_suite.py index c5c6f4ab..3378d914 100644 --- a/scripts/ccpp_suite.py +++ b/scripts/ccpp_suite.py @@ -584,7 +584,7 @@ class API(VarDictionary): 'kind':'len=*', 'units':'', 'dimensions':'()'}, _API_SOURCE, _API_DUMMY_RUN_ENV) - def __init__(self, sdfs, host_model, scheme_headers, run_env): + def __init__(self, sdfs, host_model, scheme_headers, run_env, dyn_const_dict={}): """Initialize this API. is the list of Suite Definition Files to be parsed for data needed by the CCPP cap. @@ -593,11 +593,14 @@ def __init__(self, sdfs, host_model, scheme_headers, run_env): is the list of parsed physics scheme metadata files. Every scheme referenced by an SDF in MUST be in this list, however, unused schemes are allowed. + is the dictionary (key = scheme name) of dynamic + constituent routine names is the CCPPFrameworkEnv object for this framework run. """ self.__module = 'ccpp_physics_api' self.__host = host_model self.__suites = list() + self.__dyn_const_dict = dyn_const_dict super().__init__(self.module, run_env, parent_dict=self.host_model) # Create a usable library out of scheme_headers # Structure is dictionary of dictionaries @@ -1153,6 +1156,11 @@ def suites(self): "Return the list of this API's suites" return self.__suites + @property + def dyn_const_dict(self): + """Return the dynamic constituent routine dictionary""" + return self.__dyn_const_dict + ############################################################################### if __name__ == "__main__": try: diff --git a/scripts/constituents.py b/scripts/constituents.py index 6b0ac759..c2044c5b 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -459,10 +459,11 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna 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): + const_index_func, suite_list, dyn_const_dict, err_vars): """Write out the host model routine which will instantiate constituent fields for all the constituents in . is a list of the host model's error variables. + is a dictionary (key=scheme name) of dynamic constituent routines Also write out the following routines: : Initialize constituent data : Number of constituents @@ -492,30 +493,44 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna # XXgoldyXX: ^ need to generalize host model error var type support # First up, the registration routine substmt = f"subroutine {reg_funcname}" - args = "suite_list, host_constituents " + args = "suite_list, host_constituents, dynamic_constituents " stmt = f"{substmt}({args}, {err_dummy_str})" cap.write(stmt, 1) cap.comment("Create constituent object for suites in ", 2) cap.blank_line() ConstituentVarDict.write_constituent_use_statements(cap, suite_list, 2) + # Conditionally include use statements for dynamic constituent routines + if len(dyn_const_dict) > 0: + cap.comment("Dynamic constituent routines", 2) + for scheme in dyn_const_dict: + cap.write(f"use {scheme}, only: {dyn_const_dict[scheme]}", 2) cap.blank_line() cap.comment("Dummy arguments", 2) cap.write("character(len=*), intent(in) :: suite_list(:)", 2) cap.write(f"type({CONST_PROP_TYPE}), target, intent(in) :: " + \ "host_constituents(:)", 2) + cap.write(f"type({CONST_PROP_TYPE}), allocatable, target, intent(inout) :: " + \ + "dynamic_constituents(:)", 2) for evar in err_vars: evar.write_def(cap, 2, host, dummy=True, add_intent="out") # end for cap.comment("Local variables", 2) spc = ' '*37 - cap.write("integer{} :: num_suite_consts".format(spc), 2) - cap.write("integer{} :: num_consts".format(spc), 2) - cap.write("integer{} :: index".format(spc), 2) - cap.write("integer{} :: field_ind".format(spc), 2) + cap.write(f"integer{spc} :: num_suite_consts", 2) + cap.write(f"integer{spc} :: num_consts", 2) + cap.write(f"integer{spc} :: num_dyn_consts", 2) + cap.write(f"integer{spc} :: index, index_start", 2) + cap.write(f"integer{spc} :: field_ind", 2) cap.write(f"type({CONST_PROP_TYPE}), pointer :: const_prop", 2) + # Declare dynamic constituent properties variables + for idx, scheme in enumerate(sorted(dyn_const_dict)): + cap.comment(f"dynamic constituent props variable for {scheme}", 2) + cap.write(f"type({CONST_PROP_TYPE}), allocatable, target :: dyn_const_prop_{idx}(:)", 2) + # end for cap.blank_line() cap.write("{} = 0".format(herrcode), 2) cap.write("num_consts = size(host_constituents, 1)", 2) + cap.write("num_dyn_consts = 0", 2) for suite in suite_list: const_dict = suite.constituent_dictionary() funcname = const_dict.num_consts_funcname() @@ -525,6 +540,30 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.write(f"num_suite_consts = {funcname}({errvar_str})", 2) cap.write("num_consts = num_consts + num_suite_consts", 2) # end for + # Check for dynamic constituent routines + if len(dyn_const_dict) > 0: + cap.comment("Add in dynamic constituents", 2) + for idx, scheme in enumerate(sorted(dyn_const_dict)): + cap.write(f"if ({herrcode} == 0) then", 2) + cap.write(f"call {dyn_const_dict[scheme]}(dyn_const_prop_{idx}, {obj_err_callstr})", 3) + cap.write(f"num_dyn_consts = num_dyn_consts + size(dyn_const_prop_{idx})", 3) + cap.write("end if", 2) + # end for + cap.write("num_consts = num_consts + num_dyn_consts", 2) + cap.comment("Pack dynamic_constituents array", 2) + cap.write(f"allocate(dynamic_constituents(num_dyn_consts), stat={herrcode})", 2) + cap.write(f"if ({herrcode} /= 0) then", 2) + cap.write("errmsg = 'failed to allocate dynamic_constituents'", 3) + cap.write("else", 2) + cap.write("index_start = 1", 3) + for idx, scheme in enumerate(sorted(dyn_const_dict)): + cap.write(f"do index = 1, size(dyn_const_prop_{idx}, 1)", 3) + cap.write(f"dynamic_constituents(index + index_start - 1) = dyn_const_prop_{idx}(index)", 4) + cap.write("end do", 3) + cap.write(f"index_start = size(dyn_const_prop_{idx}, 1) + 1", 3) + cap.write(f"deallocate(dyn_const_prop_{idx})", 3) + cap.write("end if", 2) + # end if cap.write("if ({} == 0) then".format(herrcode), 2) cap.comment("Initialize constituent data and field object", 3) stmt = "call {}%initialize_table(num_consts)" @@ -544,6 +583,24 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.write("end do", 3) cap.write("end if", 2) cap.blank_line() + # Register dynamic constituents + if len(dyn_const_dict) > 0: + cap.comment("Add dynamic constituent properties", 2) + cap.write(f"if ({herrcode} == 0) then", 2) + cap.write(f"do index = 1, size(dynamic_constituents, 1)", 3) + cap.write(f"if ({herrcode} == 0) then", 4) + cap.write(f"const_prop => dynamic_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(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) + # end if + # Register suite constituents for suite in suite_list: errvar_str = ConstituentVarDict.__errcode_callstr(herrcode, diff --git a/scripts/host_cap.py b/scripts/host_cap.py index d2c4ed7e..b8970478 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -617,7 +617,9 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): advect_array_func, prop_array_func, const_index_func, - api.suites, err_vars) + api.suites, + api.dyn_const_dict, + err_vars) # End with return cap_filename diff --git a/test/advection_test/cld_ice.F90 b/test/advection_test/cld_ice.F90 index b59991f2..a588138b 100644 --- a/test/advection_test/cld_ice.F90 +++ b/test/advection_test/cld_ice.F90 @@ -11,6 +11,7 @@ MODULE cld_ice PUBLIC :: cld_ice_init PUBLIC :: cld_ice_run PUBLIC :: cld_ice_final + PUBLIC :: cld_ice_dynamic_constituents real(kind_phys), private :: tcld = HUGE(1.0_kind_phys) @@ -92,30 +93,29 @@ subroutine cld_ice_final(errmsg, errflg) end subroutine cld_ice_final - subroutine cld_ice_dynamic_constituents(dyn_const, errmsg, errflg) + subroutine cld_ice_dynamic_constituents(dyn_const, errcode, errmsg) use ccpp_constituent_prop_mod, only: ccpp_constituent_properties_t type(ccpp_constituent_properties_t), allocatable, intent(out) :: dyn_const(:) + integer, intent(out) :: errcode character(len=512), intent(out) :: errmsg - integer, intent(out) :: errflg - ! Local variables - integer :: num_const errmsg = '' - errflg = 0 - num_const = 2 - allocate(dyn_const(2)) - ! check allocate? + errcode = 0 + allocate(dyn_const(2), stat=errcode) + if (errcode /= 0) then + errmsg = 'Error allocating dyn_const in cld_ice_dynamic_constituents' + end if call dyn_const(1)%instantiate(std_name='dyn_const1', long_name='dyn const1', & units='kg kg-1', default_value=0._kind_phys, & vertical_dim='vertical_layer_dimension', advected=.true., & - errcode=errflg, errmsg=errmsg) - if (errflg /= 0) then + errcode=errcode, errmsg=errmsg) + if (errcode /= 0) then return end if - call dyn_const(1)%instantiate(std_name='dyn_const2', long_name='dyn const2', & + call dyn_const(2)%instantiate(std_name='dyn_const2', long_name='dyn const2', & units='kg kg-1', default_value=0._kind_phys, & vertical_dim='vertical_layer_dimension', advected=.true., & - errcode=errflg, errmsg=errmsg) + errcode=errcode, errmsg=errmsg) end subroutine cld_ice_dynamic_constituents !! @} diff --git a/test/advection_test/cld_liq.F90 b/test/advection_test/cld_liq.F90 index 9411e0cb..4fda40e3 100644 --- a/test/advection_test/cld_liq.F90 +++ b/test/advection_test/cld_liq.F90 @@ -10,6 +10,7 @@ MODULE cld_liq PUBLIC :: cld_liq_init PUBLIC :: cld_liq_run + PUBLIC :: cld_liq_dynamic_constituents CONTAINS @@ -74,4 +75,24 @@ subroutine cld_liq_init(tfreeze, cld_liq_array, tcld, errmsg, errflg) end subroutine cld_liq_init + subroutine cld_liq_dynamic_constituents(dyn_const, errcode, errmsg) + use ccpp_constituent_prop_mod, only: ccpp_constituent_properties_t + type(ccpp_constituent_properties_t), allocatable, intent(out) :: dyn_const(:) + integer, intent(out) :: errcode + character(len=512), intent(out) :: errmsg + + errmsg = '' + errcode = 0 + allocate(dyn_const(1), stat=errcode) + if (errcode /= 0) then + errmsg = 'Error allocating dyn_const in cld_liq_dynamic_constituents' + end if + call dyn_const(1)%instantiate(std_name="dyn_const3", long_name='dyn const3', & + units='kg kg-1', default_value=0._kind_phys, & + vertical_dim='vertical_layer_dimension', advected=.true., & + errcode=errcode, errmsg=errmsg) + + end subroutine cld_liq_dynamic_constituents + + END MODULE cld_liq diff --git a/test/advection_test/cld_liq.meta b/test/advection_test/cld_liq.meta index 4c87f4b7..1b9373f9 100644 --- a/test/advection_test/cld_liq.meta +++ b/test/advection_test/cld_liq.meta @@ -2,6 +2,7 @@ [ccpp-table-properties] name = cld_liq type = scheme + dynamic_constituent_routine = cld_liq_dynamic_constituents [ccpp-arg-table] name = cld_liq_run type = scheme diff --git a/test/advection_test/run_test b/test/advection_test/run_test index 9c75aa08..1d9a6d44 100755 --- a/test/advection_test/run_test +++ b/test/advection_test/run_test @@ -127,6 +127,7 @@ ccpp_files="${utility_files},${host_files},${suite_files}" process_list="" module_list="cld_ice,cld_liq" dependencies="" +dyn_const_routines="cld_ice_dynamic_constituents,cld_liq_dynamic_constituents" suite_list="cld_suite" required_vars="ccpp_error_code,ccpp_error_message" required_vars="${required_vars},cloud_ice_dry_mixing_ratio" @@ -219,6 +220,7 @@ echo -e "\nChecking lists from command line" check_datatable ${report_prog} ${datafile} "--process-list" "${process_list}" check_datatable ${report_prog} ${datafile} "--module-list" ${module_list} check_datatable ${report_prog} ${datafile} "--dependencies" "${dependencies}" +check_datatable ${report_prog} ${datafile} "--dyn-const-routines" "${dyn_const_routines}" check_datatable ${report_prog} ${datafile} "--suite-list" ${suite_list} \ --sep ";" echo -e "\nChecking variables from command line" diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index 49a3c602..cfa4f712 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -245,7 +245,7 @@ subroutine test_host(retval, test_suites) logical :: check integer :: col_start, col_end integer :: index, sind - integer :: index_liq, index_ice + integer :: index_liq, index_ice, index_dyn integer :: time_step integer :: num_suites integer :: num_advected ! Num advected species @@ -260,6 +260,7 @@ subroutine test_host(retval, test_suites) real(kind_phys), pointer :: const_ptr(:,:,:) real(kind_phys) :: default_value type(ccpp_constituent_prop_ptr_t), pointer :: const_props(:) + type(ccpp_constituent_properties_t), allocatable :: dynamic_constituents(:) character(len=*), parameter :: subname = 'test_host' ! Initialized "final" error flag used to report a failure to the larged @@ -326,7 +327,8 @@ subroutine test_host(retval, test_suites) 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) + host_constituents, dynamic_constituents=dynamic_constituents, & + errmsg=errmsg, errflg=errflg) end if if (errflg /= 0) then write(6, '(2a)') 'ERROR register_constituents: ', trim(errmsg) @@ -339,7 +341,7 @@ subroutine test_host(retval, test_suites) errflg=errflg) call check_errflg(subname//".num_advected", errflg, errmsg, errflg_final) end if - if (num_advected /= 3) then + if (num_advected /= 6) then write(6, '(a,i0)') "ERROR: num advected constituents = ", num_advected retval = .false. return @@ -375,6 +377,18 @@ subroutine test_host(retval, test_suites) call check_errflg(subname//".index_cld_ice", errflg, errmsg, & errflg_final) + ! Check if the dynamic constituents indices can be found + call test_host_const_get_index('dyn_const1', index_dyn, errflg, errmsg) + call check_errflg(subname//".index_dyn_const1", errflg, errmsg, & + errflg_final) + call test_host_const_get_index('dyn_const2', index_dyn, errflg, errmsg) + call check_errflg(subname//".index_dyn_const2", errflg, errmsg, & + errflg_final) + call test_host_const_get_index('dyn_const3', index_dyn, errflg, errmsg) + call check_errflg(subname//".index_dyn_const3", errflg, errmsg, & + errflg_final) + + !Stop tests here if the index checks failed, as all other tests will !likely fail as well: if (errflg_final /= 0) then diff --git a/test/advection_test/test_reports.py b/test/advection_test/test_reports.py index 6d84616b..1a34462a 100644 --- a/test/advection_test/test_reports.py +++ b/test/advection_test/test_reports.py @@ -66,6 +66,7 @@ def usage(errmsg=None): _PROCESS_LIST = list() _MODULE_LIST = ["cld_ice", "cld_liq"] _SUITE_LIST = ["cld_suite"] +_DYN_CONST_ROUTINES = ["cld_ice_dynamic_constituents", "cld_liq_dynamic_constituents"] _REQUIRED_VARS_CLD = ["ccpp_error_code", "ccpp_error_message", "horizontal_loop_begin", "horizontal_loop_end", "surface_air_pressure", "temperature", @@ -154,6 +155,8 @@ def check_datatable(database, report_type, check_list, sep=','): _MODULE_LIST) NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("suite_list"), _SUITE_LIST) +NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("dyn_const_routines"), + _DYN_CONST_ROUTINES) print("\nChecking variables for CLD suite from python") NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("required_variables", value="cld_suite"), From e7804b93808c12d3531f5247a10f5849ee2378d9 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Fri, 9 Feb 2024 15:53:17 -0700 Subject: [PATCH 128/159] fix comment order - CRUCIAL --- scripts/constituents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/constituents.py b/scripts/constituents.py index c2044c5b..91dfb9da 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -462,8 +462,8 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna const_index_func, suite_list, dyn_const_dict, err_vars): """Write out the host model routine which will instantiate constituent fields for all the constituents in . - is a list of the host model's error variables. is a dictionary (key=scheme name) of dynamic constituent routines + is a list of the host model's error variables. Also write out the following routines: : Initialize constituent data : Number of constituents From bd0ae2ed44633b8d4329b02233dc07a2c49da822 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 12 Feb 2024 16:49:52 -0700 Subject: [PATCH 129/159] clean-up and switch constituents.py to fstrings --- scripts/ccpp_datafile.py | 2 + scripts/constituents.py | 234 ++++++++++++++++++--------------------- 2 files changed, 109 insertions(+), 127 deletions(-) diff --git a/scripts/ccpp_datafile.py b/scripts/ccpp_datafile.py index a6cef6e6..356a5d62 100755 --- a/scripts/ccpp_datafile.py +++ b/scripts/ccpp_datafile.py @@ -959,8 +959,10 @@ def generate_ccpp_datatable(run_env, host_model, api, scheme_headers, if first_const_routine: file_entry = ET.SubElement(datatable, "dyn_const_routines") first_const_routine = False + # end if _add_dyn_const_routine(file_entry, scheme_tdict[table].dyn_const_routine, table) # end if + # end for # Write tree datatable_tree = PrettyElementTree(datatable) datatable_tree.write(run_env.datatable_file) diff --git a/scripts/constituents.py b/scripts/constituents.py index 91dfb9da..ceded38c 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -126,10 +126,10 @@ def __init_err_var(evar, outfile, indent): stdname = evar.get_prop_value('standard_name') if stdname == 'ccpp_error_message': lname = evar.get_prop_value('local_name') - outfile.write("{} = ''".format(lname), indent) + outfile.write(f"{lname} = ''", indent) elif stdname == 'ccpp_error_code': lname = evar.get_prop_value('local_name') - outfile.write("{} = 0".format(lname), indent) + outfile.write(f"{lname} = 0", indent) # end if (no else, just ignore) def declare_public_interfaces(self, outfile, indent): @@ -138,27 +138,25 @@ def declare_public_interfaces(self, outfile, indent): outfile.write("! Public interfaces for handling constituents", indent) outfile.write("! Return the number of constituents for this suite", indent) - outfile.write("public :: {}".format(self.num_consts_funcname()), indent) + outfile.write(f"public :: {self.num_consts_funcname()}", indent) outfile.write("! Return the name of a constituent", indent) - outfile.write("public :: {}".format(self.const_name_subname()), indent) + outfile.write(f"public :: {self.const_name_subname()}", indent) outfile.write("! Copy the data for a constituent", indent) - outfile.write("public :: {}".format(self.copy_const_subname()), indent) + outfile.write(f"public :: {self.copy_const_subname()}", indent) def declare_private_data(self, outfile, indent): """Declare private suite module variables and interfaces to with indent, .""" outfile.write("! Private constituent module data", indent) if self: - stmt = "type({}), private, allocatable :: {}(:)" - outfile.write(stmt.format(CONST_PROP_TYPE, - self.constituent_prop_array_name()), - indent) + stmt = f"type({CONST_PROP_TYPE}), private, allocatable :: {self.constituent_prop_array_name()}(:)" + outfile.write(stmt, indent) # end if - stmt = "logical, private :: {} = .false." - outfile.write(stmt.format(self.constituent_prop_init_name()), indent) + stmt = f"logical, private :: {self.constituent_prop_init_name()} = .false." + outfile.write(stmt, indent) outfile.write("! Private interface for constituents", indent) - stmt = "private :: {}" - outfile.write(stmt.format(self.constituent_prop_init_consts()), indent) + stmt = f"private :: {self.constituent_prop_init_consts()}" + outfile.write(stmt, indent) @classmethod def __errcode_names(cls, err_vars): @@ -175,8 +173,8 @@ def __errcode_names(cls, err_vars): elif stdname == 'ccpp_error_message': errmsg = evar.get_prop_value('local_name') else: - emsg = "Bad errcode variable, '{}'" - raise ParseInternalError(emsg.format(stdname)) + emsg = f"Bad errcode variable, '{stdname}'" + raise ParseInternalError(emsg) # end if # end for if (not errcode) or (not errmsg): @@ -192,8 +190,7 @@ def __errcode_callstr(errcode_name, errmsg_name, suite): """ err_vars = suite.find_error_variables(any_scope=True, clone_as_out=True) errcode, errmsg = ConstituentVarDict.__errcode_names(err_vars) - errvar_str = "{}={}, {}={}".format(errcode, errcode_name, - errmsg, errmsg_name) + errvar_str = f"{errcode}={errcode_name}, {errmsg}={errmsg_name}" return errvar_str def _write_init_check(self, outfile, indent, suite_name, @@ -204,20 +201,20 @@ def _write_init_check(self, outfile, indent, suite_name, outfile.write('', 0) if use_errcode: errcode, errmsg = self.__errcode_names(err_vars) - outfile.write("{} = 0".format(errcode), indent+1) - outfile.write("{} = ''".format(errmsg), indent+1) + outfile.write(f"{errcode} = 0", indent+1) + outfile.write(f"{errmsg} = ''", indent+1) else: raise ParseInternalError("Alternative to errcode not implemented") # end if outfile.write("! Make sure that our constituent array is initialized", indent+1) - stmt = "if (.not. {}) then" - outfile.write(stmt.format(self.constituent_prop_init_name()), indent+1) + stmt = f"if (.not. {self.constituent_prop_init_name()}) then" + outfile.write(stmt, indent+1) if use_errcode: - outfile.write("{} = 1".format(errcode), indent+2) - stmt = 'errmsg = "constituent properties not ' - stmt += 'initialized for suite, {}"' - outfile.write(stmt.format(suite_name), indent+2) + outfile.write(f"{errcode} = 1", indent+2) + stmt = f'{errmsg} = "constituent properties not ' + stmt += f'initialized for suite, {suite_name}"' + outfile.write(stmt, indent+2) outfile.write("end if", indent+1) # end if (no else until an alternative error mechanism supported) @@ -230,25 +227,22 @@ def _write_index_check(self, outfile, indent, suite_name, errcode, errmsg = self.__errcode_names(err_vars) if self: outfile.write("if (index < 1) then", indent+1) - outfile.write("{} = 1".format(errcode), indent+2) - stmt = "write({}, '(a,i0,a)') 'ERROR: index (',index,') " + outfile.write(f"{errcode} = 1", indent+2) + stmt = f"write({errmsg}, '(a,i0,a)') 'ERROR: index (',index,') " stmt += "too small, must be >= 1'" - outfile.write(stmt.format(errmsg), indent+2) - stmt = "else if (index > SIZE({})) then" - outfile.write(stmt.format(self.constituent_prop_array_name()), - indent+1) - outfile.write("{} = 1".format(errcode), indent+2) - stmt = "write({}, '(2(a,i0))') 'ERROR: index (',index,') " - stmt += "too large, must be <= ', SIZE({})" - outfile.write(stmt.format(errmsg, - self.constituent_prop_array_name()), - indent+2) + outfile.write(stmt, indent+2) + stmt = f"else if (index > SIZE({self.constituent_prop_array_name()})) then" + outfile.write(stmt, indent+1) + outfile.write(f"{errcode} = 1", indent+2) + stmt = f"write({errmsg}, '(2(a,i0))') 'ERROR: index (',index,') " + stmt += f"too large, must be <= ', SIZE({self.constituent_prop_array_name()})" + outfile.write(stmt, indent+2) outfile.write("end if", indent+1) else: - outfile.write("{} = 1".format(errcode), indent+1) - stmt = "write({}, '(a,i0,a)') 'ERROR: {}, " + outfile.write(f"{errcode} = 1", indent+1) + stmt = f"write({errmsg}, '(a,i0,a)') 'ERROR: {self.name}, " stmt += "has no constituents'" - outfile.write(stmt.format(errmsg, self.name), indent+1) + outfile.write(stmt, indent+1) # end if else: raise ParseInternalError("Alternative to errcode not implemented") @@ -266,17 +260,14 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): use_errcode = all([x.get_prop_value('standard_name') in errcode_snames for x in err_vars]) errvar_alist = ", ".join([x for x in errvar_names.values()]) - errvar_alist2 = ", {}".format(errvar_alist) if errvar_alist else "" + errvar_alist2 = f", {errvar_alist}" if errvar_alist else "" call_vnames = {'ccpp_error_code' : 'errcode', 'ccpp_error_message' : 'errmsg'} - errvar_call = ", ".join(["{}={}".format(call_vnames[x], errvar_names[x]) - for x in errcode_snames]) - errvar_call2 = ", {}".format(errvar_call) if errvar_call else "" - local_call = ", ".join(["{}={}".format(errvar_names[x], errvar_names[x]) - for x in errcode_snames]) + errvar_call = ", ".join([f"{call_vnames[x]}={errvar_names[x]}" for x in errcode_snames]) + errvar_call2 = f", {errvar_call}" if errvar_call else "" + local_call = ", ".join([f"{errvar_names[x]}={errvar_names[x]}" for x in errcode_snames]) # Allocate and define constituents - stmt = "subroutine {}({})".format(self.constituent_prop_init_consts(), - errvar_alist) + stmt = f"subroutine {self.constituent_prop_init_consts()}({errvar_alist})" outfile.write(stmt, indent) outfile.write("! Allocate and fill the constituent property array", indent + 1) @@ -288,9 +279,8 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): if self: outfile.write("! Local variables", indent+1) outfile.write("integer :: index", indent+1) - stmt = "allocate({}({}))" - outfile.write(stmt.format(self.constituent_prop_array_name(), - len(self)), indent+1) + stmt = f"allocate({self.constituent_prop_array_name()}({len(self)}))" + outfile.write(stmt, indent+1) outfile.write("index = 0", indent+1) # end if for evar in err_vars: @@ -317,22 +307,20 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): f'errmsg={errvar_names["ccpp_error_message"]}'] if default_value is not None and default_value != '': init_args.append(f'default_value={default_value}') - stmt = 'call {}(index)%instantiate({})' + stmt = f'call {self.constituent_prop_array_name()}(index)%instantiate({", ".join(init_args)})' outfile.write(f'if ({errvar_names["ccpp_error_code"]} == 0) then', indent+1) - outfile.write(stmt.format(self.constituent_prop_array_name(), - ", ".join(init_args)), indent+2) + outfile.write(stmt, indent+2) outfile.write("end if", indent+1) # end for - outfile.write("{} = .true.".format(self.constituent_prop_init_name()), - indent+1) - stmt = "end subroutine {}".format(self.constituent_prop_init_consts()) + outfile.write(f"{self.constituent_prop_init_name()} = .true.", indent+1) + stmt = f"end subroutine {self.constituent_prop_init_consts()}" outfile.write(stmt, indent) outfile.write("", 0) - outfile.write("\n! {}\n".format("="*72), 1) + border = "="*72 + outfile.write(f"\n! {border}\n", 1) # Return number of constituents fname = self.num_consts_funcname() - outfile.write("integer function {}({})".format(fname, errvar_alist), - indent) + outfile.write(f"integer function {fname}({errvar_alist})", indent) outfile.write("! Return the number of constituents for this suite", indent+1) outfile.write("! Dummy arguments", indent+1) @@ -344,18 +332,16 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): # end for outfile.write("! Make sure that our constituent array is initialized", indent+1) - stmt = "if (.not. {}) then" - outfile.write(stmt.format(self.constituent_prop_init_name()), indent+1) - outfile.write("call {}({})".format(self.constituent_prop_init_consts(), - local_call), indent+2) + stmt = f"if (.not. {self.constituent_prop_init_name()}) then" + outfile.write(stmt, indent+1) + outfile.write(f"call {self.constituent_prop_init_consts()}({local_call})", indent+2) outfile.write("end if", indent+1) - outfile.write("{} = {}".format(fname, len(self)), indent+1) - outfile.write("end function {}".format(fname), indent) - outfile.write("\n! {}\n".format("="*72), 1) + outfile.write(f"{fname} = {len(self)}", indent+1) + outfile.write(f"end function {fname}", indent) + outfile.write(f"\n! {border}\n", 1) # Return the name of a constituent given an index - stmt = "subroutine {}(index, name_out{})" - outfile.write(stmt.format(self.const_name_subname(), errvar_alist2), - indent) + stmt = f"subroutine {self.const_name_subname()}(index, name_out{errvar_alist2})" + outfile.write(stmt, indent) outfile.write("! Return the name of constituent, ", indent+1) outfile.write("! Dummy arguments", indent+1) outfile.write("integer, intent(in) :: index", indent+1) @@ -371,17 +357,15 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): init_args = ['std_name=name_out', f'errcode={errvar_names["ccpp_error_code"]}', f'errmsg={errvar_names["ccpp_error_message"]}'] - stmt = "call {}(index)%standard_name({})" - outfile.write(stmt.format(self.constituent_prop_array_name(), - ", ".join(init_args)), indent+1) + stmt = f"call {self.constituent_prop_array_name()}(index)%standard_name({', '.join(init_args)})" + outfile.write(stmt, indent+1) # end if - outfile.write("end subroutine {}".format(self.const_name_subname()), - indent) - outfile.write("\n! {}\n".format("="*72), 1) + outfile.write(f"end subroutine {self.const_name_subname()}", indent) + outfile.write(f"\n! {border}\n", 1) # Copy a consitituent's properties - stmt = "subroutine {}(index, cnst_out{})" fname = self.copy_const_subname() - outfile.write(stmt.format(fname, errvar_alist2), indent) + stmt = f"subroutine {fname}(index, cnst_out{errvar_alist2})" + outfile.write(stmt, indent) outfile.write("! Copy the data for a constituent", indent+1) outfile.write("! Dummy arguments", indent+1) outfile.write("integer, intent(in) :: index", indent+1) @@ -395,11 +379,10 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): self._write_index_check(outfile, indent, suite_name, err_vars, use_errcode) if self: - stmt = "cnst_out = {}(index)" - outfile.write(stmt.format(self.constituent_prop_array_name()), - indent+1) + stmt = f"cnst_out = {self.constituent_prop_array_name()}(index)" + outfile.write(stmt, indent+1) # end if - outfile.write("end subroutine {}".format(fname), indent) + outfile.write(f"end subroutine {fname}", indent) def constituent_module_name(self): """Return the name of host model constituent module""" @@ -414,22 +397,22 @@ def constituent_module_name(self): def num_consts_funcname(self): """Return the name of the function which returns the number of constituents for this suite.""" - return "{}_num_consts".format(self.name) + return f"{self.name}_num_consts" def const_name_subname(self): """Return the name of the routine that returns a constituent's given an index""" - return "{}_const_name".format(self.name) + return f"{self.name}_const_name" def copy_const_subname(self): """Return the name of the routine that returns a copy of a constituent's metadata given an index""" - return "{}_copy_const".format(self.name) + return f"{self.name}_copy_const" @staticmethod def constituent_index_name(standard_name): """Return the index name associated with """ - return "index_of_{}".format(standard_name) + return f"index_of_{standard_name}" @staticmethod def write_constituent_use_statements(cap, suite_list, indent): @@ -485,11 +468,9 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna raise ParseInternalError(emsg(host.name)) # end if herrcode, herrmsg = ConstituentVarDict.__errcode_names(err_vars) - err_dummy_str = "{errcode}, {errmsg}".format(errcode=herrcode, - errmsg=herrmsg) - obj_err_callstr = "errcode={errcode}, errmsg={errmsg}" - obj_err_callstr = obj_err_callstr.format(errcode=herrcode, - errmsg=herrmsg) + err_dummy_str = f"{herrcode}, {herrmsg}" + obj_err_callstr = f"errcode={herrcode}, errmsg={herrmsg}" + obj_err_callstr = obj_err_callstr # XXgoldyXX: ^ need to generalize host model error var type support # First up, the registration routine substmt = f"subroutine {reg_funcname}" @@ -502,8 +483,10 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna # Conditionally include use statements for dynamic constituent routines if len(dyn_const_dict) > 0: cap.comment("Dynamic constituent routines", 2) + # end if for scheme in dyn_const_dict: cap.write(f"use {scheme}, only: {dyn_const_dict[scheme]}", 2) + # end for cap.blank_line() cap.comment("Dummy arguments", 2) cap.write("character(len=*), intent(in) :: suite_list(:)", 2) @@ -528,7 +511,7 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.write(f"type({CONST_PROP_TYPE}), allocatable, target :: dyn_const_prop_{idx}(:)", 2) # end for cap.blank_line() - cap.write("{} = 0".format(herrcode), 2) + cap.write(f"{herrcode} = 0", 2) cap.write("num_consts = size(host_constituents, 1)", 2) cap.write("num_dyn_consts = 0", 2) for suite in suite_list: @@ -553,7 +536,7 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.comment("Pack dynamic_constituents array", 2) cap.write(f"allocate(dynamic_constituents(num_dyn_consts), stat={herrcode})", 2) cap.write(f"if ({herrcode} /= 0) then", 2) - cap.write("errmsg = 'failed to allocate dynamic_constituents'", 3) + cap.write(f"{herrmsg} = 'failed to allocate dynamic_constituents'", 3) cap.write("else", 2) cap.write("index_start = 1", 3) for idx, scheme in enumerate(sorted(dyn_const_dict)): @@ -562,22 +545,23 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.write("end do", 3) cap.write(f"index_start = size(dyn_const_prop_{idx}, 1) + 1", 3) cap.write(f"deallocate(dyn_const_prop_{idx})", 3) + # end for cap.write("end if", 2) # end if - cap.write("if ({} == 0) then".format(herrcode), 2) + cap.write(f"if ({herrcode} == 0) then", 2) cap.comment("Initialize constituent data and field object", 3) - stmt = "call {}%initialize_table(num_consts)" - cap.write(stmt.format(const_obj_name), 3) + stmt = f"call {const_obj_name}%initialize_table(num_consts)" + cap.write(stmt, 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) + stmt = f"call {const_obj_name}%new_field(const_prop, {obj_err_callstr})" + cap.write(stmt, 5) cap.write("end if", 4) cap.write("nullify(const_prop)", 4) - cap.write("if ({} /= 0) then".format(herrcode), 4) + cap.write(f"if ({herrcode} /= 0) then", 4) cap.write("exit", 5) cap.write("end if", 4) cap.write("end do", 3) @@ -590,8 +574,8 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.write(f"do index = 1, size(dynamic_constituents, 1)", 3) cap.write(f"if ({herrcode} == 0) then", 4) cap.write(f"const_prop => dynamic_constituents(index)", 5) - stmt = "call {}%new_field(const_prop, {})" - cap.write(stmt.format(const_obj_name, obj_err_callstr), 5) + stmt = f"call {const_obj_name}%new_field(const_prop, {obj_err_callstr})" + cap.write(stmt, 5) cap.write("end if", 4) cap.write("nullify(const_prop)", 4) cap.write(f"if ({herrcode} /= 0) then", 4) @@ -622,12 +606,12 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.write("exit", 5) cap.write("end if", 4) cap.write(f"if ({herrcode} == 0) then", 4) - stmt = "call {}(index, const_prop, {})" - cap.write(stmt.format(funcname, errvar_str), 5) + stmt = f"call {funcname}(index, const_prop, {errvar_str})" + cap.write(stmt, 5) cap.write("end if", 4) cap.write(f"if ({herrcode} == 0) then", 4) - stmt = "call {}%new_field(const_prop, {})" - cap.write(stmt.format(const_obj_name, obj_err_callstr), 5) + stmt = f"call {const_obj_name}%new_field(const_prop, {obj_err_callstr})" + cap.write(stmt, 5) cap.write("end if", 4) cap.write("nullify(const_prop)", 4) cap.write(f"if ({herrcode} /= 0) then", 4) @@ -638,21 +622,20 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.blank_line() # end for cap.write(f"if ({herrcode} == 0) then", 2) - stmt = "call {}%lock_table({})" - cap.write(stmt.format(const_obj_name, obj_err_callstr), 3) + stmt = f"call {const_obj_name}%lock_table({obj_err_callstr})" + cap.write(stmt, 3) cap.write("end if", 2) cap.write(f"if ({herrcode} == 0) then", 2) cap.comment("Set the index for each active constituent", 3) cap.write(f"do index = 1, SIZE({const_indices_name})", 3) - stmt = "call {}%const_index(field_ind, {}(index), {})" - cap.write(stmt.format(const_obj_name, const_names_name, - obj_err_callstr), 4) + stmt = f"call {const_obj_name}%const_index(field_ind, {const_names_name}(index), {obj_err_callstr})" + cap.write(stmt, 4) cap.write("if (field_ind > 0) then", 4) cap.write(f"{const_indices_name}(index) = field_ind", 5) cap.write("else", 4) cap.write(f"{herrcode} = 1", 5) - stmt = "{} = 'No field index for '//trim({}(index))" - cap.write(stmt.format(herrmsg, const_names_name), 5) + stmt = f"{herrmsg} = 'No field index for '//trim({const_names_name}(index))" + cap.write(stmt, 5) cap.write("end if", 4) cap.write(f"if ({herrcode} /= 0) then", 4) cap.write("exit", 5) @@ -689,9 +672,9 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna evar.write_def(cap, 2, host, dummy=True, add_intent="out") # end for cap.blank_line() - call_str = "call {}%num_constituents(num_flds, advected=advected, {})" - cap.write(call_str.format(const_obj_name, obj_err_callstr), 2) - cap.write("end {}".format(substmt), 1) + call_str = f"call {const_obj_name}%num_constituents(num_flds, advected=advected, {obj_err_callstr})" + cap.write(call_str, 2) + cap.write(f"end {substmt}", 1) # Write query_consts routine substmt = f"subroutine {query_const_funcname}" cap.blank_line() @@ -714,9 +697,9 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.blank_line() cap.write(f"end {substmt}", 1) # Write copy_in routine - substmt = "subroutine {}".format(copy_in_funcname) + substmt = f"subroutine {copy_in_funcname}" cap.blank_line() - cap.write("{}(const_array, {})".format(substmt, err_dummy_str), 1) + cap.write(f"{substmt}(const_array, {err_dummy_str})", 1) cap.comment("Copy constituent field info into ", 2) cap.blank_line() cap.comment("Dummy arguments", 2) @@ -725,13 +708,12 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna evar.write_def(cap, 2, host, dummy=True, add_intent="out") # end for cap.blank_line() - cap.write("call {}%copy_in(const_array, {})".format(const_obj_name, - obj_err_callstr), 2) - cap.write("end {}".format(substmt), 1) + cap.write(f"call {const_obj_name}%copy_in(const_array, {obj_err_callstr})", 2) + cap.write(f"end {substmt}", 1) # Write copy_out routine - substmt = "subroutine {}".format(copy_out_funcname) + substmt = f"subroutine {copy_out_funcname}" cap.blank_line() - cap.write("{}(const_array, {})".format(substmt, err_dummy_str), 1) + cap.write(f"{substmt}(const_array, {err_dummy_str})", 1) cap.comment("Update constituent field info from ", 2) cap.blank_line() cap.comment("Dummy arguments", 2) @@ -740,10 +722,8 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna evar.write_def(cap, 2, host, dummy=True, add_intent="out") # end for cap.blank_line() - cap.write("call {}%copy_out(const_array, {})".format(const_obj_name, - obj_err_callstr), - 2) - cap.write("end {}".format(substmt), 1) + cap.write(f"call {const_obj_name}%copy_out(const_array, {obj_err_callstr})", 2) + cap.write(f"end {substmt}", 1) # Write constituents routine cap.blank_line() cap.write(f"function {const_array_func}() result(const_ptr)", 1) @@ -799,7 +779,7 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.blank_line() cap.write(f"call {const_obj_name}%const_index(const_index, " + \ f"stdname, {obj_err_callstr})", 2) - cap.write("end {}".format(substmt), 1) + cap.write(f"end {substmt}", 1) @staticmethod def constitutent_source_type(): From e48faa553c0b360ec6bd16cf7aa11f72e3a79d52 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 12 Feb 2024 17:54:19 -0700 Subject: [PATCH 130/159] expand testing --- test/advection_test/cld_ice.F90 | 4 +- test/advection_test/cld_liq.F90 | 2 +- test/advection_test/test_host.F90 | 105 ++++++++++++++++++++++---- test/advection_test/test_host_mod.F90 | 4 +- 4 files changed, 95 insertions(+), 20 deletions(-) diff --git a/test/advection_test/cld_ice.F90 b/test/advection_test/cld_ice.F90 index a588138b..0a1e13ee 100644 --- a/test/advection_test/cld_ice.F90 +++ b/test/advection_test/cld_ice.F90 @@ -108,11 +108,11 @@ subroutine cld_ice_dynamic_constituents(dyn_const, errcode, errmsg) call dyn_const(1)%instantiate(std_name='dyn_const1', long_name='dyn const1', & units='kg kg-1', default_value=0._kind_phys, & vertical_dim='vertical_layer_dimension', advected=.true., & - errcode=errcode, errmsg=errmsg) + min_value=1000._kind_phys, errcode=errcode, errmsg=errmsg) if (errcode /= 0) then return end if - call dyn_const(2)%instantiate(std_name='dyn_const2', long_name='dyn const2', & + call dyn_const(2)%instantiate(std_name='dyn_const2_wrt_moist_air', long_name='dyn const2', & units='kg kg-1', default_value=0._kind_phys, & vertical_dim='vertical_layer_dimension', advected=.true., & errcode=errcode, errmsg=errmsg) diff --git a/test/advection_test/cld_liq.F90 b/test/advection_test/cld_liq.F90 index 4fda40e3..63148c52 100644 --- a/test/advection_test/cld_liq.F90 +++ b/test/advection_test/cld_liq.F90 @@ -88,7 +88,7 @@ subroutine cld_liq_dynamic_constituents(dyn_const, errcode, errmsg) errmsg = 'Error allocating dyn_const in cld_liq_dynamic_constituents' end if call dyn_const(1)%instantiate(std_name="dyn_const3", long_name='dyn const3', & - units='kg kg-1', default_value=0._kind_phys, & + units='kg kg-1', default_value=1._kind_phys, & vertical_dim='vertical_layer_dimension', advected=.true., & errcode=errcode, errmsg=errmsg) diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index 8fc52178..2a151751 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -245,7 +245,8 @@ subroutine test_host(retval, test_suites) logical :: check integer :: col_start, col_end integer :: index, sind - integer :: index_liq, index_ice, index_dyn + integer :: index_liq, index_ice + integer :: index_dyn1, index_dyn2, index_dyn3 integer :: time_step integer :: num_suites integer :: num_advected ! Num advected species @@ -380,13 +381,13 @@ subroutine test_host(retval, test_suites) errflg_final) ! Check if the dynamic constituents indices can be found - call test_host_const_get_index('dyn_const1', index_dyn, errflg, errmsg) + call test_host_const_get_index('dyn_const1', index_dyn1, errflg, errmsg) call check_errflg(subname//".index_dyn_const1", errflg, errmsg, & errflg_final) - call test_host_const_get_index('dyn_const2', index_dyn, errflg, errmsg) + call test_host_const_get_index('dyn_const2_wrt_moist_air', index_dyn2, errflg, errmsg) call check_errflg(subname//".index_dyn_const2", errflg, errmsg, & errflg_final) - call test_host_const_get_index('dyn_const3', index_dyn, errflg, errmsg) + call test_host_const_get_index('dyn_const3', index_dyn3, errflg, errmsg) call check_errflg(subname//".index_dyn_const3", errflg, errmsg, & errflg_final) @@ -398,7 +399,7 @@ subroutine test_host(retval, test_suites) return end if - call init_data(const_ptr, index, index_liq, index_ice) + call init_data(const_ptr, index, index_liq, index_ice, index_dyn3) ! Check some constituent properties !++++++++++++++++++++++++++++++++++ @@ -423,6 +424,25 @@ subroutine test_host(retval, test_suites) !Reset error flag to continue testing other properties: errflg = 0 end if + !Check standard name for a dynamic constituent + call const_props(index_dyn2)%standard_name(const_str, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to get standard_name for dyn_const2, index = ", & + index_dyn2, trim(errmsg) + errflg_final = -1 !Notify test script that a failure occured + end if + if (errflg == 0) then + if (trim(const_str) /= 'dyn_const2_wrt_moist_air') then + write(6, *) "ERROR: standard name, '", trim(const_str), & + "' should be 'dyn_const2_wrt_moist_air'" + errflg_final = -1 !Notify test script that a failure occured + end if + else + !Reset error flag to continue testing other properties: + errflg = 0 + end if + !Long name: call const_props(index_liq)%long_name(const_str, errflg, errmsg) @@ -442,7 +462,24 @@ subroutine test_host(retval, test_suites) !Reset error flag to continue testing other properties: errflg = 0 end if - + !Check long name for a dynamic constituent + call const_props(index_dyn1)%long_name(const_str, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to get long_name for dyn_const1 index = ", & + index_dyn1, trim(errmsg) + errflg_final = -1 !Notify test script that a failure occured + end if + if (errflg == 0) then + if (trim(const_str) /= 'dyn const1') then + write(6, *) "ERROR: long name, '", trim(const_str), & + "' should be 'dyn const1'" + errflg_final = -1 !Notify test script that a failure occured + end if + else + !Reset error flag to continue testing other properties: + errflg = 0 + end if !Mass mixing ratio: call const_props(index_ice)%is_mass_mixing_ratio(const_log, errflg, & errmsg) @@ -461,6 +498,24 @@ subroutine test_host(retval, test_suites) !Reset error flag to continue testing other properties: errflg = 0 end if + !Check mass mixing ratio for a dynamic constituent + call const_props(index_dyn2)%is_mass_mixing_ratio(const_log, errflg, & + errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to get mass mixing ratio prop for dyn_const2 index = ", & + index_dyn2, trim(errmsg) + errflg_final = -1 !Notify test script that a failure occured + end if + if (errflg == 0) then + if (.not. const_log) then + write(6, *) "ERROR: dyn_const2 is not a mass mixing_ratio" + errflg_final = -1 !Notify test script that a failure occured + end if + else + !Reset error flag to continue testing other properties: + errflg = 0 + end if !Dry mixing ratio: call const_props(index_ice)%is_dry(const_log, errflg, errmsg) @@ -478,16 +533,34 @@ subroutine test_host(retval, test_suites) !Reset error flag to continue testing other properties: errflg = 0 end if + !Check moist mixing ratio for a dynamic constituent + call const_props(index_dyn2)%is_dry(const_log, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to get dry prop for dyn_const2 index = ", index_dyn2, trim(errmsg) + errflg_final = -1 !Notify test script that a failure occurred + end if + if (errflg == 0) then + if (const_log) then + write(6, *) "ERROR: dyn_const2 is dry" + errflg_final = -1 + end if + else + !Reset error flag to continue testing other properties: + errflg = 0 + end if + + !------------------- !------------------- !minimum value tests: !------------------- !Check that a constituent's minimum value defaults to zero: - call const_props(index_ice)%minimum(check_value, errflg, errmsg) + call const_props(index_dyn2)%minimum(check_value, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & - "to get minimum value for cld_ice index = ", index_ice, & + "to get minimum value for dyn_const2 index = ", index_dyn2, & trim(errmsg) errflg_final = -1 !Notify test script that a failure occurred end if @@ -504,17 +577,17 @@ subroutine test_host(retval, test_suites) !Check that a constituent instantiated with a specified minimum value !actually contains that minimum value property: - call const_props(index)%minimum(check_value, errflg, errmsg) + call const_props(index_dyn1)%minimum(check_value, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & - "to get minimum value for specific humidity index = ", index, & + "to get minimum value for dyn_const1 index = ", index_dyn1, & trim(errmsg) errflg_final = -1 !Notify test script that a failure occurred end if if (errflg == 0) then if (check_value /= 1000._kind_phys) then !Should be 1000 write(6, *) "ERROR: 'minimum' should give a value of 1000 ", & - "for specific humidity, as was set during instantiation." + "for dyn_const1, as was set during instantiation." errflg_final = -1 !Notify test script that a failure occured end if else @@ -524,19 +597,19 @@ subroutine test_host(retval, test_suites) !Check that setting a constituent's minimum value works !as expected: - call const_props(index_ice)%set_minimum(1._kind_phys, errflg, errmsg) + call const_props(index_dyn1)%set_minimum(1._kind_phys, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & - "to set minimum value for cld_ice index = ", index_ice, & + "to set minimum value for dyn_const1 index = ", index_dyn1, & trim(errmsg) errflg_final = -1 !Notify test script that a failure occurred end if if (errflg == 0) then - call const_props(index_ice)%minimum(check_value, errflg, errmsg) + call const_props(index_dyn1)%minimum(check_value, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, & - " trying to get minimum value for cld_ice index = ", & - index_ice, trim(errmsg) + " trying to get minimum value for dyn_const1 index = ", & + index_dyn1, trim(errmsg) errflg_final = -1 !Notify test script that a failure occurred end if end if diff --git a/test/advection_test/test_host_mod.F90 b/test/advection_test/test_host_mod.F90 index 560b7619..0ae75b3d 100644 --- a/test/advection_test/test_host_mod.F90 +++ b/test/advection_test/test_host_mod.F90 @@ -34,13 +34,14 @@ module test_host_mod contains - subroutine init_data(constituent_array, index_qv_use, index_liq, index_ice) + subroutine init_data(constituent_array, index_qv_use, index_liq, index_ice, index_dyn) ! Dummy arguments real(kind_phys), pointer :: constituent_array(:,:,:) ! From host & suites integer, intent(in) :: index_qv_use integer, intent(in) :: index_liq integer, intent(in) :: index_ice + integer, intent(in) :: index_dyn ! Local variables integer :: col @@ -60,6 +61,7 @@ subroutine init_data(constituent_array, index_qv_use, index_liq, index_ice) ind_ice = index_ice allocate(check_vals(ncols, pver, ncnst)) check_vals(:,:,:) = 0.0_kind_phys + check_vals(:,:,index_dyn) = 1.0_kind_phys do lev = 1, pver phys_state%temp(:, lev) = tfreeze + (10.0_kind_phys * (lev - 3)) qmax = real(lev, kind_phys) From 24014364d3272850707d06e409a94a208f91af90 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Sun, 25 Feb 2024 16:17:31 -0700 Subject: [PATCH 131/159] update register_constituents error handling; misc review requests --- scripts/ccpp_capgen.py | 13 ++- scripts/ccpp_datafile.py | 2 +- scripts/constituents.py | 159 ++++++++++++++---------------- test/advection_test/test_host.F90 | 2 +- 4 files changed, 88 insertions(+), 88 deletions(-) diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index db8a5fc5..1e2ac281 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -628,8 +628,17 @@ def capgen(run_env, return_db=False): # Pull out the dynamic constituent routines, if any dyn_const_dict = {} for table in scheme_tdict: - if scheme_tdict[table].dyn_const_routine is not None: - dyn_const_dict[table] = scheme_tdict[table].dyn_const_routine + routine_name = scheme_tdict[table].dyn_const_routine + if routine_name is not None: + if routine_name not in dyn_const_dict.values(): + dyn_const_dict[table] = scheme_tdict[table].dyn_const_routine + else: + # dynamic constituent routines must have unique names + scheme_name = list(dyn_const_dict.keys())[list(dyn_const_dict.values()).index(routine_name)] + errmsg = f"ERROR: Dynamic constituent routine names must be unique. Cannot add " \ + f"{routine_name} for {table}. Routine already exists in {scheme_name}. " + raise CCPPError(errmsg) + # end if # end if # end for if run_env.verbose: diff --git a/scripts/ccpp_datafile.py b/scripts/ccpp_datafile.py index 356a5d62..d76223e6 100755 --- a/scripts/ccpp_datafile.py +++ b/scripts/ccpp_datafile.py @@ -297,7 +297,7 @@ def _retrieve_dyn_const_routines(table): result = set() routines = table.find("dyn_const_routines") if routines is None: - raise CCPPDatatableError("Could not find 'dyn_const_routine' element") + raise CCPPDatatableError("Could not find 'dyn_const_routines' element") # end if for routine in routines: routine_name = routine.text diff --git a/scripts/constituents.py b/scripts/constituents.py index ceded38c..9efc3340 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -401,7 +401,7 @@ def num_consts_funcname(self): def const_name_subname(self): """Return the name of the routine that returns a constituent's - given an index""" + standard name given an index""" return f"{self.name}_const_name" def copy_const_subname(self): @@ -470,7 +470,6 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna herrcode, herrmsg = ConstituentVarDict.__errcode_names(err_vars) err_dummy_str = f"{herrcode}, {herrmsg}" obj_err_callstr = f"errcode={herrcode}, errmsg={herrmsg}" - obj_err_callstr = obj_err_callstr # XXgoldyXX: ^ need to generalize host model error var type support # First up, the registration routine substmt = f"subroutine {reg_funcname}" @@ -483,10 +482,10 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna # Conditionally include use statements for dynamic constituent routines if len(dyn_const_dict) > 0: cap.comment("Dynamic constituent routines", 2) + for scheme in dyn_const_dict: + cap.write(f"use {scheme}, only: {dyn_const_dict[scheme]}", 2) + # end for # end if - for scheme in dyn_const_dict: - cap.write(f"use {scheme}, only: {dyn_const_dict[scheme]}", 2) - # end for cap.blank_line() cap.comment("Dummy arguments", 2) cap.write("character(len=*), intent(in) :: suite_list(:)", 2) @@ -521,127 +520,119 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna errvar_str = ConstituentVarDict.__errcode_callstr(herrcode, herrmsg, suite) cap.write(f"num_suite_consts = {funcname}({errvar_str})", 2) + cap.write(f"if ({herrcode} /= 0) then", 2) + cap.write("return", 3) + cap.write("end if", 2) cap.write("num_consts = num_consts + num_suite_consts", 2) # end for # Check for dynamic constituent routines if len(dyn_const_dict) > 0: cap.comment("Add in dynamic constituents", 2) for idx, scheme in enumerate(sorted(dyn_const_dict)): - cap.write(f"if ({herrcode} == 0) then", 2) - cap.write(f"call {dyn_const_dict[scheme]}(dyn_const_prop_{idx}, {obj_err_callstr})", 3) - cap.write(f"num_dyn_consts = num_dyn_consts + size(dyn_const_prop_{idx})", 3) + cap.write(f"call {dyn_const_dict[scheme]}(dyn_const_prop_{idx}, {obj_err_callstr})", 2) + cap.write(f"if ({herrcode} /= 0) then", 2) + cap.write("return", 3) cap.write("end if", 2) + cap.write(f"num_dyn_consts = num_dyn_consts + size(dyn_const_prop_{idx})", 2) # end for cap.write("num_consts = num_consts + num_dyn_consts", 2) cap.comment("Pack dynamic_constituents array", 2) cap.write(f"allocate(dynamic_constituents(num_dyn_consts), stat={herrcode})", 2) cap.write(f"if ({herrcode} /= 0) then", 2) cap.write(f"{herrmsg} = 'failed to allocate dynamic_constituents'", 3) - cap.write("else", 2) - cap.write("index_start = 1", 3) + cap.write("return", 3) + cap.write("end if", 2) + cap.write("index_start = 0", 2) for idx, scheme in enumerate(sorted(dyn_const_dict)): - cap.write(f"do index = 1, size(dyn_const_prop_{idx}, 1)", 3) - cap.write(f"dynamic_constituents(index + index_start - 1) = dyn_const_prop_{idx}(index)", 4) - cap.write("end do", 3) - cap.write(f"index_start = size(dyn_const_prop_{idx}, 1) + 1", 3) - cap.write(f"deallocate(dyn_const_prop_{idx})", 3) + cap.write(f"do index = 1, size(dyn_const_prop_{idx}, 1)", 2) + cap.write(f"dynamic_constituents(index + index_start) = dyn_const_prop_{idx}(index)", 3) + cap.write("end do", 2) + cap.write(f"index_start = size(dyn_const_prop_{idx}, 1)", 2) + cap.write(f"deallocate(dyn_const_prop_{idx})", 2) # end for - cap.write("end if", 2) # end if - cap.write(f"if ({herrcode} == 0) then", 2) - cap.comment("Initialize constituent data and field object", 3) + cap.comment("Initialize constituent data and field object", 2) stmt = f"call {const_obj_name}%initialize_table(num_consts)" - cap.write(stmt, 3) + cap.write(stmt, 2) # Register host model constituents - cap.comment("Add host model constituent metadata", 3) - cap.write("do index = 1, size(host_constituents, 1)", 3) - cap.write(f"if ({herrcode} == 0) then", 4) - cap.write("const_prop => host_constituents(index)", 5) + cap.comment("Add host model constituent metadata", 2) + cap.write("do index = 1, size(host_constituents, 1)", 2) + cap.write("const_prop => host_constituents(index)", 3) stmt = f"call {const_obj_name}%new_field(const_prop, {obj_err_callstr})" - cap.write(stmt, 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(stmt, 3) + cap.write("nullify(const_prop)", 3) + cap.write(f"if ({herrcode} /= 0) then", 3) + cap.write("return", 4) + cap.write("end if", 3) + cap.write("end do", 2) cap.blank_line() # Register dynamic constituents if len(dyn_const_dict) > 0: cap.comment("Add dynamic constituent properties", 2) - cap.write(f"if ({herrcode} == 0) then", 2) - cap.write(f"do index = 1, size(dynamic_constituents, 1)", 3) - cap.write(f"if ({herrcode} == 0) then", 4) - cap.write(f"const_prop => dynamic_constituents(index)", 5) + cap.write(f"do index = 1, size(dynamic_constituents, 1)", 2) + cap.write(f"const_prop => dynamic_constituents(index)", 3) stmt = f"call {const_obj_name}%new_field(const_prop, {obj_err_callstr})" - cap.write(stmt, 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(stmt, 3) + cap.write("nullify(const_prop)", 3) + cap.write(f"if ({herrcode} /= 0) then", 3) + cap.write("return", 4) + cap.write("end if", 3) + cap.write("end do", 2) # end if # Register suite constituents for suite in suite_list: errvar_str = ConstituentVarDict.__errcode_callstr(herrcode, herrmsg, suite) - cap.write(f"if ({herrcode} == 0) then", 2) - cap.comment(f"Add {suite.name} constituent metadata", 3) + cap.comment(f"Add {suite.name} constituent metadata", 2) const_dict = suite.constituent_dictionary() funcname = const_dict.num_consts_funcname() - cap.write(f"num_suite_consts = {funcname}({errvar_str})", 3) + cap.write(f"num_suite_consts = {funcname}({errvar_str})", 2) + cap.write(f"if ({herrcode} /= 0) then", 2) + cap.write("return", 3) cap.write("end if", 2) funcname = const_dict.copy_const_subname() - cap.write(f"if ({herrcode} == 0) then", 2) - cap.write("do index = 1, num_suite_consts", 3) - cap.write(f"if ({herrcode} == 0) then", 4) - cap.write(f"allocate(const_prop, stat={herrcode})", 5) - cap.write("end if", 4) - cap.write(f"if ({herrcode} /= 0) then", 4) - cap.write(f'{herrmsg} = "ERROR allocating const_prop"', 5) - cap.write("exit", 5) - cap.write("end if", 4) - cap.write(f"if ({herrcode} == 0) then", 4) + cap.write("do index = 1, num_suite_consts", 2) + cap.write(f"allocate(const_prop, stat={herrcode})", 3) + cap.write(f"if ({herrcode} /= 0) then", 3) + cap.write(f'{herrmsg} = "ERROR allocating const_prop"', 4) + cap.write("return", 4) + cap.write("end if", 3) stmt = f"call {funcname}(index, const_prop, {errvar_str})" - cap.write(stmt, 5) - cap.write("end if", 4) - cap.write(f"if ({herrcode} == 0) then", 4) + cap.write(stmt, 3) + cap.write(f"if ({herrcode} /= 0) then", 3) + cap.write("return", 4) + cap.write("end if", 3) stmt = f"call {const_obj_name}%new_field(const_prop, {obj_err_callstr})" - cap.write(stmt, 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(stmt, 3) + cap.write("nullify(const_prop)", 3) + cap.write(f"if ({herrcode} /= 0) then", 3) + cap.write("return", 4) + cap.write("end if", 3) + cap.write("end do", 2) cap.blank_line() # end for - cap.write(f"if ({herrcode} == 0) then", 2) stmt = f"call {const_obj_name}%lock_table({obj_err_callstr})" - cap.write(stmt, 3) + cap.write(stmt, 2) + cap.write(f"if ({herrcode} /= 0) then", 2) + cap.write("return", 3) cap.write("end if", 2) - cap.write(f"if ({herrcode} == 0) then", 2) - cap.comment("Set the index for each active constituent", 3) - cap.write(f"do index = 1, SIZE({const_indices_name})", 3) + cap.comment("Set the index for each active constituent", 2) + cap.write(f"do index = 1, SIZE({const_indices_name})", 2) stmt = f"call {const_obj_name}%const_index(field_ind, {const_names_name}(index), {obj_err_callstr})" - cap.write(stmt, 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) + cap.write(stmt, 3) + cap.write(f"if ({herrcode} /= 0) then", 3) + cap.write("return", 4) + cap.write("end if", 3) + cap.write("if (field_ind > 0) then", 3) + cap.write(f"{const_indices_name}(index) = field_ind", 4) + cap.write("else", 3) + cap.write(f"{herrcode} = 1", 4) stmt = f"{herrmsg} = 'No field index for '//trim({const_names_name}(index))" - cap.write(stmt, 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(stmt, 4) + cap.write("return", 4) + cap.write("end if", 3) + cap.write("end do", 2) cap.write(f"end {substmt}", 1) # Write constituent_init routine substmt = f"subroutine {init_funcname}" diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index 2a151751..4a770472 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -542,7 +542,7 @@ subroutine test_host(retval, test_suites) end if if (errflg == 0) then if (const_log) then - write(6, *) "ERROR: dyn_const2 is dry" + write(6, *) "ERROR: dyn_const2 is dry and should be moist" errflg_final = -1 end if else From 744de3e696c443c787e2c279b1fcd65e8b34dceb Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Wed, 28 Feb 2024 13:28:59 -0700 Subject: [PATCH 132/159] add doctests to ccpp_datafile.py --- scripts/ccpp_datafile.py | 310 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 294 insertions(+), 16 deletions(-) diff --git a/scripts/ccpp_datafile.py b/scripts/ccpp_datafile.py index d76223e6..4636e9b2 100755 --- a/scripts/ccpp_datafile.py +++ b/scripts/ccpp_datafile.py @@ -22,14 +22,21 @@ import sys import xml.etree.ElementTree as ET # CCPP framework imports +from framework_env import CCPPFrameworkEnv from metadata_table import UNKNOWN_PROCESS_TYPE from metavar import Var from parse_tools import read_xml_file, PrettyElementTree +from parse_tools import ParseContext, ParseSource from suite_objects import VerticalLoop, Subcycle # Global data _INDENT_STR = " " +# Used for creating template variables +_MVAR_DUMMY_RUN_ENV = CCPPFrameworkEnv(None, ndict={'host_files':'', + 'scheme_files':'', + 'suites':''}) + ## datatable_report must have an action for each report type _VALID_REPORTS = [{"report" : "host_files", "type" : bool, "help" : @@ -95,7 +102,20 @@ class DatatableReport(object): __valid_actions = [x["report"] for x in _VALID_REPORTS] def __init__(self, action, value=True): - """Initialize this report as report-type, """ + """Initialize this report as report-type, + # Test a valid action + >>> DatatableReport('input_variables', False).action + 'input_variables' + >>> DatatableReport('dyn_const_routines', True).value + True + + # Test an invalid action + >>> DatatableReport('banana', True).value + Traceback (most recent call last): + ... + ValueError: Invalid action, 'banana' + + """ if action in DatatableReport.__valid_actions: self.__action = action self.__value = value @@ -105,7 +125,13 @@ def __init__(self, action, value=True): def action_is(self, action): """If matches this report type, return True. - Otherwise, return False""" + Otherwise, return False + >>> DatatableReport('suite_files', False).action_is('suite_files') + True + + >>> DatatableReport('suite_files', False).action_is('banana') + False + """ return action == self.__action @property @@ -207,7 +233,19 @@ def _read_datatable(datatable): def _find_table_section(table, elem_type): ############################################################################### """Look for and return an element type, , in . - Raise an exception if the element is not found.""" + Raise an exception if the element is not found. + # Test present section + >>> table = ET.fromstring("") + >>> _find_table_section(table, "ccpp_files").tag + 'ccpp_files' + + # Test missing section + >>> table = ET.fromstring("") + >>> _find_table_section(table, "ccpp_files").tag + Traceback (most recent call last): + ... + ccpp_datafile.CCPPDatatableError: Element type, 'ccpp_files', not found in table + """ found = table.find(elem_type) if found is None: emsg = "Element type, '{}', not found in table" @@ -219,7 +257,26 @@ def _find_table_section(table, elem_type): def _retrieve_ccpp_files(table, file_type=None): ############################################################################### """Find and retrieve a list of generated filenames from
. - If is not None, only return that file type.""" + If is not None, only return that file type. + # Test valid ccpp files + >>> table = ET.fromstring(""\ + "/path/to/file1"\ + "/path/to/file2"\ + "/path/to/file3"\ + "/path/to/file4"\ + "") + >>> _retrieve_ccpp_files(table) + ['/path/to/file1', '/path/to/file2', '/path/to/file3', '/path/to/file4'] + + # Test invalid file type + >>> table = ET.fromstring(""\ + "/path/to/file1"\ + "") + >>> _retrieve_ccpp_files(table) + Traceback (most recent call last): + ... + ccpp_datafile.CCPPDatatableError: Invalid file list entry type, 'banana' + """ ccpp_files = list() # Find the files section for section in _find_table_section(table, "ccpp_files"): @@ -239,7 +296,23 @@ def _retrieve_ccpp_files(table, file_type=None): ############################################################################### def _retrieve_process_list(table): ############################################################################### - """Find and return a list of all physics scheme processes in
.""" + """Find and return a list of all physics scheme processes in
. + # Test valid module + >>> table = ET.fromstring(""\ + ""\ + ""\ + "") + >>> _retrieve_process_list(table) + ['four=scheme2', 'three=scheme1'] + + # Test no schemes element + >>> table = ET.fromstring("") + >>> _retrieve_process_list(table) + Traceback (most recent call last): + ... + ccpp_datafile.CCPPDatatableError: Could not find 'schemes' element + """ + result = list() schemes = table.find("schemes") if schemes is None: @@ -252,12 +325,28 @@ def _retrieve_process_list(table): result.append("{}={}".format(proc, name)) # end if # end for - return result + return sorted(result) ############################################################################### def _retrieve_module_list(table): ############################################################################### - """Find and return a list of all scheme modules in
.""" + """Find and return a list of all scheme modules in
. + # Test valid module + >>> table = ET.fromstring(""\ + ""\ + ""\ + "") + >>> _retrieve_module_list(table) + ['partridge', 'turtle_dove'] + + # Test no schemes element + >>> table = ET.fromstring("") + >>> _retrieve_module_list(table) + Traceback (most recent call last): + ... + ccpp_datafile.CCPPDatatableError: Could not find 'schemes' element + + """ result = set() schemes = table.find("schemes") if schemes is None: @@ -276,7 +365,28 @@ def _retrieve_module_list(table): ############################################################################### def _retrieve_dependencies(table): ############################################################################### - """Find and return a list of all host and scheme dependencies.""" + """Find and return a list of all host and scheme dependencies. + # Test valid dependencies + >>> table = ET.fromstring("" \ + "bananaorange" \ + "") + >>> _retrieve_dependencies(table) + ['banana', 'orange'] + + # Test no dependencies + >>> table = ET.fromstring("" \ + "") + >>> _retrieve_dependencies(table) + [] + + # Test missing dependencies tag + >>> table = ET.fromstring("") + >>> _retrieve_dependencies(table) + Traceback (most recent call last): + ... + ccpp_datafile.CCPPDatatableError: Could not find 'dependencies' element + """ + result = set() depends = table.find("dependencies") if depends is None: @@ -293,7 +403,29 @@ def _retrieve_dependencies(table): ############################################################################### def _retrieve_dyn_const_routines(table): ############################################################################### - """Find and return a list of all scheme constituent routines.""" + """Find and return a list of all scheme constituent routines. + # Test valid dynamic constituent routines + >>> table = ET.fromstring("" \ + "dyn_const_get" \ + "dyn_const_2" \ + "") + >>> _retrieve_dyn_const_routines(table) + ['dyn_const_2', 'dyn_const_get'] + + # Test no dynamic constituent routines + >>> table = ET.fromstring("" \ + "") + >>> _retrieve_dyn_const_routines(table) + [] + + # Test missing dynamic constituent routines tag + >>> table = ET.fromstring("") + >>> _retrieve_dyn_const_routines(table) + Traceback (most recent call last): + ... + ccpp_datafile.CCPPDatatableError: Could not find 'dyn_const_routines' element + + """ result = set() routines = table.find("dyn_const_routines") if routines is None: @@ -311,7 +443,29 @@ def _retrieve_dyn_const_routines(table): def _find_var_dictionary(table, dict_name=None, dict_type=None): ############################################################################### """Find and return a var_dictionary named, in
. - If not found, return None""" + If not found, return None + # Test valid table with dict_name provided + >>> table = ET.fromstring(""\ + ""\ + ""\ + ""\ + "") + >>> _find_var_dictionary(table, dict_name='orange').get("name") + 'orange' + + # Test valid table with dict_type provided + >>> _find_var_dictionary(table, dict_type='host').get("name") + 'banana' + + # Test no table found (expect None) + >>> _find_var_dictionary(table, dict_name='apple') + + # Test error handling + >>> _find_var_dictionary(table) + Traceback (most recent call last): + ... + ValueError: At least one of or must contain a string + """ var_dicts = table.find("var_dictionaries") target_dict = None if (dict_name is None) and (dict_type is None): @@ -330,7 +484,19 @@ def _find_var_dictionary(table, dict_name=None, dict_type=None): ############################################################################### def _retrieve_suite_list(table): ############################################################################### - """Find and return a list of all suites found in
.""" + """Find and return a list of all suites found in
. + # Test suites are found + >>> table = ET.fromstring(""\ + ""\ + "") + >>> _retrieve_suite_list(table) + ['umbrella', 'galoshes'] + + # Test suites not found + >>> table = ET.fromstring("") + >>> _retrieve_suite_list(table) + [] + """ result = list() # First, find the API variable dictionary api_elem = table.find("api") @@ -347,7 +513,21 @@ def _retrieve_suite_list(table): ############################################################################### def _retrieve_suite_group_names(table, suite_name): ############################################################################### - """Find and return a list of the group names for this suite.""" + """Find and return a list of the group names for this suite. + # Test suites are found + >>> table = ET.fromstring(""\ + ""\ + ""\ + ""\ + "") + >>> _retrieve_suite_group_names(table, 'umbrella') + ['florence', 'delores', 'edna'] + + # Test non-present suite + >>> _retrieve_suite_group_names(table, 'poncho') + [] + """ + result = list() # First, find the API variable dictionary api_elem = table.find("api") @@ -373,6 +553,23 @@ def _is_variable_protected(table, var_name, var_dict): """Determine whether variable, , from is protected. So this by checking for the 'protected' attribute for in or any of 's ancestors (parent dictionaries). + # Test found variable + >>> table = ET.fromstring(""\ + ""\ + ""\ + ""\ + "") + >>> var_dict = _find_var_dictionary(table, dict_name="banana") + >>> _is_variable_protected(table, "hi", var_dict) + True + + >>> var_dict = _find_var_dictionary(table, dict_type="api") + >>> _is_variable_protected(table, "hello", var_dict) + False + + # Test non-present variable also returns False + >>> _is_variable_protected(table, "hiya", var_dict) + False """ protected = False while (not protected) and (var_dict is not None): @@ -402,7 +599,48 @@ def _retrieve_variable_list(table, suite_name, If suite, , is not found in
, return an empty list. If is present, return only that variable type (input or output). - If is True, do not include protected variables""" + If is True, do not include protected variables + >>> table = ET.fromstring(""\ + ""\ + ""\ + ""\ + ""\ + ""\ + "") + + # Test group variable retrieval + >>> _retrieve_variable_list(table, 'fruit', excl_prot=False) + ['var3', 'var4', 'var5'] + + >>> _retrieve_variable_list(table, 'fruit') + ['var3'] + + >>> _retrieve_variable_list(table, 'fruit', intent_type='input', excl_prot=False) + ['var3', 'var5'] + + >>> _retrieve_variable_list(table, 'fruit', intent_type='output', excl_prot=False) + ['var4', 'var5'] + + >>> _retrieve_variable_list(table, 'fruit', intent_type='input') + ['var3'] + + >>> _retrieve_variable_list(table, 'fruit', intent_type='output') + [] + + # Test host variable retrieval + >>> _retrieve_variable_list(table, 'fruit', intent_type='host', excl_prot=False) + ['var1', 'var2'] + + >>> _retrieve_variable_list(table, 'fruit', intent_type='host') + ['var1'] + + # Test invalid intent type + >>> _retrieve_variable_list(table, 'fruit', intent_type='banana') + Traceback (most recent call last): + ... + ccpp_datafile.CCPPDatatableError: Invalid intent_type, 'banana' + + """ # Note that suites do not have call lists so we have to collect # all the variables from the suite's groups. var_set = set() @@ -549,6 +787,19 @@ def _format_line(line_in, indent, line_wrap, increase_indent=True): If is True, increase the indent level for new lines created by the process. A value of less one means do not wrap the line. + >>> line = "This is a very long string that should be wrapped hopefully" + >>> _format_line(line, 1, 50) + ' This is a very long string that should be\\n wrapped hopefully\\n' + + >>> _format_line(line, 1, 50, increase_indent=False) + ' This is a very long string that should be\\n wrapped hopefully\\n' + + >>> _format_line(line, 0, 50) + 'This is a very long string that should be wrapped\\n hopefully\\n' + + >>> line = 'short line' + >>> _format_line(line, 0, 2, increase_indent=False) + 'short\\nline\\n' """ in_squote = False in_dquote = False @@ -610,7 +861,14 @@ def _format_line(line_in, indent, line_wrap, increase_indent=True): ############################################################################### def table_entry_pretty_print(entry, indent, line_wrap=-1): ############################################################################### - """Create and return a pretty print string of the contents of """ + """Create and return a pretty print string of the contents of + >>> table = ET.fromstring("") + >>> table_entry_pretty_print(table, 0) + '\\n \\n\\n' + + >>> table_entry_pretty_print(table, 1, line_wrap=20) + ' \\n \\n \\n' + """ output = "" outline = "<{}".format(entry.tag) for name in entry.attrib: @@ -668,6 +926,16 @@ def _new_var_entry(parent, var, full_entry=True): ############################################################################### """Create a variable sub-element of with information from . If is False, only include standard name and intent. + >>> parent = ET.fromstring('') + >>> var = Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '(horizontal_loop_extent)', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'DDT', ParseContext()), _MVAR_DUMMY_RUN_ENV) + >>> _new_var_entry(parent, var) + >>> table_entry_pretty_print(parent, 0) + '\\n \\n \\n horizontal_loop_extent\\n \\n \\n ddt\\n \\n \\n vname\\n \\n \\n\\n' + + >>> parent = ET.fromstring('') + >>> _new_var_entry(parent, var, full_entry=False) + >>> table_entry_pretty_print(parent, 0) + '\\n \\n\\n' """ prop_list = ["intent", "local_name"] if full_entry: @@ -805,6 +1073,12 @@ def _add_dependencies(parent, scheme_depends, host_depends): ############################################################################### """Add a section to that lists all the dependencies required by schemes or the host model. + >>> parent = ET.fromstring("") + >>> scheme_depends = ['file1', 'file2'] + >>> host_depends = ['file3', 'file4'] + >>> _add_dependencies(parent, scheme_depends, host_depends) + >>> table_entry_pretty_print(parent, 0) + '\\n \\n \\n file3\\n \\n \\n file4\\n \\n \\n file1\\n \\n \\n file2\\n \\n \\n\\n' """ file_entry = ET.SubElement(parent, "dependencies") for hfile in host_depends: @@ -820,11 +1094,15 @@ def _add_dependencies(parent, scheme_depends, host_depends): def _add_dyn_const_routine(file_entry, routine, scheme): ############################################################################### """Add a section to that lists all the constituent routines - for the suite""" + for the suite + >>> file_entry = ET.fromstring("") + >>> _add_dyn_const_routine(file_entry, 'test_dyn_const', 'test_scheme') + >>> table_entry_pretty_print(file_entry, 0) + '\\n \\n \\n test_dyn_const\\n \\n\\n' + """ entry = ET.SubElement(file_entry, "dyn_const_routine") entry.text = routine entry.set("parent", scheme) - # end for ############################################################################### def _add_generated_files(parent, host_files, suite_files, ccpp_kinds, src_dir): From 2c58eedf8ade9bde6a408bf99338c07bd876b2b1 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Fri, 1 Mar 2024 14:41:27 -0700 Subject: [PATCH 133/159] review comments; add in vertical transform bugfix --- scripts/ccpp_capgen.py | 2 +- scripts/ccpp_datafile.py | 15 ++++++++------- scripts/var_props.py | 18 ++++++++++++++---- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index 1e2ac281..6617248f 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -631,7 +631,7 @@ def capgen(run_env, return_db=False): routine_name = scheme_tdict[table].dyn_const_routine if routine_name is not None: if routine_name not in dyn_const_dict.values(): - dyn_const_dict[table] = scheme_tdict[table].dyn_const_routine + dyn_const_dict[table] = routine_name else: # dynamic constituent routines must have unique names scheme_name = list(dyn_const_dict.keys())[list(dyn_const_dict.values()).index(routine_name)] diff --git a/scripts/ccpp_datafile.py b/scripts/ccpp_datafile.py index 4636e9b2..4fc52318 100755 --- a/scripts/ccpp_datafile.py +++ b/scripts/ccpp_datafile.py @@ -431,13 +431,9 @@ def _retrieve_dyn_const_routines(table): if routines is None: raise CCPPDatatableError("Could not find 'dyn_const_routines' element") # end if - for routine in routines: - routine_name = routine.text - if routine_name is not None: - result.add(routine_name) - # end if + routine_names = [routine.text for routine in routines if routine.text] # end for - return sorted(result) + return sorted(routine_names) ############################################################################### def _find_var_dictionary(table, dict_name=None, dict_type=None): @@ -457,8 +453,13 @@ def _find_var_dictionary(table, dict_name=None, dict_type=None): >>> _find_var_dictionary(table, dict_type='host').get("name") 'banana' + # Test valid table with both dict_type and dict_name provided + >>> _find_var_dictionary(table, dict_type='host', dict_name='banana').get("name") + 'banana' + # Test no table found (expect None) - >>> _find_var_dictionary(table, dict_name='apple') + >>> _find_var_dictionary(table, dict_name='apple') is None + True # Test error handling >>> _find_var_dictionary(table) diff --git a/scripts/var_props.py b/scripts/var_props.py index c61d9ae4..dc4c24c4 100755 --- a/scripts/var_props.py +++ b/scripts/var_props.py @@ -808,6 +808,19 @@ class VarCompatObj: _DOCTEST_RUNENV) #doctest: +ELLIPSIS + # Test that a 1-D var with no vertical transform works + >>> VarCompatObj("var_stdname", "real", "kind_phys", "m", ['vertical_layer_dimension'], "var1_lname", False, \ + "var_stdname", "real", "kind_phys", "m", ['vertical_layer_dimension'], "var2_lname", False, \ + _DOCTEST_RUNENV) #doctest: +ELLIPSIS + + + # Test that a 1-D var with vertical flipping works and that it + # produces the correct reverse transformation + >>> VarCompatObj("var_stdname", "real", "kind_phys", "m", ['vertical_layer_dimension'], "var1_lname", False,\ + "var_stdname", "real", "kind_phys", "m", ['vertical_layer_dimension'], "var2_lname", True, \ + _DOCTEST_RUNENV).reverse_transform("var1_lname", "var2_lname", ('k',), ('nk-k+1',)) + 'var1_lname(nk-k+1) = var2_lname(k)' + # Test that a 2-D var with unit conversion m->km works >>> VarCompatObj("var_stdname", "real", "kind_phys", "m", ['horizontal_dimension'], "var1_lname", False, \ "var_stdname", "real", "kind_phys", "km", ['horizontal_dimension'], "var2_lname", False, \ @@ -928,12 +941,9 @@ def __init__(self, var1_stdname, var1_type, var1_kind, var1_units, # end if if self.__compat: # Check dimensions - ##XXgoldyXX: For now, we always have to create a dimension - ## transform because we do not know if the vertical - ## dimension is flipped. if var1_dims or var2_dims: _, vdim_ind = find_vertical_dimension(var1_dims) - if (var1_dims != var2_dims) or (vdim_ind >= 0): + if (var1_dims != var2_dims): self.__dim_transforms = self._get_dim_transforms(var1_dims, var2_dims) self.__compat = self.__dim_transforms is not None From d8b706a536f254c2872a35a104cce59c8b7fa506 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 11 Mar 2024 16:48:07 -0600 Subject: [PATCH 134/159] fix merge --- scripts/host_cap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/host_cap.py b/scripts/host_cap.py index 457ac649..adb7f529 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -577,7 +577,7 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): for suite in api.suites: mspc = (max_suite_len - len(suite.module))*' ' spart_list = suite_part_list(suite, stage) - for spart in sorted(spart_list): + for _, spart in sorted(enumerate(spart_list)): stmt = "use {}, {}only: {}" cap.write(stmt.format(suite.module, mspc, spart.name), 2) # End for From b1ffd3f0f9c47227fbcd4a57e2175a76bc83f357 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 11 Mar 2024 17:09:08 -0600 Subject: [PATCH 135/159] another merge fix --- scripts/host_cap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/host_cap.py b/scripts/host_cap.py index adb7f529..1c5f92df 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -530,7 +530,7 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): # Look for any loop-variable mismatch for suite in api.suites: spart_list = suite_part_list(suite, stage) - for spart in sorted(spart_list): + for spart in spart_list: spart_args = spart.call_list.variable_list() for sp_var in spart_args: stdname = sp_var.get_prop_value('standard_name') From 3988c447e06492772f18aeccff849b7c04c5baba Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 18 Mar 2024 13:23:41 -0600 Subject: [PATCH 136/159] merge in NCAR/main --- requirements.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..be9f272b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +black +flake8 +pytest From a0a3ea2f0350125cf152a294e2e236fbcda82489 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 18 Mar 2024 14:05:01 -0600 Subject: [PATCH 137/159] fix merge --- scripts/suite_objects.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 937fdfd9..da08bace 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -2474,11 +2474,7 @@ def write(self, outfile, host_arglist, indent, const_mod, # Allocate local arrays outfile.write('\n! Allocate local arrays', indent+1) alloc_stmt = "allocate({}({}))" -<<<<<<< HEAD for lname in sorted(allocatable_var_set): - var = subpart_vars[lname][0] -======= - for lname in allocatable_var_set: var = subpart_allocate_vars[lname][0] dims = var.get_dimensions() alloc_str = self.allocate_dim_str(dims, var.context) @@ -2486,7 +2482,6 @@ def write(self, outfile, host_arglist, indent, const_mod, # end for for lname in optional_var_set: var = subpart_optional_vars[lname][0] ->>>>>>> NCAR/feature/capgen dims = var.get_dimensions() alloc_str = self.allocate_dim_str(dims, var.context) outfile.write(alloc_stmt.format(lname, alloc_str), indent+1) From d4029f107dd088d3a3fdf4959e94cb358ab85c63 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 18 Mar 2024 14:33:04 -0600 Subject: [PATCH 138/159] fix test; bring back python.yaml workflow --- .github/workflows/python.yaml | 49 +++++++++++++++++++++++++++++++++++ pytest.ini | 2 ++ test/capgen_test/run_test | 2 +- 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/python.yaml create mode 100644 pytest.ini diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml new file mode 100644 index 00000000..bf1e7c36 --- /dev/null +++ b/.github/workflows/python.yaml @@ -0,0 +1,49 @@ +name: Python package + +on: + workflow_dispatch: + pull_request: + branches: [feature/capgen, main] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.8', '3.9', '3.10', '3.11'] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest + - name: Test with pytest + run: | + export PYTHONPATH=$(pwd)/scripts:$(pwd)/scripts/parse_tools + pytest -v + + doctest: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.8', '3.9', '3.10', '3.11'] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest + - name: Doctest + run: | + export PYTHONPATH=$(pwd)/scripts:$(pwd)/scripts/parse_tools + pytest -v scripts/ --doctest-modules diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..d08180f1 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = -ra --ignore=scripts/metadata2html.py --ignore-glob=test/**/test_reports.py \ No newline at end of file diff --git a/test/capgen_test/run_test b/test/capgen_test/run_test index 0d5d44f7..5476b076 100755 --- a/test/capgen_test/run_test +++ b/test/capgen_test/run_test @@ -128,7 +128,7 @@ ccpp_files="${utility_files}" ccpp_files="${ccpp_files},${build_dir}/ccpp/test_host_ccpp_cap.F90" ccpp_files="${ccpp_files},${build_dir}/ccpp/ccpp_ddt_suite_cap.F90" ccpp_files="${ccpp_files},${build_dir}/ccpp/ccpp_temp_suite_cap.F90" -process_list="setter=temp_set,adjusting=temp_calc_adjust" +process_list="adjusting=temp_calc_adjust,setter=temp_set" module_list="environ_conditions,make_ddt,temp_adjust,temp_calc_adjust,temp_set" dependencies="bar.F90,foo.F90,qux.F90" suite_list="ddt_suite;temp_suite" From a4548a0ebcf2f84d6940733f4021743b440588f0 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Thu, 21 Mar 2024 12:57:23 -0600 Subject: [PATCH 139/159] remove target attribute from dyn_const_prop variable declarations --- scripts/constituents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/constituents.py b/scripts/constituents.py index 9efc3340..81952644 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -507,7 +507,7 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna # Declare dynamic constituent properties variables for idx, scheme in enumerate(sorted(dyn_const_dict)): cap.comment(f"dynamic constituent props variable for {scheme}", 2) - cap.write(f"type({CONST_PROP_TYPE}), allocatable, target :: dyn_const_prop_{idx}(:)", 2) + cap.write(f"type({CONST_PROP_TYPE}), allocatable :: dyn_const_prop_{idx}(:)", 2) # end for cap.blank_line() cap.write(f"{herrcode} = 0", 2) From 930c3dbcb6b5abf58712b88e1904f4b9ec025bdd Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 22 Apr 2024 13:13:26 -0600 Subject: [PATCH 140/159] code cleanup; review requests; include python3.12 in ci testing --- .github/workflows/python.yaml | 4 ++-- scripts/ccpp_capgen.py | 6 ++++-- scripts/constituents.py | 3 +-- test/advection_test/test_host.F90 | 6 +++--- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index bf1e7c36..16a91253 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.8', '3.9', '3.10', '3.11'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v3 @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v3 diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index d8d5b4c4..e573bbf0 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -638,14 +638,16 @@ def capgen(run_env, return_db=False): scheme_headers, scheme_tdict = parse_scheme_files(scheme_files, run_env) # Pull out the dynamic constituent routines, if any dyn_const_dict = {} + dyn_val_dict = {} for table in scheme_tdict: routine_name = scheme_tdict[table].dyn_const_routine if routine_name is not None: - if routine_name not in dyn_const_dict.values(): + if routine_name not in dyn_val_dict: dyn_const_dict[table] = routine_name + dyn_val_dict[routine_name] = table else: # dynamic constituent routines must have unique names - scheme_name = list(dyn_const_dict.keys())[list(dyn_const_dict.values()).index(routine_name)] + scheme_name = dyn_val_dict[routine_name] errmsg = f"ERROR: Dynamic constituent routine names must be unique. Cannot add " \ f"{routine_name} for {table}. Routine already exists in {scheme_name}. " raise CCPPError(errmsg) diff --git a/scripts/constituents.py b/scripts/constituents.py index 81952644..f57c9c09 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -473,7 +473,7 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna # XXgoldyXX: ^ need to generalize host model error var type support # First up, the registration routine substmt = f"subroutine {reg_funcname}" - args = "suite_list, host_constituents, dynamic_constituents " + args = "host_constituents, dynamic_constituents " stmt = f"{substmt}({args}, {err_dummy_str})" cap.write(stmt, 1) cap.comment("Create constituent object for suites in ", 2) @@ -488,7 +488,6 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna # end if cap.blank_line() cap.comment("Dummy arguments", 2) - cap.write("character(len=*), intent(in) :: suite_list(:)", 2) cap.write(f"type({CONST_PROP_TYPE}), target, intent(in) :: " + \ "host_constituents(:)", 2) cap.write(f"type({CONST_PROP_TYPE}), allocatable, target, intent(inout) :: " + \ diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index 4a770472..9a290319 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -329,9 +329,9 @@ subroutine test_host(retval, test_suites) errcode=errflg, errmsg=errmsg) call check_errflg(subname//'.initialize', errflg, errmsg, errflg_final) if (errflg == 0) then - call test_host_ccpp_register_constituents(suite_names(:), & - host_constituents, dynamic_constituents=dynamic_constituents, & - errmsg=errmsg, errflg=errflg) + call test_host_ccpp_register_constituents(host_constituents, & + dynamic_constituents=dynamic_constituents, & + errmsg=errmsg, errflg=errflg) end if if (errflg /= 0) then write(6, '(2a)') 'ERROR register_constituents: ', trim(errmsg) From 7c3a0cdb929d34553be8b39a0dc2f818f1bb3ca0 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 22 Apr 2024 16:46:39 -0600 Subject: [PATCH 141/159] back off 3.12 doctests for now --- .github/workflows/python.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index 16a91253..5f573a68 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.8', '3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v3 From 2906220e3de6d6a06bec16a486947c15ea39ae29 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Tue, 23 Apr 2024 19:51:02 -0600 Subject: [PATCH 142/159] add script to check fortran vs metadata without a host model --- scripts/ccpp_capgen.py | 19 +++- .../offline_check_fortran_vs_metadata.py | 101 ++++++++++++++++++ test/capgen_test/temp_adjust.F90 | 6 +- 3 files changed, 119 insertions(+), 7 deletions(-) create mode 100644 scripts/fortran_tools/offline_check_fortran_vs_metadata.py diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index 042f6d16..68650074 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -312,19 +312,30 @@ def compare_fheader_to_mheader(meta_header, fort_header, logger): # end if for mind, mvar in enumerate(mlist): lname = mvar.get_prop_value('local_name') + mname = mvar.get_prop_value('standard_name') arrayref = is_arrayspec(lname) fvar, find = find_var_in_list(lname, flist) # Check for consistency between optional variables in metadata and # optional variables in fortran. Error if optional attribute is # missing from fortran declaration. + # first check: if metadata says the variable is optional, does the fortran match? mopt = mvar.get_prop_value('optional') if find and mopt: fopt = fvar.get_prop_value('optional') if (not fopt): - errmsg = 'Missing optional attribute in fortran declaration for variable {}, in file {}' + errmsg = 'Missing "optional" attribute in fortran declaration for variable {}, for {}' errors_found = add_error(errors_found, errmsg.format(mname,title)) # end if # end if + # now check: if fortran says the variable is optional, does the metadata match? + if fvar: + fopt = fvar.get_prop_value('optional') + mopt = mvar.get_prop_value('optional') + if (fopt and not mopt): + errmsg = 'Missing "optional" metadata property for variable {}, for {}' + errors_found = add_error(errors_found, errmsg.format(mname, title)) + # end if + # end if if mind >= flen: if arrayref: # Array reference, variable not in Fortran table @@ -511,7 +522,7 @@ def parse_host_model_files(host_filenames, host_name, run_env): return host_model ############################################################################### -def parse_scheme_files(scheme_filenames, run_env): +def parse_scheme_files(scheme_filenames, run_env, known_ddts=None): ############################################################################### """ Gather information from scheme files (e.g., init, run, and finalize @@ -519,7 +530,9 @@ def parse_scheme_files(scheme_filenames, run_env): """ table_dict = {} # Duplicate check and for dependencies processing header_dict = {} # To check for duplicates - known_ddts = list() + if not known_ddts: + known_ddts = list() + # end if logger = run_env.logger for filename in scheme_filenames: logger.info('Reading CCPP schemes from {}'.format(filename)) diff --git a/scripts/fortran_tools/offline_check_fortran_vs_metadata.py b/scripts/fortran_tools/offline_check_fortran_vs_metadata.py new file mode 100644 index 00000000..8e450a0d --- /dev/null +++ b/scripts/fortran_tools/offline_check_fortran_vs_metadata.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 + +""" +Recursively compare all fortran and metadata files in user-supplied directory, and report any problems +USAGE: set PYTHONPATH environment variable to the the scripts directory, then: + python offline_check_fortran_vs_metadata.py --directory (--debug) +""" + + +import sys +import os +import logging +import argparse + +# CCPP framework imports +from framework_env import CCPPFrameworkEnv +from fortran_tools import parse_fortran_file +from metadata_table import parse_metadata_file +from ccpp_capgen import find_associated_fortran_file +from ccpp_capgen import check_fortran_against_metadata, parse_scheme_files +from parse_tools import init_log, set_log_level +from parse_tools import register_fortran_ddt_name +from parse_tools import CCPPError, ParseInternalError + +_LOGGER = init_log(os.path.basename(__file__)) +_DUMMY_RUN_ENV = CCPPFrameworkEnv(_LOGGER, ndict={'host_files':'', + 'scheme_files':'', + 'suites':''}) +_CCPP_FRAMEWORK_DDT_TYPES = ["ccpp_hash_table_t", + "ccpp_hashable_t", + "ccpp_hashable_char_t", + "ccpp_constituent_prop_ptr_t"] + + + +def find_files_to_compare(directory): + metadata_files = [] + for root, _, files in os.walk(directory, topdown=True): + for name in files: + if os.path.splitext(name)[1] == '.meta': + metadata_files.append(os.path.join(root, name)) + # end if + # end for + # end for + return metadata_files + +def compare_fortran_and_metadata(scheme_directory, run_env): + ## Check for files + metadata_files = find_files_to_compare(scheme_directory) + # Pre-register base CCPP DDT types: + for ddt_name in _CCPP_FRAMEWORK_DDT_TYPES: + register_fortran_ddt_name(ddt_name) + # end for + # Perform checks + parse_scheme_files(metadata_files, run_env, known_ddts=['ccpp_constituent_prop_ptr_t']) + +def parse_command_line(arguments, description): + """Parse command-line arguments""" + parser = argparse.ArgumentParser(description=description, + formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument("--directory", type=str, required=True, + metavar='top-level directory to analyze - REQUIRED', + help="""Full path to scheme directory""") + parser.add_argument("--debug", action='store_true', default=False, + help="""turn on debug mode for additional verbosity""") + pargs = parser.parse_args(arguments) + return pargs + +def _main_func(): + """Parse command line, then parse indicated host, scheme, and suite files. + Finally, generate code to allow host model to run indicated CCPP suites.""" + pargs = parse_command_line(sys.argv[1:], __doc__) + logger = _LOGGER + if pargs.debug: + set_log_level(logger, logging.DEBUG) + else: + set_log_level(logger, logging.INFO) + # end if + compare_fortran_and_metadata(pargs.directory, _DUMMY_RUN_ENV) + print('All checks passed!') + +############################################################################### + +if __name__ == "__main__": + try: + _main_func() + sys.exit(0) + except ParseInternalError as pie: + _LOGGER.exception(pie) + sys.exit(-1) + except CCPPError as ccpp_err: + if _LOGGER.getEffectiveLevel() <= logging.DEBUG: + _LOGGER.exception(ccpp_err) + else: + _LOGGER.error(ccpp_err) + # end if + sys.exit(1) + finally: + logging.shutdown() + # end try + diff --git a/test/capgen_test/temp_adjust.F90 b/test/capgen_test/temp_adjust.F90 index 5aba4c0b..a619eb80 100644 --- a/test/capgen_test/temp_adjust.F90 +++ b/test/capgen_test/temp_adjust.F90 @@ -27,7 +27,7 @@ subroutine temp_adjust_run(foo, timestep, temp_prev, temp_layer, qv, ps, & REAL(kind_phys), intent(in) :: temp_prev(:) REAL(kind_phys), intent(inout) :: temp_layer(foo) character(len=512), intent(out) :: errmsg - integer, optional, intent(out) :: errflg + integer, intent(out) :: errflg real(kind_phys), optional, intent(in) :: innie real(kind_phys), optional, intent(out) :: outie real(kind_phys), optional, intent(inout) :: optsie @@ -36,9 +36,7 @@ subroutine temp_adjust_run(foo, timestep, temp_prev, temp_layer, qv, ps, & integer :: col_index errmsg = '' - if (present(errflg)) then - errflg = 0 - end if + errflg = 0 do col_index = 1, foo temp_layer(col_index) = temp_layer(col_index) + temp_prev(col_index) From adff339c5f0eb7b577130863a47d924d42900a1c Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Wed, 24 Apr 2024 01:42:28 -0600 Subject: [PATCH 143/159] add check that routine exists in fortran; use module-level dynamic constituents variable in cap rather than provided by host --- scripts/ccpp_capgen.py | 28 ++++++++++++++++++--- scripts/ccpp_fortran_to_metadata.py | 2 +- scripts/constituents.py | 18 ++++++------- scripts/fortran_tools/parse_fortran_file.py | 16 +++++++++--- scripts/host_cap.py | 15 ++++++++++- scripts/metadata_table.py | 1 + test/advection_test/test_host.F90 | 2 -- 7 files changed, 61 insertions(+), 21 deletions(-) diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index e573bbf0..51388dc2 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -387,7 +387,8 @@ def compare_fheader_to_mheader(meta_header, fort_header, logger): ############################################################################### def check_fortran_against_metadata(meta_headers, fort_headers, - mfilename, ffilename, logger): + mfilname, ffilename, logger, + dyn_routines=None, fortran_routines=None): ############################################################################### """Compare a set of metadata headers from against the code in the associated Fortran file, . @@ -439,6 +440,17 @@ def check_fortran_against_metadata(meta_headers, fort_headers, 's' if num_errors > 1 else '', mfilename, ffilename)) # end if + # Check that any dynamic constituent routines declared in the metadata are + # present in the Fortran + if dyn_routines: + for routine in dyn_routines: + if routine not in fortran_routines: + # throw an error - it's not in the Fortran + errmsg = f"Dynamic constituent routine {routine} not found in fortran {ffilename}" + raise CCPPError(errmsg) + # end if + # end for + # end if # No return, an exception is raised on error ############################################################################### @@ -470,7 +482,7 @@ def parse_host_model_files(host_filenames, host_name, run_env): # parse metadata file mtables = parse_metadata_file(filename, known_ddts, run_env) fort_file = find_associated_fortran_file(filename) - ftables = parse_fortran_file(fort_file, run_env) + ftables, _ = parse_fortran_file(fort_file, run_env) # Check Fortran against metadata (will raise an exception on error) mheaders = list() for sect in [x.sections() for x in mtables]: @@ -526,7 +538,7 @@ def parse_scheme_files(scheme_filenames, run_env): # parse metadata file mtables = parse_metadata_file(filename, known_ddts, run_env) fort_file = find_associated_fortran_file(filename) - ftables = parse_fortran_file(fort_file, run_env) + ftables, additional_routines = parse_fortran_file(fort_file, run_env) # Check Fortran against metadata (will raise an exception on error) mheaders = list() for sect in [x.sections() for x in mtables]: @@ -536,8 +548,16 @@ def parse_scheme_files(scheme_filenames, run_env): for sect in [x.sections() for x in ftables]: fheaders.extend(sect) # end for + dyn_routines = [] + for table in mtables: + if table.dyn_const_routine: + dyn_routines.append(table.dyn_const_routine) + # end if + # end for check_fortran_against_metadata(mheaders, fheaders, - filename, fort_file, logger) + filename, fort_file, logger, + dyn_routines=dyn_routines, + fortran_routines=additional_routines) # Check for duplicate tables, then add to dict for table in mtables: if table.table_name in table_dict: diff --git a/scripts/ccpp_fortran_to_metadata.py b/scripts/ccpp_fortran_to_metadata.py index 8fc1d682..4c301d1d 100755 --- a/scripts/ccpp_fortran_to_metadata.py +++ b/scripts/ccpp_fortran_to_metadata.py @@ -177,7 +177,7 @@ def parse_fortran_files(filenames, run_env, output_dir, sep, logger): for filename in filenames: logger.info('Looking for arg_tables from {}'.format(filename)) reset_standard_name_counter() - ftables = parse_fortran_file(filename, run_env) + ftables, _ = parse_fortran_file(filename, run_env) # Create metadata filename filepath = '.'.join(os.path.basename(filename).split('.')[0:-1]) fname = filepath + '.meta' diff --git a/scripts/constituents.py b/scripts/constituents.py index f57c9c09..9b1579bf 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -440,7 +440,7 @@ def write_constituent_use_statements(cap, suite_list, indent): @staticmethod def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcname, query_const_funcname, copy_in_funcname, copy_out_funcname, - const_obj_name, const_names_name, const_indices_name, + const_obj_name, dyn_const_name, const_names_name, const_indices_name, const_array_func, advect_array_func, prop_array_func, const_index_func, suite_list, dyn_const_dict, err_vars): """Write out the host model routine which will @@ -473,7 +473,7 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna # XXgoldyXX: ^ need to generalize host model error var type support # First up, the registration routine substmt = f"subroutine {reg_funcname}" - args = "host_constituents, dynamic_constituents " + args = "host_constituents " stmt = f"{substmt}({args}, {err_dummy_str})" cap.write(stmt, 1) cap.comment("Create constituent object for suites in ", 2) @@ -490,8 +490,6 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.comment("Dummy arguments", 2) cap.write(f"type({CONST_PROP_TYPE}), target, intent(in) :: " + \ "host_constituents(:)", 2) - cap.write(f"type({CONST_PROP_TYPE}), allocatable, target, intent(inout) :: " + \ - "dynamic_constituents(:)", 2) for evar in err_vars: evar.write_def(cap, 2, host, dummy=True, add_intent="out") # end for @@ -535,16 +533,16 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.write(f"num_dyn_consts = num_dyn_consts + size(dyn_const_prop_{idx})", 2) # end for cap.write("num_consts = num_consts + num_dyn_consts", 2) - cap.comment("Pack dynamic_constituents array", 2) - cap.write(f"allocate(dynamic_constituents(num_dyn_consts), stat={herrcode})", 2) + cap.comment(f"Pack {dyn_const_name} array", 2) + cap.write(f"allocate({dyn_const_name}(num_dyn_consts), stat={herrcode})", 2) cap.write(f"if ({herrcode} /= 0) then", 2) - cap.write(f"{herrmsg} = 'failed to allocate dynamic_constituents'", 3) + cap.write(f"{herrmsg} = 'failed to allocate {dyn_const_name}'", 3) cap.write("return", 3) cap.write("end if", 2) cap.write("index_start = 0", 2) for idx, scheme in enumerate(sorted(dyn_const_dict)): cap.write(f"do index = 1, size(dyn_const_prop_{idx}, 1)", 2) - cap.write(f"dynamic_constituents(index + index_start) = dyn_const_prop_{idx}(index)", 3) + cap.write(f"{dyn_const_name}(index + index_start) = dyn_const_prop_{idx}(index)", 3) cap.write("end do", 2) cap.write(f"index_start = size(dyn_const_prop_{idx}, 1)", 2) cap.write(f"deallocate(dyn_const_prop_{idx})", 2) @@ -568,8 +566,8 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna # Register dynamic constituents if len(dyn_const_dict) > 0: cap.comment("Add dynamic constituent properties", 2) - cap.write(f"do index = 1, size(dynamic_constituents, 1)", 2) - cap.write(f"const_prop => dynamic_constituents(index)", 3) + cap.write(f"do index = 1, size({dyn_const_name}, 1)", 2) + cap.write(f"const_prop => {dyn_const_name}(index)", 3) stmt = f"call {const_obj_name}%new_field(const_prop, {obj_err_callstr})" cap.write(stmt, 3) cap.write("nullify(const_prop)", 3) diff --git a/scripts/fortran_tools/parse_fortran_file.py b/scripts/fortran_tools/parse_fortran_file.py index a67816e4..33e17a8e 100644 --- a/scripts/fortran_tools/parse_fortran_file.py +++ b/scripts/fortran_tools/parse_fortran_file.py @@ -40,6 +40,7 @@ _FIXED_CONTINUE_RE = re.compile(r"(?i) [^0 ]") _BLANK_RE = re.compile(r"\s+") _ARG_TABLE_START_RE = re.compile(r"(?i)\s*![!>]\s*(?:\\section)?\s*arg_table_"+FORTRAN_ID) +_SUBOURTINE_RE = re.compile(r"(?i)\s*subroutine\s+"+FORTRAN_ID) _PREFIX_SPECS = [r"(?:recursive)", r"(?:pure)", r"(?:elemental)"] _PREFIX_SPEC = r"(?:{})?\s*".format('|'.join(_PREFIX_SPECS)) _SUBNAME_SPEC = r"subroutine\s*" @@ -904,12 +905,14 @@ def parse_module(pobj, statements, run_env): statements = read_statements(pobj, statements) inmodule = pobj.in_region('MODULE', region_name=mod_name) active_table = None + additional_subroutines = [] while inmodule and (statements is not None): while statements: statement = statements.pop(0) # End module pmatch = _ENDMODULE_RE.match(statement) asmatch = _ARG_TABLE_START_RE.match(statement) + smatch = _SUBROUTINE_RE.match(statement) if asmatch is not None: active_table = asmatch.group(1) elif pmatch is not None: @@ -917,6 +920,13 @@ def parse_module(pobj, statements, run_env): pobj.leave_region('MODULE', region_name=mod_name) inmodule = False break + elif smatch is not None: + # parse out the subroutine name + routine_name = statement.split()[1] + if '(' in routine_name: + routine_name = routine_name.split('(')[0] + # end if + additional_subroutines.append(routine_name) elif active_table is not None: statements, mheader = parse_scheme_metadata(statements, pobj, mod_name, @@ -945,7 +955,7 @@ def parse_module(pobj, statements, run_env): statements = read_statements(pobj) # End if # End while - return statements, mtables + return statements, mtables, additional_subroutines ######################################################################## @@ -970,14 +980,14 @@ def parse_fortran_file(filename, run_env): elif _MODULE_RE.match(statement) is not None: # push statement back so parse_module can use it statements.insert(0, statement) - statements, ptables = parse_module(pobj, statements, run_env) + statements, ptables, additional_routines = parse_module(pobj, statements, run_env) mtables.extend(ptables) # End if if (statements is not None) and (len(statements) == 0): statements = read_statements(pobj) # End if # End while - return mtables + return mtables, additional_routines ######################################################################## diff --git a/scripts/host_cap.py b/scripts/host_cap.py index 1c5f92df..ced0f218 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -11,7 +11,7 @@ from ccpp_suite import API, API_SOURCE_NAME from ccpp_state_machine import CCPP_STATE_MACH from constituents import ConstituentVarDict, CONST_DDT_NAME, CONST_DDT_MOD -from constituents import CONST_OBJ_STDNAME +from constituents import CONST_OBJ_STDNAME, CONST_PROP_TYPE from ddt_library import DDTLibrary from file_utils import KINDS_MODULE from framework_env import CCPPFrameworkEnv @@ -175,6 +175,13 @@ def constituent_model_object_name(host_model): # end if return hvar.get_prop_value('local_name') +############################################################################### +def dynamic_constituent_array_name(host_model): +############################################################################### + """Return the name of the allocatable dynamic constituent properites array""" + hstr = f"{host_model.name}_dynamic_constituents" + return unique_local_name(hstr, host_model) + ############################################################################### def constituent_model_const_stdnames(host_model): ############################################################################### @@ -363,6 +370,9 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): # end if ddt_lib.collect_ddt_fields(const_dict, const_var, run_env, skip_duplicates=True) + # Declare the allocatable dynamic constituents array + dyn_const_name = dynamic_constituent_array_name(host_model) + cap.write(f"type({CONST_PROP_TYPE}), allocatable, target :: {dyn_const_name}(:)", 1) # Declare variable for the constituent standard names array max_csname = max([len(x) for x in const_stdnames]) if const_stdnames else 0 num_const_fields = len(const_stdnames) @@ -485,6 +495,7 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): # End for mspc = ' '*(maxmod - len(CONST_DDT_MOD)) cap.write(f"use {CONST_DDT_MOD}, {mspc}only: {CONST_DDT_NAME}", 1) + cap.write(f"use {CONST_DDT_MOD}, {mspc}only: {CONST_PROP_TYPE}", 1) cap.write_preamble() max_suite_len = 0 for suite in api.suites: @@ -654,10 +665,12 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): cap.write("", 0) const_names_name = constituent_model_const_stdnames(host_model) const_indices_name = constituent_model_const_indices(host_model) + dyn_const_name = dynamic_constituent_array_name(host_model) ConstituentVarDict.write_host_routines(cap, host_model, reg_name, init_name, numconsts_name, queryconsts_name, copyin_name, copyout_name, const_obj_name, + dyn_const_name, const_names_name, const_indices_name, const_array_func, diff --git a/scripts/metadata_table.py b/scripts/metadata_table.py index 6840f6be..b0c9149c 100755 --- a/scripts/metadata_table.py +++ b/scripts/metadata_table.py @@ -63,6 +63,7 @@ type = scheme relative_path = dependencies = + dynamic_constituent_routine = [ccpp-arg-table] name = diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index 9a290319..df956b55 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -262,7 +262,6 @@ subroutine test_host(retval, test_suites) real(kind_phys) :: default_value real(kind_phys) :: check_value type(ccpp_constituent_prop_ptr_t), pointer :: const_props(:) - type(ccpp_constituent_properties_t), allocatable :: dynamic_constituents(:) character(len=*), parameter :: subname = 'test_host' ! Initialized "final" error flag used to report a failure to the larged @@ -330,7 +329,6 @@ subroutine test_host(retval, test_suites) call check_errflg(subname//'.initialize', errflg, errmsg, errflg_final) if (errflg == 0) then call test_host_ccpp_register_constituents(host_constituents, & - dynamic_constituents=dynamic_constituents, & errmsg=errmsg, errflg=errflg) end if if (errflg /= 0) then From d29a035ddd731c9139d470bb4a8ac018e712a817 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Wed, 24 Apr 2024 01:48:13 -0600 Subject: [PATCH 144/159] fix typo --- scripts/ccpp_capgen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index 51388dc2..3791ef61 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -387,7 +387,7 @@ def compare_fheader_to_mheader(meta_header, fort_header, logger): ############################################################################### def check_fortran_against_metadata(meta_headers, fort_headers, - mfilname, ffilename, logger, + mfilename, ffilename, logger, dyn_routines=None, fortran_routines=None): ############################################################################### """Compare a set of metadata headers from against the From 9ce41eec83690b59a74dbf1677b254acba712086 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Wed, 24 Apr 2024 10:32:56 -0600 Subject: [PATCH 145/159] code cleanup; code review --- scripts/ccpp_capgen.py | 10 ++++++---- .../offline_check_fortran_vs_metadata.py | 16 ++++++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) mode change 100644 => 100755 scripts/fortran_tools/offline_check_fortran_vs_metadata.py diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index 68650074..768ec6ff 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -323,8 +323,9 @@ def compare_fheader_to_mheader(meta_header, fort_header, logger): if find and mopt: fopt = fvar.get_prop_value('optional') if (not fopt): - errmsg = 'Missing "optional" attribute in fortran declaration for variable {}, for {}' - errors_found = add_error(errors_found, errmsg.format(mname,title)) + errmsg = f'Missing "optional" attribute in fortran declaration for variable {mname}, ' \ + 'for {title}' + errors_found = add_error(errors_found, errmsg) # end if # end if # now check: if fortran says the variable is optional, does the metadata match? @@ -332,8 +333,9 @@ def compare_fheader_to_mheader(meta_header, fort_header, logger): fopt = fvar.get_prop_value('optional') mopt = mvar.get_prop_value('optional') if (fopt and not mopt): - errmsg = 'Missing "optional" metadata property for variable {}, for {}' - errors_found = add_error(errors_found, errmsg.format(mname, title)) + errmsg = f'Missing "optional" metadata property for variable {mname}, ' \ + 'for {title}' + errors_found = add_error(errors_found, errmsg) # end if # end if if mind >= flen: diff --git a/scripts/fortran_tools/offline_check_fortran_vs_metadata.py b/scripts/fortran_tools/offline_check_fortran_vs_metadata.py old mode 100644 new mode 100755 index 8e450a0d..5a6569c3 --- a/scripts/fortran_tools/offline_check_fortran_vs_metadata.py +++ b/scripts/fortran_tools/offline_check_fortran_vs_metadata.py @@ -2,15 +2,19 @@ """ Recursively compare all fortran and metadata files in user-supplied directory, and report any problems -USAGE: set PYTHONPATH environment variable to the the scripts directory, then: - python offline_check_fortran_vs_metadata.py --directory (--debug) +USAGE: + ./offline_check_fortran_vs_metadata.py --directory (--debug) """ import sys import os +import glob import logging import argparse +import site +# Enable imports from parent directory +site.addsitedir(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # CCPP framework imports from framework_env import CCPPFrameworkEnv @@ -35,12 +39,8 @@ def find_files_to_compare(directory): metadata_files = [] - for root, _, files in os.walk(directory, topdown=True): - for name in files: - if os.path.splitext(name)[1] == '.meta': - metadata_files.append(os.path.join(root, name)) - # end if - # end for + for file in glob.glob(os.path.join(directory,'**','*.meta'), recursive=True): + metadata_files.append(file) # end for return metadata_files From eee30cb5d26261683369efc0901bb29331893679 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Wed, 24 Apr 2024 11:39:51 -0600 Subject: [PATCH 146/159] allow for skipping of ddt check; skip ddt check from offline script --- scripts/ccpp_capgen.py | 8 ++--- .../offline_check_fortran_vs_metadata.py | 12 +------ scripts/metadata_table.py | 35 ++++++++++++------- 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index 768ec6ff..1a381497 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -524,7 +524,7 @@ def parse_host_model_files(host_filenames, host_name, run_env): return host_model ############################################################################### -def parse_scheme_files(scheme_filenames, run_env, known_ddts=None): +def parse_scheme_files(scheme_filenames, run_env, skip_ddt_check=False): ############################################################################### """ Gather information from scheme files (e.g., init, run, and finalize @@ -532,14 +532,14 @@ def parse_scheme_files(scheme_filenames, run_env, known_ddts=None): """ table_dict = {} # Duplicate check and for dependencies processing header_dict = {} # To check for duplicates - if not known_ddts: - known_ddts = list() + known_ddts = list() # end if logger = run_env.logger for filename in scheme_filenames: logger.info('Reading CCPP schemes from {}'.format(filename)) # parse metadata file - mtables = parse_metadata_file(filename, known_ddts, run_env) + mtables = parse_metadata_file(filename, known_ddts, run_env, + skip_ddt_check=skip_ddt_check) fort_file = find_associated_fortran_file(filename) ftables = parse_fortran_file(fort_file, run_env) # Check Fortran against metadata (will raise an exception on error) diff --git a/scripts/fortran_tools/offline_check_fortran_vs_metadata.py b/scripts/fortran_tools/offline_check_fortran_vs_metadata.py index 5a6569c3..fc15fd89 100755 --- a/scripts/fortran_tools/offline_check_fortran_vs_metadata.py +++ b/scripts/fortran_tools/offline_check_fortran_vs_metadata.py @@ -30,12 +30,6 @@ _DUMMY_RUN_ENV = CCPPFrameworkEnv(_LOGGER, ndict={'host_files':'', 'scheme_files':'', 'suites':''}) -_CCPP_FRAMEWORK_DDT_TYPES = ["ccpp_hash_table_t", - "ccpp_hashable_t", - "ccpp_hashable_char_t", - "ccpp_constituent_prop_ptr_t"] - - def find_files_to_compare(directory): metadata_files = [] @@ -47,12 +41,8 @@ def find_files_to_compare(directory): def compare_fortran_and_metadata(scheme_directory, run_env): ## Check for files metadata_files = find_files_to_compare(scheme_directory) - # Pre-register base CCPP DDT types: - for ddt_name in _CCPP_FRAMEWORK_DDT_TYPES: - register_fortran_ddt_name(ddt_name) - # end for # Perform checks - parse_scheme_files(metadata_files, run_env, known_ddts=['ccpp_constituent_prop_ptr_t']) + parse_scheme_files(metadata_files, run_env, skip_ddt_check=True) def parse_command_line(arguments, description): """Parse command-line arguments""" diff --git a/scripts/metadata_table.py b/scripts/metadata_table.py index 946e9782..8753505c 100755 --- a/scripts/metadata_table.py +++ b/scripts/metadata_table.py @@ -179,7 +179,7 @@ def _parse_config_line(line, context): ######################################################################## -def parse_metadata_file(filename, known_ddts, run_env): +def parse_metadata_file(filename, known_ddts, run_env, skip_ddt_check=False): """Parse and return list of parsed metadata tables""" # Read all lines of the file at once meta_tables = [] @@ -196,7 +196,8 @@ def parse_metadata_file(filename, known_ddts, run_env): while curr_line is not None: if MetadataTable.table_start(curr_line): new_table = MetadataTable(run_env, parse_object=parse_obj, - known_ddts=known_ddts) + known_ddts=known_ddts, + skip_ddt_check=skip_ddt_check) ntitle = new_table.table_name if ntitle not in table_titles: meta_tables.append(new_table) @@ -271,7 +272,8 @@ class MetadataTable(): def __init__(self, run_env, table_name_in=None, table_type_in=None, dependencies=None, relative_path=None, known_ddts=None, - var_dict=None, module=None, parse_object=None): + var_dict=None, module=None, parse_object=None, + skip_ddt_check=False): """Initialize a MetadataTable, either with a name, , and type, , or with information from a file (). if is None, and are @@ -317,7 +319,8 @@ def __init__(self, run_env, table_name_in=None, table_type_in=None, sect = MetadataSection(self.table_name, self.table_type, run_env, title=stitle, type_in=self.table_type, module=module, - var_dict=var_dict, known_ddts=known_ddts) + var_dict=var_dict, known_ddts=known_ddts, + skip_ddt_check=skip_ddt_check) self.__sections.append(sect) # end if else: @@ -342,10 +345,10 @@ def __init__(self, run_env, table_name_in=None, table_type_in=None, known_ddts = [] # end if self.__start_context = ParseContext(context=self.__pobj) - self.__init_from_file(known_ddts, self.__run_env) + self.__init_from_file(known_ddts, self.__run_env, skip_ddt_check=skip_ddt_check) # end if - def __init_from_file(self, known_ddts, run_env): + def __init_from_file(self, known_ddts, run_env, skip_ddt_check=False): """ Read the table preamble, assume the caller already figured out the first line of the header using the header_start method.""" curr_line, _ = self.__pobj.next_line() @@ -407,7 +410,8 @@ def __init_from_file(self, known_ddts, run_env): skip_rest_of_section = False section = MetadataSection(self.table_name, self.table_type, run_env, parse_object=self.__pobj, - known_ddts=known_ddts) + known_ddts=known_ddts, + skip_ddt_check=skip_ddt_check) # Some table types only allow for one associated section if ((len(self.__sections) == 1) and (self.table_type in _SINGLETON_TABLE_TYPES)): @@ -623,7 +627,7 @@ class MetadataSection(ParseSource): def __init__(self, table_name, table_type, run_env, parse_object=None, title=None, type_in=None, module=None, process_type=None, - var_dict=None, known_ddts=None): + var_dict=None, known_ddts=None, skip_ddt_check=False): """Initialize a new MetadataSection object. If is not None, initialize from the current file and location in . @@ -693,7 +697,8 @@ def __init__(self, table_name, table_type, run_env, parse_object=None, known_ddts = [] # end if self.__start_context = ParseContext(context=self.__pobj) - self.__init_from_file(table_name, table_type, known_ddts, run_env) + self.__init_from_file(table_name, table_type, known_ddts, run_env, + skip_ddt_check=skip_ddt_check) # end if # Register this header if it is a DDT if self.header_type == 'ddt': @@ -724,7 +729,7 @@ def _default_module(self): # end if return def_mod - def __init_from_file(self, table_name, table_type, known_ddts, run_env): + def __init_from_file(self, table_name, table_type, known_ddts, run_env, skip_ddt_check=False): """ Read the section preamble, assume the caller already figured out the first line of the header using the header_start method.""" start_ctx = context_string(self.__pobj) @@ -809,7 +814,8 @@ def __init_from_file(self, table_name, table_type, known_ddts, run_env): valid_lines = True self.__variables = VarDictionary(self.title, run_env) while valid_lines: - newvar, curr_line = self.parse_variable(curr_line, known_ddts) + newvar, curr_line = self.parse_variable(curr_line, known_ddts, + skip_ddt_check=skip_ddt_check) valid_lines = newvar is not None if valid_lines: if run_env.verbose: @@ -828,7 +834,7 @@ def __init_from_file(self, table_name, table_type, known_ddts, run_env): # end if # end while - def parse_variable(self, curr_line, known_ddts): + def parse_variable(self, curr_line, known_ddts, skip_ddt_check=False): """Parse a new metadata variable beginning on . The header line has the format [ ]. """ @@ -872,7 +878,10 @@ def parse_variable(self, curr_line, known_ddts): pval_str = prop[1].strip() if ((pname == 'type') and (not check_fortran_intrinsic(pval_str, error=False))): - if pval_str in known_ddts: + if skip_ddt_check or pval_str in known_ddts: + if skip_ddt_check: + register_fortran_ddt_name(pval_str) + # end if pval = pval_str pname = 'ddt_type' else: From 807d35acdc5939cddb9e95d09144789cd55fed18 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Fri, 26 Apr 2024 16:24:10 -0600 Subject: [PATCH 147/159] handle dimensions for promoted variables --- scripts/ccpp_suite.py | 40 +++++++++++++++++++++++++++++++ scripts/suite_objects.py | 17 +++++++++++-- test/capgen_test/temp_adjust.F90 | 3 ++- test/capgen_test/temp_adjust.meta | 7 ++++++ test/capgen_test/temp_set.F90 | 3 ++- test/capgen_test/temp_set.meta | 7 ++++++ 6 files changed, 73 insertions(+), 4 deletions(-) diff --git a/scripts/ccpp_suite.py b/scripts/ccpp_suite.py index d19ca4e8..993af65f 100644 --- a/scripts/ccpp_suite.py +++ b/scripts/ccpp_suite.py @@ -23,6 +23,7 @@ from parse_tools import init_log, set_log_to_null from suite_objects import CallList, Group, Scheme from metavar import CCPP_LOOP_VAR_STDNAMES +from var_props import find_horizontal_dimension, find_vertical_dimension # pylint: disable=too-many-lines @@ -308,6 +309,45 @@ def find_variable(self, standard_name=None, source_var=None, self.add_variable(var, self.__run_env) # Remove the variable from the group group.remove_variable(standard_name) + # Make sure the variable's dimensions are available + # at the init stage (for allocation) + for group in self.groups: + # only add dimension variables to init phase calling list + if group.name == self.__suite_init_group.name: + horiz_dim = find_horizontal_dimension(var.get_dimensions())[0] + vert_dim = find_vertical_dimension(var.get_dimensions())[0] + if horiz_dim and 'horizontal_loop' in horiz_dim: + # can't use horizontal_loop_being/end/extent in init phase + # must allocate to horizontal_dimension + new_horiz_dim = 'ccpp_constant_one:horizontal_dimension' + else: + new_horiz_dim = horiz_dim + # end if + if horiz_dim: + # make new variable with "correct" dimensions + if vert_dim: + new_dims = [new_horiz_dim, vert_dim] + else: + new_dims = [new_horiz_dim] + # end if + subst_dict = {'dimensions': new_dims} + prop_dict = var.copy_prop_dict(subst_dict=subst_dict) + temp_var = Var(prop_dict, + ParseSource(var.get_prop_value('scheme'), + var.get_prop_value('local_name'), var.context), + self.__run_env) + # Add dimensions if they're not already there + group.add_variable_dimensions(temp_var, [], + adjust_intent=True, + to_dict=group.call_list) + else: + # Add dimensions if they're not already there + group.add_variable_dimensions(var, [], + adjust_intent=True, + to_dict=group.call_list) + # end if + # end if + # end for else: emsg = ("Group, {}, claimed it had created {} " "but variable was not found") diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index f611c5d2..2397996f 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -2230,9 +2230,22 @@ def analyze(self, phase, suite_vars, scheme_library, ddt_library, self.run_env.logger.debug("{}".format(self)) # end if - def allocate_dim_str(self, dims, context): + def allocate_dim_str(self, dims, context, suite_var=False): """Create the dimension string for an allocate statement""" rdims = list() + if suite_var: + # adjust horizontal dimension if horizontal_loop_* specified + horiz_dim = find_horizontal_dimension(dims)[0] + vert_dim = find_vertical_dimension(dims)[0] + if horiz_dim and 'horizontal_loop' in horiz_dim: + new_horiz_dim = 'ccpp_constant_one:horizontal_dimension' + if vert_dim: + dims = [new_horiz_dim, vert_dim] + else: + dims = [new_horiz_dim] + # end if + # end if + # end if for dim in dims: rdparts = list() dparts = dim.split(':') @@ -2500,7 +2513,7 @@ def write(self, outfile, host_arglist, indent, const_mod, if dims: timestep_var = svar.get_prop_value('persistence') if group_type == timestep_var: - alloc_str = self.allocate_dim_str(dims, svar.context) + alloc_str = self.allocate_dim_str(dims, svar.context, suite_var=True) lname = svar.get_prop_value('local_name') outfile.write(alloc_stmt.format(lname, alloc_str), indent+1) diff --git a/test/capgen_test/temp_adjust.F90 b/test/capgen_test/temp_adjust.F90 index 5aba4c0b..988f5a25 100644 --- a/test/capgen_test/temp_adjust.F90 +++ b/test/capgen_test/temp_adjust.F90 @@ -18,7 +18,7 @@ MODULE temp_adjust !! \htmlinclude arg_table_temp_adjust_run.html !! subroutine temp_adjust_run(foo, timestep, temp_prev, temp_layer, qv, ps, & - errmsg, errflg, innie, outie, optsie) + to_promote, errmsg, errflg, innie, outie, optsie) integer, intent(in) :: foo real(kind_phys), intent(in) :: timestep @@ -26,6 +26,7 @@ subroutine temp_adjust_run(foo, timestep, temp_prev, temp_layer, qv, ps, & real(kind_phys), intent(inout) :: ps(:) REAL(kind_phys), intent(in) :: temp_prev(:) REAL(kind_phys), intent(inout) :: temp_layer(foo) + real(kind_phys), intent(in) :: to_promote(:) character(len=512), intent(out) :: errmsg integer, optional, intent(out) :: errflg real(kind_phys), optional, intent(in) :: innie diff --git a/test/capgen_test/temp_adjust.meta b/test/capgen_test/temp_adjust.meta index 17eabcdb..b3190c72 100644 --- a/test/capgen_test/temp_adjust.meta +++ b/test/capgen_test/temp_adjust.meta @@ -50,6 +50,13 @@ units = Pa dimensions = (horizontal_loop_extent) intent = inout +[ to_promote ] + standard_name = promote_this_variable_to_suite + units = K + dimensions = (horizontal_loop_extent) + type = real + kind = kind_phys + intent = in [ errmsg ] standard_name = ccpp_error_message long_name = Error message for error handling in CCPP diff --git a/test/capgen_test/temp_set.F90 b/test/capgen_test/temp_set.F90 index fc2a9f39..45284e04 100644 --- a/test/capgen_test/temp_set.F90 +++ b/test/capgen_test/temp_set.F90 @@ -19,7 +19,7 @@ MODULE temp_set !! \htmlinclude arg_table_temp_set_run.html !! SUBROUTINE temp_set_run(ncol, lev, timestep, temp_level, temp, ps, & - errmsg, errflg) + to_promote, errmsg, errflg) !---------------------------------------------------------------- IMPLICIT NONE !---------------------------------------------------------------- @@ -29,6 +29,7 @@ SUBROUTINE temp_set_run(ncol, lev, timestep, temp_level, temp, ps, & real(kind_phys), intent(in) :: timestep real(kind_phys), intent(in) :: ps(:) REAL(kind_phys), INTENT(inout) :: temp_level(:, :) + real(kind_phys), intent(out) :: to_promote(:, :) character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg !---------------------------------------------------------------- diff --git a/test/capgen_test/temp_set.meta b/test/capgen_test/temp_set.meta index c3c919e5..f1dfc60f 100644 --- a/test/capgen_test/temp_set.meta +++ b/test/capgen_test/temp_set.meta @@ -47,6 +47,13 @@ units = Pa dimensions = (horizontal_loop_extent) intent = in +[ to_promote ] + standard_name = promote_this_variable_to_suite + units = K + dimensions = (horizontal_loop_extent, vertical_layer_dimension) + type = real + kind = kind_phys + intent = out [ errmsg ] standard_name = ccpp_error_message long_name = Error message for error handling in CCPP From da558694713b917bdd45765d90d6eabf7972a7dc Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Sat, 27 Apr 2024 20:56:27 -0600 Subject: [PATCH 148/159] check for compatibility before adding constituent; add more testing --- scripts/ccpp_capgen.py | 23 +++- scripts/constituents.py | 10 +- scripts/host_cap.py | 13 ++- src/ccpp_constituent_prop_mod.F90 | 73 +++++++++++- test/advection_test/test_host.F90 | 47 +++++++- test/unit_tests/sample_files/test_host.meta | 1 + .../duplicate_dyn_const.F90 | 96 ++++++++++++++++ .../duplicate_dyn_const.meta | 104 ++++++++++++++++++ .../dyn_const_not_present.F90 | 75 +++++++++++++ .../dyn_const_not_present.meta | 104 ++++++++++++++++++ .../sample_scheme_files/temp_adjust.F90 | 21 ++++ .../sample_scheme_files/temp_adjust.meta | 1 + test/unit_tests/test_metadata_scheme_file.py | 25 +++++ test/unit_tests/test_metadata_table.py | 3 + 14 files changed, 583 insertions(+), 13 deletions(-) create mode 100644 test/unit_tests/sample_scheme_files/duplicate_dyn_const.F90 create mode 100644 test/unit_tests/sample_scheme_files/duplicate_dyn_const.meta create mode 100644 test/unit_tests/sample_scheme_files/dyn_const_not_present.F90 create mode 100644 test/unit_tests/sample_scheme_files/dyn_const_not_present.meta diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index 3791ef61..a83f35af 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -580,6 +580,23 @@ def parse_scheme_files(scheme_filenames, run_env): # end if # end for # end for + # Check for duplicate dynamic constituent routine names + dyn_val_dict = {} + for table in table_dict: + routine_name = table_dict[table].dyn_const_routine + if routine_name: + if routine_name in dyn_val_dict: + # dynamic constituent routines must have unique names + scheme_name = dyn_val_dict[routine_name] + errmsg = f"ERROR: Dynamic constituent routine names must be unique. Cannot add " \ + f"{routine_name} for {table}. Routine already exists in {scheme_name}. " + raise CCPPError(errmsg) + else: + dyn_val_dict[routine_name] = table + # end if + # end if + # end for + return header_dict.values(), table_dict ############################################################################### @@ -665,12 +682,6 @@ def capgen(run_env, return_db=False): if routine_name not in dyn_val_dict: dyn_const_dict[table] = routine_name dyn_val_dict[routine_name] = table - else: - # dynamic constituent routines must have unique names - scheme_name = dyn_val_dict[routine_name] - errmsg = f"ERROR: Dynamic constituent routine names must be unique. Cannot add " \ - f"{routine_name} for {table}. Routine already exists in {scheme_name}. " - raise CCPPError(errmsg) # end if # end if # end for diff --git a/scripts/constituents.py b/scripts/constituents.py index 9b1579bf..18bcbbe3 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -439,7 +439,7 @@ def write_constituent_use_statements(cap, suite_list, indent): @staticmethod def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcname, - query_const_funcname, copy_in_funcname, copy_out_funcname, + query_const_funcname, copy_in_funcname, copy_out_funcname, cleanup_funcname, const_obj_name, dyn_const_name, const_names_name, const_indices_name, const_array_func, advect_array_func, prop_array_func, const_index_func, suite_list, dyn_const_dict, err_vars): @@ -712,6 +712,14 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.blank_line() cap.write(f"call {const_obj_name}%copy_out(const_array, {obj_err_callstr})", 2) cap.write(f"end {substmt}", 1) + # Write cleanup routine + substmt = f"subroutine {cleanup_funcname}" + cap.blank_line() + cap.write(f"{substmt}()", 1) + cap.comment("Deallocate dynamic constituent array", 2) + cap.blank_line() + cap.write(f"deallocate({dyn_const_name})", 2) + cap.write(f"end {substmt}", 1) # Write constituents routine cap.blank_line() cap.write(f"function {const_array_func}() result(const_ptr)", 1) diff --git a/scripts/host_cap.py b/scripts/host_cap.py index ced0f218..1428aed6 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -149,6 +149,14 @@ def constituent_copyout_subname(host_model): Because this is a user interface API function, the name is fixed.""" return f"{host_model.name}_ccpp_update_constituents" +############################################################################### +def constituent_cleanup_subname(host_model): +############################################################################### + """Return the name of the subroutine to deallocate dynamic constituent + arrays + Because this is a user interface API function, the name is fixed.""" + return f"{host_model.name}_ccpp_deallocate_dynamic_constituents" + ############################################################################### def unique_local_name(loc_name, host_model): ############################################################################### @@ -521,6 +529,8 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): cap.write(f"public :: {copyin_name}", 1) copyout_name = constituent_copyout_subname(host_model) cap.write(f"public :: {copyout_name}", 1) + cleanup_name = constituent_cleanup_subname(host_model) + cap.write(f"public :: {cleanup_name}", 1) const_array_func = constituent_model_consts(host_model) cap.write(f"public :: {const_array_func}", 1) advect_array_func = constituent_model_advected_consts(host_model) @@ -668,7 +678,8 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): dyn_const_name = dynamic_constituent_array_name(host_model) ConstituentVarDict.write_host_routines(cap, host_model, reg_name, init_name, numconsts_name, queryconsts_name, - copyin_name, copyout_name, + copyin_name, copyout_name, + cleanup_name, const_obj_name, dyn_const_name, const_names_name, diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index a7c38bc9..159ca814 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -76,6 +76,7 @@ module ccpp_constituent_prop_mod procedure :: molar_mass => ccp_molar_mass procedure :: default_value => ccp_default_value procedure :: has_default => ccp_has_default + procedure :: is_match => ccp_is_match ! Copy method (be sure to update this anytime fields are added) procedure :: copyConstituent generic :: assignment(=) => copyConstituent @@ -942,6 +943,48 @@ subroutine ccp_has_default(this, val_out, errcode, errmsg) end subroutine ccp_has_default + !######################################################################## + + logical function ccp_is_match(this, comp_props) result(is_match) + ! Return .true. iff the constituent's properties match the checked + ! attributes of another constituent properties object + ! Since this is a private function, error checking for locked status + ! is *not* performed. + + ! Dummy arguments + class(ccpp_constituent_properties_t), intent(in) :: this + type(ccpp_constituent_properties_t), intent(in) :: comp_props + ! Local variable + logical :: val, comp_val + logical :: check + + ! By default, every constituent is a match + is_match = .true. + ! Check: advected, thermo_active, water_species + ! peverwhee: also check min_value, molar_mass, default_value? + call this%is_advected(val) + call comp_props%is_advected(comp_val) + if (val /= comp_val) then + is_match = .false. + return + end if + + call this%is_thermo_active(val) + call comp_props%is_thermo_active(comp_val) + if (val /= comp_val) then + is_match = .false. + return + end if + + call this%is_water_species(val) + call comp_props%is_water_species(comp_val) + if (val /= comp_val) then + is_match = .false. + return + end if + + end function ccp_is_match + !######################################################################## ! ! CCPP_MODEL_CONSTITUENTS_T (constituent field data) methods @@ -1090,13 +1133,35 @@ subroutine ccp_model_const_add_metadata(this, field_data, errcode, errmsg) integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg ! Local variables - character(len=errmsg_len) :: error - character(len=*), parameter :: subname = 'ccp_model_const_add_metadata' + character(len=errmsg_len) :: error + character(len=*), parameter :: subname = 'ccp_model_const_add_metadata' + type(ccpp_constituent_properties_t), pointer :: cprop + character(len=stdname_len) :: standard_name + integer :: cindex + logical :: match if (this%okay_to_add(errcode=errcode, errmsg=errmsg, & warn_func=subname)) then error = '' -!!XXgoldyXX: Add check on key to see if incompatible item already there. + ! Check to see if standard name is already in the table + call field_data%standard_name(standard_name, errcode, errmsg) + cprop => this%find_const(standard_name) + if (associated(cprop)) then + ! Standard name already in table, let's see if the existing constituent is the same + cindex = cprop%const_index() + match = cprop%is_match(field_data) + if (match) then + ! Existing constituent is a match - no need to throw an error, just don't add + return + else + ! Existing constituent is not a match - this is ane rror + call append_errvars(1, "ERROR: Trying to add constituent " // & + trim(standard_name) // " but an incompatible" // & + " constituent with this name already exists", subname, & + errcode=errcode, errmsg=errmsg) + return + end if + end if call this%hash_table%add_hash_key(field_data, error) if (len_trim(error) > 0) then call append_errvars(1, trim(error), subname, errcode=errcode, errmsg=errmsg) @@ -1541,7 +1606,7 @@ subroutine ccp_model_const_index(this, index, standard_name, errcode, errmsg) character(len=*), parameter :: subname = "ccp_model_const_index" if (this%const_props_locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then - cprop => this%find_const(standard_name, errcode=errcode, errmsg=errmsg) + cprop => this%find_const(standard_name) if (associated(cprop)) then index = cprop%const_index() else diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index df956b55..2196416b 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -23,7 +23,7 @@ module test_prog character(len=cm), pointer :: suite_required_vars(:) => NULL() end type suite_info - type(ccpp_constituent_properties_t), private, target :: host_constituents(1) + type(ccpp_constituent_properties_t), private, target, allocatable :: host_constituents(:) private :: check_list @@ -225,6 +225,7 @@ subroutine test_host(retval, test_suites) use test_host_mod, only: num_time_steps use test_host_mod, only: init_data, compare_data use test_host_mod, only: ncols, pver + use test_host_ccpp_cap, only: test_host_ccpp_deallocate_dynamic_constituents use test_host_ccpp_cap, only: test_host_ccpp_register_constituents use test_host_ccpp_cap, only: test_host_ccpp_is_scheme_constituent use test_host_ccpp_cap, only: test_host_ccpp_initialize_constituents @@ -256,6 +257,7 @@ subroutine test_host(retval, test_suites) character(len=128), allocatable :: suite_names(:) character(len=256) :: const_str character(len=512) :: errmsg + character(len=512) :: expected_error integer :: errflg integer :: errflg_final ! Used to notify testing script of test failure real(kind_phys), pointer :: const_ptr(:,:,:) @@ -321,11 +323,54 @@ subroutine test_host(retval, test_suites) ! Register the constituents to find out what needs advecting + ! DO A COUPLE OF TESTS FIRST + + ! First confirm the correct error occurs if you try to add an + ! incompatible constituent with the same standard name + expected_error = 'ccp_model_const_add_metadata ERROR: Trying to add ' //& + 'constituent specific_humidity but an incompatible ' // & + 'constituent with this name already exists' + allocate(host_constituents(2)) call host_constituents(1)%instantiate(std_name="specific_humidity", & long_name="Specific humidity", units="kg kg-1", & vertical_dim="vertical_layer_dimension", advected=.true., & min_value=1000._kind_phys, molar_mass=2000._kind_phys, & errcode=errflg, errmsg=errmsg) + call host_constituents(2)%instantiate(std_name="specific_humidity", & + long_name="Specific humidity", units="kg kg-1", & + vertical_dim="vertical_layer_dimension", advected=.false., & + min_value=1000._kind_phys, molar_mass=2000._kind_phys, & + errcode=errflg, errmsg=errmsg) + call check_errflg(subname//'.initialize', errflg, errmsg, errflg_final) + if (errflg == 0) then + call test_host_ccpp_register_constituents(host_constituents, & + errmsg=errmsg, errflg=errflg) + end if + ! Check the error + if (errflg == 0) then + write(6, '(2a)') 'ERROR register_constituents: expected this error: ', & + trim(expected_error) + else + if (trim(errmsg) /= trim(expected_error)) then + write(6, '(4a)') 'ERROR register_constituents: expected this error: ', & + trim(expected_error), ' Got: ', trim(errmsg) + end if + end if + ! Now try again but with a compatible constituent - should be ignored when + ! the constituents object is created + call test_host_ccpp_deallocate_dynamic_constituents() + deallocate(host_constituents) + allocate(host_constituents(2)) + call host_constituents(1)%instantiate(std_name="specific_humidity", & + long_name="Specific humidity", units="kg kg-1", & + vertical_dim="vertical_layer_dimension", advected=.true., & + min_value=1000._kind_phys, molar_mass=2000._kind_phys, & + errcode=errflg, errmsg=errmsg) + call host_constituents(2)%instantiate(std_name="specific_humidity", & + long_name="Specific humidity", units="kg kg-1", & + vertical_dim="vertical_layer_dimension", advected=.true., & + min_value=1000._kind_phys, molar_mass=2000._kind_phys, & + errcode=errflg, errmsg=errmsg) call check_errflg(subname//'.initialize', errflg, errmsg, errflg_final) if (errflg == 0) then call test_host_ccpp_register_constituents(host_constituents, & diff --git a/test/unit_tests/sample_files/test_host.meta b/test/unit_tests/sample_files/test_host.meta index f618f871..b3bbc001 100644 --- a/test/unit_tests/sample_files/test_host.meta +++ b/test/unit_tests/sample_files/test_host.meta @@ -2,6 +2,7 @@ name = test_host type = host dependencies = + dynamic_constituent_routine = dyn_consts ######################################################################## [ccpp-arg-table] diff --git a/test/unit_tests/sample_scheme_files/duplicate_dyn_const.F90 b/test/unit_tests/sample_scheme_files/duplicate_dyn_const.F90 new file mode 100644 index 00000000..1b64c17e --- /dev/null +++ b/test/unit_tests/sample_scheme_files/duplicate_dyn_const.F90 @@ -0,0 +1,96 @@ +! Test parameterization with no vertical level +! + +MODULE duplicate_dyn_const + + USE ccpp_kinds, ONLY: kind_phys + + IMPLICIT NONE + PRIVATE + + PUBLIC :: duplicate_dyn_const_init + PUBLIC :: duplicate_dyn_const_run + PUBLIC :: duplicate_dyn_const_finalize + PUBLIC :: dyn_consts + +CONTAINS + + !> \section arg_table_duplicate_dyn_const_run Argument Table + !! \htmlinclude arg_table_duplicate_dyn_const_run.html + !! + subroutine duplicate_dyn_const_run(foo, timestep, temp_prev, temp_layer, qv, ps, & + errmsg, errflg) + + integer, intent(in) :: foo + real(kind_phys), intent(in) :: timestep + real(kind_phys), intent(inout) :: qv(:) + real(kind_phys), intent(inout) :: ps(:) + REAL(kind_phys), intent(in) :: temp_prev(:) + REAL(kind_phys), intent(inout) :: temp_layer(foo) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + !---------------------------------------------------------------- + + integer :: col_index + + errmsg = '' + errflg = 0 + + do col_index = 1, foo + temp_layer(col_index) = temp_layer(col_index) + temp_prev(col_index) + qv(col_index) = qv(col_index) + 1.0_kind_phys + end do + + END SUBROUTINE duplicate_dyn_const_run + + !> \section arg_table_duplicate_dyn_const_init Argument Table + !! \htmlinclude arg_table_duplicate_dyn_const_init.html + !! + subroutine duplicate_dyn_const_init (errmsg, errflg) + + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + ! This routine currently does nothing + + errmsg = '' + errflg = 0 + + end subroutine duplicate_dyn_const_init + + !> \section arg_table_duplicate_dyn_const_finalize Argument Table + !! \htmlinclude arg_table_duplicate_dyn_const_finalize.html + !! + subroutine duplicate_dyn_const_finalize (errmsg, errflg) + + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + ! This routine currently does nothing + + errmsg = '' + errflg = 0 + + end subroutine duplicate_dyn_const_finalize + + subroutine dyn_consts(dyn_const, errcode, errmsg) + use ccpp_constituent_prop_mod, only: ccpp_constituent_properties_t + type(ccpp_constituent_properties_t), allocatable, intent(out) :: dyn_const(:) + integer, intent(out) :: errcode + character(len=512), intent(out) :: errmsg + + errmsg = '' + errcode = 0 + allocate(dyn_const(1), stat=errcode) + if (errcode /= 0) then + errmsg = 'Error allocating dyn_const in dyn_consts' + end if + call dyn_const(1)%instantiate(std_name="dyn_const1", long_name='dyn const1', & + units='kg kg-1', default_value=1._kind_phys, & + vertical_dim='vertical_layer_dimension', advected=.true., & + errcode=errcode, errmsg=errmsg) + + end subroutine dyn_consts + + +END MODULE duplicate_dyn_const diff --git a/test/unit_tests/sample_scheme_files/duplicate_dyn_const.meta b/test/unit_tests/sample_scheme_files/duplicate_dyn_const.meta new file mode 100644 index 00000000..f369f8b2 --- /dev/null +++ b/test/unit_tests/sample_scheme_files/duplicate_dyn_const.meta @@ -0,0 +1,104 @@ +[ccpp-table-properties] + name = duplicate_dyn_const + type = scheme + dynamic_constituent_routine = dyn_consts + +######################################################################## +[ccpp-arg-table] + name = duplicate_dyn_const_run + type = scheme +[ foo ] + standard_name = horizontal_loop_extent + type = integer + units = count + dimensions = () + intent = in +[ timestep ] + standard_name = time_step_for_physics + long_name = time step + units = s + dimensions = () + type = real + kind = kind_phys + intent = in +[ temp_prev ] + standard_name = potential_temperature_at_previous_timestep + units = K + dimensions = (horizontal_loop_extent) + type = real + kind = kind_phys + intent = in +[ temp_layer ] + standard_name = potential_temperature + units = K + dimensions = (horizontal_loop_extent) + type = real + kind = kind_phys + intent = inout +[ qv ] + standard_name = water_vapor_specific_humidity + units = kg kg-1 + dimensions = (horizontal_loop_extent) + type = real + kind = kind_phys + advected = true + intent = inout +[ ps ] + standard_name = surface_air_pressure + state_variable = true + type = real + kind = kind_phys + units = Pa + dimensions = (horizontal_loop_extent) + intent = inout +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out +[ccpp-arg-table] + name = duplicate_dyn_const_init + type = scheme +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out +[ccpp-arg-table] + name = duplicate_dyn_const_finalize + type = scheme +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out diff --git a/test/unit_tests/sample_scheme_files/dyn_const_not_present.F90 b/test/unit_tests/sample_scheme_files/dyn_const_not_present.F90 new file mode 100644 index 00000000..2dcebe31 --- /dev/null +++ b/test/unit_tests/sample_scheme_files/dyn_const_not_present.F90 @@ -0,0 +1,75 @@ +! Test parameterization with no vertical level +! + +MODULE temp_adjust + + USE ccpp_kinds, ONLY: kind_phys + + IMPLICIT NONE + PRIVATE + + PUBLIC :: temp_adjust_init + PUBLIC :: temp_adjust_run + PUBLIC :: temp_adjust_finalize + +CONTAINS + + !> \section arg_table_temp_adjust_run Argument Table + !! \htmlinclude arg_table_temp_adjust_run.html + !! + subroutine temp_adjust_run(foo, timestep, temp_prev, temp_layer, qv, ps, & + errmsg, errflg) + + integer, intent(in) :: foo + real(kind_phys), intent(in) :: timestep + real(kind_phys), intent(inout) :: qv(:) + real(kind_phys), intent(inout) :: ps(:) + REAL(kind_phys), intent(in) :: temp_prev(:) + REAL(kind_phys), intent(inout) :: temp_layer(foo) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + !---------------------------------------------------------------- + + integer :: col_index + + errmsg = '' + errflg = 0 + + do col_index = 1, foo + temp_layer(col_index) = temp_layer(col_index) + temp_prev(col_index) + qv(col_index) = qv(col_index) + 1.0_kind_phys + end do + + END SUBROUTINE temp_adjust_run + + !> \section arg_table_temp_adjust_init Argument Table + !! \htmlinclude arg_table_temp_adjust_init.html + !! + subroutine temp_adjust_init (errmsg, errflg) + + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + ! This routine currently does nothing + + errmsg = '' + errflg = 0 + + end subroutine temp_adjust_init + + !> \section arg_table_temp_adjust_finalize Argument Table + !! \htmlinclude arg_table_temp_adjust_finalize.html + !! + subroutine temp_adjust_finalize (errmsg, errflg) + + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + ! This routine currently does nothing + + errmsg = '' + errflg = 0 + + end subroutine temp_adjust_finalize + +END MODULE temp_adjust diff --git a/test/unit_tests/sample_scheme_files/dyn_const_not_present.meta b/test/unit_tests/sample_scheme_files/dyn_const_not_present.meta new file mode 100644 index 00000000..05638ded --- /dev/null +++ b/test/unit_tests/sample_scheme_files/dyn_const_not_present.meta @@ -0,0 +1,104 @@ +[ccpp-table-properties] + name = temp_adjust + type = scheme + dynamic_constituent_routine = dyn_consts + +######################################################################## +[ccpp-arg-table] + name = temp_adjust_run + type = scheme +[ foo ] + standard_name = horizontal_loop_extent + type = integer + units = count + dimensions = () + intent = in +[ timestep ] + standard_name = time_step_for_physics + long_name = time step + units = s + dimensions = () + type = real + kind = kind_phys + intent = in +[ temp_prev ] + standard_name = potential_temperature_at_previous_timestep + units = K + dimensions = (horizontal_loop_extent) + type = real + kind = kind_phys + intent = in +[ temp_layer ] + standard_name = potential_temperature + units = K + dimensions = (horizontal_loop_extent) + type = real + kind = kind_phys + intent = inout +[ qv ] + standard_name = water_vapor_specific_humidity + units = kg kg-1 + dimensions = (horizontal_loop_extent) + type = real + kind = kind_phys + advected = true + intent = inout +[ ps ] + standard_name = surface_air_pressure + state_variable = true + type = real + kind = kind_phys + units = Pa + dimensions = (horizontal_loop_extent) + intent = inout +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out +[ccpp-arg-table] + name = temp_adjust_init + type = scheme +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out +[ccpp-arg-table] + name = temp_adjust_finalize + type = scheme +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out diff --git a/test/unit_tests/sample_scheme_files/temp_adjust.F90 b/test/unit_tests/sample_scheme_files/temp_adjust.F90 index 2dcebe31..31139d67 100644 --- a/test/unit_tests/sample_scheme_files/temp_adjust.F90 +++ b/test/unit_tests/sample_scheme_files/temp_adjust.F90 @@ -11,6 +11,7 @@ MODULE temp_adjust PUBLIC :: temp_adjust_init PUBLIC :: temp_adjust_run PUBLIC :: temp_adjust_finalize + PUBLIC :: dyn_consts CONTAINS @@ -72,4 +73,24 @@ subroutine temp_adjust_finalize (errmsg, errflg) end subroutine temp_adjust_finalize + subroutine dyn_consts(dyn_const, errcode, errmsg) + use ccpp_constituent_prop_mod, only: ccpp_constituent_properties_t + type(ccpp_constituent_properties_t), allocatable, intent(out) :: dyn_const(:) + integer, intent(out) :: errcode + character(len=512), intent(out) :: errmsg + + errmsg = '' + errcode = 0 + allocate(dyn_const(1), stat=errcode) + if (errcode /= 0) then + errmsg = 'Error allocating dyn_const in dyn_consts' + end if + call dyn_const(1)%instantiate(std_name="dyn_const1", long_name='dyn const1', & + units='kg kg-1', default_value=1._kind_phys, & + vertical_dim='vertical_layer_dimension', advected=.true., & + errcode=errcode, errmsg=errmsg) + + end subroutine dyn_consts + + END MODULE temp_adjust diff --git a/test/unit_tests/sample_scheme_files/temp_adjust.meta b/test/unit_tests/sample_scheme_files/temp_adjust.meta index 3c5ce877..063409d6 100644 --- a/test/unit_tests/sample_scheme_files/temp_adjust.meta +++ b/test/unit_tests/sample_scheme_files/temp_adjust.meta @@ -1,6 +1,7 @@ [ccpp-table-properties] name = temp_adjust type = scheme + dynamic_constituent_routine = dyn_consts ######################################################################## [ccpp-arg-table] diff --git a/test/unit_tests/test_metadata_scheme_file.py b/test/unit_tests/test_metadata_scheme_file.py index 595962c0..e36f531f 100644 --- a/test/unit_tests/test_metadata_scheme_file.py +++ b/test/unit_tests/test_metadata_scheme_file.py @@ -35,6 +35,10 @@ - Correctly interpret Fortran with preprocessor logic which affects the subroutine statement and/or the dummy argument statements resulting in incorrect Fortran + - Correctly detect a missing dynamic constituent routine + in Fortran + - Correctly raise an error if there are two dynamic + constituent routines with the same name Assumptions: @@ -341,6 +345,27 @@ def test_scheme_ddt_only(self): scheme_headers, table_dict = parse_scheme_files(scheme_files, self._run_env_ccpp) + def test_not_present_dynamic_constituents_routine(self): + scheme_files = [os.path.join(self._sample_files_dir, + "dyn_const_not_present.meta")] + + with self.assertRaises(CCPPError) as context: + _, _ = parse_scheme_files(scheme_files, self._run_env_ccpp) + + emsg = "Dynamic constituent routine dyn_consts not found in fortran" + self.assertTrue(emsg in str(context.exception)) + + def test_duplicate_dynamic_constituents_routine_name(self): + scheme_files = [os.path.join(self._sample_files_dir, + "temp_adjust.meta"), + os.path.join(self._sample_files_dir, + "duplicate_dyn_const.meta")] + + with self.assertRaises(CCPPError) as context: + _, _ = parse_scheme_files(scheme_files, self._run_env_ccpp) + emsg = "ERROR: Dynamic constituent routine names must be unique." + self.assertTrue(emsg in str(context.exception)) + if __name__ == "__main__": unittest.main() diff --git a/test/unit_tests/test_metadata_table.py b/test/unit_tests/test_metadata_table.py index 3a01f98b..fe6154ad 100755 --- a/test/unit_tests/test_metadata_table.py +++ b/test/unit_tests/test_metadata_table.py @@ -47,6 +47,7 @@ def test_good_host_file(self): #Verify that: # no dependencies is returned as '' # rel_path is returned as None + # dynamic_constituent_routine is returned as 'dyn_consts' # size of returned list equals number of headers in the test file # ccpp-table-properties name is 'test_host' dependencies = result[0].dependencies @@ -55,6 +56,8 @@ def test_good_host_file(self): self.assertEqual(len(dependencies), 0) self.assertIsNone(rel_path) self.assertEqual(len(result), 1) + dyn_const_routine = result[0].dyn_const_routine + self.assertEqual(dyn_const_routine, 'dyn_consts') titles = [elem.table_name for elem in result] self.assertIn('test_host', titles, msg="Header name 'test_host' is expected but not found") From 2a2312454362706719dce4126b4a53b8ddbfd014 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Sat, 27 Apr 2024 21:17:57 -0600 Subject: [PATCH 149/159] add unit comparison --- src/ccpp_constituent_prop_mod.F90 | 61 ++++++++++++++++++++++++++++--- test/advection_test/test_host.F90 | 8 ++-- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index 159ca814..a60e3c95 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -57,6 +57,7 @@ module ccpp_constituent_prop_mod procedure :: is_instantiated => ccp_is_instantiated procedure :: standard_name => ccp_get_standard_name procedure :: long_name => ccp_get_long_name + procedure :: units => ccp_get_units procedure :: is_layer_var => ccp_is_layer_var procedure :: is_interface_var => ccp_is_interface_var procedure :: is_2d_var => ccp_is_2d_var @@ -99,6 +100,7 @@ module ccpp_constituent_prop_mod ! Informational methods procedure :: standard_name => ccpt_get_standard_name procedure :: long_name => ccpt_get_long_name + procedure :: units => ccpt_get_units procedure :: is_layer_var => ccpt_is_layer_var procedure :: is_interface_var => ccpt_is_interface_var procedure :: is_2d_var => ccpt_is_2d_var @@ -501,6 +503,25 @@ end subroutine ccp_get_long_name !####################################################################### + subroutine ccp_get_units(this, units, errcode, errmsg) + ! Return this constituent's units + + ! Dummy arguments + class(ccpp_constituent_properties_t), intent(in) :: this + character(len=*), intent(out) :: units + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + + if (this%is_instantiated(errcode, errmsg)) then + units = this%var_units + else + units = '' + end if + + end subroutine ccp_get_units + + !####################################################################### + subroutine ccp_get_vertical_dimension(this, vert_dim, errcode, errmsg) ! Return the standard name of this constituent's vertical dimension @@ -956,29 +977,36 @@ logical function ccp_is_match(this, comp_props) result(is_match) type(ccpp_constituent_properties_t), intent(in) :: comp_props ! Local variable logical :: val, comp_val + character(len=stdname_len) :: char_val, char_comp_val logical :: check ! By default, every constituent is a match is_match = .true. - ! Check: advected, thermo_active, water_species - ! peverwhee: also check min_value, molar_mass, default_value? + ! Check: advected, thermo_active, water_species, units call this%is_advected(val) call comp_props%is_advected(comp_val) - if (val /= comp_val) then + if (val .neqv. comp_val) then is_match = .false. return end if call this%is_thermo_active(val) call comp_props%is_thermo_active(comp_val) - if (val /= comp_val) then + if (val .neqv. comp_val) then is_match = .false. return end if call this%is_water_species(val) call comp_props%is_water_species(comp_val) - if (val /= comp_val) then + if (val .neqv. comp_val) then + is_match = .false. + return + end if + + call this%units(char_val) + call comp_props%units(char_comp_val) + if (trim(char_val) /= trim(char_comp_val)) then is_match = .false. return end if @@ -1921,6 +1949,29 @@ end subroutine ccpt_get_long_name !####################################################################### + subroutine ccpt_get_units(this, units, errcode, errmsg) + ! Return this constituent's units + + ! Dummy arguments + class(ccpp_constituent_prop_ptr_t), intent(in) :: this + character(len=*), intent(out) :: units + integer, optional, intent(out) :: errcode + character(len=*), optional, intent(out) :: errmsg + ! Local variable + character(len=*), parameter :: subname = 'ccpt_get_units' + + if (associated(this%prop)) then + call this%prop%units(units, errcode, errmsg) + else + units = '' + call append_errvars(1, ": invalid constituent pointer", & + subname, errcode=errcode, errmsg=errmsg) + end if + + end subroutine ccpt_get_units + + !####################################################################### + subroutine ccpt_get_vertical_dimension(this, vert_dim, errcode, errmsg) ! Return the standard name of this constituent's vertical dimension diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index 2196416b..6b91aac4 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -334,12 +334,12 @@ subroutine test_host(retval, test_suites) call host_constituents(1)%instantiate(std_name="specific_humidity", & long_name="Specific humidity", units="kg kg-1", & vertical_dim="vertical_layer_dimension", advected=.true., & - min_value=1000._kind_phys, molar_mass=2000._kind_phys, & + min_value=1000._kind_phys, molar_mass=2000._kind_phys, & errcode=errflg, errmsg=errmsg) call host_constituents(2)%instantiate(std_name="specific_humidity", & - long_name="Specific humidity", units="kg kg-1", & - vertical_dim="vertical_layer_dimension", advected=.false., & - min_value=1000._kind_phys, molar_mass=2000._kind_phys, & + long_name="Specific humidity", units="kg kg", & + vertical_dim="vertical_layer_dimension", advected=.true., & + min_value=1000._kind_phys, molar_mass=2000._kind_phys, & errcode=errflg, errmsg=errmsg) call check_errflg(subname//'.initialize', errflg, errmsg, errflg_final) if (errflg == 0) then From 4c4807b8fd86d6d8299e24c56cc1efb5d7e77e54 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Sat, 27 Apr 2024 23:23:55 -0600 Subject: [PATCH 150/159] code review --- scripts/ccpp_capgen.py | 1 - scripts/ccpp_datafile.py | 1 - scripts/host_cap.py | 10 +++++----- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index a83f35af..b23494e0 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -667,7 +667,6 @@ def capgen(run_env, return_db=False): # end if # Next, parse the scheme files # 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 diff --git a/scripts/ccpp_datafile.py b/scripts/ccpp_datafile.py index 914d870d..e01bb2a1 100755 --- a/scripts/ccpp_datafile.py +++ b/scripts/ccpp_datafile.py @@ -426,7 +426,6 @@ def _retrieve_dyn_const_routines(table): ccpp_datafile.CCPPDatatableError: Could not find 'dyn_const_routines' element """ - result = set() routines = table.find("dyn_const_routines") if routines is None: raise CCPPDatatableError("Could not find 'dyn_const_routines' element") diff --git a/scripts/host_cap.py b/scripts/host_cap.py index 1428aed6..91d91493 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -107,7 +107,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): @@ -240,7 +240,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) ############################################################################### @@ -248,14 +248,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) ############################################################################### @@ -263,7 +263,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) ############################################################################### From 12b4eab0995c1b60f2a7e5978a0b731cfe287830 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 29 Apr 2024 13:38:48 -0600 Subject: [PATCH 151/159] update logic to exclude nested subroutines from routine parsing --- scripts/fortran_tools/parse_fortran_file.py | 20 ++-- .../dyn_const_not_present_nested.F90 | 100 +++++++++++++++++ .../dyn_const_not_present_nested.meta | 104 ++++++++++++++++++ .../sample_scheme_files/temp_adjust.F90 | 2 +- test/unit_tests/test_metadata_scheme_file.py | 10 ++ 5 files changed, 227 insertions(+), 9 deletions(-) create mode 100644 test/unit_tests/sample_scheme_files/dyn_const_not_present_nested.F90 create mode 100644 test/unit_tests/sample_scheme_files/dyn_const_not_present_nested.meta diff --git a/scripts/fortran_tools/parse_fortran_file.py b/scripts/fortran_tools/parse_fortran_file.py index 33e17a8e..9bc39efa 100644 --- a/scripts/fortran_tools/parse_fortran_file.py +++ b/scripts/fortran_tools/parse_fortran_file.py @@ -40,7 +40,6 @@ _FIXED_CONTINUE_RE = re.compile(r"(?i) [^0 ]") _BLANK_RE = re.compile(r"\s+") _ARG_TABLE_START_RE = re.compile(r"(?i)\s*![!>]\s*(?:\\section)?\s*arg_table_"+FORTRAN_ID) -_SUBOURTINE_RE = re.compile(r"(?i)\s*subroutine\s+"+FORTRAN_ID) _PREFIX_SPECS = [r"(?:recursive)", r"(?:pure)", r"(?:elemental)"] _PREFIX_SPEC = r"(?:{})?\s*".format('|'.join(_PREFIX_SPECS)) _SUBNAME_SPEC = r"subroutine\s*" @@ -906,6 +905,8 @@ def parse_module(pobj, statements, run_env): inmodule = pobj.in_region('MODULE', region_name=mod_name) active_table = None additional_subroutines = [] + seen_contains = False + insub = False while inmodule and (statements is not None): while statements: statement = statements.pop(0) @@ -913,6 +914,8 @@ def parse_module(pobj, statements, run_env): pmatch = _ENDMODULE_RE.match(statement) asmatch = _ARG_TABLE_START_RE.match(statement) smatch = _SUBROUTINE_RE.match(statement) + esmatch = _END_SUBROUTINE_RE.match(statement) + seen_contains = seen_contains or is_contains_statement(statement, insub) if asmatch is not None: active_table = asmatch.group(1) elif pmatch is not None: @@ -920,13 +923,6 @@ def parse_module(pobj, statements, run_env): pobj.leave_region('MODULE', region_name=mod_name) inmodule = False break - elif smatch is not None: - # parse out the subroutine name - routine_name = statement.split()[1] - if '(' in routine_name: - routine_name = routine_name.split('(')[0] - # end if - additional_subroutines.append(routine_name) elif active_table is not None: statements, mheader = parse_scheme_metadata(statements, pobj, mod_name, @@ -949,6 +945,14 @@ def parse_module(pobj, statements, run_env): active_table = None inmodule = pobj.in_region('MODULE', region_name=mod_name) break + elif smatch is not None and not seen_contains: + routine_name = smatch.group(1).strip() + additional_subroutines.append(routine_name) + insub = True + elif esmatch is not None and not seen_contains: + insub = False + elif esmatch is not None: + seen_contains = False # End if # End while if inmodule and (statements is not None) and (len(statements) == 0): diff --git a/test/unit_tests/sample_scheme_files/dyn_const_not_present_nested.F90 b/test/unit_tests/sample_scheme_files/dyn_const_not_present_nested.F90 new file mode 100644 index 00000000..259d4d66 --- /dev/null +++ b/test/unit_tests/sample_scheme_files/dyn_const_not_present_nested.F90 @@ -0,0 +1,100 @@ +! Test parameterization with no vertical level +! + +MODULE temp_adjust + + USE ccpp_kinds, ONLY: kind_phys + + IMPLICIT NONE + PRIVATE + + PUBLIC :: temp_adjust_init + PUBLIC :: temp_adjust_run + PUBLIC :: temp_adjust_finalize + PUBLIC :: dyn_consts + +CONTAINS + + !> \section arg_table_temp_adjust_run Argument Table + !! \htmlinclude arg_table_temp_adjust_run.html + !! + subroutine temp_adjust_run(foo, timestep, temp_prev, temp_layer, qv, ps, & + errmsg, errflg) + + integer, intent(in) :: foo + real(kind_phys), intent(in) :: timestep + real(kind_phys), intent(inout) :: qv(:) + real(kind_phys), intent(inout) :: ps(:) + REAL(kind_phys), intent(in) :: temp_prev(:) + REAL(kind_phys), intent(inout) :: temp_layer(foo) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + !---------------------------------------------------------------- + + integer :: col_index + + errmsg = '' + errflg = 0 + + do col_index = 1, foo + temp_layer(col_index) = temp_layer(col_index) + temp_prev(col_index) + qv(col_index) = qv(col_index) + 1.0_kind_phys + end do + + END SUBROUTINE temp_adjust_run + + !> \section arg_table_temp_adjust_init Argument Table + !! \htmlinclude arg_table_temp_adjust_init.html + !! + subroutine temp_adjust_init (errmsg, errflg) + + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + ! This routine currently does nothing + + errmsg = '' + errflg = 0 + + end subroutine temp_adjust_init + + !> \section arg_table_temp_adjust_finalize Argument Table + !! \htmlinclude arg_table_temp_adjust_finalize.html + !! + subroutine temp_adjust_finalize (errmsg, errflg) + + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + + ! This routine currently does nothing + + errmsg = '' + errflg = 0 + + end subroutine temp_adjust_finalize + + subroutine some_subroutine() + contains + subroutine dyn_consts(dyn_const, errcode, errmsg) + use ccpp_constituent_prop_mod, only: ccpp_constituent_properties_t + type(ccpp_constituent_properties_t), allocatable, intent(out) :: dyn_const(:) + integer, intent(out) :: errcode + character(len=512), intent(out) :: errmsg + + errmsg = '' + errcode = 0 + allocate(dyn_const(1), stat=errcode) + if (errcode /= 0) then + errmsg = 'Error allocating dyn_const in dyn_consts' + end if + call dyn_const(1)%instantiate(std_name="dyn_const1", long_name='dyn const1', & + units='kg kg-1', default_value=1._kind_phys, & + vertical_dim='vertical_layer_dimension', advected=.true., & + errcode=errcode, errmsg=errmsg) + + end subroutine dyn_consts + + end subroutine some_subroutine + + +END MODULE temp_adjust diff --git a/test/unit_tests/sample_scheme_files/dyn_const_not_present_nested.meta b/test/unit_tests/sample_scheme_files/dyn_const_not_present_nested.meta new file mode 100644 index 00000000..05638ded --- /dev/null +++ b/test/unit_tests/sample_scheme_files/dyn_const_not_present_nested.meta @@ -0,0 +1,104 @@ +[ccpp-table-properties] + name = temp_adjust + type = scheme + dynamic_constituent_routine = dyn_consts + +######################################################################## +[ccpp-arg-table] + name = temp_adjust_run + type = scheme +[ foo ] + standard_name = horizontal_loop_extent + type = integer + units = count + dimensions = () + intent = in +[ timestep ] + standard_name = time_step_for_physics + long_name = time step + units = s + dimensions = () + type = real + kind = kind_phys + intent = in +[ temp_prev ] + standard_name = potential_temperature_at_previous_timestep + units = K + dimensions = (horizontal_loop_extent) + type = real + kind = kind_phys + intent = in +[ temp_layer ] + standard_name = potential_temperature + units = K + dimensions = (horizontal_loop_extent) + type = real + kind = kind_phys + intent = inout +[ qv ] + standard_name = water_vapor_specific_humidity + units = kg kg-1 + dimensions = (horizontal_loop_extent) + type = real + kind = kind_phys + advected = true + intent = inout +[ ps ] + standard_name = surface_air_pressure + state_variable = true + type = real + kind = kind_phys + units = Pa + dimensions = (horizontal_loop_extent) + intent = inout +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out +[ccpp-arg-table] + name = temp_adjust_init + type = scheme +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out +[ccpp-arg-table] + name = temp_adjust_finalize + type = scheme +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out diff --git a/test/unit_tests/sample_scheme_files/temp_adjust.F90 b/test/unit_tests/sample_scheme_files/temp_adjust.F90 index 31139d67..4b78642d 100644 --- a/test/unit_tests/sample_scheme_files/temp_adjust.F90 +++ b/test/unit_tests/sample_scheme_files/temp_adjust.F90 @@ -73,7 +73,7 @@ subroutine temp_adjust_finalize (errmsg, errflg) end subroutine temp_adjust_finalize - subroutine dyn_consts(dyn_const, errcode, errmsg) + subroutine dyn_consts (dyn_const, errcode, errmsg) use ccpp_constituent_prop_mod, only: ccpp_constituent_properties_t type(ccpp_constituent_properties_t), allocatable, intent(out) :: dyn_const(:) integer, intent(out) :: errcode diff --git a/test/unit_tests/test_metadata_scheme_file.py b/test/unit_tests/test_metadata_scheme_file.py index e36f531f..fb0ce2f4 100644 --- a/test/unit_tests/test_metadata_scheme_file.py +++ b/test/unit_tests/test_metadata_scheme_file.py @@ -355,6 +355,16 @@ def test_not_present_dynamic_constituents_routine(self): emsg = "Dynamic constituent routine dyn_consts not found in fortran" self.assertTrue(emsg in str(context.exception)) + def test_not_present_nested_dynamic_constituents_routine(self): + scheme_files = [os.path.join(self._sample_files_dir, + "dyn_const_not_present_nested.meta")] + + with self.assertRaises(CCPPError) as context: + _, _ = parse_scheme_files(scheme_files, self._run_env_ccpp) + + emsg = "Dynamic constituent routine dyn_consts not found in fortran" + self.assertTrue(emsg in str(context.exception)) + def test_duplicate_dynamic_constituents_routine_name(self): scheme_files = [os.path.join(self._sample_files_dir, "temp_adjust.meta"), From 3a6a3e2ed3b97847133d50c1c0f91f5fd7cc4c0c Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Tue, 30 Apr 2024 10:52:57 -0600 Subject: [PATCH 152/159] update vardictionary doctests and add 3.12 testing --- .github/workflows/python.yaml | 2 +- scripts/metavar.py | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index 5f573a68..16a91253 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v3 diff --git a/scripts/metavar.py b/scripts/metavar.py index 44971c7a..e84cd2b7 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -1502,12 +1502,18 @@ class VarDictionary(OrderedDict): VarDictionary(foo) >>> VarDictionary('bar', _MVAR_DUMMY_RUN_ENV, variables={}) VarDictionary(bar) - >>> VarDictionary('baz', _MVAR_DUMMY_RUN_ENV, variables=Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV)) #doctest: +ELLIPSIS - VarDictionary(baz, [('hi_mom', )]) + >>> test_dict = VarDictionary('baz', _MVAR_DUMMY_RUN_ENV, variables=Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV)) + >>> print(test_dict.name) + baz + >>> print(test_dict.variable_list()) #doctest: +ELLIPSIS + [] >>> print("{}".format(VarDictionary('baz', _MVAR_DUMMY_RUN_ENV, variables=Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV)))) VarDictionary(baz, ['hi_mom']) - >>> VarDictionary('qux', _MVAR_DUMMY_RUN_ENV, variables=[Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV)]) #doctest: +ELLIPSIS - VarDictionary(qux, [('hi_mom', )]) + >>> test_dict = VarDictionary('qux', _MVAR_DUMMY_RUN_ENV, variables=[Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV)]) + >>> print(test_dict.name) + qux + >>> print(test_dict.variable_list()) #doctest: +ELLIPSIS + [] >>> VarDictionary('boo', _MVAR_DUMMY_RUN_ENV).add_variable(Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV), _MVAR_DUMMY_RUN_ENV) >>> VarDictionary('who', _MVAR_DUMMY_RUN_ENV, variables=[Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, ParseSource('vname', 'scheme', ParseContext()), _MVAR_DUMMY_RUN_ENV)]).prop_list('local_name') From 9e43b78522f0235a662c74d6084de72babd30817 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Thu, 16 May 2024 09:52:43 -0600 Subject: [PATCH 153/159] code cleanup; review requests --- scripts/ccpp_capgen.py | 6 ++---- scripts/fortran_tools/offline_check_fortran_vs_metadata.py | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index 1a381497..f6990133 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -324,17 +324,16 @@ def compare_fheader_to_mheader(meta_header, fort_header, logger): fopt = fvar.get_prop_value('optional') if (not fopt): errmsg = f'Missing "optional" attribute in fortran declaration for variable {mname}, ' \ - 'for {title}' + f'for {title}' errors_found = add_error(errors_found, errmsg) # end if # end if # now check: if fortran says the variable is optional, does the metadata match? if fvar: fopt = fvar.get_prop_value('optional') - mopt = mvar.get_prop_value('optional') if (fopt and not mopt): errmsg = f'Missing "optional" metadata property for variable {mname}, ' \ - 'for {title}' + f'for {title}' errors_found = add_error(errors_found, errmsg) # end if # end if @@ -533,7 +532,6 @@ def parse_scheme_files(scheme_filenames, run_env, skip_ddt_check=False): table_dict = {} # Duplicate check and for dependencies processing header_dict = {} # To check for duplicates known_ddts = list() - # end if logger = run_env.logger for filename in scheme_filenames: logger.info('Reading CCPP schemes from {}'.format(filename)) diff --git a/scripts/fortran_tools/offline_check_fortran_vs_metadata.py b/scripts/fortran_tools/offline_check_fortran_vs_metadata.py index fc15fd89..94570ec7 100755 --- a/scripts/fortran_tools/offline_check_fortran_vs_metadata.py +++ b/scripts/fortran_tools/offline_check_fortran_vs_metadata.py @@ -21,7 +21,7 @@ from fortran_tools import parse_fortran_file from metadata_table import parse_metadata_file from ccpp_capgen import find_associated_fortran_file -from ccpp_capgen import check_fortran_against_metadata, parse_scheme_files +from ccpp_capgen import parse_scheme_files from parse_tools import init_log, set_log_level from parse_tools import register_fortran_ddt_name from parse_tools import CCPPError, ParseInternalError From b668b2e7d74e44b5bef76ac9455794d0a8b19ea6 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Thu, 16 May 2024 11:49:59 -0600 Subject: [PATCH 154/159] code clean-up; add test for promoted variable with no horiz dimension --- scripts/ccpp_suite.py | 51 ++++++++++++------------------- scripts/suite_objects.py | 17 ++--------- test/capgen_test/run_test | 2 ++ test/capgen_test/temp_adjust.F90 | 3 +- test/capgen_test/temp_adjust.meta | 7 +++++ test/capgen_test/temp_set.F90 | 3 +- test/capgen_test/temp_set.meta | 7 +++++ test/capgen_test/test_reports.py | 1 + 8 files changed, 42 insertions(+), 49 deletions(-) diff --git a/scripts/ccpp_suite.py b/scripts/ccpp_suite.py index 993af65f..91414cbd 100644 --- a/scripts/ccpp_suite.py +++ b/scripts/ccpp_suite.py @@ -23,7 +23,7 @@ from parse_tools import init_log, set_log_to_null from suite_objects import CallList, Group, Scheme from metavar import CCPP_LOOP_VAR_STDNAMES -from var_props import find_horizontal_dimension, find_vertical_dimension +from var_props import is_horizontal_dimension # pylint: disable=too-many-lines @@ -314,38 +314,25 @@ def find_variable(self, standard_name=None, source_var=None, for group in self.groups: # only add dimension variables to init phase calling list if group.name == self.__suite_init_group.name: - horiz_dim = find_horizontal_dimension(var.get_dimensions())[0] - vert_dim = find_vertical_dimension(var.get_dimensions())[0] - if horiz_dim and 'horizontal_loop' in horiz_dim: - # can't use horizontal_loop_being/end/extent in init phase - # must allocate to horizontal_dimension - new_horiz_dim = 'ccpp_constant_one:horizontal_dimension' - else: - new_horiz_dim = horiz_dim - # end if - if horiz_dim: - # make new variable with "correct" dimensions - if vert_dim: - new_dims = [new_horiz_dim, vert_dim] - else: - new_dims = [new_horiz_dim] + dims = var.get_dimensions() + # replace horizontal loop dimension if necessary + for idx, dim in enumerate(dims): + if is_horizontal_dimension(dim): + if 'horizontal_loop' in dim: + dims[idx] = 'ccpp_constant_one:horizontal_dimension' + # end if # end if - subst_dict = {'dimensions': new_dims} - prop_dict = var.copy_prop_dict(subst_dict=subst_dict) - temp_var = Var(prop_dict, - ParseSource(var.get_prop_value('scheme'), - var.get_prop_value('local_name'), var.context), - self.__run_env) - # Add dimensions if they're not already there - group.add_variable_dimensions(temp_var, [], - adjust_intent=True, - to_dict=group.call_list) - else: - # Add dimensions if they're not already there - group.add_variable_dimensions(var, [], - adjust_intent=True, - to_dict=group.call_list) - # end if + # end for + subst_dict = {'dimensions': dims} + prop_dict = var.copy_prop_dict(subst_dict=subst_dict) + temp_var = Var(prop_dict, + ParseSource(var.get_prop_value('scheme'), + var.get_prop_value('local_name'), var.context), + self.__run_env) + # Add dimensions if they're not already there + group.add_variable_dimensions(temp_var, [], + adjust_intent=True, + to_dict=group.call_list) # end if # end for else: diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 2397996f..f611c5d2 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -2230,22 +2230,9 @@ def analyze(self, phase, suite_vars, scheme_library, ddt_library, self.run_env.logger.debug("{}".format(self)) # end if - def allocate_dim_str(self, dims, context, suite_var=False): + def allocate_dim_str(self, dims, context): """Create the dimension string for an allocate statement""" rdims = list() - if suite_var: - # adjust horizontal dimension if horizontal_loop_* specified - horiz_dim = find_horizontal_dimension(dims)[0] - vert_dim = find_vertical_dimension(dims)[0] - if horiz_dim and 'horizontal_loop' in horiz_dim: - new_horiz_dim = 'ccpp_constant_one:horizontal_dimension' - if vert_dim: - dims = [new_horiz_dim, vert_dim] - else: - dims = [new_horiz_dim] - # end if - # end if - # end if for dim in dims: rdparts = list() dparts = dim.split(':') @@ -2513,7 +2500,7 @@ def write(self, outfile, host_arglist, indent, const_mod, if dims: timestep_var = svar.get_prop_value('persistence') if group_type == timestep_var: - alloc_str = self.allocate_dim_str(dims, svar.context, suite_var=True) + alloc_str = self.allocate_dim_str(dims, svar.context) lname = svar.get_prop_value('local_name') outfile.write(alloc_stmt.format(lname, alloc_str), indent+1) diff --git a/test/capgen_test/run_test b/test/capgen_test/run_test index 6798f584..15f48a53 100755 --- a/test/capgen_test/run_test +++ b/test/capgen_test/run_test @@ -151,6 +151,7 @@ required_vars_temp="${required_vars_temp},horizontal_dimension" required_vars_temp="${required_vars_temp},horizontal_loop_begin" required_vars_temp="${required_vars_temp},horizontal_loop_end" required_vars_temp="${required_vars_temp},index_of_water_vapor_specific_humidity" +required_vars_temp="${required_vars_temp},number_of_tracers" required_vars_temp="${required_vars_temp},potential_temperature" required_vars_temp="${required_vars_temp},potential_temperature_at_interface" required_vars_temp="${required_vars_temp},potential_temperature_increment" @@ -164,6 +165,7 @@ input_vars_temp="${input_vars_temp},horizontal_dimension" input_vars_temp="${input_vars_temp},horizontal_loop_begin" input_vars_temp="${input_vars_temp},horizontal_loop_end" input_vars_temp="${input_vars_temp},index_of_water_vapor_specific_humidity" +input_vars_temp="${input_vars_temp},number_of_tracers" input_vars_temp="${input_vars_temp},potential_temperature" input_vars_temp="${input_vars_temp},potential_temperature_at_interface" input_vars_temp="${input_vars_temp},potential_temperature_increment" diff --git a/test/capgen_test/temp_adjust.F90 b/test/capgen_test/temp_adjust.F90 index 988f5a25..c3cc50f0 100644 --- a/test/capgen_test/temp_adjust.F90 +++ b/test/capgen_test/temp_adjust.F90 @@ -18,7 +18,7 @@ MODULE temp_adjust !! \htmlinclude arg_table_temp_adjust_run.html !! subroutine temp_adjust_run(foo, timestep, temp_prev, temp_layer, qv, ps, & - to_promote, errmsg, errflg, innie, outie, optsie) + to_promote, promote_pcnst, errmsg, errflg, innie, outie, optsie) integer, intent(in) :: foo real(kind_phys), intent(in) :: timestep @@ -27,6 +27,7 @@ subroutine temp_adjust_run(foo, timestep, temp_prev, temp_layer, qv, ps, & REAL(kind_phys), intent(in) :: temp_prev(:) REAL(kind_phys), intent(inout) :: temp_layer(foo) real(kind_phys), intent(in) :: to_promote(:) + real(kind_phys), intent(in) :: promote_pcnst(:) character(len=512), intent(out) :: errmsg integer, optional, intent(out) :: errflg real(kind_phys), optional, intent(in) :: innie diff --git a/test/capgen_test/temp_adjust.meta b/test/capgen_test/temp_adjust.meta index b3190c72..02b5fa73 100644 --- a/test/capgen_test/temp_adjust.meta +++ b/test/capgen_test/temp_adjust.meta @@ -57,6 +57,13 @@ type = real kind = kind_phys intent = in +[ promote_pcnst ] + standard_name = promote_this_variable_with_no_horizontal_dimension + units = K + dimensions = (number_of_tracers) + type = real + kind = kind_phys + intent = in [ errmsg ] standard_name = ccpp_error_message long_name = Error message for error handling in CCPP diff --git a/test/capgen_test/temp_set.F90 b/test/capgen_test/temp_set.F90 index 45284e04..a780433f 100644 --- a/test/capgen_test/temp_set.F90 +++ b/test/capgen_test/temp_set.F90 @@ -19,7 +19,7 @@ MODULE temp_set !! \htmlinclude arg_table_temp_set_run.html !! SUBROUTINE temp_set_run(ncol, lev, timestep, temp_level, temp, ps, & - to_promote, errmsg, errflg) + to_promote, promote_pcnst, errmsg, errflg) !---------------------------------------------------------------- IMPLICIT NONE !---------------------------------------------------------------- @@ -30,6 +30,7 @@ SUBROUTINE temp_set_run(ncol, lev, timestep, temp_level, temp, ps, & real(kind_phys), intent(in) :: ps(:) REAL(kind_phys), INTENT(inout) :: temp_level(:, :) real(kind_phys), intent(out) :: to_promote(:, :) + real(kind_phys), intent(out) :: promote_pcnst(:) character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg !---------------------------------------------------------------- diff --git a/test/capgen_test/temp_set.meta b/test/capgen_test/temp_set.meta index f1dfc60f..b6c403ce 100644 --- a/test/capgen_test/temp_set.meta +++ b/test/capgen_test/temp_set.meta @@ -54,6 +54,13 @@ type = real kind = kind_phys intent = out +[ promote_pcnst ] + standard_name = promote_this_variable_with_no_horizontal_dimension + units = K + dimensions = (number_of_tracers) + type = real + kind = kind_phys + intent = out [ errmsg ] standard_name = ccpp_error_message long_name = Error message for error handling in CCPP diff --git a/test/capgen_test/test_reports.py b/test/capgen_test/test_reports.py index 8682a7b0..5b50215f 100644 --- a/test/capgen_test/test_reports.py +++ b/test/capgen_test/test_reports.py @@ -79,6 +79,7 @@ def usage(errmsg=None): _REQUIRED_VARS_DDT = _INPUT_VARS_DDT + _OUTPUT_VARS_DDT _PROT_VARS_TEMP = ["horizontal_loop_begin", "horizontal_loop_end", "horizontal_dimension", "vertical_layer_dimension", + "number_of_tracers", # Added for --debug "index_of_water_vapor_specific_humidity", "vertical_interface_dimension"] From 1e139b5d3bf84aa7d8f038e7082e5b467ccd2917 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Thu, 16 May 2024 14:45:38 -0600 Subject: [PATCH 155/159] code review; cleanup --- pytest.ini | 3 +- scripts/ccpp_datafile.py | 28 +-- src/ccpp_hash_table.F90 | 2 +- test/advection_test/test_host.F90 | 252 ++++++++++---------- test/capgen_test/test_reports.py | 8 +- test/var_compatibility_test/test_reports.py | 4 +- 6 files changed, 149 insertions(+), 148 deletions(-) diff --git a/pytest.ini b/pytest.ini index d08180f1..59386b60 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,2 +1,3 @@ [pytest] -addopts = -ra --ignore=scripts/metadata2html.py --ignore-glob=test/**/test_reports.py \ No newline at end of file +addopts = -ra --ignore=scripts/metadata2html.py --ignore-glob=test/**/test_reports.py + diff --git a/scripts/ccpp_datafile.py b/scripts/ccpp_datafile.py index e01bb2a1..20dee70d 100755 --- a/scripts/ccpp_datafile.py +++ b/scripts/ccpp_datafile.py @@ -593,13 +593,13 @@ def _is_variable_protected(table, var_name, var_dict): ############################################################################### def _retrieve_variable_list(table, suite_name, - intent_type=None, excl_prot=True): + intent_type=None, exclude_protected=True): ############################################################################### """Find and return a list of all the required variables in . If suite, , is not found in
, return an empty list. If is present, return only that variable type (input or output). - If is True, do not include protected variables + If is True, do not include protected variables >>> table = ET.fromstring(""\ ""\ ""\ @@ -609,16 +609,16 @@ def _retrieve_variable_list(table, suite_name, "") # Test group variable retrieval - >>> _retrieve_variable_list(table, 'fruit', excl_prot=False) + >>> _retrieve_variable_list(table, 'fruit', exclude_protected=False) ['var3', 'var4', 'var5'] >>> _retrieve_variable_list(table, 'fruit') ['var3'] - >>> _retrieve_variable_list(table, 'fruit', intent_type='input', excl_prot=False) + >>> _retrieve_variable_list(table, 'fruit', intent_type='input', exclude_protected=False) ['var3', 'var5'] - >>> _retrieve_variable_list(table, 'fruit', intent_type='output', excl_prot=False) + >>> _retrieve_variable_list(table, 'fruit', intent_type='output', exclude_protected=False) ['var4', 'var5'] >>> _retrieve_variable_list(table, 'fruit', intent_type='input') @@ -628,7 +628,7 @@ def _retrieve_variable_list(table, suite_name, [] # Test host variable retrieval - >>> _retrieve_variable_list(table, 'fruit', intent_type='host', excl_prot=False) + >>> _retrieve_variable_list(table, 'fruit', intent_type='host', exclude_protected=False) ['var1', 'var2'] >>> _retrieve_variable_list(table, 'fruit', intent_type='host') @@ -657,14 +657,14 @@ def _retrieve_variable_list(table, suite_name, emsg = "Invalid intent_type, '{}'" raise CCPPDatatableError(emsg.format(intent_type)) # end if - if excl_prot or (intent_type == "host"): + if exclude_protected or (intent_type == "host"): host_dict = _find_var_dictionary(table, dict_type="host") if host_dict is not None: hvars = host_dict.find("variables") if hvars is not None: for var in hvars: vname = var.get("name") - if excl_prot: + if exclude_protected: exclude = _is_variable_protected(table, vname, host_dict) else: @@ -697,7 +697,7 @@ def _retrieve_variable_list(table, suite_name, for var in gvars: vname = var.get("name") vintent = var.get("intent") - if excl_prot: + if exclude_protected: exclude = vname in excl_vars if not exclude: exclude = _is_variable_protected(table, vname, @@ -717,7 +717,7 @@ def _retrieve_variable_list(table, suite_name, return sorted(var_set) ############################################################################### -def datatable_report(datatable, action, sep, excl_prot=False): +def datatable_report(datatable, action, sep, exclude_protected=False): ############################################################################### """Perform a lookup on and return the result. """ @@ -752,17 +752,17 @@ def datatable_report(datatable, action, sep, excl_prot=False): result = _retrieve_suite_list(table) elif action.action_is("required_variables"): result = _retrieve_variable_list(table, action.value, - excl_prot=excl_prot) + exclude_protected=exclude_protected) elif action.action_is("input_variables"): result = _retrieve_variable_list(table, action.value, intent_type="input", - excl_prot=excl_prot) + exclude_protected=exclude_protected) elif action.action_is("output_variables"): result = _retrieve_variable_list(table, action.value, intent_type="output", - excl_prot=excl_prot) + exclude_protected=exclude_protected) elif action.action_is("host_variables"): - result = _retrieve_variable_list(table, "host", excl_prot=excl_prot, + result = _retrieve_variable_list(table, "host", exclude_protected=exclude_protected, intent_type="host") else: result = '' diff --git a/src/ccpp_hash_table.F90 b/src/ccpp_hash_table.F90 index 57ef3106..147ca5f0 100644 --- a/src/ccpp_hash_table.F90 +++ b/src/ccpp_hash_table.F90 @@ -396,7 +396,7 @@ subroutine hash_iterator_initialize(this, hash_table) ! Dummy arguments class(ccpp_hash_iterator_t) :: this - type(ccpp_hash_table_t), intent(in), target :: hash_table + class(ccpp_hash_table_t), target :: hash_table this%hash_table => hash_table this%index = 0 diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index 6b91aac4..d2fa8b97 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -308,7 +308,7 @@ subroutine test_host(retval, test_suites) ! specific_humidity should not be an existing constituent if (is_constituent) then write(6, *) "ERROR: specific humidity is already a constituent" - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if call test_host_ccpp_is_scheme_constituent('cloud_ice_dry_mixing_ratio', & is_constituent, errflg, errmsg) @@ -318,7 +318,7 @@ subroutine test_host(retval, test_suites) if (.not. is_constituent) then write(6, *) "ERROR: cloud_ice_dry_mixing ratio not found in ", & "host cap constituent list" - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if @@ -395,8 +395,8 @@ subroutine test_host(retval, test_suites) ! Initialize constituent data call test_host_ccpp_initialize_constituents(ncols, pver, errflg, errmsg) - !Stop tests here if initialization failed (as all other tests will likely - !fail as well: + ! Stop tests here if initialization failed (as all other tests will likely + ! fail as well: if (errflg /= 0) then retval = .false. return @@ -405,19 +405,19 @@ subroutine test_host(retval, test_suites) ! Initialize our 'data' const_ptr => test_host_constituents_array() - !Check if the specific humidity index can be found: + ! Check if the specific humidity index can be found: call test_host_const_get_index('specific_humidity', index, & errflg, errmsg) call check_errflg(subname//".index_specific_humidity", errflg, errmsg, & errflg_final) - !Check if the cloud liquid index can be found: + ! Check if the cloud liquid index can be found: call test_host_const_get_index('cloud_liquid_dry_mixing_ratio', & index_liq, errflg, errmsg) call check_errflg(subname//".index_cld_liq", errflg, errmsg, & errflg_final) - !Check if the cloud ice index can be found: + ! Check if the cloud ice index can be found: call test_host_const_get_index('cloud_ice_dry_mixing_ratio', & index_ice, errflg, errmsg) call check_errflg(subname//".index_cld_ice", errflg, errmsg, & @@ -435,8 +435,8 @@ subroutine test_host(retval, test_suites) errflg_final) - !Stop tests here if the index checks failed, as all other tests will - !likely fail as well: + ! 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 @@ -445,127 +445,127 @@ subroutine test_host(retval, test_suites) call init_data(const_ptr, index, index_liq, index_ice, index_dyn3) ! Check some constituent properties - !++++++++++++++++++++++++++++++++++ + ! ++++++++++++++++++++++++++++++++++ const_props => test_host_model_const_properties() - !Standard name: + ! Standard name: call const_props(index)%standard_name(const_str, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to get standard_name for specific_humidity, index = ", & index, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if if (errflg == 0) then if (trim(const_str) /= 'specific_humidity') then write(6, *) "ERROR: standard name, '", trim(const_str), & "' should be 'specific_humidity'" - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if - !Check standard name for a dynamic constituent + ! Check standard name for a dynamic constituent call const_props(index_dyn2)%standard_name(const_str, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to get standard_name for dyn_const2, index = ", & index_dyn2, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if if (errflg == 0) then if (trim(const_str) /= 'dyn_const2_wrt_moist_air') then write(6, *) "ERROR: standard name, '", trim(const_str), & "' should be 'dyn_const2_wrt_moist_air'" - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if - !Long name: + ! Long name: call const_props(index_liq)%long_name(const_str, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to get long_name for cld_liq index = ", & index_liq, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if if (errflg == 0) then if (trim(const_str) /= 'Cloud liquid dry mixing ratio') then write(6, *) "ERROR: long name, '", trim(const_str), & "' should be 'Cloud liquid dry mixing ratio'" - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if - !Check long name for a dynamic constituent + ! Check long name for a dynamic constituent call const_props(index_dyn1)%long_name(const_str, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to get long_name for dyn_const1 index = ", & index_dyn1, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if if (errflg == 0) then if (trim(const_str) /= 'dyn const1') then write(6, *) "ERROR: long name, '", trim(const_str), & "' should be 'dyn const1'" - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if - !Mass mixing ratio: + ! Mass mixing ratio: call const_props(index_ice)%is_mass_mixing_ratio(const_log, errflg, & errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to get mass mixing ratio prop for cld_ice index = ", & index_ice, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if if (errflg == 0) then if (.not. const_log) then write(6, *) "ERROR: cloud ice is not a mass mixing_ratio" - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if - !Check mass mixing ratio for a dynamic constituent + ! Check mass mixing ratio for a dynamic constituent call const_props(index_dyn2)%is_mass_mixing_ratio(const_log, errflg, & errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to get mass mixing ratio prop for dyn_const2 index = ", & index_dyn2, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if if (errflg == 0) then if (.not. const_log) then write(6, *) "ERROR: dyn_const2 is not a mass mixing_ratio" - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if - !Dry mixing ratio: + ! Dry mixing ratio: call const_props(index_ice)%is_dry(const_log, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to get dry prop for cld_ice index = ", index_ice, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if if (errflg == 0) then if (.not. const_log) then @@ -573,15 +573,15 @@ subroutine test_host(retval, test_suites) errflg_final = -1 end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if - !Check moist mixing ratio for a dynamic constituent + ! Check moist mixing ratio for a dynamic constituent call const_props(index_dyn2)%is_dry(const_log, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to get dry prop for dyn_const2 index = ", index_dyn2, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if if (errflg == 0) then if (const_log) then @@ -589,63 +589,63 @@ subroutine test_host(retval, test_suites) errflg_final = -1 end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if - !------------------- + ! ------------------- - !------------------- - !minimum value tests: - !------------------- + ! ------------------- + ! minimum value tests: + ! ------------------- - !Check that a constituent's minimum value defaults to zero: + ! Check that a constituent's minimum value defaults to zero: call const_props(index_dyn2)%minimum(check_value, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to get minimum value for dyn_const2 index = ", index_dyn2, & trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if if (errflg == 0) then - if (check_value /= 0._kind_phys) then !Should be zero + if (check_value /= 0._kind_phys) then ! Should be zero write(6, *) "ERROR: 'minimum' should default to zero for all ", & "constituents unless set by host model or scheme metadata." - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if - !Check that a constituent instantiated with a specified minimum value - !actually contains that minimum value property: + ! Check that a constituent instantiated with a specified minimum value + ! actually contains that minimum value property: call const_props(index_dyn1)%minimum(check_value, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to get minimum value for dyn_const1 index = ", index_dyn1, & trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if if (errflg == 0) then if (check_value /= 1000._kind_phys) then !Should be 1000 write(6, *) "ERROR: 'minimum' should give a value of 1000 ", & "for dyn_const1, as was set during instantiation." - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if - !Check that setting a constituent's minimum value works - !as expected: + ! Check that setting a constituent's minimum value works + ! as expected: call const_props(index_dyn1)%set_minimum(1._kind_phys, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to set minimum value for dyn_const1 index = ", index_dyn1, & trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if if (errflg == 0) then call const_props(index_dyn1)%minimum(check_value, errflg, errmsg) @@ -653,53 +653,53 @@ subroutine test_host(retval, test_suites) write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, & " trying to get minimum value for dyn_const1 index = ", & index_dyn1, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if end if if (errflg == 0) then - if (check_value /= 1._kind_phys) then !Should now be one + if (check_value /= 1._kind_phys) then ! Should now be one write(6, *) "ERROR: 'set_minimum' did not set constituent", & " minimum value correctly." - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if - !---------------------- - !molecular weight tests: - !---------------------- + ! ---------------------- + ! molecular weight tests: + ! ---------------------- - !Check that a constituent instantiated with a specified molecular - !weight actually contains that molecular weight property value: + ! Check that a constituent instantiated with a specified molecular + ! weight actually contains that molecular weight property value: call const_props(index)%molar_mass(check_value, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to get molecular weight for specific humidity index = ", & index, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if if (errflg == 0) then - if (check_value /= 2000._kind_phys) then !Should be 2000 + if (check_value /= 2000._kind_phys) then ! Should be 2000 write(6, *) "ERROR: 'molar_mass' should give a value of 2000 ", & "for specific humidity, as was set during instantiation." - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if - !Check that setting a constituent's molecular weight works - !as expected: + ! Check that setting a constituent's molecular weight works + ! as expected: call const_props(index_ice)%set_molar_mass(1._kind_phys, errflg, & errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to set molecular weight for cld_ice index = ", index_ice, & trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if if (errflg == 0) then call const_props(index_ice)%molar_mass(check_value, errflg, errmsg) @@ -707,51 +707,51 @@ subroutine test_host(retval, test_suites) write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, & " trying to get molecular weight for cld_ice index = ", & index_ice, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if end if if (errflg == 0) then - if (check_value /= 1._kind_phys) then !Should be equal to one + if (check_value /= 1._kind_phys) then ! Should be equal to one write(6, *) "ERROR: 'set_molar_mass' did not set constituent", & " molecular weight value correctly." - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if - !------------------- - !thermo-active tests: - !------------------- + ! ------------------- + ! thermo-active tests: + ! ------------------- - !Check that being thermodynamically active defaults to False: + ! Check that being thermodynamically active defaults to False: call const_props(index_ice)%is_thermo_active(check, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to get thermo_active prop for cld_ice index = ", index_ice, & trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if if (errflg == 0) then - if (check) then !Should be False + if (check) then ! Should be False write(6, *) "ERROR: 'is_thermo_active' should default to False ", & "for all constituents unless set by host model." - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if - !Check that setting a constituent to be thermodynamically active works - !as expected: + ! Check that setting a constituent to be thermodynamically active works + ! as expected: call const_props(index_ice)%set_thermo_active(.true., errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to set thermo_active prop for cld_ice index = ", index_ice, & trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if if (errflg == 0) then call const_props(index_ice)%is_thermo_active(check, errflg, errmsg) @@ -759,52 +759,52 @@ subroutine test_host(retval, test_suites) write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, & " trying to get thermo_active prop for cld_ice index = ", & index_ice, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if end if if (errflg == 0) then - if (.not. check) then !Should now be True + if (.not. check) then ! Should now be True write(6, *) "ERROR: 'set_thermo_active' did not set", & " thermo_active constituent property correctly." - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if - !------------------- + ! ------------------- - !------------------- - !water-species tests: - !------------------- + ! ------------------- + ! water-species tests: + ! ------------------- - !Check that being a water species defaults to False: + ! Check that being a water species defaults to False: call const_props(index_liq)%is_water_species(check, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to get water_species prop for cld_liq index = ", index_liq, & trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if if (errflg == 0) then - if (check) then !Should be False + if (check) then ! Should be False write(6, *) "ERROR: 'is_water_species' should default to False ", & "for all constituents unless set by host model." - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if - !Check that setting a constituent to be a water species works - !as expected: + ! Check that setting a constituent to be a water species works + ! as expected: call const_props(index_liq)%set_water_species(.true., errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to set water_species prop for cld_liq index = ", index_liq, & trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if if (errflg == 0) then call const_props(index_liq)%is_water_species(check, errflg, errmsg) @@ -812,73 +812,73 @@ subroutine test_host(retval, test_suites) write(6, '(a,i0,a,i0,/,a)') "ERROR: Error, ", errflg, & " trying to get water_species prop for cld_liq index = ", & index_liq, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if end if if (errflg == 0) then - if (.not. check) then !Should now be True + if (.not. check) then ! Should now be True write(6, *) "ERROR: 'set_water_species' did not set", & " water_species constituent property correctly." - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if - !------------------- + ! ------------------- - !Check that setting a constituent's default value works as expected + ! Check that setting a constituent's default value works as expected call const_props(index_liq)%has_default(has_default, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,2a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to check for default for cld_liq index = ", index_liq, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if if (errflg == 0) then if (has_default) then write(6, *) "ERROR: cloud liquid mass_mixing_ratio should not have default but does" - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if call const_props(index_ice)%has_default(has_default, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,2a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to check for default for cld_ice index = ", index_ice, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if if (errflg == 0) then if (.not. has_default) then write(6, *) "ERROR: cloud ice mass_mixing_ratio should have default but doesn't" - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if call const_props(index_ice)%default_value(default_value, errflg, errmsg) if (errflg /= 0) then write(6, '(a,i0,2a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & "to grab default for cld_ice index = ", index_ice, trim(errmsg) - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if if (errflg == 0) then if (default_value /= 0.0_kind_phys) then write(6, *) "ERROR: cloud ice mass_mixing_ratio default is ", default_value, & " but should be 0.0" - errflg_final = -1 !Notify test script that a failure occurred + errflg_final = -1 ! Notify test script that a failure occurred end if else - !Reset error flag to continue testing other properties: + ! Reset error flag to continue testing other properties: errflg = 0 end if - !++++++++++++++++++++++++++++++++++ + ! ++++++++++++++++++++++++++++++++++ - !Set error flag to the "final" value, because any error - !above will likely result in a large number of failures - !below: + ! Set error flag to the "final" value, because any error + ! above will likely result in a large number of failures + ! below: errflg = errflg_final ! Use the suite information to setup the run @@ -975,12 +975,12 @@ subroutine test_host(retval, test_suites) end if end if - !Make sure "final" flag is non-zero if "errflg" is: + ! Make sure "final" flag is non-zero if "errflg" is: if (errflg /= 0) then - errflg_final = -1 !Notify test script that a failure occured + errflg_final = -1 ! Notify test script that a failure occured end if - !Set return value to False if any errors were found: + ! Set return value to False if any errors were found: retval = errflg_final == 0 end subroutine test_host diff --git a/test/capgen_test/test_reports.py b/test/capgen_test/test_reports.py index 8682a7b0..45611e01 100644 --- a/test/capgen_test/test_reports.py +++ b/test/capgen_test/test_reports.py @@ -118,14 +118,14 @@ def fields_string(field_type, field_list, sep): return fmsg def check_datatable(database, report_type, check_list, - sep=',', excl_prot=False): + sep=',', exclude_protected=False): """Run a database report and check the return string. If an error is found, print an error message. Return the number of errors""" if sep is None: sep = ',' # end if - test_str = datatable_report(database, report_type, sep, excl_prot=excl_prot) + test_str = datatable_report(database, report_type, sep, exclude_protected=exclude_protected) test_list = [x for x in test_str.split(sep) if x] missing = list() unexpected = list() @@ -182,13 +182,13 @@ def check_datatable(database, report_type, check_list, _REQUIRED_VARS_TEMP + _PROT_VARS_TEMP) NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("required_variables", value="temp_suite"), - _REQUIRED_VARS_TEMP, excl_prot=True) + _REQUIRED_VARS_TEMP, exclude_protected=True) NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("input_variables", value="temp_suite"), _INPUT_VARS_TEMP + _PROT_VARS_TEMP) NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("input_variables", value="temp_suite"), - _INPUT_VARS_TEMP, excl_prot=True) + _INPUT_VARS_TEMP, exclude_protected=True) NUM_ERRORS += check_datatable(_DATABASE, DatatableReport("output_variables", value="temp_suite"), _OUTPUT_VARS_TEMP) diff --git a/test/var_compatibility_test/test_reports.py b/test/var_compatibility_test/test_reports.py index aaf08953..f7c37897 100755 --- a/test/var_compatibility_test/test_reports.py +++ b/test/var_compatibility_test/test_reports.py @@ -101,14 +101,14 @@ def fields_string(field_type, field_list, sep): return fmsg def check_datatable(database, report_type, check_list, - sep=',', excl_prot=False): + sep=',', exclude_protected=False): """Run a database report and check the return string. If an error is found, print an error message. Return the number of errors""" if sep is None: sep = ',' # end if - test_str = datatable_report(database, report_type, sep, excl_prot=excl_prot) + test_str = datatable_report(database, report_type, sep, exclude_protected=exclude_protected) test_list = [x for x in test_str.split(sep) if x] missing = list() unexpected = list() From 15a16ea8f064d8cee1421dce0ca1b21c970f1f2a Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Thu, 16 May 2024 15:11:19 -0600 Subject: [PATCH 156/159] only run pytests on test/ directory in python.yaml workflow --- .github/workflows/python.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index 16a91253..c57461cb 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -25,7 +25,7 @@ jobs: - name: Test with pytest run: | export PYTHONPATH=$(pwd)/scripts:$(pwd)/scripts/parse_tools - pytest -v + pytest -v test/ doctest: runs-on: ubuntu-latest From 6e165e5c483ece98e3dfc407f1e4d67d1a5f5eaf Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Wed, 29 May 2024 16:50:15 -0600 Subject: [PATCH 157/159] address review comments --- scripts/ccpp_capgen.py | 5 ----- scripts/constituents.py | 2 +- src/ccpp_constituent_prop_mod.F90 | 11 +++++------ test/advection_test/test_host.F90 | 15 +++++++++++++++ .../sample_scheme_files/duplicate_dyn_const.F90 | 2 +- .../sample_scheme_files/dyn_const_not_present.F90 | 2 +- test/var_compatibility_test/run_test | 2 +- 7 files changed, 24 insertions(+), 15 deletions(-) diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index ca1d17b3..b16998f3 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -673,11 +673,6 @@ def capgen(run_env, return_db=False): # end if # First up, handle the host files host_model = parse_host_model_files(host_files, host_name, run_env) - # We always need to parse the ccpp_constituent_prop_ptr_t DDT - const_prop_mod = os.path.join(src_dir, "ccpp_constituent_prop_mod.meta") - if const_prop_mod not in scheme_files: - scheme_files = [const_prop_mod] + scheme_files - # end if # Next, parse the scheme files # We always need to parse the ccpp_constituent_prop_ptr_t DDT const_prop_mod = os.path.join(src_dir, "ccpp_constituent_prop_mod.meta") diff --git a/scripts/constituents.py b/scripts/constituents.py index 18bcbbe3..4fc78993 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -500,7 +500,7 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.write(f"integer{spc} :: num_dyn_consts", 2) cap.write(f"integer{spc} :: index, index_start", 2) cap.write(f"integer{spc} :: field_ind", 2) - cap.write(f"type({CONST_PROP_TYPE}), pointer :: const_prop", 2) + cap.write(f"type({CONST_PROP_TYPE}), pointer :: const_prop => NULL()", 2) # Declare dynamic constituent properties variables for idx, scheme in enumerate(sorted(dyn_const_dict)): cap.comment(f"dynamic constituent props variable for {scheme}", 2) diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index a60e3c95..d8da7de8 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -1163,9 +1163,8 @@ subroutine ccp_model_const_add_metadata(this, field_data, errcode, errmsg) ! Local variables character(len=errmsg_len) :: error character(len=*), parameter :: subname = 'ccp_model_const_add_metadata' - type(ccpp_constituent_properties_t), pointer :: cprop + type(ccpp_constituent_properties_t), pointer :: cprop => NULL() character(len=stdname_len) :: standard_name - integer :: cindex logical :: match if (this%okay_to_add(errcode=errcode, errmsg=errmsg, & @@ -1176,13 +1175,12 @@ subroutine ccp_model_const_add_metadata(this, field_data, errcode, errmsg) cprop => this%find_const(standard_name) if (associated(cprop)) then ! Standard name already in table, let's see if the existing constituent is the same - cindex = cprop%const_index() match = cprop%is_match(field_data) if (match) then ! Existing constituent is a match - no need to throw an error, just don't add return else - ! Existing constituent is not a match - this is ane rror + ! Existing constituent is not a match - this is an error call append_errvars(1, "ERROR: Trying to add constituent " // & trim(standard_name) // " but an incompatible" // & " constituent with this name already exists", subname, & @@ -1261,6 +1259,7 @@ function ccp_model_const_find_const(this, standard_name, errcode, errmsg) & character(len=*), parameter :: subname = 'ccp_model_const_find_const' nullify(cprop) + hval => this%hash_table%table_value(standard_name, errmsg=error) if (len_trim(error) > 0) then call append_errvars(1, trim(error), subname, & @@ -1630,7 +1629,7 @@ subroutine ccp_model_const_index(this, index, standard_name, errcode, errmsg) integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg ! Local variables - type(ccpp_constituent_properties_t), pointer :: cprop + type(ccpp_constituent_properties_t), pointer :: cprop => NULL() character(len=*), parameter :: subname = "ccp_model_const_index" if (this%const_props_locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then @@ -1660,7 +1659,7 @@ subroutine ccp_model_const_metadata(this, standard_name, const_data, & integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg ! Local variables - type(ccpp_constituent_properties_t), pointer :: cprop + type(ccpp_constituent_properties_t), pointer :: cprop => NULL() character(len=*), parameter :: subname = "ccp_model_const_metadata" if (this%const_props_locked(errcode=errcode, errmsg=errmsg, warn_func=subname)) then diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index d2fa8b97..fe1f9e0b 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -592,6 +592,21 @@ subroutine test_host(retval, test_suites) ! Reset error flag to continue testing other properties: errflg = 0 end if + call const_props(index_dyn2)%is_moist(const_log, errflg, errmsg) + if (errflg /= 0) then + write(6, '(a,i0,a,a,i0,/,a)') "ERROR: Error, ", errflg, " trying ", & + "to get moist prop for dyn_const2 index = ", index_dyn2, trim(errmsg) + errflg_final = -1 ! Notify test script that a failure occurred + end if + if (errflg == 0) then + if (.not. const_log) then + write(6, *) "ERROR: dyn_const2 is not moist but should be" + errflg_final = -1 + end if + else + ! Reset error flag to continue testing other properties: + errflg = 0 + end if ! ------------------- diff --git a/test/unit_tests/sample_scheme_files/duplicate_dyn_const.F90 b/test/unit_tests/sample_scheme_files/duplicate_dyn_const.F90 index 1b64c17e..2eb43a89 100644 --- a/test/unit_tests/sample_scheme_files/duplicate_dyn_const.F90 +++ b/test/unit_tests/sample_scheme_files/duplicate_dyn_const.F90 @@ -1,4 +1,4 @@ -! Test parameterization with no vertical level +! Test parameterization with a dynamic constituents routine with the same name as another parameterization's ! MODULE duplicate_dyn_const diff --git a/test/unit_tests/sample_scheme_files/dyn_const_not_present.F90 b/test/unit_tests/sample_scheme_files/dyn_const_not_present.F90 index 2dcebe31..615f0467 100644 --- a/test/unit_tests/sample_scheme_files/dyn_const_not_present.F90 +++ b/test/unit_tests/sample_scheme_files/dyn_const_not_present.F90 @@ -1,4 +1,4 @@ -! Test parameterization with no vertical level +! Test parameterization that is missing the specified dynamic constituents routine ! MODULE temp_adjust diff --git a/test/var_compatibility_test/run_test b/test/var_compatibility_test/run_test index 4d6d0ad8..a5edac37 100755 --- a/test/var_compatibility_test/run_test +++ b/test/var_compatibility_test/run_test @@ -6,7 +6,7 @@ scriptdir="$( cd $( dirname $0 ); pwd -P )" ## ## Option default values ## -defdir="va_build" +defdir="ct_build" build_dir="${currdir}/${defdir}" cleanup="PASS" # Other supported options are ALWAYS and NEVER verbosity=0 From 8297a6b1b63bdd7c764d0eb50ebfe780ff8544f2 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Thu, 6 Jun 2024 15:23:21 -0600 Subject: [PATCH 158/159] fix incorrect subname --- src/ccpp_constituent_prop_mod.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index d8da7de8..3370bff4 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -2109,7 +2109,7 @@ subroutine ccpt_is_water_species(this, val_out, errcode, errmsg) integer, optional, intent(out) :: errcode character(len=*), optional, intent(out) :: errmsg ! Local variable - character(len=*), parameter :: subname = 'ccpt_is_thermo_active' + character(len=*), parameter :: subname = 'ccpt_is_water_species' if (associated(this%prop)) then call this%prop%is_water_species(val_out, errcode, errmsg) From 97c3cc5e0e2bfbe67253609af97236636a8fc4c7 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Fri, 7 Jun 2024 09:54:35 -0600 Subject: [PATCH 159/159] fix indexing --- scripts/constituents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/constituents.py b/scripts/constituents.py index 4fc78993..84f19b20 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -544,7 +544,7 @@ def write_host_routines(cap, host, reg_funcname, init_funcname, num_const_funcna cap.write(f"do index = 1, size(dyn_const_prop_{idx}, 1)", 2) cap.write(f"{dyn_const_name}(index + index_start) = dyn_const_prop_{idx}(index)", 3) cap.write("end do", 2) - cap.write(f"index_start = size(dyn_const_prop_{idx}, 1)", 2) + cap.write(f"index_start = index_start + size(dyn_const_prop_{idx}, 1)", 2) cap.write(f"deallocate(dyn_const_prop_{idx})", 2) # end for # end if