diff --git a/scripts/metavar.py b/scripts/metavar.py index ed1f0155..b686453e 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -1634,7 +1634,7 @@ def add_variable(self, newvar, run_env, exists_ok=False, gen_unique=False, # end if if cvar is not None: compat = cvar.compatible(newvar, run_env) - if compat: + if compat.compat: # Check for intent mismatch vintent = cvar.get_prop_value('intent') dintent = newvar.get_prop_value('intent') diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index d472acf4..7317e75d 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -107,13 +107,14 @@ def add_variable(self, newvar, run_env, exists_ok=False, gen_unique=False, super().add_variable(newvar, run_env, exists_ok=exists_ok, gen_unique=gen_unique, adjust_intent=adjust_intent) - def call_string(self, cldicts=None, is_func_call=False, subname=None): + def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_list=None): """Return a dummy argument string for this call list. may be a list of VarDictionary objects to search for local_names (default is to use self). should be set to True to construct a call statement. If is False, construct a subroutine dummy argument list. + may be a list of local_name substitutions. """ arg_str = "" arg_sep = "" @@ -157,6 +158,17 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None): lname = dummy # end if # end if + # Modify Scheme call_list to handle local_name change for this var. + # Are there any variable transforms for this scheme? + # If so, change Var's local_name need to local dummy array containing + # transformed argument, var_trans_local. + if sub_lname_list: + for (var_trans_local, var_lname, sname, rindices, lindices, compat_obj) in sub_lname_list: + if (sname == stdname): + lname = var_trans_local + # end if + # end for + # end if if is_func_call: if cldicts is not None: use_dicts = cldicts @@ -898,9 +910,6 @@ def match_variable(self, var, run_env): 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. @@ -933,6 +942,14 @@ def match_variable(self, var, run_env): # end if # end if # end if + # We have a match! + # Are the Scheme's and Host's compatible? + # If so, create compatibility object, containing any necessary + # forward/reverse transforms to/from and . + if dict_var is not None: + dict_var = self.parent.find_variable(source_var=var, any_scope=True) + compat_obj = var.compatible(dict_var, run_env) + # end if return found_var, dict_var, var_vdim, new_vdims, missing_vert, compat_obj def in_process_split(self): @@ -1262,7 +1279,7 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): # end if # Is this a conditionally allocated variable? - # If so, declare localpointer varaible. This is needed to + # If so, declare localpointer variable. 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') @@ -1611,9 +1628,25 @@ def add_var_transform(self, var, compat_obj, vert_dim): from to perform the transformation. Determine the indices needed for the transform and save for use during write stage""" - # Add dummy variable (_local) needed for transformation. - dummy = var.clone(var.get_prop_value('local_name')+'_local') - self.__group.manage_variable(dummy) + # Add local variable (_local) needed for transformation. + # Do not let the Group manage this variable. Handle local var + # when writing Group. + prop_dict = var.copy_prop_dict() + prop_dict['local_name'] = var.get_prop_value('local_name')+'_local' + # This is a local variable. + if 'intent' in prop_dict: + del prop_dict['intent'] + # end if + local_trans_var = Var(prop_dict, + ParseSource(_API_SOURCE_NAME, + _API_LOCAL_VAR_NAME, var.context), + self.run_env) + found = self.__group.find_variable(source_var=local_trans_var, any_scope=False) + if not found: + lmsg = "Adding new local variable, '{}', for variable transform" + self.run_env.logger.info(lmsg.format(local_trans_var.get_prop_value('local_name'))) + self.__group.transform_locals.append(local_trans_var) + # end if # Create indices (default) for transform. lindices = [':']*var.get_rank() @@ -1652,26 +1685,35 @@ def add_var_transform(self, var, compat_obj, vert_dim): #hdim = find_horizontal_dimension(var.get_dimensions()) #if compat_obj.has_dim_transforms: - # - # Register any reverse (pre-Scheme) transforms. - # + # Register any reverse (pre-Scheme) transforms. Also, save local_name used in + # transform (used in write stage). if (var.get_prop_value('intent') != 'out'): - self.__reverse_transforms.append([dummy.get_prop_value('local_name'), + lmsg = "Automatic unit conversion from '{}' to '{}' for '{}' before entering '{}'" + self.run_env.logger.info(lmsg.format(compat_obj.v2_units, + compat_obj.v1_units, + compat_obj.v2_stdname, + compat_obj.v1_stdname)) + self.__reverse_transforms.append([local_trans_var.get_prop_value('local_name'), var.get_prop_value('local_name'), + var.get_prop_value('standard_name'), rindices, lindices, compat_obj]) - - # + # end if # Register any forward (post-Scheme) transforms. - # if (var.get_prop_value('intent') != 'in'): + lmsg = "Automatic unit conversion from '{}' to '{}' for '{}' after returning '{}'" + self.run_env.logger.info(lmsg.format(compat_obj.v1_units, + compat_obj.v2_units, + compat_obj.v1_stdname, + compat_obj.v2_stdname)) self.__forward_transforms.append([var.get_prop_value('local_name'), - dummy.get_prop_value('local_name'), + var.get_prop_value('standard_name'), + local_trans_var.get_prop_value('local_name'), lindices, rindices, compat_obj]) - + # end if def write_var_transform(self, var, dummy, rindices, lindices, compat_obj, outfile, indent, forward): """Write variable transformation needed to call this Scheme in . - is the varaible that needs transformation before and after calling Scheme. + is the variable that needs transformation before and after calling Scheme. is the local variable needed for the transformation.. are the LHS indices of for reverse transforms (before Scheme). are the RHS indices of for reverse transforms (before Scheme). @@ -1709,7 +1751,8 @@ def write(self, outfile, errcode, errmsg, indent): cldicts.extend(self.__group.suite_dicts()) my_args = self.call_list.call_string(cldicts=cldicts, is_func_call=True, - subname=self.subroutine_name) + subname=self.subroutine_name, + sub_lname_list = self.__reverse_transforms) # outfile.write('', indent) outfile.write('if ({} == 0) then'.format(errcode), indent) @@ -1737,8 +1780,15 @@ def write(self, outfile, errcode, errmsg, indent): if len(self.__reverse_transforms) > 0: outfile.comment('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) + for rcnt, (dummy, var_lname, var_sname, rindices, lindices, compat_obj) in enumerate(self.__reverse_transforms): + # Any transform(s) were added during the Group's analyze phase, but + # the local_name(s) of the assoicated with the transform(s) + # may have since changed. Here we need to use the standard_name + # from and replace its local_name with the local_name from the + # Group's call_list. + lvar = self.__group.call_list.find_variable(standard_name=var_sname) + lvar_lname = lvar.get_prop_value('local_name') + tstmt = self.write_var_transform(lvar_lname, dummy, rindices, lindices, compat_obj, outfile, indent+1, False) # end for outfile.write('',indent+1) # @@ -1778,8 +1828,15 @@ def write(self, outfile, errcode, errmsg, indent): if len(self.__forward_transforms) > 0: outfile.comment('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 fcnt, (var_lname, var_sname, dummy, lindices, rindices, compat_obj) in enumerate(self.__forward_transforms): + # Any transform(s) were added during the Group's analyze phase, but + # the local_name(s) of the assoicated with the transform(s) + # may have since changed. Here we need to use the standard_name + # from and replace its local_name with the local_name from the + # Group's call_list. + lvar = self.__group.call_list.find_variable(standard_name=var_sname) + lvar_lname = lvar.get_prop_value('local_name') + tstmt = self.write_var_transform(lvar_lname, dummy, rindices, lindices, compat_obj, outfile, indent+1, True) # end for outfile.write('', indent) outfile.write('end if', indent) @@ -2125,6 +2182,7 @@ def __init__(self, group_xml, transition, parent, context, run_env): self._phase_check_stmts = list() self._set_state = None self._ddt_library = None + self.transform_locals = list() def phase_match(self, scheme_name): """If scheme_name matches the group phase, return the group and @@ -2391,6 +2449,19 @@ def write(self, outfile, host_arglist, indent, const_mod, # end if pointer_var_set.append([name,kind,dimstr,vtype]) # end for + # Any arguments used in variable transforms before or after the + # Scheme call? If so, declare local copy for reuse in the Group cap. + for ivar in self.transform_locals: + lname = ivar.get_prop_value('local_name') + opt_var = ivar.get_prop_value('optional') + dims = ivar.get_dimensions() + if (dims is not None) and dims: + subpart_allocate_vars[lname] = (ivar, item, opt_var) + allocatable_var_set.add(lname) + else: + subpart_scalar_vars[lname] = (ivar, item, opt_var) + # end if + # end for # end for # First, write out the subroutine header @@ -2499,6 +2570,14 @@ def write(self, outfile, host_arglist, indent, const_mod, self._phase_check_stmts.write(outfile, indent, {'errcode' : errcode, 'errmsg' : errmsg, 'funcname' : self.name}) + # Write any loop match calculations + outfile.write("! Set horizontal loop extent",indent+1) + for vmatch in self._loop_var_matches: + action = vmatch.write_action(self, dict2=self.call_list) + if action: + outfile.write(action, indent+1) + # end if + # end for # Allocate local arrays outfile.write('\n! Allocate local arrays', indent+1) alloc_stmt = "allocate({}({}))" @@ -2530,13 +2609,6 @@ def write(self, outfile, host_arglist, indent, const_mod, # end if dims (do not allocate scalars) # end for # end if - # Write any loop match calculations - for vmatch in self._loop_var_matches: - action = vmatch.write_action(self, dict2=self.call_list) - if action: - outfile.write(action, indent+1) - # end if - # end for # Write the scheme and subcycle calls for item in self.parts: item.write(outfile, errcode, errmsg, indent + 1) diff --git a/scripts/var_props.py b/scripts/var_props.py index c41bd3cc..fcbed74c 100755 --- a/scripts/var_props.py +++ b/scripts/var_props.py @@ -876,6 +876,10 @@ def __init__(self, var1_stdname, var1_type, var1_kind, var1_units, self.__v2_context = v2_context self.__v1_kind = var1_kind self.__v2_kind = var2_kind + self.v1_units = var1_units + self.v2_units = var2_units + self.v1_stdname = var1_stdname + self.v2_stdname = var2_stdname # Default (null) transform information self.__dim_transforms = None self.__kind_transforms = None @@ -966,8 +970,8 @@ def __init__(self, var1_stdname, var1_type, var1_kind, var1_units, incompat_reason.append(emsg) # end if elif var1_units != var2_units: - self.__equiv = False # Try to find a set of unit conversions + self.__equiv = False self.__unit_transforms = self._get_unit_convstrs(var1_units, var2_units) # end if diff --git a/test/unit_tests/test_var_transforms.py b/test/unit_tests/test_var_transforms.py index 9fa5fb31..d0b5800e 100755 --- a/test/unit_tests/test_var_transforms.py +++ b/test/unit_tests/test_var_transforms.py @@ -136,7 +136,7 @@ def test_valid_unit_change(self): compat = real_scalar1.compatible(real_scalar2, self.__run_env) self.assertIsInstance(compat, VarCompatObj, msg=self.__inst_emsg.format(type(compat))) - self.assertFalse(compat) + self.assertFalse(compat.equiv) self.assertTrue(compat.compat) self.assertEqual(compat.incompat_reason, '') self.assertFalse(compat.has_kind_transforms) @@ -150,7 +150,7 @@ def test_valid_unit_change(self): compat = real_scalar1.compatible(real_scalar2, self.__run_env) self.assertIsInstance(compat, VarCompatObj, msg=self.__inst_emsg.format(type(compat))) - self.assertFalse(compat) + self.assertFalse(compat.equiv) self.assertTrue(compat.compat) self.assertEqual(compat.incompat_reason, '') self.assertFalse(compat.has_kind_transforms) diff --git a/test/var_compatibility_test/effr_diag.F90 b/test/var_compatibility_test/effr_diag.F90 new file mode 100644 index 00000000..38b87c1a --- /dev/null +++ b/test/var_compatibility_test/effr_diag.F90 @@ -0,0 +1,42 @@ +!Test unit conversions for intent in, inout, out variables +! + +module effr_diag + + use ccpp_kinds, only: kind_phys + + implicit none + private + + public :: effr_diag_run + +contains + + !> \section arg_table_effr_diag_run Argument Table + !! \htmlinclude arg_table_effr_diag_run.html + !! + subroutine effr_diag_run( effrr_in, errmsg, errflg) + + real(kind_phys), intent(in) :: effrr_in(:,:) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + !---------------------------------------------------------------- + real(kind_phys) :: effrr_min, effrr_max + + errmsg = '' + errflg = 0 + + call cmp_effr_diag(effrr_in, effrr_min, effrr_max) + + end subroutine effr_diag_run + + subroutine cmp_effr_diag(effr, effr_min, effr_max) + real(kind_phys), intent(in) :: effr(:,:) + real(kind_phys), intent(out) :: effr_min, effr_max + + ! Do some diagnostic calcualtions... + effr_min = minval(effr) + effr_max = maxval(effr) + + end subroutine cmp_effr_diag +end module effr_diag diff --git a/test/var_compatibility_test/effr_diag.meta b/test/var_compatibility_test/effr_diag.meta new file mode 100644 index 00000000..ebb765f2 --- /dev/null +++ b/test/var_compatibility_test/effr_diag.meta @@ -0,0 +1,31 @@ +[ccpp-table-properties] + name = effr_diag + type = scheme + dependencies = +[ccpp-arg-table] + name = effr_diag_run + type = scheme +[effrr_in] + standard_name = effective_radius_of_stratiform_cloud_rain_particle + long_name = effective radius of cloud rain particle in micrometer + units = um + dimensions = (horizontal_loop_extent,vertical_layer_dimension) + type = real + kind = kind_phys + intent = in + top_at_one = True +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out diff --git a/test/var_compatibility_test/effr_post.F90 b/test/var_compatibility_test/effr_post.F90 new file mode 100644 index 00000000..ca04c247 --- /dev/null +++ b/test/var_compatibility_test/effr_post.F90 @@ -0,0 +1,34 @@ +!Test unit conversions for intent in, inout, out variables +! + +module effr_post + + use ccpp_kinds, only: kind_phys + + implicit none + private + + public :: effr_post_run + +contains + + !> \section arg_table_effr_post_run Argument Table + !! \htmlinclude arg_table_effr_post_run.html + !! + subroutine effr_post_run( effrr_inout, errmsg, errflg) + + real(kind_phys), intent(inout) :: effrr_inout(:,:) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + !---------------------------------------------------------------- + real(kind_phys) :: effrr_min, effrr_max + + errmsg = '' + errflg = 0 + + ! Do some post-processing on effrr... + effrr_inout(:,:) = effrr_inout(:,:)*1._kind_phys + + end subroutine effr_post_run + + end module effr_post diff --git a/test/var_compatibility_test/effr_post.meta b/test/var_compatibility_test/effr_post.meta new file mode 100644 index 00000000..d65be238 --- /dev/null +++ b/test/var_compatibility_test/effr_post.meta @@ -0,0 +1,30 @@ +[ccpp-table-properties] + name = effr_post + type = scheme + dependencies = +[ccpp-arg-table] + name = effr_post_run + type = scheme +[effrr_inout] + standard_name = effective_radius_of_stratiform_cloud_rain_particle + long_name = effective radius of cloud rain particle in micrometer + units = m + dimensions = (horizontal_loop_extent,vertical_layer_dimension) + type = real + kind = kind_phys + intent = inout +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out diff --git a/test/var_compatibility_test/effr_pre.F90 b/test/var_compatibility_test/effr_pre.F90 new file mode 100644 index 00000000..ba6ea2b9 --- /dev/null +++ b/test/var_compatibility_test/effr_pre.F90 @@ -0,0 +1,34 @@ +!Test unit conversions for intent in, inout, out variables +! + +module effr_pre + + use ccpp_kinds, only: kind_phys + + implicit none + private + + public :: effr_pre_run + +contains + + !> \section arg_table_effr_pre_run Argument Table + !! \htmlinclude arg_table_effr_pre_run.html + !! + subroutine effr_pre_run( effrr_inout, errmsg, errflg) + + real(kind_phys), intent(inout) :: effrr_inout(:,:) + character(len=512), intent(out) :: errmsg + integer, intent(out) :: errflg + !---------------------------------------------------------------- + real(kind_phys) :: effrr_min, effrr_max + + errmsg = '' + errflg = 0 + + ! Do some pre-processing on effrr... + effrr_inout(:,:) = effrr_inout(:,:)*1._kind_phys + + end subroutine effr_pre_run + + end module effr_pre diff --git a/test/var_compatibility_test/effr_pre.meta b/test/var_compatibility_test/effr_pre.meta new file mode 100644 index 00000000..d6f67ec3 --- /dev/null +++ b/test/var_compatibility_test/effr_pre.meta @@ -0,0 +1,30 @@ +[ccpp-table-properties] + name = effr_pre + type = scheme + dependencies = +[ccpp-arg-table] + name = effr_pre_run + type = scheme +[effrr_inout] + standard_name = effective_radius_of_stratiform_cloud_rain_particle + long_name = effective radius of cloud rain particle in micrometer + units = m + dimensions = (horizontal_loop_extent,vertical_layer_dimension) + type = real + kind = kind_phys + intent = inout +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + dimensions = () + type = character + kind = len=512 + intent = out +[ errflg ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + dimensions = () + type = integer + intent = out diff --git a/test/var_compatibility_test/run_test b/test/var_compatibility_test/run_test index a5edac37..5a1d6b5c 100755 --- a/test/var_compatibility_test/run_test +++ b/test/var_compatibility_test/run_test @@ -127,7 +127,7 @@ ccpp_files="${utility_files}" ccpp_files="${ccpp_files},${build_dir}/ccpp/test_host_ccpp_cap.F90" ccpp_files="${ccpp_files},${build_dir}/ccpp/ccpp_var_compatibility_suite_cap.F90" #process_list="" -module_list="effr_calc" +module_list="effr_calc,effr_diag,effr_post,effr_pre" #dependencies="" suite_list="var_compatibility_suite" required_vars_var_compatibility="ccpp_error_code,ccpp_error_message" @@ -162,10 +162,10 @@ 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_rain_particle" output_vars_var_compatibility="${output_vars_var_compatibility},effective_radius_of_stratiform_cloud_snow_particle" output_vars_var_compatibility="${output_vars_var_compatibility},scalar_variable_for_testing" - ## ## 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 3a805a8e..3bb50da4 100644 --- a/test/var_compatibility_test/test_host.F90 +++ b/test/var_compatibility_test/test_host.F90 @@ -361,11 +361,12 @@ program test 'flag_indicating_cloud_microphysics_has_graupel ', & 'flag_indicating_cloud_microphysics_has_ice '/) - character(len=cm), target :: test_outvars1(7) = (/ & + character(len=cm), target :: test_outvars1(8) = (/ & '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_rain_particle ', & 'effective_radius_of_stratiform_cloud_snow_particle ', & 'cloud_ice_number_concentration ', & 'scalar_variable_for_testing ' /) diff --git a/test/var_compatibility_test/test_reports.py b/test/var_compatibility_test/test_reports.py index f7c37897..6f10fc6d 100755 --- a/test/var_compatibility_test/test_reports.py +++ b/test/var_compatibility_test/test_reports.py @@ -65,7 +65,7 @@ def usage(errmsg=None): _CCPP_FILES = _UTILITY_FILES + \ [os.path.join(_BUILD_DIR, "ccpp", "test_host_ccpp_cap.F90"), os.path.join(_BUILD_DIR, "ccpp", "ccpp_var_compatibility_suite_cap.F90")] -_MODULE_LIST = ["effr_calc"] +_MODULE_LIST = ["effr_calc", "effr_diag", "effr_post", "effr_pre"] _SUITE_LIST = ["var_compatibility_suite"] _INPUT_VARS_VAR_ACTION = ["horizontal_loop_begin", "horizontal_loop_end", "horizontal_dimension", "vertical_layer_dimension", "effective_radius_of_stratiform_cloud_liquid_water_particle", @@ -81,6 +81,7 @@ def usage(errmsg=None): "effective_radius_of_stratiform_cloud_liquid_water_particle", "effective_radius_of_stratiform_cloud_snow_particle", "cloud_ice_number_concentration", + "effective_radius_of_stratiform_cloud_rain_particle", "scalar_variable_for_testing"] _REQUIRED_VARS_VAR_ACTION = _INPUT_VARS_VAR_ACTION + _OUTPUT_VARS_VAR_ACTION diff --git a/test/var_compatibility_test/var_compatibility_files.txt b/test/var_compatibility_test/var_compatibility_files.txt index 5f7db5b2..6d83c980 100644 --- a/test/var_compatibility_test/var_compatibility_files.txt +++ b/test/var_compatibility_test/var_compatibility_files.txt @@ -1 +1,4 @@ effr_calc.meta +effr_diag.meta +effr_pre.meta +effr_post.meta diff --git a/test/var_compatibility_test/var_compatibility_suite.xml b/test/var_compatibility_test/var_compatibility_suite.xml index 4b70fe48..5956a8bd 100644 --- a/test/var_compatibility_test/var_compatibility_suite.xml +++ b/test/var_compatibility_test/var_compatibility_suite.xml @@ -2,6 +2,9 @@ + effr_pre effr_calc + effr_post + effr_diag