#!/usr/bin/env python # import collections import copy import getopt import logging import os import sys import types import xml.etree.ElementTree as ET from common import encode_container from common import CCPP_ERROR_FLAG_VARIABLE, CCPP_ERROR_MSG_VARIABLE, CCPP_LOOP_COUNTER from common import CCPP_TYPE, STANDARD_VARIABLE_TYPES, STANDARD_CHARACTER_TYPE from common import CCPP_STATIC_API_MODULE, CCPP_STATIC_SUBROUTINE_NAME from mkcap import Var ############################################################################### #STANDARD_VARIABLE_TYPES = [ 'character', 'integer', 'logical', 'real' ] #CCPP_ERROR_FLAG_VARIABLE = 'error_flag' CCPP_STAGES = [ 'init', 'run', 'finalize' ] # Maximum number of dimensions of an array allowed by the Fortran 2008 standard FORTRAN_ARRAY_MAX_DIMS = 15 ############################################################################### def extract_parents_and_indices_from_local_name(local_name): """Break apart local_name into the different components (members of DDTs) to determine all variables that are required; this must work for complex constructs such as Atm(mytile)%q(:,:,:,Atm2(mytile2)%graupel), with result parent = 'Atm', indices = [mytile, Atm2, mytile2]""" # First, extract all variables/indices in parentheses (used for subsetting) indices = [] while '(' in local_name: for i in xrange(len(local_name)): if local_name[i] == '(': last_open = i elif local_name[i] == ')': last_closed = i break index_set = local_name[last_open+1:last_closed].split(',') for index_group in index_set: for index in index_group.split(':'): if index: if '%' in index: indices.append(index[:index.find('%')]) else: # Skip hard-coded integers that are not variables try: int(index) except ValueError: indices.append(index) # Remove this innermost index group (...) from local_name local_name = local_name.replace(local_name[last_open:last_closed+1], '') # Remove duplicates from indices indices = list(set(indices)) # Derive parent of actual variable (now that all subsets have been processed) if '%' in local_name: parent = local_name[:local_name.find('%')] else: parent = local_name return (parent, indices) def create_argument_list_wrapped(arguments): """Create a wrapped argument list, remove trailing ',' """ argument_list = '' length = 0 for argument in arguments: argument_list += argument + ',' length += len(argument)+1 # Split args so that lines don't exceed 260 characters (for PGI) if length > 70 and not argument == arguments[-1]: argument_list += ' &\n ' length = 0 if argument_list: argument_list = argument_list.rstrip(',') return argument_list def create_argument_list_wrapped_explicit(arguments, additional_vars_following = False): """Create a wrapped argument list with explicit arguments x=y. If no additional variables are added (additional_vars_following == False), remove trailing ',' """ argument_list = '' length = 0 for argument in arguments: argument_list += argument + '=' + argument + ',' length += 2*len(argument)+2 # Split args so that lines don't exceed 260 characters (for PGI) if length > 70 and not argument == arguments[-1]: argument_list += ' &\n ' length = 0 if argument_list and not additional_vars_following: argument_list = argument_list.rstrip(',') return argument_list def create_arguments_module_use_var_defs(variable_dictionary, metadata_define, tmpvars = None): """Given a dictionary of standard names and variables, and a metadata dictionary with the variable definitions by the host model, create a list of arguments (local names), module use statements (for derived data types and non-standard kinds), and the variable definition statements.""" arguments = [] module_use = [] var_defs = [] local_kind_and_type_vars = [] for standard_name in variable_dictionary.keys(): # Add variable local name and variable definitions arguments.append(variable_dictionary[standard_name].local_name) var_defs.append(variable_dictionary[standard_name].print_def_intent()) # Add special kind variables and derived data type definitions to module use statements if variable_dictionary[standard_name].type in STANDARD_VARIABLE_TYPES and variable_dictionary[standard_name].kind \ and not variable_dictionary[standard_name].type == STANDARD_CHARACTER_TYPE: kind_var_standard_name = variable_dictionary[standard_name].kind if not kind_var_standard_name in local_kind_and_type_vars: if not kind_var_standard_name in metadata_define.keys(): raise Exception("Kind {kind} not defined by host model".format(kind=kind_var_standard_name)) kind_var = metadata_define[kind_var_standard_name][0] module_use.append(kind_var.print_module_use()) local_kind_and_type_vars.append(kind_var_standard_name) elif not variable_dictionary[standard_name].type in STANDARD_VARIABLE_TYPES: type_var_standard_name = variable_dictionary[standard_name].type if not type_var_standard_name in local_kind_and_type_vars: if not type_var_standard_name in metadata_define.keys(): raise Exception("Type {type} not defined by host model".format(type=type_var_standard_name)) type_var = metadata_define[type_var_standard_name][0] module_use.append(type_var.print_module_use()) local_kind_and_type_vars.append(type_var_standard_name) # Add any local variables (required for unit conversions, array transformations, ...) if tmpvars: var_defs.append('! Local variables for unit conversions, array transformations, ...') for tmpvar in tmpvars: var_defs.append(tmpvar.print_def_local()) return (arguments, module_use, var_defs) class API(object): header=''' ! ! This work (Common Community Physics Package), 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 API for the CCPP static build !! ! module {module} {module_use} implicit none private public :: {subroutines} contains ''' sub = ''' subroutine {subroutine}({ccpp_var_name}, suite_name, group_name, ierr) use ccpp_types, only : ccpp_t implicit none type(ccpp_t), intent(inout) :: {ccpp_var_name} character(len=*), intent(in) :: suite_name character(len=*), optional, intent(in) :: group_name integer, intent(out) :: ierr ierr = 0 {suite_switch} else write({ccpp_var_name}%errmsg,'(*(a))') 'Invalid suite ' // trim(suite_name) ierr = 1 end if {ccpp_var_name}%errflg = ierr end subroutine {subroutine} ''' footer = ''' end module {module} ''' def __init__(self, **kwargs): self._filename = CCPP_STATIC_API_MODULE + '.F90' self._module = CCPP_STATIC_API_MODULE self._subroutines = None self._suites = [] self._directory = '.' for key, value in kwargs.items(): setattr(self, "_"+key, value) @property def filename(self): '''Get the filename to write API to.''' return self._filename @filename.setter def filename(self, value): self._filename = value @property def directory(self): '''Get the directory to write API to.''' return self._directory @directory.setter def directory(self, value): self._directory = value @property def module(self): '''Get the module name of the API.''' return self._module @property def subroutines(self): '''Get the subroutines names of the API to.''' return self._subroutines def write(self): """Write API for static build""" if not self._suites: raise Exception("No suites specified for generating API") suites = self._suites # Module use statements for suite and group caps module_use = '' for suite in suites: for subroutine in suite.subroutines: module_use += ' use {module}, only: {subroutine}\n'.format(module=suite.module, subroutine=subroutine) for group in suite.groups: for subroutine in group.subroutines: module_use += ' use {module}, only: {subroutine}\n'.format(module=group.module, subroutine=subroutine) # Add all variables required to module use statements. This is for the API only, # because the static API imports all variables from modules instead of receiving them # via the argument list. Special handling for a single variable of type CCPP_TYPE (ccpp_t), # which comes in as a scalar for any potential block/thread via the argument list. ccpp_var = None parent_standard_names = [] for ccpp_stage in CCPP_STAGES: for suite in suites: for parent_standard_name in suite.parents[ccpp_stage].keys(): if not parent_standard_name in parent_standard_names: parent_var = suite.parents[ccpp_stage][parent_standard_name] # Identify which variable is of type CCPP_TYPE (need local name) if parent_var.type == CCPP_TYPE: if ccpp_var and not ccpp_var.local_name==parent_var.local_name: raise Exception('There can be only one variable of type {0}, found {1} and {2}'.format( CCPP_TYPE, ccpp_var.local_name, parent_var.local_name)) ccpp_var = parent_var continue module_use += ' {0}\n'.format(parent_var.print_module_use()) parent_standard_names.append(parent_standard_name) if not ccpp_var: raise Exception('No variable of type {0} found - need a scalar instance.'.format(CCPP_TYPE)) elif not ccpp_var.rank == '': raise Exception('CCPP variable {0} of type {1} must be a scalar.'.format(ccpp_var.local_name, CCPP_TYPE)) del parent_standard_names # Create a subroutine for each stage self._subroutines=[] subs = '' for ccpp_stage in CCPP_STAGES: suite_switch = '' for suite in suites: # Calls to groups of schemes for this stage group_calls = '' for group in suite.groups: # The and groups require special treatment, # since they can only be run in the respective stage (init/finalize) if (group.init and not ccpp_stage == 'init') or \ (group.finalize and not ccpp_stage == 'finalize'): continue if not group_calls: clause = 'if' else: clause = 'else if' argument_list_group = create_argument_list_wrapped_explicit(group.arguments[ccpp_stage]) group_calls += ''' {clause} (trim(group_name)=="{group_name}") then ierr = {suite_name}_{group_name}_{stage}_cap({arguments})'''.format(clause=clause, suite_name=group.suite, group_name=group.name, stage=ccpp_stage, arguments=argument_list_group) group_calls += ''' else write({ccpp_var_name}%errmsg, '(*(a))') 'Group ' // trim(group_name) // ' not found' ierr = 1 end if '''.format(ccpp_var_name=ccpp_var.local_name, group_name=group.name) # Call to entire suite for this stage # Create argument list for calling the full suite argument_list_suite = create_argument_list_wrapped_explicit(suite.arguments[ccpp_stage]) suite_call = ''' ierr = {suite_name}_{stage}_cap({arguments}) '''.format(suite_name=suite.name, stage=ccpp_stage, arguments=argument_list_suite) # Add call to all groups of this suite and to the entire suite if not suite_switch: clause = 'if' else: clause = 'else if' suite_switch += ''' {clause} (trim(suite_name)=="{suite_name}") then if (present(group_name)) then {group_calls} else {suite_call} end if '''.format(clause=clause, suite_name=suite.name, group_calls=group_calls, suite_call=suite_call) subroutine = CCPP_STATIC_SUBROUTINE_NAME.format(stage=ccpp_stage) self._subroutines.append(subroutine) subs += API.sub.format(subroutine=subroutine, ccpp_var_name=ccpp_var.local_name, suite_switch=suite_switch) # Write output to stdout or file if (self.filename is not sys.stdout): filepath = os.path.split(self.filename)[0] if filepath and not os.path.isdir(filepath): os.makedirs(filepath) f = open(self.filename, 'w') else: f = sys.stdout f.write(API.header.format(module=self._module, module_use=module_use, subroutines=','.join(self._subroutines))) f.write(subs) f.write(Suite.footer.format(module=self._module)) if (f is not sys.stdout): f.close() return def write_sourcefile(self, source_filename): success = True filepath = os.path.split(source_filename)[0] if filepath and not os.path.isdir(filepath): os.makedirs(filepath) f = open(source_filename, 'w') contents = """# The CCPP static API is defined here. # # This file is auto-generated using ccpp_prebuild.py # at compile time, do not edit manually. # export CCPP_STATIC_API=\"{filename}\" """.format(filename=os.path.abspath(os.path.join(self.directory,self.filename))) f.write(contents) f.close() return success class Suite(object): header=''' ! ! This work (Common Community Physics Package), 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 cap module for the CCPP suite !! ! module {module} {module_use} implicit none private public :: {subroutines} contains ''' sub = ''' function {subroutine}({arguments}) result(ierr) {module_use} implicit none integer :: ierr {var_defs} ierr = 0 {body} end function {subroutine} ''' footer = ''' end module {module} ''' def __init__(self, **kwargs): self._name = None self._sdf_name = None self._all_schemes_called = None self._all_subroutines_called = None self._caps = None self._module = None self._subroutines = None self._parents = { ccpp_stage : {} for ccpp_stage in CCPP_STAGES } self._arguments = { ccpp_stage : [] for ccpp_stage in CCPP_STAGES } for key, value in kwargs.items(): setattr(self, "_"+key, value) @property def name(self): '''Get the name of the suite.''' return self._name @property def sdf_name(self): '''Get the name of the suite definition file.''' return self._sdf_name @sdf_name.setter def sdf_name(self, value): self._sdf_name = value def parse(self): '''Parse the suite definition file.''' success = True if not os.path.exists(self._sdf_name): logging.critical("Suite definition file {0} not found.".format(self._sdf_name)) success = False return success tree = ET.parse(self._sdf_name) suite_xml = tree.getroot() self._name = suite_xml.get('name') # Validate name of suite in XML tag against filename; could be moved to common.py if not (os.path.basename(self._sdf_name) == 'suite_{}.xml'.format(self._name)): logging.critical("Invalid suite name {0} in suite definition file {1}.".format( self._name, self._sdf_name)) success = False return success # Flattened lists of all schemes and subroutines in SDF self._all_schemes_called = [] self._all_subroutines_called = [] # Build hierarchical structure as in SDF self._groups = [] for group_xml in suite_xml: subcycles = [] # Add suite-wide init scheme to group 'init', similar for finalize if group_xml.tag.lower() == 'init' or group_xml.tag.lower() == 'finalize': self._all_schemes_called.append(group_xml.text) self._all_subroutines_called.append(group_xml.text + '_' + group_xml.tag.lower()) schemes = [group_xml.text] subcycles.append(Subcycle(loop=1, schemes=schemes)) if group_xml.tag.lower() == 'init': self._groups.append(Group(name=group_xml.tag.lower(), subcycles=subcycles, suite=self._name, init=True)) elif group_xml.tag.lower() == 'finalize': self._groups.append(Group(name=group_xml.tag.lower(), subcycles=subcycles, suite=self._name, finalize=True)) continue # Parse subcycles of all regular groups for subcycle_xml in group_xml: schemes = [] for scheme_xml in subcycle_xml: self._all_schemes_called.append(scheme_xml.text) schemes.append(scheme_xml.text) loop=int(subcycle_xml.get('loop')) for ccpp_stage in CCPP_STAGES: self._all_subroutines_called.append(scheme_xml.text + '_' + ccpp_stage) subcycles.append(Subcycle(loop=loop, schemes=schemes)) self._groups.append(Group(name=group_xml.get('name'), subcycles=subcycles, suite=self._name)) # Remove duplicates from list of all subroutines an schemes self._all_schemes_called = list(set(self._all_schemes_called)) self._all_subroutines_called = list(set(self._all_subroutines_called)) return success def print_debug(self): '''Basic debugging output about the suite.''' print "ALL SUBROUTINES:" print self._all_subroutines_called print "STRUCTURED:" print self._groups for group in self._groups: group.print_debug() @property def all_schemes_called(self): '''Get the list of all schemes.''' return self._all_schemes_called @property def all_subroutines_called(self): '''Get the list of all subroutines.''' return self._all_subroutines_called @property def module(self): '''Get the list of the module generated for this suite.''' return self._module @property def subroutines(self): '''Get the list of all subroutines generated for this suite.''' return self._subroutines @property def caps(self): '''Get the list of all caps.''' return self._caps @property def groups(self): '''Get the list of groups in this suite.''' return self._groups @property def parents(self): '''Get the parent variables for the suite.''' return self._parents @parents.setter def parents(self, value): self._parents = value @property def arguments(self): '''Get the argument list for the suite.''' return self._arguments @arguments.setter def arguments(self, value): self._arguments = value def write(self, metadata_request, metadata_define, arguments): """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 self._module = 'ccpp_{suite_name}_cap'.format(suite_name=self._name) self._filename = '{module_name}.F90'.format(module_name=self._module) # Init self._subroutines = [] # Write group caps and generate module use statements; combine the argument lists # and variable definitions for all groups into a suite argument list. This may # require adjusting the intent of the variables. module_use = '' for group in self._groups: group.write(metadata_request, metadata_define, arguments) for subroutine in group.subroutines: module_use += ' use {m}, only: {s}\n'.format(m=group.module, s=subroutine) for ccpp_stage in CCPP_STAGES: for parent_standard_name in group.parents[ccpp_stage].keys(): if parent_standard_name in self.parents[ccpp_stage]: if self.parents[ccpp_stage][parent_standard_name].intent == 'in' and \ not group.parents[ccpp_stage][parent_standard_name].intent == 'in': self.parents[ccpp_stage][parent_standard_name].intent = 'inout' elif self.parents[ccpp_stage][parent_standard_name].intent == 'out' and \ not group.parents[ccpp_stage][parent_standard_name].intent == 'out': self.parents[ccpp_stage][parent_standard_name].intent = 'inout' else: self.parents[ccpp_stage][parent_standard_name] = copy.deepcopy(group.parents[ccpp_stage][parent_standard_name]) subs = '' for ccpp_stage in CCPP_STAGES: # Create a wrapped argument list for calling the suite, # get module use statements and variable definitions (self.arguments[ccpp_stage], sub_module_use, sub_var_defs) = \ create_arguments_module_use_var_defs(self.parents[ccpp_stage], metadata_define) argument_list_suite = create_argument_list_wrapped(self.arguments[ccpp_stage]) body = '' for group in self._groups: # Groups 'init'/'finalize' are only run in stages 'init'/'finalize' if (group.init and not ccpp_stage == 'init') or \ (group.finalize and not ccpp_stage == 'finalize'): continue # Create a wrapped argument list for calling the group (arguments_group, dummy, dummy) = create_arguments_module_use_var_defs(group.parents[ccpp_stage], metadata_define) argument_list_group = create_argument_list_wrapped_explicit(arguments_group) # Write to body that calls the groups for this stage body += ''' ierr = {suite_name}_{group_name}_{stage}_cap({arguments}) if (ierr/=0) return '''.format(suite_name=self._name, group_name=group.name, stage=ccpp_stage, arguments=argument_list_group) # Add name of subroutine in the suite cap to list of subroutine names subroutine = '{name}_{stage}_cap'.format(name=self._name, stage=ccpp_stage) self._subroutines.append(subroutine) # Add subroutine to output subs += Suite.sub.format(subroutine=subroutine, arguments=argument_list_suite, module_use='\n '.join(sub_module_use), var_defs='\n '.join(sub_var_defs), body=body) # Write cap to stdout or file if (self._filename is not sys.stdout): f = open(self._filename, 'w') else: f = sys.stdout f.write(Suite.header.format(module=self._module, module_use=module_use, subroutines=', &\n '.join(self._subroutines))) f.write(subs) f.write(Suite.footer.format(module=self._module)) if (f is not sys.stdout): f.close() # Create list of all caps generated (for groups and suite) self._caps = [ self._filename ] for group in self._groups: self._caps.append(group.filename) ############################################################################### class Group(object): header=''' ! ! This work (Common Community Physics Package), 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 cap module for the CCPP {group} group !! ! module {module} {module_use} implicit none private public :: {subroutines} logical, save :: initialized = .false. contains ''' sub = ''' function {subroutine}({argument_list}) result(ierr) {module_use} implicit none integer :: ierr {var_defs} ierr = 0 {initialized_test_block} {body} {initialized_set_block} end function {subroutine} ''' footer = ''' end module {module} ''' initialized_test_blocks = { 'init' : ''' if (initialized) return ''', 'run' : ''' if (.not.initialized) then write({target_name_msg},'(*(a))') '{name}_run called before {name}_init' {target_name_flag} = 1 return end if ''', 'finalize' : ''' if (.not.initialized) return ''', } initialized_set_blocks = { 'init' : ''' initialized = .true. ''', 'run' : '', 'finalize' : ''' initialized = .false. ''', } def __init__(self, **kwargs): self._name = '' self._suite = None self._filename = 'sys.stdout' self._init = False self._finalize = False self._module = None self._subroutines = None self._pset = None self._parents = { ccpp_stage : {} for ccpp_stage in CCPP_STAGES } self._arguments = { ccpp_stage : [] for ccpp_stage in CCPP_STAGES } for key, value in kwargs.items(): setattr(self, "_"+key, value) def write(self, metadata_request, metadata_define, arguments): # Create an inverse lookup table of local variable names defined (by the host model) and standard names standard_name_by_local_name_define = {} for standard_name in metadata_define.keys(): standard_name_by_local_name_define[metadata_define[standard_name][0].local_name] = standard_name # First get target names of standard CCPP variables for subcycling and error handling ccpp_loop_counter_target_name = metadata_request[CCPP_LOOP_COUNTER][0].target ccpp_error_flag_target_name = metadata_request[CCPP_ERROR_FLAG_VARIABLE][0].target ccpp_error_msg_target_name = metadata_request[CCPP_ERROR_MSG_VARIABLE][0].target # module_use = '' self._module = 'ccpp_{suite}_{name}_cap'.format(name=self._name, suite=self._suite) self._filename = '{module_name}.F90'.format(module_name=self._module) self._subroutines = [] local_subs = '' # for ccpp_stage in CCPP_STAGES: # The special init and finalize routines are only run in that stage if self._init and not ccpp_stage == 'init': continue elif self._finalize and not ccpp_stage == 'finalize': continue # For mapping local variable names to standard names local_vars = {} # For mapping temporary variable names (for unit conversions, etc) to local variable names tmpvar_cnt = 0 tmpvars = {} # body = '' var_defs = '' for subcycle in self._subcycles: if subcycle.loop > 1 and ccpp_stage == 'run': body += ''' associate(cnt => {loop_var_name}) do cnt=1,{loop_cnt}\n\n'''.format(loop_var_name=ccpp_loop_counter_target_name,loop_cnt=subcycle.loop) for scheme_name in subcycle.schemes: # actions_before and actions_after capture operations such # as unit conversions, transformations that have to happen # before and/or after the call to the subroutine (scheme) actions_before = '' actions_after = '' # module_name = scheme_name subroutine_name = scheme_name + '_' + ccpp_stage container = encode_container(module_name, scheme_name, subroutine_name) # Skip entirely empty routines if not arguments[module_name][scheme_name][subroutine_name]: continue error_check = '' args = '' length = 0 # Extract all variables needed (including indices for components/slices of arrays) for var_standard_name in arguments[module_name][scheme_name][subroutine_name]: # Pick the correct variable for this module/scheme/subroutine # from the list of requested variable for var in metadata_request[var_standard_name]: if container == var.container: break if not var_standard_name in local_vars.keys(): if not var_standard_name in metadata_define.keys(): raise Exception('Variable {standard_name} not defined in host model metadata'.format( standard_name=var_standard_name)) var_local_name_define = metadata_define[var_standard_name][0].local_name # Break apart var_local_name_define into the different components (members of DDTs) # to determine all variables that are required (parent_local_name_define, parent_local_names_define_indices) = \ extract_parents_and_indices_from_local_name(var_local_name_define) # Check for each of the derived parent local names as defined by the host model # if they are registered (i.e. if there is a standard name for it). Note that # the output of extract_parents_and_indices_from_local_name is stripped of any # array subset information, i.e. a local name 'Atm(:)%...' will produce a # parent local name 'Atm'. Since the rank of tha parent variable is not known # at this point and since the local name in the host model metadata table could # contain '(:)', '(:,:)', ... (up to the rank of the array), we search for the # maximum number of dimensions allowed by the Fortran standard. for local_name_define in [parent_local_name_define] + parent_local_names_define_indices: parent_standard_name = None parent_var = None for i in xrange(FORTRAN_ARRAY_MAX_DIMS+1): if i==0: dims_string = '' else: # (:) for i==1, (:,:) for i==2, ... dims_string = '(' + ','.join([':' for j in xrange(i)]) + ')' if local_name_define+dims_string in standard_name_by_local_name_define.keys(): parent_standard_name = standard_name_by_local_name_define[local_name_define+dims_string] parent_var = metadata_define[parent_standard_name][0] break if not parent_var: raise Exception('Parent variable {parent} of {child} with standard name '.format( parent=local_name_define, child=var_local_name_define)+\ '{standard_name} not defined in host model metadata'.format( standard_name=var_standard_name)) # Reset local name for entire array to a notation without (:), (:,:), etc.; # this is needed for the var.print_def_intent() routine to work correctly parent_var.local_name = local_name_define # Add variable to dictionary of parent variables, if not already there. # Set or update intent, depending on whether the variable is an index # in var_local_name_define or the actual parent of that variable. if not parent_standard_name in self.parents[ccpp_stage].keys(): self.parents[ccpp_stage][parent_standard_name] = copy.deepcopy(parent_var) # Copy the intent of the actual variable being processed if local_name_define == parent_local_name_define: self.parents[ccpp_stage][parent_standard_name].intent = var.intent # It's an index for the actual variable being processed --> intent(in) else: self.parents[ccpp_stage][parent_standard_name].intent = 'in' elif self.parents[ccpp_stage][parent_standard_name].intent == 'in': # Adjust the intent if the actual variable is not intent(in) if local_name_define == parent_local_name_define and not var.intent == 'in': self.parents[ccpp_stage][parent_standard_name].intent = 'inout' # It's an index for the actual variable being processed, intent is ok #else: # # nothing to do elif self.parents[ccpp_stage][parent_standard_name].intent == 'out': # Adjust the intent if the actual variable is not intent(out) if local_name_define == parent_local_name_define and not var.intent == 'out': self.parents[ccpp_stage][parent_standard_name].intent = 'inout' # Adjust the intent, because the variable is also used as index variable else: self.parents[ccpp_stage][parent_standard_name].intent = 'inout' # Record this information in the local_vars dictionary local_vars[var_standard_name] = { 'name' : metadata_define[var_standard_name][0].local_name, 'kind' : metadata_define[var_standard_name][0].kind, 'parent_standard_name' : parent_standard_name } else: parent_standard_name = local_vars[var_standard_name]['parent_standard_name'] # Update intent information if necessary if self.parents[ccpp_stage][parent_standard_name].intent == 'in' and not var.intent == 'in': self.parents[ccpp_stage][parent_standard_name].intent = 'inout' elif self.parents[ccpp_stage][parent_standard_name].intent == 'out' and not var.intent == 'out': self.parents[ccpp_stage][parent_standard_name].intent = 'inout' # Add necessary actions before/after while populating the subroutine's argument list kind_string = '_' + local_vars[var_standard_name]['kind'] if local_vars[var_standard_name]['kind'] else '' if var.actions['out']: if local_vars[var_standard_name]['name'] in tmpvars.keys(): # If the variable already has a local variable (tmpvar), reuse it tmpvar = tmpvars[local_vars[var_standard_name]['name']] else: # Add a local variable (tmpvar) for this variable tmpvar_cnt += 1 tmpvar = copy.deepcopy(var) tmpvar.local_name = 'tmpvar{0}'.format(tmpvar_cnt) tmpvars[local_vars[var_standard_name]['name']] = tmpvar if var.rank: # Add allocate statement if the variable has a rank > 0 actions_before += ' allocate({t}, source={v})\n'.format(t=tmpvar.local_name, v=local_vars[var_standard_name]['name']) if var.actions['in']: # Add unit conversion before entering the subroutine actions_before += ' {t} = {c}\n'.format(t=tmpvar.local_name, c=var.actions['in'].format(var=local_vars[var_standard_name]['name'], kind=kind_string)) # Add unit conversion after returning from the subroutine actions_after += ' {v} = {c}\n'.format(v=local_vars[var_standard_name]['name'], c=var.actions['out'].format(var=tmpvar.local_name, kind=kind_string)) if var.rank: # Add deallocate statement if the variable has a rank > 0 actions_after += ' deallocate({t})\n'.format(t=tmpvar.local_name) # Add to argument list arg = '{local_name}={var_name},'.format(local_name=var.local_name, var_name=tmpvar.local_name) elif var.actions['in']: # Add to argument list, call action in argument list action = var.actions['in'].format(var=local_vars[var_standard_name]['name'], kind=kind_string) arg = '{local_name}={action},'.format(local_name=var.local_name, action=action) else: # Add to argument list arg = '{local_name}={var_name},'.format(local_name=var.local_name, var_name=local_vars[var_standard_name]['name']) args += arg length += len(arg) # Split args so that lines don't exceed 260 characters (for PGI) if length > 70 and not var_standard_name == arguments[module_name][scheme_name][subroutine_name][-1]: args += ' &\n ' length = 0 args = args.rstrip(',') subroutine_call = ''' {actions_before} call {subroutine_name}({args}) {actions_after} '''.format(subroutine_name=subroutine_name, args=args, actions_before=actions_before.rstrip('\n'), actions_after=actions_after.rstrip('\n')) error_check = '''if ({target_name_flag}/=0) then {target_name_msg} = "An error occured in {subroutine_name}: " // trim({target_name_msg}) ierr={target_name_flag} return end if '''.format(target_name_flag=ccpp_error_flag_target_name, target_name_msg=ccpp_error_msg_target_name, subroutine_name=subroutine_name) body += ''' {subroutine_call} {error_check} '''.format(subroutine_call=subroutine_call, error_check=error_check) module_use += ' use {m}, only: {s}\n'.format(m=module_name, s=subroutine_name) if subcycle.loop > 1 and ccpp_stage == 'run': body += ''' end do end associate ''' # Get list of arguments, module use statement and variable definitions for this subroutine (=stage for the group) (self.arguments[ccpp_stage], sub_module_use, sub_var_defs) = create_arguments_module_use_var_defs( self.parents[ccpp_stage], metadata_define, tmpvars.values()) sub_argument_list = create_argument_list_wrapped(self.arguments[ccpp_stage]) subroutine = self._suite + '_' + self._name + '_' + ccpp_stage + '_cap' self._subroutines.append(subroutine) # Test and set blocks for initialization status initialized_test_block = Group.initialized_test_blocks[ccpp_stage].format( target_name_flag=ccpp_error_flag_target_name, target_name_msg=ccpp_error_msg_target_name, name=self._name) initialized_set_block = Group.initialized_set_blocks[ccpp_stage].format( target_name_flag=ccpp_error_flag_target_name, target_name_msg=ccpp_error_msg_target_name, name=self._name) # Create subroutine local_subs += Group.sub.format(subroutine=subroutine, argument_list=sub_argument_list, module_use='\n '.join(sub_module_use), initialized_test_block=initialized_test_block, initialized_set_block=initialized_set_block, var_defs='\n '.join(sub_var_defs), body=body) # Write output to stdout or file if (self.filename is not sys.stdout): f = open(self.filename, 'w') else: f = sys.stdout f.write(Group.header.format(group=self._name, module=self._module, module_use=module_use, subroutines=', &\n '.join(self._subroutines))) f.write(local_subs) f.write(Group.footer.format(module=self._module)) if (f is not sys.stdout): f.close() return @property def name(self): '''Get the name of the group.''' return self._name @name.setter def name(self, value): self._name = value @property def filename(self): '''Get the filename of write the output to.''' return self._filename @filename.setter def filename(self, value): self._filename = value @property def init(self): '''Get the init flag.''' return self._init @init.setter def init(self, value): if not type(value) == types.BooleanType: raise Exception("Invalid type {0} of argument value, boolean expected".format(type(value))) self._init = value @property def finalize(self): '''Get the finalize flag.''' return self._finalize @finalize.setter def finalize(self, value): if not type(value) == types.BooleanType: raise Exception("Invalid type {0} of argument value, boolean expected".format(type(value))) self._finalize = value @property def suite(self): '''Get the suite name.''' return self._suite @property def module(self): '''Get the module name.''' return self._module @property def subcycles(self): '''Get the subcycles.''' return self._subcycles @property def subroutines(self): '''Get the subroutine names.''' return self._subroutines def print_debug(self): '''Basic debugging output about the group.''' print self._name for subcycle in self._subcycles: subcycle.print_debug() @property def pset(self): '''Get the unique physics set of this group.''' return self._pset @pset.setter def pset(self, value): self._pset = value @property def parents(self): '''Get the parent variables for the group.''' return self._parents @parents.setter def parents(self, value): self._parents = value @property def arguments(self): '''Get the argument list of the group.''' return self._arguments @arguments.setter def arguments(self, value): self._arguments = value class Subcycle(object): def __init__(self, **kwargs): self._filename = 'sys.stdout' self._schemes = None for key, value in kwargs.items(): setattr(self, "_"+key, value) @property def loop(self): '''Get the list of loop.''' return self._loop @loop.setter def loop(self, value): if not type(value) is int: raise Exception("Invalid type {0} of argument value, integer expected".format(type(value))) self._loop = value @property def schemes(self): '''Get the list of schemes.''' return self._schemes @schemes.setter def schemes(self, value): if not type(value) is list: raise Exception("Invalid type {0} of argument value, list expected".format(type(value))) self._schemes = value def print_debug(self): '''Basic debugging output about the subcycle.''' print self._loop for scheme in self._schemes: print scheme ############################################################################### if __name__ == "__main__": main()