From 964b776f3f88ea01a0edc0e72a158ee6f6d835e8 Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Tue, 30 Jan 2024 19:36:34 +0000 Subject: [PATCH 01/19] Add optional keyword. Work in progress. --- scripts/ccpp_datafile.py | 2 +- scripts/metavar.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/ccpp_datafile.py b/scripts/ccpp_datafile.py index 1fd5f830..3c5092ed 100755 --- a/scripts/ccpp_datafile.py +++ b/scripts/ccpp_datafile.py @@ -654,7 +654,7 @@ def _new_var_entry(parent, var, full_entry=True): "diagnostic_name", "diagnostic_name_fixed", "kind", "persistence", "polymorphic", "protected", "state_variable", "type", "units", "molar_mass", - "advected", "top_at_one"]) + "advected", "top_at_one", "optional"]) 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 eb1973e1..893de796 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -207,6 +207,8 @@ class Var: default_in=False), VariableProperty('top_at_one', bool, optional_in=True, default_in=False), + VariableProperty('optional', bool, optional_in=True, + default_in=False), VariableProperty('target', bool, optional_in=True, default_in=False)] From 224fad3a4ea8c1e7d442d0e95827ab59cb6c3d69 Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Tue, 30 Jan 2024 22:29:49 +0000 Subject: [PATCH 02/19] Work in progress. --- scripts/ccpp_capgen.py | 15 +++++++++++++++ scripts/suite_objects.py | 12 +++++++++++- test/var_compatibility_test/effr_calc.F90 | 7 +++---- test/var_compatibility_test/effr_calc.meta | 8 +------- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index cbd46599..46f731c1 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -293,6 +293,21 @@ def compare_fheader_to_mheader(meta_header, fort_header, logger): # end for list_match = mlen == flen # end if + # Check for consistency between optional variables in metadata and + # optional variables in fortran. Error if optional attribute is + # missing from fortran declaration. + for mind, mvar in enumerate(mlist): + mopt = mvar.get_prop_value('optional') + mname = mvar.get_prop_value('local_name') + fvar, find = find_var_in_list(mname, flist) + if find and mopt: + fopt = fvar.get_prop_value('optional') + if (not fopt): + errmsg = 'Missing optional attribute in fortran declaration for variable {}, in file {}' + errors_found = add_error(errors_found, errmsg.format(mname,title)) + # end if + # end if + # end for if not list_match: if fht in _EXTRA_VARIABLE_TABLE_TYPES: if flen > mlen: diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 96d6649c..7e7ec13d 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -871,10 +871,20 @@ def match_variable(self, var, run_env): new_vdims = list() new_dict_dims = dict_dims match = True + # end if # Create compatability object, containing any necessary forward/reverse # transforms from and compat_obj = var.compatible(dict_var, run_env) - + # If variable is defined as "inactive" by the host, ensure that + # this variable is declared as "optional" by the scheme. If + # not satisfied, return error. + host_var_active = dict_var.get_prop_value('active') + scheme_var_optional = var.get_prop_value('optional') + if (not scheme_var_optional and host_var_active.lower() != '.true.'): + errmsg = "Non optional scheme arguments for conditionally allocatable variables" + sname = dict_var.get_prop_value('standard_name') + errmsg += ", {}".format(sname) + raise CCPPError(errmsg) # end if # Add the variable to the parent call tree if dict_dims == new_dict_dims: diff --git a/test/var_compatibility_test/effr_calc.F90 b/test/var_compatibility_test/effr_calc.F90 index b877e99a..1e9eec57 100644 --- a/test/var_compatibility_test/effr_calc.F90 +++ b/test/var_compatibility_test/effr_calc.F90 @@ -16,16 +16,15 @@ module effr_calc !! \htmlinclude arg_table_effr_calc_run.html !! subroutine effr_calc_run(ncol, nlev, effrr_in, effrg_in, effrl_inout, & - effri_out, effrs_inout, has_graupel, errmsg, errflg) + effri_out, effrs_inout, errmsg, errflg) integer, intent(in) :: ncol integer, intent(in) :: nlev real(kind_phys), intent(in) :: effrr_in(:,:) - real(kind_phys), intent(in) :: effrg_in(:,:) + real(kind_phys), intent(in),optional :: effrg_in(:,:) real(kind_phys), intent(inout) :: effrl_inout(:,:) real(kind_phys), intent(out) :: effri_out(:,:) real(8),intent(inout) :: effrs_inout(:,:) - logical, intent(in) :: has_graupel character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg !---------------------------------------------------------------- @@ -40,7 +39,7 @@ subroutine effr_calc_run(ncol, nlev, effrr_in, effrg_in, effrl_inout, & errflg = 0 effrr_local = effrr_in - if (has_graupel) effrg_local = effrg_in + if (present(effrg_in)) effrg_local = effrg_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 diff --git a/test/var_compatibility_test/effr_calc.meta b/test/var_compatibility_test/effr_calc.meta index 14687f21..e615e739 100644 --- a/test/var_compatibility_test/effr_calc.meta +++ b/test/var_compatibility_test/effr_calc.meta @@ -34,6 +34,7 @@ type = real kind = kind_phys intent = in + optional = .true. [effrl_inout] standard_name = effective_radius_of_stratiform_cloud_liquid_water_particle long_name = effective radius of cloud liquid water particle in micrometer @@ -59,13 +60,6 @@ kind = 8 intent = inout top_at_one = .true. -[has_graupel] - standard_name = flag_indicating_cloud_microphysics_has_graupel - long_name = flag indicating that the cloud microphysics produces graupel - units = flag - dimensions = () - type = logical - intent = in [ errmsg ] standard_name = ccpp_error_message long_name = Error message for error handling in CCPP From aa652cdba040b615c1cd397a50afc66e1b952cd9 Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Tue, 30 Jan 2024 22:49:00 +0000 Subject: [PATCH 03/19] Add optional attribute to varaible declarations in caps --- scripts/metavar.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/metavar.py b/scripts/metavar.py index 893de796..8c154724 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -1079,6 +1079,7 @@ def write_def(self, outfile, indent, wdict, allocatable=False, raise CCPPError(errmsg.format(name)) # end if # end if + optional = self.get_prop_value('optional') if protected and dummy: intent_str = 'intent(in) ' elif allocatable: @@ -1091,6 +1092,9 @@ def write_def(self, outfile, indent, wdict, allocatable=False, alloval = self.get_prop_value('allocatable') if (intent.lower()[-3:] == 'out') and alloval: intent_str = f"allocatable, intent({intent})" + elif optional: + intent_str = f"intent({intent}),{' '*(5 - len(intent))}" + intent_str += 'optional ' else: intent_str = f"intent({intent}){' '*(5 - len(intent))}" # end if From 924867832bdc67ac2ea2281a69eb554197213ed6 Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Thu, 1 Feb 2024 23:08:07 +0000 Subject: [PATCH 04/19] Work in progress --- scripts/metavar.py | 18 +++++++++-- scripts/suite_objects.py | 66 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 79 insertions(+), 5 deletions(-) diff --git a/scripts/metavar.py b/scripts/metavar.py index 8c154724..e49f0776 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -1027,8 +1027,8 @@ def conditional(self, vdicts): vars_needed.append(dvar) return (conditional, vars_needed) - def write_def(self, outfile, indent, wdict, allocatable=False, - dummy=False, add_intent=None, extra_space=0, public=False): + def write_def(self, outfile, indent, wdict, allocatable=False, target=None, + pointer=None, 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 @@ -1085,6 +1085,8 @@ def write_def(self, outfile, indent, wdict, allocatable=False, elif allocatable: if dimstr or polymorphic: intent_str = 'allocatable ' + if target: + intent_str += ',target ' else: intent_str = ' '*13 # end if @@ -1132,10 +1134,20 @@ 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) + if pointer: + name = name+'_ptr' + if kind: + dstr = "{type}({kind}){cspc}pointer :: {name}{dims} => null()" + cspc = comma + ' '*(extra_space + 20 - len(vtype) - len(kind)) + else: + dstr = "{type}{cspc}pointer :: {name}{dims} => null()" + cspc = comma + ' '*(extra_space + 22 - len(vtype)) + # end if + outfile.write(dstr.format(type=vtype, kind=kind, intent=intent_str, + name=name, dims=dimstr, cspc=cspc), indent) def is_ddt(self): """Return True iff is a DDT type.""" diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 41ac376e..436cc3dd 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1093,6 +1093,7 @@ def __init__(self, scheme_xml, context, parent, run_env): self.__var_debug_checks = list() self.__forward_transforms = list() self.__reverse_transforms = list() + self.__optional_vars = list() super().__init__(name, context, parent, run_env, active_call_list=True) def update_group_call_list_variable(self, var): @@ -1226,6 +1227,14 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): compat_obj.has_unit_transforms or compat_obj.has_kind_transforms): self.add_var_transform(var, compat_obj, vert_dim) + # end if + + # Is this a conditionally allocated variable? + # If so, declare local target and pointer varaibles. This is needed to + # pass inactive (not present) status through the caps. + if var.get_prop_value('optional'): + self.add_optional_var(var) + # end if # end for if self.needs_vertical is not None: @@ -1535,6 +1544,12 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er outfile.write('',indent) # end if + def add_optional_var(self, var): + newvar_ptr = var.clone(var.get_prop_value('local_name')+'_local_ptr') + self.add_variable(newvar_ptr, self.run_env, exists_ok=True) + self.__optional_vars.append([var.get_prop_value('local_name')+'_local', + newvar_ptr.get_prop_value('local_name')]) + def add_var_transform(self, var, compat_obj, vert_dim): """Register any variable transformation needed by for this Scheme. For any transformation identified in , create dummy variable @@ -1608,6 +1623,7 @@ def write_var_transform(self, var, dummy, rindices, lindices, compat_obj, rvar_lname=dummy, lvar_indices=rindices, rvar_indices=lindices) + # end if outfile.write(stmt, indent) def write(self, outfile, errcode, errmsg, indent): @@ -1633,6 +1649,12 @@ def write(self, outfile, errcode, errmsg, indent): outfile.write('! Compute reverse (pre-scheme) transforms', indent+1) for (dummy, var, rindices, lindices, compat_obj) in self.__reverse_transforms: tstmt = self.write_var_transform(var, dummy, rindices, lindices, compat_obj, outfile, indent+1, False) + outfile.write('',indent+1) + # Associate conditionally allocated variables. + outfile.write('! Associate conditional variables', indent+1) + for (lname, lname_ptr) in self.__optional_vars: + outfile.write(f"{lname_ptr} => {lname}", indent+1) + # end if # Write the scheme call. stmt = 'call {}({})' outfile.write('',indent+1) @@ -2175,9 +2197,12 @@ def write(self, outfile, host_arglist, indent, const_mod, # Collect information on local variables subpart_vars = {} allocatable_var_set = set() + subpart_ptrs = {} + pointer_var_set = set() for item in [self]:# + self.parts: for var in item.declarations(): lname = var.get_prop_value('local_name') + opt_var = var.get_prop_value('optional') if lname in subpart_vars: if subpart_vars[lname][0].compatible(var, self.run_env): pass # We already are going to declare this variable @@ -2185,13 +2210,30 @@ def write(self, outfile, host_arglist, indent, const_mod, errmsg = "Duplicate Group variable, {}" raise ParseInternalError(errmsg.format(lname)) # end if + #elif not opt_var: else: - subpart_vars[lname] = (var, item) + subpart_vars[lname] = (var, item, False, False) dims = var.get_dimensions() if (dims is not None) and dims: allocatable_var_set.add(lname) # end if # end if + opt_var = var.get_prop_value('optional') + #if lname in subpart_ptrs: + # if subpart_ptrs[lname][0].compatible(var, self.run_env): + # pass + # else: + # errmsg = "Duplicate Group variable, {}" + # raise ParseInternalError(errmsg.format(lname)) + # # end if + if opt_var: + subpart_vars[lname] = (var, item, True, True) + subpart_ptrs[lname+'_ptr'] = (var, item) + dims = var.get_dimensions() + if (dims is not None) and dims: + pointer_var_set.add(lname+'_ptr') + # endif + # end if # end for # end for # First, write out the subroutine header @@ -2231,13 +2273,18 @@ def write(self, outfile, host_arglist, indent, const_mod, self.call_list.declare_variables(outfile, indent+1, dummy=True) if subpart_vars: outfile.write('\n! Local Variables', indent+1) + # end if # Write out local variables for key in subpart_vars: var = subpart_vars[key][0] spdict = subpart_vars[key][1] + target = subpart_vars[key][2] + pointer= subpart_vars[key][3] var.write_def(outfile, indent+1, spdict, - allocatable=(key in allocatable_var_set)) + allocatable=(key in allocatable_var_set), + target=target, pointer=pointer) # end for + outfile.write('', 0) # Get error variable names if self.run_env.use_error_obj: @@ -2258,20 +2305,25 @@ def write(self, outfile, host_arglist, indent, const_mod, raise CCPPError(errmsg.format(self.name)) # end if # Initialize error variables + outfile.write("! Initialize ccpp error handling", 2) outfile.write("{} = 0".format(errcode), 2) outfile.write("{} = ''".format(errmsg), 2) + outfile.write("",2) # end if # Output threaded region check (except for run phase) if not self.run_phase(): + outfile.write("! Output threaded region check ",indent+1) Group.__thread_check.write(outfile, indent, {'phase' : self.phase(), 'errcode' : errcode, 'errmsg' : errmsg}) # Check state machine + outfile.write("! Check state machine",indent+1) self._phase_check_stmts.write(outfile, indent, {'errcode' : errcode, 'errmsg' : errmsg, 'funcname' : self.name}) # Allocate local arrays + outfile.write('\n! Allocate local arrays', indent+1) alloc_stmt = "allocate({}({}))" for lname in allocatable_var_set: var = subpart_vars[lname][0] @@ -2281,6 +2333,7 @@ def write(self, outfile, host_arglist, indent, const_mod, # end for # Allocate suite vars if allocate: + outfile.write('\n! Allocate suite_vars', indent+1) for svar in suite_vars.variable_list(): dims = svar.get_dimensions() if dims: @@ -2306,9 +2359,18 @@ def write(self, outfile, host_arglist, indent, const_mod, item.write(outfile, errcode, errmsg, indent + 1) # end for # Deallocate local arrays + if allocatable_var_set: + outfile.write('\n! Deallocate local arrays', indent+1) + # end if for lname in allocatable_var_set: outfile.write('deallocate({})'.format(lname), indent+1) # end for + # Nullify local pointers + if pointer_var_set: + outfile.write('\n! Nullify local pointers', indent+1) + # end if + for lname in pointer_var_set: + outfile.write('nullify({})'.format(lname), indent+1) # Deallocate suite vars if deallocate: for svar in suite_vars.variable_list(): From 7a0c4c9d424ff0e91f4e8723696e4032d7e84ab0 Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Fri, 2 Feb 2024 21:15:49 +0000 Subject: [PATCH 05/19] added active logic to pointer association --- scripts/suite_objects.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 436cc3dd..20304764 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1233,7 +1233,7 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): # If so, declare local target and pointer varaibles. This is needed to # pass inactive (not present) status through the caps. if var.get_prop_value('optional'): - self.add_optional_var(var) + self.add_optional_var(var, dict_var) # end if # end for @@ -1544,12 +1544,18 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er outfile.write('',indent) # end if - def add_optional_var(self, var): + def add_optional_var(self, var, dict_var): newvar_ptr = var.clone(var.get_prop_value('local_name')+'_local_ptr') self.add_variable(newvar_ptr, self.run_env, exists_ok=True) - self.__optional_vars.append([var.get_prop_value('local_name')+'_local', + self.__optional_vars.append([dict_var, var.get_prop_value('local_name')+'_local', newvar_ptr.get_prop_value('local_name')]) + def write_optional_var(self, var, cldicts, lname, lname_ptr, indent, outfile): + (conditional, _) = var.conditional(cldicts) + outfile.write(f"if {conditional} then", indent) + outfile.write(f"{lname_ptr} => {lname}", indent+1) + outfile.write(f"end if", indent) + def add_var_transform(self, var, compat_obj, vert_dim): """Register any variable transformation needed by for this Scheme. For any transformation identified in , create dummy variable @@ -1652,8 +1658,8 @@ def write(self, outfile, errcode, errmsg, indent): outfile.write('',indent+1) # Associate conditionally allocated variables. outfile.write('! Associate conditional variables', indent+1) - for (lname, lname_ptr) in self.__optional_vars: - outfile.write(f"{lname_ptr} => {lname}", indent+1) + for (var, lname, lname_ptr) in self.__optional_vars: + tstmt = self.write_optional_var(var, cldicts, lname, lname_ptr, indent+1, outfile) # end if # Write the scheme call. stmt = 'call {}({})' From 423dc71b06b4d7ed79e34cc9479213676d669a2e Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Thu, 8 Feb 2024 05:42:27 +0000 Subject: [PATCH 06/19] Working now --- scripts/metavar.py | 27 +-- scripts/suite_objects.py | 160 ++++++++++++------ test/var_compatibility_test/effr_calc.F90 | 14 +- test/var_compatibility_test/effr_calc.meta | 19 +++ test/var_compatibility_test/run_test | 12 +- test/var_compatibility_test/test_host.F90 | 18 +- .../var_compatibility_test/test_host_data.F90 | 29 +++- .../test_host_data.meta | 19 +++ test/var_compatibility_test/test_host_mod.F90 | 14 +- .../var_compatibility_test/test_host_mod.meta | 8 +- test/var_compatibility_test/test_reports.py | 7 +- 11 files changed, 241 insertions(+), 86 deletions(-) diff --git a/scripts/metavar.py b/scripts/metavar.py index e49f0776..2b126f7c 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -1028,7 +1028,7 @@ def conditional(self, vdicts): return (conditional, vars_needed) def write_def(self, outfile, indent, wdict, allocatable=False, target=None, - pointer=None, dummy=False, add_intent=None, extra_space=0, public=False): + 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 @@ -1096,7 +1096,7 @@ def write_def(self, outfile, indent, wdict, allocatable=False, target=None, intent_str = f"allocatable, intent({intent})" elif optional: intent_str = f"intent({intent}),{' '*(5 - len(intent))}" - intent_str += 'optional ' + intent_str += 'target, optional ' else: intent_str = f"intent({intent}){' '*(5 - len(intent))}" # end if @@ -1137,17 +1137,18 @@ def write_def(self, outfile, indent, wdict, allocatable=False, target=None, outfile.write(dstr.format(type=vtype, kind=kind, intent=intent_str, name=name, dims=dimstr, cspc=cspc, sname=stdname), indent) - if pointer: - name = name+'_ptr' - if kind: - dstr = "{type}({kind}){cspc}pointer :: {name}{dims} => null()" - cspc = comma + ' '*(extra_space + 20 - len(vtype) - len(kind)) - else: - dstr = "{type}{cspc}pointer :: {name}{dims} => null()" - cspc = comma + ' '*(extra_space + 22 - len(vtype)) - # end if - outfile.write(dstr.format(type=vtype, kind=kind, intent=intent_str, - name=name, dims=dimstr, cspc=cspc), indent) + + def write_ptr_def(self, outfile, indent, name, kind, dimstr, vtype, extra_space=0): + comma = ',' + if kind: + dstr = "{type}({kind}){cspc}pointer :: {name}{dims} => null()" + cspc = comma + ' '*(extra_space + 20 - len(vtype) - len(kind)) + else: + dstr = "{type}{cspc}pointer :: {name}{dims} => null()" + cspc = comma + ' '*(extra_space + 22 - len(vtype)) + # end if + outfile.write(dstr.format(type=vtype, kind=kind, name=name, dims=dimstr, + cspc=cspc), indent) def is_ddt(self): """Return True iff is a DDT type.""" diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 93737021..5ea349a6 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -143,6 +143,11 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None): raise CCPPError(errmsg.format(stdname, clnames)) # end if lname = dvar.get_prop_value('local_name') + # Optional variables in the caps are associated with + # local pointers of _ptr + if dvar.get_prop_value('optional'): + lname = dummy+'_ptr' + # end if else: cldict = None aref = var.array_ref(local_name=dummy) @@ -1223,17 +1228,20 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): # end if # end if # Are there any forward/reverse transforms for this variable? + has_transform = False if compat_obj is not None and (compat_obj.has_vert_transforms or compat_obj.has_unit_transforms or compat_obj.has_kind_transforms): self.add_var_transform(var, compat_obj, vert_dim) + has_transform = True # end if # Is this a conditionally allocated variable? # If so, declare local target and pointer varaibles. This is needed to # pass inactive (not present) status through the caps. if var.get_prop_value('optional'): - self.add_optional_var(var, dict_var) + newvar_ptr = var.clone(var.get_prop_value('local_name')+'_ptr') + self.__optional_vars.append([dict_var, var, newvar_ptr, has_transform]) # end if # end for @@ -1434,9 +1442,9 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er tmp_indent = indent + 1 outfile.write(f"if {conditional} then", indent) # end if - outfile.write(f"! Assign value of {local_name} to {internal_var}", tmp_indent) + outfile.write(f"! Assign value of {local_name} to {internal_var_lname}", tmp_indent) outfile.write(f"{internal_var_lname} = {local_name}", tmp_indent) - outfile.write('',indent) + outfile.write('',tmp_indent) if conditional != '.true.': outfile.write(f"end if", indent) # end if @@ -1530,7 +1538,7 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er tmp_indent = indent + 1 outfile.write(f"if {conditional} then", indent) # end if - outfile.write(f"! Assign lower/upper bounds of {local_name} to {internal_var}", tmp_indent+1) + outfile.write(f"! Assign lower/upper bounds of {local_name} to {internal_var_lname}", tmp_indent+1) outfile.write(f"{internal_var_lname} = {local_name}{lbound_string}", tmp_indent+1) outfile.write(f"{internal_var_lname} = {local_name}{ubound_string}", tmp_indent+1) if conditional != '.true.': @@ -1538,18 +1546,34 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er # end if outfile.write('',indent) - def add_optional_var(self, var, dict_var): - newvar_ptr = var.clone(var.get_prop_value('local_name')+'_local_ptr') - self.add_variable(newvar_ptr, self.run_env, exists_ok=True) - self.__optional_vars.append([dict_var, var.get_prop_value('local_name')+'_local', - newvar_ptr.get_prop_value('local_name')]) - - def write_optional_var(self, var, cldicts, lname, lname_ptr, indent, outfile): - (conditional, _) = var.conditional(cldicts) + def associate_optional_var(self, dict_var, var, var_ptr, has_transform, cldicts, indent, outfile): + """Write local pointer association for optional variables.""" + (conditional, _) = dict_var.conditional(cldicts) + if (has_transform): + lname = var.get_prop_value('local_name')+'_local' + else: + lname = var.get_prop_value('local_name') + # end if + lname_ptr = var_ptr.get_prop_value('local_name') outfile.write(f"if {conditional} then", indent) outfile.write(f"{lname_ptr} => {lname}", indent+1) outfile.write(f"end if", indent) + def assign_pointer_to_var(self, dict_var, var, var_ptr, has_transform, cldicts, indent, outfile): + """Assign local pointer to variable.""" + intent = var.get_prop_value('intent') + if (intent == 'out' or intent == 'inout'): + (conditional, _) = dict_var.conditional(cldicts) + if (has_transform): + lname = var.get_prop_value('local_name')+'_local' + else: + lname = var.get_prop_value('local_name') + # end if + lname_ptr = var_ptr.get_prop_value('local_name') + outfile.write(f"if {conditional} then", indent) + outfile.write(f"{lname} = {lname_ptr}", indent+1) + outfile.write(f"end if", indent) + def add_var_transform(self, var, compat_obj, vert_dim): """Register any variable transformation needed by for this Scheme. For any transformation identified in , create dummy variable @@ -1637,35 +1661,67 @@ def write(self, outfile, errcode, errmsg, indent): my_args = self.call_list.call_string(cldicts=cldicts, is_func_call=True, subname=self.subroutine_name) - + # outfile.write('', indent) outfile.write('if ({} == 0) then'.format(errcode), indent) - + # # Write debug checks (operating on variables # coming from the group's call list) + # + if self.__var_debug_checks: + outfile.write('! Run debug tests', indent+1) + # end if for (var, internal_var) in self.__var_debug_checks: stmt = self.write_var_debug_check(var, internal_var, cldicts, outfile, errcode, errmsg, indent+1) + # end for + # # Write any reverse (pre-Scheme) transforms. - outfile.write('! Compute reverse (pre-scheme) transforms', indent+1) + # + if self.__reverse_transforms: + outfile.write('! Compute reverse (pre-scheme) transforms', indent+1) + # end if for (dummy, var, rindices, lindices, compat_obj) in self.__reverse_transforms: tstmt = self.write_var_transform(var, dummy, rindices, lindices, compat_obj, outfile, indent+1, False) + # end for outfile.write('',indent+1) - # Associate conditionally allocated variables. - outfile.write('! Associate conditional variables', indent+1) - for (var, lname, lname_ptr) in self.__optional_vars: - tstmt = self.write_optional_var(var, cldicts, lname, lname_ptr, indent+1, outfile) - # end if + # + # Associate any conditionally allocated variables. + # + if self.__optional_vars: + outfile.write('! Associate conditional variables', indent+1) + # end if + for (dict_var, var, var_ptr, has_transform) in self.__optional_vars: + tstmt = self.associate_optional_var(dict_var, var, var_ptr, has_transform, cldicts, indent+1, outfile) + # end for + # # Write the scheme call. + # stmt = 'call {}({})' outfile.write('',indent+1) outfile.write('! Call scheme', indent+1) outfile.write(stmt.format(self.subroutine_name, my_args), indent+1) - # Write any forward (post-Scheme) transforms. outfile.write('',indent+1) - outfile.write('! Compute forward (post-scheme) transforms', indent+1) + # + # Copy any local pointers. + # + ptrct=True + for (dict_var, var, var_ptr, has_transform) in self.__optional_vars: + if ptrct: + outfile.write('! Copy any local pointers to dummy/local variables', indent+1) + ptrct=False + # end if + tstmt = self.assign_pointer_to_var(dict_var, var, var_ptr, has_transform, cldicts, indent+1, outfile) + # end for + outfile.write('',indent+1) + # + # Write any forward (post-Scheme) transforms. + # + if self.__forward_transforms: + outfile.write('! Compute forward (post-scheme) transforms', indent+1) + # end if for (var, dummy, lindices, rindices, compat_obj) in self.__forward_transforms: tstmt = self.write_var_transform(var, dummy, rindices, lindices, compat_obj, outfile, indent+1, True) - # + # for outfile.write('', indent) outfile.write('end if', indent) @@ -2197,12 +2253,10 @@ def write(self, outfile, host_arglist, indent, const_mod, # Collect information on local variables subpart_vars = {} allocatable_var_set = set() - subpart_ptrs = {} - pointer_var_set = set() + pointer_var_set = list() for item in [self]:# + self.parts: for var in item.declarations(): lname = var.get_prop_value('local_name') - opt_var = var.get_prop_value('optional') if lname in subpart_vars: if subpart_vars[lname][0].compatible(var, self.run_env): pass # We already are going to declare this variable @@ -2210,29 +2264,34 @@ def write(self, outfile, host_arglist, indent, const_mod, errmsg = "Duplicate Group variable, {}" raise ParseInternalError(errmsg.format(lname)) # end if - #elif not opt_var: else: - subpart_vars[lname] = (var, item, False, False) + opt_var = var.get_prop_value('optional') + subpart_vars[lname] = (var, item, opt_var) dims = var.get_dimensions() if (dims is not None) and dims: allocatable_var_set.add(lname) # end if # end if - opt_var = var.get_prop_value('optional') - #if lname in subpart_ptrs: - # if subpart_ptrs[lname][0].compatible(var, self.run_env): - # pass - # else: - # errmsg = "Duplicate Group variable, {}" - # raise ParseInternalError(errmsg.format(lname)) - # # end if + # end for + # All optional dummy variables within group need to have + # an associated pointer array declared. + for cvar in self.call_list.variable_list(): + opt_var = cvar.get_prop_value('optional') if opt_var: - subpart_vars[lname] = (var, item, True, True) - subpart_ptrs[lname+'_ptr'] = (var, item) - dims = var.get_dimensions() - if (dims is not None) and dims: - pointer_var_set.add(lname+'_ptr') - # endif + name = cvar.get_prop_value('local_name')+'_ptr' + kind = cvar.get_prop_value('kind') + dims = cvar.get_dimensions() + if cvar.is_ddt(): + vtype = 'type' + else: + vtype = cvar.get_prop_value('type') + # end if + if dims: + dimstr = '(:' + ',:'*(len(dims) - 1) + ')' + else: + dimstr = '' + # end if + pointer_var_set.append([name,kind,dimstr,vtype]) # end if # end for # end for @@ -2279,12 +2338,17 @@ def write(self, outfile, host_arglist, indent, const_mod, var = subpart_vars[key][0] spdict = subpart_vars[key][1] target = subpart_vars[key][2] - pointer= subpart_vars[key][3] var.write_def(outfile, indent+1, spdict, allocatable=(key in allocatable_var_set), - target=target, pointer=pointer) + target=target) + # end for + # Write out local pointer variables. + if pointer_var_set: + outfile.write('\n! Local Pointer variables', indent+1) + # end if + for (name, kind, dim, vtype) in pointer_var_set: + var.write_ptr_def(outfile, indent+1, name, kind, dim, vtype) # end for - outfile.write('', 0) # Get error variable names if self.run_env.use_error_obj: @@ -2356,7 +2420,7 @@ def write(self, outfile, host_arglist, indent, const_mod, # end for # Write the scheme and subcycle calls for item in self.parts: - item.write(outfile, errcode, errmsg, indent + 1) + item.write(outfile, errcode, errmsg, indent + 1) # end for # Deallocate local arrays if allocatable_var_set: @@ -2369,8 +2433,8 @@ def write(self, outfile, host_arglist, indent, const_mod, if pointer_var_set: outfile.write('\n! Nullify local pointers', indent+1) # end if - for lname in pointer_var_set: - outfile.write('nullify({})'.format(lname), indent+1) + for (name, kind, dim, vtype) in pointer_var_set: + outfile.write('nullify({})'.format(name), indent+1) # Deallocate suite vars if deallocate: for svar in suite_vars.variable_list(): diff --git a/test/var_compatibility_test/effr_calc.F90 b/test/var_compatibility_test/effr_calc.F90 index 1e9eec57..1d9cb787 100644 --- a/test/var_compatibility_test/effr_calc.F90 +++ b/test/var_compatibility_test/effr_calc.F90 @@ -15,15 +15,17 @@ module effr_calc !> \section arg_table_effr_calc_run Argument Table !! \htmlinclude arg_table_effr_calc_run.html !! - subroutine effr_calc_run(ncol, nlev, effrr_in, effrg_in, effrl_inout, & - effri_out, effrs_inout, errmsg, errflg) + subroutine effr_calc_run(ncol, nlev, effrr_in, effrg_in, ncg_in, nci_out, & + effrl_inout, effri_out, effrs_inout, errmsg, errflg) integer, intent(in) :: ncol integer, intent(in) :: nlev real(kind_phys), intent(in) :: effrr_in(:,:) real(kind_phys), intent(in),optional :: effrg_in(:,:) + real(kind_phys), intent(in),optional :: ncg_in(:,:) + real(kind_phys), intent(out),optional :: nci_out(:,:) real(kind_phys), intent(inout) :: effrl_inout(:,:) - real(kind_phys), intent(out) :: effri_out(:,:) + real(kind_phys), intent(out),optional :: effri_out(:,:) real(8),intent(inout) :: effrs_inout(:,:) character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg @@ -34,14 +36,18 @@ subroutine effr_calc_run(ncol, nlev, effrr_in, effrg_in, effrl_inout, & real(kind_phys), parameter :: re_qi_avg = 75. ! microns real(kind_phys) :: effrr_local(ncol,nlev) real(kind_phys) :: effrg_local(ncol,nlev) + real(kind_phys) :: ncg_in_local(ncol,nlev) + real(kind_phys) :: nci_out_local(ncol,nlev) errmsg = '' errflg = 0 effrr_local = effrr_in if (present(effrg_in)) effrg_local = effrg_in + if (present(ncg_in)) ncg_in_local = ncg_in + if (present(nci_out)) nci_out_local = nci_out effrl_inout = min(max(effrl_inout,re_qc_min),re_qc_max) - effri_out = re_qi_avg + if (present(effri_out)) effri_out = re_qi_avg effrs_inout = effrs_inout + 10.0 ! in micrometer end subroutine effr_calc_run diff --git a/test/var_compatibility_test/effr_calc.meta b/test/var_compatibility_test/effr_calc.meta index e615e739..aea9981f 100644 --- a/test/var_compatibility_test/effr_calc.meta +++ b/test/var_compatibility_test/effr_calc.meta @@ -35,6 +35,24 @@ kind = kind_phys intent = in optional = .true. +[ncg_in] + standard_name = cloud_graupel_number_concentration + long_name = number concentration of cloud graupel + units = kg-1 + dimensions = (horizontal_loop_extent,vertical_layer_dimension) + type = real + kind = kind_phys + intent = in + optional = .true. +[nci_out] + standard_name = cloud_ice_number_concentration + long_name = number concentration of cloud ice + units = kg-1 + dimensions = (horizontal_loop_extent,vertical_layer_dimension) + type = real + kind = kind_phys + intent = out + optional = .true. [effrl_inout] standard_name = effective_radius_of_stratiform_cloud_liquid_water_particle long_name = effective radius of cloud liquid water particle in micrometer @@ -51,6 +69,7 @@ type = real kind = kind_phys intent = out + optional=.true. [effrs_inout] standard_name = effective_radius_of_stratiform_cloud_snow_particle long_name = effective radius of cloud snow particle in micrometer diff --git a/test/var_compatibility_test/run_test b/test/var_compatibility_test/run_test index 9239c5d1..5bc4b4de 100755 --- a/test/var_compatibility_test/run_test +++ b/test/var_compatibility_test/run_test @@ -48,7 +48,7 @@ docleanup() { echo "WARNING: Cannot clean ${build_dir}" else cd ${currdir} - rm -rf ${build_dir} +# rm -rf ${build_dir} fi } @@ -131,30 +131,38 @@ module_list="effr_calc" #dependencies="" suite_list="var_compatibility_suite" required_vars_var_compatibility="ccpp_error_code,ccpp_error_message" +required_vars_var_compatibility="${required_vars_var_compatibility},cloud_graupel_number_concentration" +required_vars_var_compatibility="${required_vars_var_compatibility},cloud_ice_number_concentration" required_vars_var_compatibility="${required_vars_var_compatibility},effective_radius_of_stratiform_cloud_graupel" required_vars_var_compatibility="${required_vars_var_compatibility},effective_radius_of_stratiform_cloud_ice_particle" required_vars_var_compatibility="${required_vars_var_compatibility},effective_radius_of_stratiform_cloud_liquid_water_particle" required_vars_var_compatibility="${required_vars_var_compatibility},effective_radius_of_stratiform_cloud_rain_particle" required_vars_var_compatibility="${required_vars_var_compatibility},effective_radius_of_stratiform_cloud_snow_particle" required_vars_var_compatibility="${required_vars_var_compatibility},flag_indicating_cloud_microphysics_has_graupel" +required_vars_var_compatibility="${required_vars_var_compatibility},flag_indicating_cloud_microphysics_has_ice" required_vars_var_compatibility="${required_vars_var_compatibility},horizontal_dimension" required_vars_var_compatibility="${required_vars_var_compatibility},horizontal_loop_begin" required_vars_var_compatibility="${required_vars_var_compatibility},horizontal_loop_end" required_vars_var_compatibility="${required_vars_var_compatibility},vertical_layer_dimension" -input_vars_var_compatibility="effective_radius_of_stratiform_cloud_graupel" +input_vars_var_compatibility="cloud_graupel_number_concentration" +#input_vars_var_compatibility="${input_vars_var_compatibility},cloud_ice_number_concentration" +input_vars_var_compatibility="${input_vars_var_compatibility},effective_radius_of_stratiform_cloud_graupel" input_vars_var_compatibility="${input_vars_var_compatibility},effective_radius_of_stratiform_cloud_liquid_water_particle" input_vars_var_compatibility="${input_vars_var_compatibility},effective_radius_of_stratiform_cloud_rain_particle" input_vars_var_compatibility="${input_vars_var_compatibility},effective_radius_of_stratiform_cloud_snow_particle" input_vars_var_compatibility="${input_vars_var_compatibility},flag_indicating_cloud_microphysics_has_graupel" +input_vars_var_compatibility="${input_vars_var_compatibility},flag_indicating_cloud_microphysics_has_ice" input_vars_var_compatibility="${input_vars_var_compatibility},horizontal_dimension" input_vars_var_compatibility="${input_vars_var_compatibility},horizontal_loop_begin" input_vars_var_compatibility="${input_vars_var_compatibility},horizontal_loop_end" input_vars_var_compatibility="${input_vars_var_compatibility},vertical_layer_dimension" output_vars_var_compatibility="ccpp_error_code,ccpp_error_message" +output_vars_var_compatibility="${output_vars_var_compatibility},cloud_ice_number_concentration" output_vars_var_compatibility="${output_vars_var_compatibility},effective_radius_of_stratiform_cloud_ice_particle" output_vars_var_compatibility="${output_vars_var_compatibility},effective_radius_of_stratiform_cloud_liquid_water_particle" output_vars_var_compatibility="${output_vars_var_compatibility},effective_radius_of_stratiform_cloud_snow_particle" + ## ## Run a database report and check the return string ## $1 is the report program file diff --git a/test/var_compatibility_test/test_host.F90 b/test/var_compatibility_test/test_host.F90 index 12215bd1..22964197 100644 --- a/test/var_compatibility_test/test_host.F90 +++ b/test/var_compatibility_test/test_host.F90 @@ -351,21 +351,24 @@ program test character(len=cs), target :: test_parts1(1) = (/ 'radiation ' /) - character(len=cm), target :: test_invars1(5) = (/ & + character(len=cm), target :: test_invars1(7) = (/ & 'effective_radius_of_stratiform_cloud_rain_particle ', & 'effective_radius_of_stratiform_cloud_liquid_water_particle', & 'effective_radius_of_stratiform_cloud_snow_particle ', & 'effective_radius_of_stratiform_cloud_graupel ', & - 'flag_indicating_cloud_microphysics_has_graupel '/) + 'cloud_graupel_number_concentration ', & + 'flag_indicating_cloud_microphysics_has_graupel ', & + 'flag_indicating_cloud_microphysics_has_ice '/) - character(len=cm), target :: test_outvars1(5) = (/ & + character(len=cm), target :: test_outvars1(6) = (/ & 'ccpp_error_code ', & 'ccpp_error_message ', & 'effective_radius_of_stratiform_cloud_ice_particle ', & 'effective_radius_of_stratiform_cloud_liquid_water_particle', & - 'effective_radius_of_stratiform_cloud_snow_particle ' /) + 'effective_radius_of_stratiform_cloud_snow_particle ', & + 'cloud_ice_number_concentration '/) - character(len=cm), target :: test_reqvars1(8) = (/ & + character(len=cm), target :: test_reqvars1(11) = (/ & 'ccpp_error_code ', & 'ccpp_error_message ', & 'effective_radius_of_stratiform_cloud_rain_particle ', & @@ -373,7 +376,10 @@ program test 'effective_radius_of_stratiform_cloud_liquid_water_particle', & 'effective_radius_of_stratiform_cloud_snow_particle ', & 'effective_radius_of_stratiform_cloud_graupel ', & - 'flag_indicating_cloud_microphysics_has_graupel '/) + 'cloud_graupel_number_concentration ', & + 'cloud_ice_number_concentration ', & + 'flag_indicating_cloud_microphysics_has_graupel ', & + 'flag_indicating_cloud_microphysics_has_ice '/) type(suite_info) :: test_suites(1) logical :: run_okay diff --git a/test/var_compatibility_test/test_host_data.F90 b/test/var_compatibility_test/test_host_data.F90 index b17fb916..5e1b1eda 100644 --- a/test/var_compatibility_test/test_host_data.F90 +++ b/test/var_compatibility_test/test_host_data.F90 @@ -9,18 +9,21 @@ module test_host_data effrr, & ! effective radius of cloud rain effrl, & ! effective radius of cloud liquid water effri, & ! effective radius of cloud ice - effrg ! effective radius of cloud graupel + effrg, & ! effective radius of cloud graupel + ncg, & ! number concentration of cloud graupel + nci ! number concentration of cloud ice end type physics_state public allocate_physics_state contains - subroutine allocate_physics_state(cols, levels, state, has_graupel) + subroutine allocate_physics_state(cols, levels, state, has_graupel, has_ice) integer, intent(in) :: cols integer, intent(in) :: levels type(physics_state), intent(out) :: state logical, intent(in) :: has_graupel + logical, intent(in) :: has_ice if (allocated(state%effrr)) then deallocate(state%effrr) @@ -32,16 +35,30 @@ subroutine allocate_physics_state(cols, levels, state, has_graupel) end if allocate(state%effrl(cols, levels)) - if (allocated(state%effri)) then - deallocate(state%effri) - end if - allocate(state%effri(cols, levels)) + if (has_ice) then + if (allocated(state%effri)) then + deallocate(state%effri) + end if + allocate(state%effri(cols, levels)) + endif if (has_graupel) then if (allocated(state%effrg)) then deallocate(state%effrg) end if allocate(state%effrg(cols, levels)) + + if (allocated(state%ncg)) then + deallocate(state%ncg) + end if + allocate(state%ncg(cols, levels)) + endif + + if (has_ice) then + if (allocated(state%nci)) then + deallocate(state%nci) + end if + allocate(state%nci(cols, levels)) endif end subroutine allocate_physics_state diff --git a/test/var_compatibility_test/test_host_data.meta b/test/var_compatibility_test/test_host_data.meta index 1d29572c..4e22ae0c 100644 --- a/test/var_compatibility_test/test_host_data.meta +++ b/test/var_compatibility_test/test_host_data.meta @@ -25,6 +25,7 @@ dimensions = (horizontal_dimension,vertical_layer_dimension) type = real kind = kind_phys + active = (flag_indicating_cloud_microphysics_has_ice) [effrg] standard_name = effective_radius_of_stratiform_cloud_graupel long_name = effective radius of cloud graupel in meter @@ -33,3 +34,21 @@ type = real kind = kind_phys active = (flag_indicating_cloud_microphysics_has_graupel) +[ncg] + standard_name = cloud_graupel_number_concentration + long_name = number concentration of cloud graupel + units = kg-1 + dimensions = (horizontal_dimension,vertical_layer_dimension) + type = real + kind = kind_phys + intent = in + active = (flag_indicating_cloud_microphysics_has_graupel) +[nci] + standard_name = cloud_ice_number_concentration + long_name = number concentration of cloud ice + units = kg-1 + dimensions = (horizontal_dimension,vertical_layer_dimension) + type = real + kind = kind_phys + intent = in + active = (flag_indicating_cloud_microphysics_has_ice) \ No newline at end of file diff --git a/test/var_compatibility_test/test_host_mod.F90 b/test/var_compatibility_test/test_host_mod.F90 index 2761d9fa..314b109e 100644 --- a/test/var_compatibility_test/test_host_mod.F90 +++ b/test/var_compatibility_test/test_host_mod.F90 @@ -13,7 +13,8 @@ module test_host_mod integer, parameter :: pver = 4 type(physics_state) :: phys_state real(kind_phys) :: effrs(ncols, pver) - logical, parameter :: mp_has_graupel = .true. + logical, parameter :: has_ice = .true. + logical, parameter :: has_graupel = .true. public :: init_data public :: compare_data @@ -23,13 +24,18 @@ module test_host_mod subroutine init_data() ! Allocate and initialize state - call allocate_physics_state(ncols, pver, phys_state, mp_has_graupel) + call allocate_physics_state(ncols, pver, phys_state, has_graupel, has_ice) phys_state%effrr = 1.0E-3 ! 1000 microns, in meter phys_state%effrl = 1.0E-4 ! 100 microns, in meter - phys_state%effri = 5.0E-5 ! 50 microns, in meter +! phys_state%effri = 5.0E-5 ! 50 microns, in meter effrs = 5.0E-4 ! 500 microns, in meter - if (mp_has_graupel) then + if (has_graupel) then phys_state%effrg = 2.5E-4 ! 250 microns, in meter + phys_state%ncg = 40 + endif + if (has_ice) then + phys_state%effri = 5.0E-5 ! 50 microns, in meter + phys_state%nci = 80 endif end subroutine init_data diff --git a/test/var_compatibility_test/test_host_mod.meta b/test/var_compatibility_test/test_host_mod.meta index 7789dbed..51a2f5c3 100644 --- a/test/var_compatibility_test/test_host_mod.meta +++ b/test/var_compatibility_test/test_host_mod.meta @@ -28,7 +28,13 @@ dimensions = (horizontal_dimension,vertical_layer_dimension) type = real kind = kind_phys -[mp_has_graupel] +[has_ice] + standard_name = flag_indicating_cloud_microphysics_has_ice + long_name = flag indicating that the cloud microphysics produces ice + units = flag + dimensions = () + type = logical +[has_graupel] standard_name = flag_indicating_cloud_microphysics_has_graupel long_name = flag indicating that the cloud microphysics produces graupel units = flag diff --git a/test/var_compatibility_test/test_reports.py b/test/var_compatibility_test/test_reports.py index a56457bc..fa65f1b9 100755 --- a/test/var_compatibility_test/test_reports.py +++ b/test/var_compatibility_test/test_reports.py @@ -72,11 +72,14 @@ def usage(errmsg=None): "effective_radius_of_stratiform_cloud_rain_particle", "effective_radius_of_stratiform_cloud_snow_particle", "effective_radius_of_stratiform_cloud_graupel", - "flag_indicating_cloud_microphysics_has_graupel"] + "cloud_graupel_number_concentration", + "flag_indicating_cloud_microphysics_has_graupel", + "flag_indicating_cloud_microphysics_has_ice"] _OUTPUT_VARS_VAR_ACTION = ["ccpp_error_code", "ccpp_error_message", "effective_radius_of_stratiform_cloud_ice_particle", "effective_radius_of_stratiform_cloud_liquid_water_particle", - "effective_radius_of_stratiform_cloud_snow_particle"] + "effective_radius_of_stratiform_cloud_snow_particle", + "cloud_ice_number_concentration"] _REQUIRED_VARS_VAR_ACTION = _INPUT_VARS_VAR_ACTION + _OUTPUT_VARS_VAR_ACTION def fields_string(field_type, field_list, sep): From aa596721956b36756c45816a95e343cb55ff5928 Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Thu, 8 Feb 2024 18:33:37 +0000 Subject: [PATCH 07/19] Added null pointers to caps when scheme has optional argument, but not provided by host --- scripts/suite_objects.py | 130 ++++++++++++++++----- test/var_compatibility_test/effr_calc.F90 | 3 +- test/var_compatibility_test/effr_calc.meta | 9 ++ 3 files changed, 112 insertions(+), 30 deletions(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 5ea349a6..b2b6c5c8 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1237,7 +1237,7 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): # end if # Is this a conditionally allocated variable? - # If so, declare local target and pointer varaibles. This is needed to + # If so, declare localpointer varaible. This is needed to # pass inactive (not present) status through the caps. if var.get_prop_value('optional'): newvar_ptr = var.clone(var.get_prop_value('local_name')+'_ptr') @@ -1538,9 +1538,9 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er tmp_indent = indent + 1 outfile.write(f"if {conditional} then", indent) # end if - outfile.write(f"! Assign lower/upper bounds of {local_name} to {internal_var_lname}", tmp_indent+1) - outfile.write(f"{internal_var_lname} = {local_name}{lbound_string}", tmp_indent+1) - outfile.write(f"{internal_var_lname} = {local_name}{ubound_string}", tmp_indent+1) + outfile.write(f"! Assign lower/upper bounds of {local_name} to {internal_var_lname}", tmp_indent) + outfile.write(f"{internal_var_lname} = {local_name}{lbound_string}", tmp_indent) + outfile.write(f"{internal_var_lname} = {local_name}{ubound_string}", tmp_indent) if conditional != '.true.': outfile.write(f"end if", indent) # end if @@ -1548,21 +1548,7 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er def associate_optional_var(self, dict_var, var, var_ptr, has_transform, cldicts, indent, outfile): """Write local pointer association for optional variables.""" - (conditional, _) = dict_var.conditional(cldicts) - if (has_transform): - lname = var.get_prop_value('local_name')+'_local' - else: - lname = var.get_prop_value('local_name') - # end if - lname_ptr = var_ptr.get_prop_value('local_name') - outfile.write(f"if {conditional} then", indent) - outfile.write(f"{lname_ptr} => {lname}", indent+1) - outfile.write(f"end if", indent) - - def assign_pointer_to_var(self, dict_var, var, var_ptr, has_transform, cldicts, indent, outfile): - """Assign local pointer to variable.""" - intent = var.get_prop_value('intent') - if (intent == 'out' or intent == 'inout'): + if (dict_var): (conditional, _) = dict_var.conditional(cldicts) if (has_transform): lname = var.get_prop_value('local_name')+'_local' @@ -1571,8 +1557,27 @@ def assign_pointer_to_var(self, dict_var, var, var_ptr, has_transform, cldicts, # end if lname_ptr = var_ptr.get_prop_value('local_name') outfile.write(f"if {conditional} then", indent) - outfile.write(f"{lname} = {lname_ptr}", indent+1) + outfile.write(f"{lname_ptr} => {lname}", indent+1) outfile.write(f"end if", indent) + # end if + + def assign_pointer_to_var(self, dict_var, var, var_ptr, has_transform, cldicts, indent, outfile): + """Assign local pointer to variable.""" + if (dict_var): + intent = var.get_prop_value('intent') + if (intent == 'out' or intent == 'inout'): + (conditional, _) = dict_var.conditional(cldicts) + if (has_transform): + lname = var.get_prop_value('local_name')+'_local' + else: + lname = var.get_prop_value('local_name') + # end if + lname_ptr = var_ptr.get_prop_value('local_name') + outfile.write(f"if {conditional} then", indent) + outfile.write(f"{lname} = {lname_ptr}", indent+1) + outfile.write(f"end if", indent) + # end if + # end if def add_var_transform(self, var, compat_obj, vert_dim): """Register any variable transformation needed by for this Scheme. @@ -1669,11 +1674,20 @@ def write(self, outfile, errcode, errmsg, indent): # coming from the group's call list) # if self.__var_debug_checks: - outfile.write('! Run debug tests', indent+1) + outfile.write('! ##################################################################', indent+1) + outfile.write('! Begin debug tests', indent+1) + outfile.write('! ##################################################################', indent+1) + outfile.write('', indent+1) # end if for (var, internal_var) in self.__var_debug_checks: stmt = self.write_var_debug_check(var, internal_var, cldicts, outfile, errcode, errmsg, indent+1) # end for + if self.__var_debug_checks: + outfile.write('! ##################################################################', indent+1) + outfile.write('! End debug tests', indent+1) + outfile.write('! ##################################################################', indent+1) + outfile.write('', indent+1) + # end if # # Write any reverse (pre-Scheme) transforms. # @@ -2252,12 +2266,17 @@ def write(self, outfile, host_arglist, indent, const_mod, # end if # Collect information on local variables subpart_vars = {} + subpart_opts = {} + subpart_scl = {} allocatable_var_set = set() + optional_var_set = set() pointer_var_set = list() + inactive_var_set = set() for item in [self]:# + self.parts: for var in item.declarations(): lname = var.get_prop_value('local_name') - if lname in subpart_vars: + sname = var.get_prop_value('standard_name') + if (lname in subpart_vars) or (lname in subpart_opts) or (lname in subpart_scl): if subpart_vars[lname][0].compatible(var, self.run_env): pass # We already are going to declare this variable else: @@ -2266,10 +2285,20 @@ def write(self, outfile, host_arglist, indent, const_mod, # end if else: opt_var = var.get_prop_value('optional') - subpart_vars[lname] = (var, item, opt_var) dims = var.get_dimensions() if (dims is not None) and dims: - allocatable_var_set.add(lname) + if opt_var: + if (self.call_list.find_variable(standard_name=sname)): + subpart_opts[lname] = (var, item, opt_var) + optional_var_set.add(lname) + else: + inactive_var_set.add(var) + else: + subpart_vars[lname] = (var, item, opt_var) + allocatable_var_set.add(lname) + # end if + else: + subpart_scl[lname] = (var, item, opt_var) # end if # end if # end for @@ -2294,6 +2323,25 @@ def write(self, outfile, host_arglist, indent, const_mod, pointer_var_set.append([name,kind,dimstr,vtype]) # end if # end for + # Any optional arguments that are not requested by the host need to have + # a local null pointer passed from the group to the scheme. + for ivar in inactive_var_set: + name = ivar.get_prop_value('local_name')+'_ptr' + kind = ivar.get_prop_value('kind') + dims = ivar.get_dimensions() + if ivar.is_ddt(): + vtype = 'type' + else: + vtype = ivar.get_prop_value('type') + # end if + if dims: + dimstr = '(:' + ',:'*(len(dims) - 1) + ')' + else: + dimstr = '' + # end if + pointer_var_set.append([name,kind,dimstr,vtype]) + # end for + # end for # First, write out the subroutine header subname = self.name @@ -2330,10 +2378,19 @@ def write(self, outfile, host_arglist, indent, const_mod, self.run_env.logger.debug(msg.format(self.name, call_vars)) # end if self.call_list.declare_variables(outfile, indent+1, dummy=True) + # DECLARE local variables if subpart_vars: outfile.write('\n! Local Variables', indent+1) # end if - # Write out local variables + # Scalars + for key in subpart_scl: + var = subpart_scl[key][0] + spdict = subpart_scl[key][1] + target = subpart_scl[key][2] + var.write_def(outfile, indent+1, spdict, + allocatable=False, target=target) + # end for + # Allocatable arrays for key in subpart_vars: var = subpart_vars[key][0] spdict = subpart_vars[key][1] @@ -2342,10 +2399,16 @@ def write(self, outfile, host_arglist, indent, const_mod, allocatable=(key in allocatable_var_set), target=target) # end for - # Write out local pointer variables. - if pointer_var_set: - outfile.write('\n! Local Pointer variables', indent+1) - # end if + # Target arrays. + for key in subpart_opts: + var = subpart_opts[key][0] + spdict = subpart_opts[key][1] + target = subpart_opts[key][2] + var.write_def(outfile, indent+1, spdict, + allocatable=(key in optional_var_set), + target=target) + # end for + # Pointer variables. for (name, kind, dim, vtype) in pointer_var_set: var.write_ptr_def(outfile, indent+1, name, kind, dim, vtype) # end for @@ -2395,6 +2458,12 @@ def write(self, outfile, host_arglist, indent, const_mod, alloc_str = self.allocate_dim_str(dims, var.context) outfile.write(alloc_stmt.format(lname, alloc_str), indent+1) # end for + for lname in optional_var_set: + var = subpart_opts[lname][0] + dims = var.get_dimensions() + alloc_str = self.allocate_dim_str(dims, var.context) + outfile.write(alloc_stmt.format(lname, alloc_str), indent+1) + # end for # Allocate suite vars if allocate: outfile.write('\n! Allocate suite_vars', indent+1) @@ -2429,6 +2498,9 @@ def write(self, outfile, host_arglist, indent, const_mod, for lname in allocatable_var_set: outfile.write('deallocate({})'.format(lname), indent+1) # end for + for lname in optional_var_set: + outfile.write('deallocate({})'.format(lname), indent+1) + # end for # Nullify local pointers if pointer_var_set: outfile.write('\n! Nullify local pointers', indent+1) diff --git a/test/var_compatibility_test/effr_calc.F90 b/test/var_compatibility_test/effr_calc.F90 index 1d9cb787..70ccc511 100644 --- a/test/var_compatibility_test/effr_calc.F90 +++ b/test/var_compatibility_test/effr_calc.F90 @@ -16,7 +16,7 @@ module effr_calc !! \htmlinclude arg_table_effr_calc_run.html !! subroutine effr_calc_run(ncol, nlev, effrr_in, effrg_in, ncg_in, nci_out, & - effrl_inout, effri_out, effrs_inout, errmsg, errflg) + effrl_inout, effri_out, effrs_inout, ncl_out, errmsg, errflg) integer, intent(in) :: ncol integer, intent(in) :: nlev @@ -27,6 +27,7 @@ subroutine effr_calc_run(ncol, nlev, effrr_in, effrg_in, ncg_in, nci_out, & real(kind_phys), intent(inout) :: effrl_inout(:,:) real(kind_phys), intent(out),optional :: effri_out(:,:) real(8),intent(inout) :: effrs_inout(:,:) + real(kind_phys), intent(out),optional :: ncl_out(:,:) character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg !---------------------------------------------------------------- diff --git a/test/var_compatibility_test/effr_calc.meta b/test/var_compatibility_test/effr_calc.meta index aea9981f..97fab3c0 100644 --- a/test/var_compatibility_test/effr_calc.meta +++ b/test/var_compatibility_test/effr_calc.meta @@ -79,6 +79,15 @@ kind = 8 intent = inout top_at_one = .true. +[ncl_out] + standard_name = cloud_liquid_number_concentration + long_name = number concentration of cloud liquid + units = kg-1 + dimensions = (horizontal_loop_extent,vertical_layer_dimension) + type = real + kind = kind_phys + intent = out + optional=.true. [ errmsg ] standard_name = ccpp_error_message long_name = Error message for error handling in CCPP From 7b2026543e65d8aa67669658fe568a0483007447 Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Mon, 12 Feb 2024 21:30:34 +0000 Subject: [PATCH 08/19] Address reviewers comments. Add whitespace to autogenerated code for readability --- scripts/metavar.py | 59 ++++++++++------- scripts/suite_objects.py | 63 ++++++++++--------- scripts/var_props.py | 6 +- test/var_compatibility_test/run_test | 2 +- .../test_host_data.meta | 2 +- 5 files changed, 76 insertions(+), 56 deletions(-) diff --git a/scripts/metavar.py b/scripts/metavar.py index 2b126f7c..a1f41b8c 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -1084,9 +1084,10 @@ def write_def(self, outfile, indent, wdict, allocatable=False, target=None, intent_str = 'intent(in) ' elif allocatable: if dimstr or polymorphic: - intent_str = 'allocatable ' + intent_str = 'allocatable ' if target: - intent_str += ',target ' + intent_str = 'allocatable,' + intent_str += ' target' else: intent_str = ' '*13 # end if @@ -1096,12 +1097,12 @@ def write_def(self, outfile, indent, wdict, allocatable=False, target=None, intent_str = f"allocatable, intent({intent})" elif optional: intent_str = f"intent({intent}),{' '*(5 - len(intent))}" - intent_str += 'target, optional ' + intent_str += 'target, optional ' else: intent_str = f"intent({intent}){' '*(5 - len(intent))}" # end if elif not dummy: - intent_str = '' + intent_str = ' '*20 else: intent_str = ' '*13 # end if @@ -1119,36 +1120,39 @@ def write_def(self, outfile, indent, wdict, allocatable=False, target=None, extra_space -= len(targ) if self.is_ddt(): if polymorphic: - dstr = "class({kind}){cspc}{intent} :: {name}{dims} ! {sname}" - cspc = comma + ' '*(extra_space + 12 - len(kind)) + dstr = "class({kind}){cspace}{intent} :: {name}{dims}" + cspace = comma + ' '*(extra_space + 12 - len(kind)) else: - dstr = "type({kind}){cspc}{intent} :: {name}{dims} ! {sname}" - cspc = comma + ' '*(extra_space + 13 - len(kind)) + dstr = "type({kind}){cspace}{intent} :: {name}{dims}" + cspace = comma + ' '*(extra_space + 13 - len(kind)) # end if else: if kind: - dstr = "{type}({kind}){cspc}{intent} :: {name}{dims} ! {sname}" - cspc = comma + ' '*(extra_space + 17 - len(vtype) - len(kind)) + dstr = "{type}({kind}){cspace}{intent} :: {name}{dims}" + cspace = comma + ' '*(extra_space + 17 - len(vtype) - len(kind)) else: - dstr = "{type}{cspc}{intent} :: {name}{dims} ! {sname}" - cspc = comma + ' '*(extra_space + 19 - len(vtype)) + dstr = "{type}{cspace}{intent} :: {name}{dims}" + cspace = 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, + name=name, dims=dimstr, cspace=cspace, sname=stdname), indent) def write_ptr_def(self, outfile, indent, name, kind, dimstr, vtype, extra_space=0): - comma = ',' + """Write the definition line for local null pointer declaration to .""" + comma = ', ' if kind: - dstr = "{type}({kind}){cspc}pointer :: {name}{dims} => null()" - cspc = comma + ' '*(extra_space + 20 - len(vtype) - len(kind)) + dstr = "{type}({kind}){cspace}pointer :: {name}{dims}{cspace2} => null()" + cspace = comma + ' '*(extra_space + 20 - len(vtype) - len(kind)) + cspace2 = ' '*(20 -len(name) - len(dimstr)) else: - dstr = "{type}{cspc}pointer :: {name}{dims} => null()" - cspc = comma + ' '*(extra_space + 22 - len(vtype)) + dstr = "{type}{cspace}pointer :: {name}{dims}{cspace2} => null()" + cspace = comma + ' '*(extra_space + 22 - len(vtype)) + cspace2 = ' '*(20 -len(name) - len(dimstr)) # end if outfile.write(dstr.format(type=vtype, kind=kind, name=name, dims=dimstr, - cspc=cspc), indent) + cspace=cspace, cspace2=cspace2), indent) def is_ddt(self): """Return True iff is a DDT type.""" @@ -1888,8 +1892,21 @@ def declare_variables(self, outfile, indent, dummy=False, any_scope=False) if self.include_var_in_list(var, std_vars=std_vars, loop_vars=loop_vars, consts=consts): - self[standard_name].write_def(outfile, indent, self, - dummy=dummy) + if (not var.get_prop_value('optional')): + self[standard_name].write_def(outfile, indent, self, + dummy=dummy) + # end if + # end if + # end for + for standard_name in self.keys(): + var = self.find_variable(standard_name=standard_name, + any_scope=False) + if self.include_var_in_list(var, std_vars=std_vars, + loop_vars=loop_vars, consts=consts): + if (var.get_prop_value('optional')): + self[standard_name].write_def(outfile, indent, self, + dummy=dummy) + # end if # end if # end for diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index b2b6c5c8..b2a48652 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1718,11 +1718,11 @@ def write(self, outfile, errcode, errmsg, indent): # # Copy any local pointers. # - ptrct=True + first_ptr_declaration=True for (dict_var, var, var_ptr, has_transform) in self.__optional_vars: - if ptrct: + if first_ptr_declaration: outfile.write('! Copy any local pointers to dummy/local variables', indent+1) - ptrct=False + first_ptr_declaration=False # end if tstmt = self.assign_pointer_to_var(dict_var, var, var_ptr, has_transform, cldicts, indent+1, outfile) # end for @@ -2265,9 +2265,9 @@ def write(self, outfile, host_arglist, indent, const_mod, group_type = 'run' # Allocate for entire run # end if # Collect information on local variables - subpart_vars = {} - subpart_opts = {} - subpart_scl = {} + subpart_allocate_vars = {} + subpart_optional_vars = {} + subpart_scalar_vars = {} allocatable_var_set = set() optional_var_set = set() pointer_var_set = list() @@ -2276,8 +2276,8 @@ def write(self, outfile, host_arglist, indent, const_mod, for var in item.declarations(): lname = var.get_prop_value('local_name') sname = var.get_prop_value('standard_name') - if (lname in subpart_vars) or (lname in subpart_opts) or (lname in subpart_scl): - if subpart_vars[lname][0].compatible(var, self.run_env): + if (lname in subpart_allocate_vars) or (lname in subpart_optional_vars) or (lname in subpart_scalar_vars): + if subpart_allocate_vars[lname][0].compatible(var, self.run_env): pass # We already are going to declare this variable else: errmsg = "Duplicate Group variable, {}" @@ -2289,16 +2289,16 @@ def write(self, outfile, host_arglist, indent, const_mod, if (dims is not None) and dims: if opt_var: if (self.call_list.find_variable(standard_name=sname)): - subpart_opts[lname] = (var, item, opt_var) + subpart_optional_vars[lname] = (var, item, opt_var) optional_var_set.add(lname) else: inactive_var_set.add(var) else: - subpart_vars[lname] = (var, item, opt_var) + subpart_allocate_vars[lname] = (var, item, opt_var) allocatable_var_set.add(lname) # end if else: - subpart_scl[lname] = (var, item, opt_var) + subpart_scalar_vars[lname] = (var, item, opt_var) # end if # end if # end for @@ -2366,7 +2366,7 @@ def write(self, outfile, host_arglist, indent, const_mod, call_vars = self.call_list.variable_list() self._ddt_library.write_ddt_use_statements(call_vars, outfile, indent+1, pad=modmax) - decl_vars = [x[0] for x in subpart_vars.values()] + decl_vars = [x[0] for x in subpart_allocate_vars.values()] self._ddt_library.write_ddt_use_statements(decl_vars, outfile, indent+1, pad=modmax) outfile.write('', 0) @@ -2379,36 +2379,36 @@ def write(self, outfile, host_arglist, indent, const_mod, # end if self.call_list.declare_variables(outfile, indent+1, dummy=True) # DECLARE local variables - if subpart_vars: + if subpart_allocate_vars: outfile.write('\n! Local Variables', indent+1) # end if # Scalars - for key in subpart_scl: - var = subpart_scl[key][0] - spdict = subpart_scl[key][1] - target = subpart_scl[key][2] + for key in subpart_scalar_vars: + var = subpart_scalar_vars[key][0] + spdict = subpart_scalar_vars[key][1] + target = subpart_scalar_vars[key][2] var.write_def(outfile, indent+1, spdict, allocatable=False, target=target) # end for # Allocatable arrays - for key in subpart_vars: - var = subpart_vars[key][0] - spdict = subpart_vars[key][1] - target = subpart_vars[key][2] + for key in subpart_allocate_vars: + var = subpart_allocate_vars[key][0] + spdict = subpart_allocate_vars[key][1] + target = subpart_allocate_vars[key][2] var.write_def(outfile, indent+1, spdict, allocatable=(key in allocatable_var_set), target=target) # end for # Target arrays. - for key in subpart_opts: - var = subpart_opts[key][0] - spdict = subpart_opts[key][1] - target = subpart_opts[key][2] + for key in subpart_optional_vars: + var = subpart_optional_vars[key][0] + spdict = subpart_optional_vars[key][1] + target = subpart_optional_vars[key][2] var.write_def(outfile, indent+1, spdict, allocatable=(key in optional_var_set), target=target) # end for - # Pointer variables. + # Pointer variables for (name, kind, dim, vtype) in pointer_var_set: var.write_ptr_def(outfile, indent+1, name, kind, dim, vtype) # end for @@ -2453,13 +2453,13 @@ def write(self, outfile, host_arglist, indent, const_mod, outfile.write('\n! Allocate local arrays', indent+1) alloc_stmt = "allocate({}({}))" for lname in allocatable_var_set: - var = subpart_vars[lname][0] + var = subpart_allocate_vars[lname][0] dims = var.get_dimensions() alloc_str = self.allocate_dim_str(dims, var.context) outfile.write(alloc_stmt.format(lname, alloc_str), indent+1) # end for for lname in optional_var_set: - var = subpart_opts[lname][0] + var = subpart_optional_vars[lname][0] dims = var.get_dimensions() alloc_str = self.allocate_dim_str(dims, var.context) outfile.write(alloc_stmt.format(lname, alloc_str), indent+1) @@ -2496,17 +2496,18 @@ def write(self, outfile, host_arglist, indent, const_mod, outfile.write('\n! Deallocate local arrays', indent+1) # end if for lname in allocatable_var_set: - outfile.write('deallocate({})'.format(lname), indent+1) + outfile.write('if (allocated({})) {} deallocate({})'.format(lname,' '*(20-len(lname)),lname), indent+1) # end for for lname in optional_var_set: - outfile.write('deallocate({})'.format(lname), indent+1) + outfile.write('if (allocated({})) {} deallocate({})'.format(lname,' '*(20-len(lname)),lname), indent+1) # end for # Nullify local pointers if pointer_var_set: outfile.write('\n! Nullify local pointers', indent+1) # end if for (name, kind, dim, vtype) in pointer_var_set: - outfile.write('nullify({})'.format(name), indent+1) + #cspace = ' '*(15-len(name)) + outfile.write('if (associated({})) {} nullify({})'.format(name,' '*(15-len(name)),name), indent+1) # Deallocate suite vars if deallocate: for svar in suite_vars.variable_list(): diff --git a/scripts/var_props.py b/scripts/var_props.py index c61d9ae4..cfbf27f5 100755 --- a/scripts/var_props.py +++ b/scripts/var_props.py @@ -983,7 +983,8 @@ def forward_transform(self, lvar_lname, rvar_lname, rvar_indices, lvar_indices, # end if rhs_term = self.__unit_transforms[0].format(var=rhs_term, kind=kind) # end if - return f"{lhs_term} = {rhs_term}" + cspace = ' '*(24-len(lhs_term)) + return f"{lhs_term}{cspace} = {rhs_term}" def reverse_transform(self, lvar_lname, rvar_lname, rvar_indices, lvar_indices, adjust_hdim=None, flip_vdim=None): @@ -1023,7 +1024,8 @@ def reverse_transform(self, lvar_lname, rvar_lname, rvar_indices, lvar_indices, # end if rhs_term = self.__unit_transforms[1].format(var=rhs_term, kind=kind) # end if - return f"{lhs_term} = {rhs_term}" + cspace = ' '*(30-len(lhs_term)) + return f"{lhs_term}{cspace} = {rhs_term}" def _get_kind_convstrs(self, var1_kind, var2_kind, run_env): """Attempt to determine if no transformation is required (i.e., if diff --git a/test/var_compatibility_test/run_test b/test/var_compatibility_test/run_test index 5bc4b4de..fbf2241e 100755 --- a/test/var_compatibility_test/run_test +++ b/test/var_compatibility_test/run_test @@ -48,7 +48,7 @@ docleanup() { echo "WARNING: Cannot clean ${build_dir}" else cd ${currdir} -# rm -rf ${build_dir} + rm -rf ${build_dir} fi } diff --git a/test/var_compatibility_test/test_host_data.meta b/test/var_compatibility_test/test_host_data.meta index 4e22ae0c..fbe78dbe 100644 --- a/test/var_compatibility_test/test_host_data.meta +++ b/test/var_compatibility_test/test_host_data.meta @@ -51,4 +51,4 @@ type = real kind = kind_phys intent = in - active = (flag_indicating_cloud_microphysics_has_ice) \ No newline at end of file + active = (flag_indicating_cloud_microphysics_has_ice) From d7bad295eefa03fccbcf043fea7b2a5fed1bbabd Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Mon, 12 Feb 2024 21:37:04 +0000 Subject: [PATCH 09/19] Revert some changes --- scripts/var_props.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/var_props.py b/scripts/var_props.py index cfbf27f5..c61d9ae4 100755 --- a/scripts/var_props.py +++ b/scripts/var_props.py @@ -983,8 +983,7 @@ def forward_transform(self, lvar_lname, rvar_lname, rvar_indices, lvar_indices, # end if rhs_term = self.__unit_transforms[0].format(var=rhs_term, kind=kind) # end if - cspace = ' '*(24-len(lhs_term)) - return f"{lhs_term}{cspace} = {rhs_term}" + return f"{lhs_term} = {rhs_term}" def reverse_transform(self, lvar_lname, rvar_lname, rvar_indices, lvar_indices, adjust_hdim=None, flip_vdim=None): @@ -1024,8 +1023,7 @@ def reverse_transform(self, lvar_lname, rvar_lname, rvar_indices, lvar_indices, # end if rhs_term = self.__unit_transforms[1].format(var=rhs_term, kind=kind) # end if - cspace = ' '*(30-len(lhs_term)) - return f"{lhs_term}{cspace} = {rhs_term}" + return f"{lhs_term} = {rhs_term}" def _get_kind_convstrs(self, var1_kind, var2_kind, run_env): """Attempt to determine if no transformation is required (i.e., if From 9337a2e1d81a951390ad55066f84ce91b6ef9a81 Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Mon, 12 Feb 2024 21:42:29 +0000 Subject: [PATCH 10/19] Add optional attribute to capgen test --- test/capgen_test/temp_adjust.F90 | 4 ++-- test/capgen_test/temp_adjust.meta | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/capgen_test/temp_adjust.F90 b/test/capgen_test/temp_adjust.F90 index 356a86d1..5aba4c0b 100644 --- a/test/capgen_test/temp_adjust.F90 +++ b/test/capgen_test/temp_adjust.F90 @@ -22,7 +22,7 @@ subroutine temp_adjust_run(foo, timestep, temp_prev, temp_layer, qv, ps, & integer, intent(in) :: foo real(kind_phys), intent(in) :: timestep - real(kind_phys), intent(inout) :: qv(:) + real(kind_phys), intent(inout),optional :: qv(:) real(kind_phys), intent(inout) :: ps(:) REAL(kind_phys), intent(in) :: temp_prev(:) REAL(kind_phys), intent(inout) :: temp_layer(foo) @@ -42,7 +42,7 @@ subroutine temp_adjust_run(foo, timestep, temp_prev, temp_layer, qv, ps, & 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 + if (present(qv)) qv(col_index) = qv(col_index) + 1.0_kind_phys end do if (present(innie) .and. present(outie) .and. present(optsie)) then outie = innie * optsie diff --git a/test/capgen_test/temp_adjust.meta b/test/capgen_test/temp_adjust.meta index fc03a434..17eabcdb 100644 --- a/test/capgen_test/temp_adjust.meta +++ b/test/capgen_test/temp_adjust.meta @@ -41,6 +41,7 @@ kind = kind_phys intent = inout diagnostic_name_fixed = Q + optional = True [ ps ] standard_name = surface_air_pressure state_variable = true From 3e4f88f66ba22d4573f8a055cd5c0d2162abd22d Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Mon, 12 Feb 2024 21:48:38 +0000 Subject: [PATCH 11/19] Fix doctests. --- scripts/fortran_tools/parse_fortran.py | 2 +- scripts/metavar.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/fortran_tools/parse_fortran.py b/scripts/fortran_tools/parse_fortran.py index 9aa4de7a..5ad328b9 100644 --- a/scripts/fortran_tools/parse_fortran.py +++ b/scripts/fortran_tools/parse_fortran.py @@ -676,7 +676,7 @@ def parse_fortran_var_decl(line, source, run_env): >>> parse_fortran_var_decl("integer :: foo = 0", ParseSource('foo.F90', 'module', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('local_name') 'foo' >>> parse_fortran_var_decl("integer :: foo", ParseSource('foo.F90', 'module', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('optional') - + 'False' >>> parse_fortran_var_decl("integer, optional :: foo", ParseSource('foo.F90', 'module', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('optional') 'True' >>> parse_fortran_var_decl("integer, dimension(:) :: foo", ParseSource('foo.F90', 'module', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('dimensions') diff --git a/scripts/metavar.py b/scripts/metavar.py index a1f41b8c..4669ca24 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -151,8 +151,7 @@ class Var: Traceback (most recent call last): ParseSyntaxError: Invalid intent variable property, 'ino', at :1 >>> Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in', 'optional' : 'false'}, ParseSource('vname', 'SCHEME', ParseContext()), _MVAR_DUMMY_RUN_ENV) #doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ParseSyntaxError: Invalid variable property name, 'optional', at :1 + # Check that two variables that differ in their units - m vs km - are compatible >>> Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm', \ 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, \ From 80da2a58a5c524cb3a4843dde54383961e4b40a7 Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Mon, 12 Feb 2024 21:59:03 +0000 Subject: [PATCH 12/19] Fix doctest again --- scripts/metavar.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/metavar.py b/scripts/metavar.py index 4669ca24..ce6252ef 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -151,7 +151,8 @@ class Var: Traceback (most recent call last): ParseSyntaxError: Invalid intent variable property, 'ino', at :1 >>> Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in', 'optional' : 'false'}, ParseSource('vname', 'SCHEME', ParseContext()), _MVAR_DUMMY_RUN_ENV) #doctest: +IGNORE_EXCEPTION_DETAIL - + + # Check that two variables that differ in their units - m vs km - are compatible >>> Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm', \ 'dimensions' : '()', 'type' : 'real', 'intent' : 'in'}, \ From c34496fcd147a51e6991a463cc179239d54b6c14 Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Mon, 12 Feb 2024 22:13:08 +0000 Subject: [PATCH 13/19] Fix doctest again again --- scripts/metavar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/metavar.py b/scripts/metavar.py index ce6252ef..62aadbb7 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -151,7 +151,7 @@ class Var: Traceback (most recent call last): ParseSyntaxError: Invalid intent variable property, 'ino', at :1 >>> Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in', 'optional' : 'false'}, ParseSource('vname', 'SCHEME', ParseContext()), _MVAR_DUMMY_RUN_ENV) #doctest: +IGNORE_EXCEPTION_DETAIL - + # Check that two variables that differ in their units - m vs km - are compatible >>> Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm', \ From 9125aaeaff132c003f5b28c1fabe455236375dc0 Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Mon, 12 Feb 2024 22:15:55 +0000 Subject: [PATCH 14/19] Fix doctest again again again --- scripts/fortran_tools/parse_fortran.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/fortran_tools/parse_fortran.py b/scripts/fortran_tools/parse_fortran.py index 5ad328b9..e7d3c495 100644 --- a/scripts/fortran_tools/parse_fortran.py +++ b/scripts/fortran_tools/parse_fortran.py @@ -676,7 +676,7 @@ def parse_fortran_var_decl(line, source, run_env): >>> parse_fortran_var_decl("integer :: foo = 0", ParseSource('foo.F90', 'module', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('local_name') 'foo' >>> parse_fortran_var_decl("integer :: foo", ParseSource('foo.F90', 'module', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('optional') - 'False' + False >>> parse_fortran_var_decl("integer, optional :: foo", ParseSource('foo.F90', 'module', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('optional') 'True' >>> parse_fortran_var_decl("integer, dimension(:) :: foo", ParseSource('foo.F90', 'module', ParseContext()), _DUMMY_RUN_ENV)[0].get_prop_value('dimensions') From 34aa7f57186a1df07f93bb6aa87a9805db583a76 Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Thu, 22 Feb 2024 14:53:29 -0700 Subject: [PATCH 15/19] Update scripts/metavar.py Co-authored-by: Dom Heinzeller --- scripts/metavar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/metavar.py b/scripts/metavar.py index 62aadbb7..0b0d2a15 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -150,7 +150,7 @@ class Var: >>> Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'ino'}, ParseSource('vname', 'SCHEME', ParseContext()), _MVAR_DUMMY_RUN_ENV) #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ParseSyntaxError: Invalid intent variable property, 'ino', at :1 - >>> Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in', 'optional' : 'false'}, ParseSource('vname', 'SCHEME', ParseContext()), _MVAR_DUMMY_RUN_ENV) #doctest: +IGNORE_EXCEPTION_DETAIL + >>> Var({'local_name' : 'foo', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '()', 'type' : 'real', 'intent' : 'in', 'optional' : 'false'}, ParseSource('vname', 'SCHEME', ParseContext()), _MVAR_DUMMY_RUN_ENV) #doctest: +ELLIPSIS # Check that two variables that differ in their units - m vs km - are compatible From adad65090aff42033b3877e14859e78fa52d6ba8 Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Mon, 4 Mar 2024 22:01:51 +0000 Subject: [PATCH 16/19] Address reviewers comments --- scripts/ccpp_capgen.py | 26 +++++++++------------- scripts/metavar.py | 19 +++------------- scripts/suite_objects.py | 4 +++- test/var_compatibility_test/effr_calc.meta | 14 ++++++------ 4 files changed, 24 insertions(+), 39 deletions(-) diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index 46f731c1..042f6d16 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -293,21 +293,6 @@ def compare_fheader_to_mheader(meta_header, fort_header, logger): # end for list_match = mlen == flen # end if - # Check for consistency between optional variables in metadata and - # optional variables in fortran. Error if optional attribute is - # missing from fortran declaration. - for mind, mvar in enumerate(mlist): - mopt = mvar.get_prop_value('optional') - mname = mvar.get_prop_value('local_name') - fvar, find = find_var_in_list(mname, flist) - if find and mopt: - fopt = fvar.get_prop_value('optional') - if (not fopt): - errmsg = 'Missing optional attribute in fortran declaration for variable {}, in file {}' - errors_found = add_error(errors_found, errmsg.format(mname,title)) - # end if - # end if - # end for if not list_match: if fht in _EXTRA_VARIABLE_TABLE_TYPES: if flen > mlen: @@ -329,6 +314,17 @@ def compare_fheader_to_mheader(meta_header, fort_header, logger): lname = mvar.get_prop_value('local_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. + mopt = mvar.get_prop_value('optional') + if find and mopt: + fopt = fvar.get_prop_value('optional') + if (not fopt): + errmsg = 'Missing optional attribute in fortran declaration for variable {}, in file {}' + errors_found = add_error(errors_found, errmsg.format(mname,title)) + # end if + # end if if mind >= flen: if arrayref: # Array reference, variable not in Fortran table diff --git a/scripts/metavar.py b/scripts/metavar.py index 0b0d2a15..f29692f0 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -1027,7 +1027,7 @@ def conditional(self, vdicts): vars_needed.append(dvar) return (conditional, vars_needed) - def write_def(self, outfile, indent, wdict, allocatable=False, target=None, + def write_def(self, outfile, indent, wdict, allocatable=False, target=False, 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. @@ -1892,21 +1892,8 @@ def declare_variables(self, outfile, indent, dummy=False, any_scope=False) if self.include_var_in_list(var, std_vars=std_vars, loop_vars=loop_vars, consts=consts): - if (not var.get_prop_value('optional')): - self[standard_name].write_def(outfile, indent, self, - dummy=dummy) - # end if - # end if - # end for - for standard_name in self.keys(): - var = self.find_variable(standard_name=standard_name, - any_scope=False) - if self.include_var_in_list(var, std_vars=std_vars, - loop_vars=loop_vars, consts=consts): - if (var.get_prop_value('optional')): - self[standard_name].write_def(outfile, indent, self, - dummy=dummy) - # end if + self[standard_name].write_def(outfile, indent, self, + dummy=dummy) # end if # end for diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index b2a48652..0d2674f8 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -2293,6 +2293,7 @@ def write(self, outfile, host_arglist, indent, const_mod, optional_var_set.add(lname) else: inactive_var_set.add(var) + # end if else: subpart_allocate_vars[lname] = (var, item, opt_var) allocatable_var_set.add(lname) @@ -2379,7 +2380,7 @@ def write(self, outfile, host_arglist, indent, const_mod, # end if self.call_list.declare_variables(outfile, indent+1, dummy=True) # DECLARE local variables - if subpart_allocate_vars: + if subpart_allocate_vars or subpart_scalar_vars or subpart_optional_vars: outfile.write('\n! Local Variables', indent+1) # end if # Scalars @@ -2508,6 +2509,7 @@ def write(self, outfile, host_arglist, indent, const_mod, for (name, kind, dim, vtype) in pointer_var_set: #cspace = ' '*(15-len(name)) outfile.write('if (associated({})) {} nullify({})'.format(name,' '*(15-len(name)),name), indent+1) + # end fo # Deallocate suite vars if deallocate: for svar in suite_vars.variable_list(): diff --git a/test/var_compatibility_test/effr_calc.meta b/test/var_compatibility_test/effr_calc.meta index 97fab3c0..55709d40 100644 --- a/test/var_compatibility_test/effr_calc.meta +++ b/test/var_compatibility_test/effr_calc.meta @@ -25,7 +25,7 @@ type = real kind = kind_phys intent = in - top_at_one = .true. + top_at_one = True [effrg_in] standard_name = effective_radius_of_stratiform_cloud_graupel long_name = effective radius of cloud graupel in micrometer @@ -34,7 +34,7 @@ type = real kind = kind_phys intent = in - optional = .true. + optional = True [ncg_in] standard_name = cloud_graupel_number_concentration long_name = number concentration of cloud graupel @@ -43,7 +43,7 @@ type = real kind = kind_phys intent = in - optional = .true. + optional = True [nci_out] standard_name = cloud_ice_number_concentration long_name = number concentration of cloud ice @@ -52,7 +52,7 @@ type = real kind = kind_phys intent = out - optional = .true. + optional = True [effrl_inout] standard_name = effective_radius_of_stratiform_cloud_liquid_water_particle long_name = effective radius of cloud liquid water particle in micrometer @@ -69,7 +69,7 @@ type = real kind = kind_phys intent = out - optional=.true. + optional = True [effrs_inout] standard_name = effective_radius_of_stratiform_cloud_snow_particle long_name = effective radius of cloud snow particle in micrometer @@ -78,7 +78,7 @@ type = real kind = 8 intent = inout - top_at_one = .true. + top_at_one = True [ncl_out] standard_name = cloud_liquid_number_concentration long_name = number concentration of cloud liquid @@ -87,7 +87,7 @@ type = real kind = kind_phys intent = out - optional=.true. + optional = True [ errmsg ] standard_name = ccpp_error_message long_name = Error message for error handling in CCPP From 00a905fcf258c94383e0a6a2ffbab9f598907303 Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Wed, 6 Mar 2024 10:03:04 -0700 Subject: [PATCH 17/19] Update scripts/suite_objects.py Co-authored-by: goldy <1588651+gold2718@users.noreply.github.com> --- scripts/suite_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 0d2674f8..017b4528 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1675,7 +1675,7 @@ def write(self, outfile, errcode, errmsg, indent): # if self.__var_debug_checks: outfile.write('! ##################################################################', indent+1) - outfile.write('! Begin debug tests', indent+1) + outfile.comment('Begin debug tests', indent+1) outfile.write('! ##################################################################', indent+1) outfile.write('', indent+1) # end if From b4991bef94955680f32505b69f75dc2e05c9ea24 Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Wed, 6 Mar 2024 10:03:19 -0700 Subject: [PATCH 18/19] Update scripts/suite_objects.py Co-authored-by: goldy <1588651+gold2718@users.noreply.github.com> --- scripts/suite_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 017b4528..def72565 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1684,7 +1684,7 @@ def write(self, outfile, errcode, errmsg, indent): # end for if self.__var_debug_checks: outfile.write('! ##################################################################', indent+1) - outfile.write('! End debug tests', indent+1) + outfile.comment('End debug tests', indent+1) outfile.write('! ##################################################################', indent+1) outfile.write('', indent+1) # end if From b0ad2df402c34687244d787470131f92edab50cd Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Wed, 6 Mar 2024 10:03:33 -0700 Subject: [PATCH 19/19] Update scripts/suite_objects.py Co-authored-by: goldy <1588651+gold2718@users.noreply.github.com> --- scripts/suite_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index def72565..38b87642 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1735,7 +1735,7 @@ def write(self, outfile, errcode, errmsg, indent): # end if for (var, dummy, lindices, rindices, compat_obj) in self.__forward_transforms: tstmt = self.write_var_transform(var, dummy, rindices, lindices, compat_obj, outfile, indent+1, True) - # for + # end for outfile.write('', indent) outfile.write('end if', indent)