#!/usr/bin/env python3 """ Class and supporting code to hold all information on CCPP constituent variables. A constituent variable is defined and maintained by the CCPP Framework instead of the host model. The ConstituentVarDict class contains methods to generate the necessary code 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 ######################################################################## CONST_DDT_NAME = "ccpp_model_constituents_t" CONST_DDT_MOD = "ccpp_constituent_prop_mod" CONST_PROP_TYPE = "ccpp_constituent_properties_t" ######################################################################## class ConstituentVarDict(VarDictionary): """A class to hold all the constituent variables for a CCPP Suite. Also contains methods to generate the necessary code for runtime allocation and support for these variables. """ __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): """Create a specialized VarDictionary for constituents. The main difference is functionality to allocate and support these variables with special functions for the host model. The main reason for a separate dictionary is that these are not proper Suite variables but will belong to the host model at run time. The feature of the VarDictionary class is required 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) def find_variable(self, standard_name=None, source_var=None, any_scope=True, clone=None, search_call_list=False, loop_subst=False): """Attempt to return the variable matching . if is None, the standard name from is used. It is an error to pass both and if the standard name of is not the same as . If is True, search parent scopes if not in current scope. Note: Unlike the version of this method, the case for CCPP_CONSTANT_VARS is not handled -- it should have been handled by a lower level. If the variable is not found but is a constituent variable type, create the variable in this dictionary Note that although the argument is accepted for consistency, cloning is not handled at this level. If the variable is not found and is not a constituent variable, return None. """ if standard_name is None: if source_var is None: emsg = "One of or must be passed." raise ParseInternalError(emsg) # end if standard_name = source_var.get_prop_value('standard_name') elif source_var is not None: stest = source_var.get_prop_value('standard_name') if stest != standard_name: emsg = ("Only one of or may " + "be passed.") raise ParseInternalError(emsg) # end if # end if if standard_name in self: var = self[standard_name] elif any_scope and (self.parent is not None): srch_clist = search_call_list var = self.parent.find_variable(standard_name=standard_name, source_var=source_var, any_scope=any_scope, clone=None, search_call_list=srch_clist, loop_subst=loop_subst) else: var = None # end if if (var is None) and source_var and source_var.is_constituent(): # If we did not find the variable and it is a constituent type, # add a clone of to our dictionary. # First, maybe do a loop substitution dims = source_var.get_dimensions() newdims = list() for dim in dims: dstdnames = dim.split(':') new_dnames = list() for dstdname in dstdnames: if dstdname == 'horizontal_loop_extent': new_dnames.append('horizontal_dimension') elif dstdname == 'horizontal_loop_end': new_dnames.append('horizontal_dimension') elif dstdname == 'horizontal_loop_begin': new_dnames.append('ccpp_constant_one') else: new_dnames.append(dstdname) # end if # end for newdims.append(':'.join(new_dnames)) # end for var = source_var.clone({'dimensions' : newdims}, remove_intent=True, source_type=self.__constituent_type) self.add_variable(var, self.__run_env) return var @staticmethod def __init_err_var(evar, outfile, indent): """If is a known error variable, generate the code to initialize it as an output variable. If unknown, simply ignore. """ stdname = evar.get_prop_value('standard_name') if stdname == 'ccpp_error_message': lname = evar.get_prop_value('local_name') outfile.write("{} = ''".format(lname), indent) elif stdname == 'ccpp_error_code': lname = evar.get_prop_value('local_name') outfile.write("{} = 0".format(lname), indent) # end if (no else, just ignore) def declare_public_interfaces(self, outfile, indent): """Declare the public constituent interfaces. Declarations are written to at 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("! Return the name of a constituent", indent) outfile.write("public :: {}".format(self.const_name_subname()), indent) outfile.write("! Copy the data for a constituent", indent) outfile.write("public :: {}".format(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(self.constituent_prop_type_name(), self.constituent_prop_array_name()), indent) # end if stmt = "logical, private :: {} = .false." outfile.write(stmt.format(self.constituent_prop_init_name()), indent) outfile.write("! Private interface for constituents", indent) stmt = "private :: {}" outfile.write(stmt.format(self.constituent_prop_init_consts()), indent) @classmethod def __errcode_names(cls, err_vars): """Return the ( ) where is the local name for ccpp_error_code in and is the local name for ccpp_error_message in . if either variable is not found in , return None.""" errcode = None errmsg = None for evar in err_vars: stdname = evar.get_prop_value('standard_name') if stdname == 'ccpp_error_code': errcode = evar.get_prop_value('local_name') elif stdname == 'ccpp_error_message': errmsg = evar.get_prop_value('local_name') else: emsg = "Bad errcode variable, '{}'" raise ParseInternalError(emsg.format(stdname)) # end if # end for if (not errcode) or (not errmsg): raise ParseInternalError("Unsupported error scheme") # end if return errcode, errmsg @staticmethod def __errcode_callstr(errcode_name, errmsg_name, suite): """Create and return the error code calling string for . is the calling routine's ccpp_error_code variable name. is the calling routine's ccpp_error_message variable name. """ 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) return errvar_str def _write_init_check(self, outfile, indent, suite_name, err_vars, use_errcode): """Write a check to to make sure the constituent properties are initialized. Write code to initialize the error variables and/or set them to error values.""" 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) 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) 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("end if", indent+1) # end if (no else until an alternative error mechanism supported) def _write_index_check(self, outfile, indent, suite_name, err_vars, use_errcode): """Write a check to to make sure the "index" input is in bounds. Write code to set error variables if index is out of bounds.""" if use_errcode: 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,') " 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("end if", indent+1) else: outfile.write("{} = 1".format(errcode), indent+1) stmt = "write({}, '(a,i0,a)') 'ERROR: {}, " stmt += "has no constituents'" outfile.write(stmt.format(errmsg, self.name), indent+1) # end if else: raise ParseInternalError("Alternative to errcode not implemented") # end if def write_constituent_routines(self, outfile, indent, suite_name, err_vars): """Write the subroutine that, when called allocates and defines the suite-cap module variable describing the constituent species for this suite. Code is written to starting at indent, .""" # Format our error variables errvar_names = {x.get_prop_value('standard_name') : x.get_prop_value('local_name') for x in err_vars} errcode_snames = ('ccpp_error_code', 'ccpp_error_message') 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 "" 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]) # Allocate and define constituents stmt = "subroutine {}({})".format(self.constituent_prop_init_consts(), errvar_alist) outfile.write(stmt, indent) outfile.write("! Allocate and fill the constituent property array", indent + 1) outfile.write("! for this suite", indent+1) outfile.write("! Dummy arguments", indent+1) for evar in err_vars: evar.write_def(outfile, indent+1, self, dummy=True) # end for 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) outfile.write("index = 0", indent+1) # end if for std_name, var in self.items(): outfile.write("index = index + 1", indent+1) long_name = var.get_prop_value('long_name') dims = var.get_dim_stdnames() if 'vertical_layer_dimension' in dims: vertical_dim = 'vertical_layer_dimension' elif 'vertical_interface_dimension' in dims: vertical_dim = 'vertical_interface_dimension' else: vertical_dim = '' # end if advect_str = self.TF_string(var.get_prop_value('advected')) 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) # 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()) outfile.write(stmt, indent) outfile.write("", 0) outfile.write("\n! {}\n".format("="*72), 1) # Return number of constituents fname = self.num_consts_funcname() outfile.write("integer function {}({})".format(fname, errvar_alist), indent) outfile.write("! Return the number of constituents for this suite", indent+1) outfile.write("! Dummy arguments", indent+1) for evar in err_vars: evar.write_def(outfile, indent+1, self, dummy=True) # end for for evar in err_vars: self.__init_err_var(evar, outfile, indent+1) # 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) 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) # 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) outfile.write("! Return the name of constituent, ", indent+1) outfile.write("! Dummy arguments", indent+1) outfile.write("integer, intent(in) :: index", indent+1) outfile.write("character(len=*), intent(out) :: name_out", indent+1) for evar in err_vars: evar.write_def(outfile, indent+1, self, dummy=True) # end for self._write_init_check(outfile, indent, suite_name, err_vars, use_errcode) self._write_index_check(outfile, indent, suite_name, err_vars, use_errcode) if self: stmt = "call {}(index)%standard_name(name_out{})" outfile.write(stmt.format(self.constituent_prop_array_name(), errvar_call2), indent+1) # end if outfile.write("end subroutine {}".format(self.const_name_subname()), indent) outfile.write("\n! {}\n".format("="*72), 1) # Copy a consitituent's properties stmt = "subroutine {}(index, cnst_out{})" fname = self.copy_const_subname() outfile.write(stmt.format(fname, errvar_alist2), 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) stmt = "type({}), intent(out) :: cnst_out" outfile.write(stmt.format(self.constituent_prop_type_name()), indent+1) for evar in err_vars: evar.write_def(outfile, indent+1, self, dummy=True) # end for self._write_init_check(outfile, indent, suite_name, err_vars, use_errcode) 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) # end if outfile.write("end subroutine {}".format(fname), indent) def constituent_module_name(self): """Return the name of host model constituent module""" 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)) raise ParseInternalError(emsg) # end if return self.parent.parent.constituent_module 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) 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) 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) @staticmethod def constituent_index_name(standard_name): """Return the index name associated with """ return "index_of_{}".format(standard_name) @staticmethod def write_constituent_use_statements(cap, suite_list, indent): """Write the suite use statements needed by the constituent initialization routines.""" maxmod = max([len(s.module) for s in suite_list]) smod = len(CONST_DDT_MOD) maxmod = max(maxmod, smod) use_str = "use {},{} only: {}" spc = ' '*(maxmod - smod) cap.write(use_str.format(CONST_DDT_MOD, spc, CONST_PROP_TYPE), indent) cap.write('! Suite constituent interfaces', indent) for suite in suite_list: const_dict = suite.constituent_dictionary() smod = suite.module spc = ' '*(maxmod - len(smod)) fname = const_dict.num_consts_funcname() cap.write(use_str.format(smod, spc, fname), indent) fname = const_dict.const_name_subname() cap.write(use_str.format(smod, spc, fname), indent) fname = const_dict.copy_const_subname() cap.write(use_str.format(smod, spc, fname), indent) # end for @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, suite_list, err_vars): """Write out the host model routine which will instantiate constituent fields for all the constituents in . is a list of the host model's error variables. Also write out the following routines: : Number of constituents : Collect constituent fields for host : Update constituent fields from host Output is written to . """ # XXgoldyXX: v need to generalize host model error var type support use_errcode = [x.get_prop_value('standard_name') in ('ccpp_error_code' 'ccpp_error_message') for x in err_vars] if not use_errcode: emsg = "Error object not supported for {}" 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) # 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) 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) for evar in err_vars: evar.write_def(cap, 2, host, dummy=True, add_intent="out") # end for cap.write("! 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("", 0) cap.write("{} = 0".format(herrcode), 2) cap.write("num_consts = 0", 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) errvar_str = ConstituentVarDict.__errcode_callstr(herrcode, herrmsg, suite) cap.write("num_suite_consts = {}({})".format(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) stmt = "call {}%initialize_table(num_consts)" cap.write(stmt.format(const_obj_name), 3) cap.write("end if", 2) 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) const_dict = suite.constituent_dictionary() funcname = const_dict.num_consts_funcname() cap.write("num_suite_consts = {}({})".format(funcname, errvar_str), 3) cap.write("end if", 2) funcname = const_dict.copy_const_subname() cap.write("do index = 1, num_suite_consts", 2) cap.write("allocate(const_prop, stat={})".format(herrcode), 3) cap.write("if ({} /= 0) then".format(herrcode), 3) cap.write('{} = "ERROR allocating const_prop"'.format(herrmsg), 4) cap.write("end if", 3) cap.write("if ({} == 0) then".format(herrcode), 3) 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) 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) # end for cap.write("if ({} == 0) then".format(herrcode), 2) stmt = "call {}%lock_table(ncols, num_layers, num_interfaces, {})" cap.write(stmt.format(const_obj_name, obj_err_callstr), 3) cap.write("end if", 2) cap.write("! Set the index for each active constituent", 2) cap.write("do index = 1, SIZE({})".format(const_indices_name), 2) stmt = "field_ind = {}%field_index({}(index), {})" cap.write(stmt.format(const_obj_name, const_names_name, obj_err_callstr), 3) cap.write("if (field_ind > 0) then", 3) cap.write("{}(index) = field_ind".format(const_indices_name), 4) cap.write("else", 3) cap.write("{} = 1".format(herrcode), 4) stmt = "{} = 'No field index for '//trim({}(index))" cap.write(stmt.format(herrmsg, const_names_name), 4) cap.write("end if", 3) cap.write("if ({} /= 0) then".format(herrcode), 3) cap.write("exit", 4) cap.write("end if", 3) cap.write("end do", 2) cap.write("end {}".format(substmt), 1) # Next, write num_consts routine substmt = "function {}".format(num_const_funcname) cap.write("", 0) cap.write("integer {}({})".format(substmt, err_dummy_str), 1) cap.write("! Return the number of constituent fields for this run", 2) cap.write("", 0) cap.write("! Dummy arguments", 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) cap.write("end {}".format(substmt), 1) # Next, 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.write("", 0) cap.write("! Dummy arguments", 2) cap.write("real(kind_phys), intent(out) :: const_array(:,:,:)", 2) for evar in err_vars: evar.write_def(cap, 2, host, dummy=True, add_intent="out") # end for cap.write("", 0) cap.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 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.write("", 0) cap.write("! 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) cap.write("end {}".format(substmt), 1) @staticmethod def constitutent_source_type(): """Return the source type for constituent species""" return ConstituentVarDict.__constituent_type @staticmethod def constituent_prop_array_name(): """Return the name of the constituent properties array for this suite""" return ConstituentVarDict.__const_prop_array_name @staticmethod def constituent_prop_init_name(): """Return the name of the array initialized flag for this suite""" return ConstituentVarDict.__const_prop_init_name @staticmethod def constituent_prop_init_consts(): """Return the name of the routine to initialize the constituent 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) @staticmethod def TF_string(tf_val): """Return a string of the Fortran equivalent of """ if tf_val: tf_str = ".true." else: tf_str = ".false." # end if return tf_str