From b140da37be6c8f26f6300ee123906210cd39271d Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Wed, 12 Mar 2025 12:43:29 -0600 Subject: [PATCH 01/59] Update src/CMakeLists.txt: use modern cmake features to install Fortran modules in the correct place (#4) Update CMakeLists.txt to: - Bring in updates from NCAR ccpp-framework main as needed and update CMakeLists.txt to modern cmake version 3 - Support NEPTUNE cmake build while retaining compatibility with UFS/SCM --- src/CMakeLists.txt | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index eaa78afe..3b787aec 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,26 +4,7 @@ set(SOURCES_F90 ccpp_types.F90 ) -# Generate list of Fortran modules from defined sources -foreach(source_f90 ${SOURCES_F90}) - string(REGEX REPLACE ".F90" ".mod" module_f90 ${source_f90}) - list(APPEND MODULES_F90 ${CMAKE_CURRENT_BINARY_DIR}/${module_f90}) -endforeach() - -#------------------------------------------------------------------------------ -# Add the toplevel source directory to our include directoies (for .h) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) - -# Add the toplevel binary directory to our include directoies (for .mod) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - -# Set a cached variable containing the includes, so schemes can use them -set(${PACKAGE}_INCLUDE_DIRS - "${CMAKE_CURRENT_SOURCE_DIR}$${CMAKE_CURRENT_BINARY_DIR}" - CACHE FILEPATH "${PACKAGE} include directories") -set(${PACKAGE}_LIB_DIRS - "${CMAKE_CURRENT_BINARY_DIR}" - CACHE FILEPATH "${PACKAGE} library directories") +set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_INCLUDEDIR}) #------------------------------------------------------------------------------ # Define the executable and what to link @@ -37,16 +18,17 @@ set_target_properties(ccpp_framework PROPERTIES VERSION ${PROJECT_VERSION} # Installation # target_include_directories(ccpp_framework PUBLIC - $ - $ + INTERFACE $ + $ ) + # Define where to install the library install(TARGETS ccpp_framework EXPORT ccpp_framework-targets ARCHIVE DESTINATION lib LIBRARY DESTINATION lib - RUNTIME DESTINATION lib + RUNTIME DESTINATION bin ) # Export our configuration @@ -55,5 +37,4 @@ install(EXPORT ccpp_framework-targets DESTINATION lib/cmake ) -# Define where to install the Fortran modules -install(FILES ${MODULES_F90} DESTINATION include) +install(DIRECTORY ${CMAKE_Fortran_MODULE_DIRECTORY}/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) From 463d5ad7d8f52b786c74518751240548d8bbae10 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Tue, 18 Mar 2025 21:25:43 -0600 Subject: [PATCH 02/59] Add missing 'include(GNUInstallDirs)' in src/CMakeLists.txt --- src/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3b787aec..4ff78a81 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,3 +1,5 @@ +include(GNUInstallDirs) + #------------------------------------------------------------------------------ # Set the sources set(SOURCES_F90 From addae26f14e127d525c040262d908acf99d70639 Mon Sep 17 00:00:00 2001 From: dustinswales Date: Fri, 26 Sep 2025 10:53:43 -0600 Subject: [PATCH 03/59] Create test to illustrate GNU failure --- test/capgen_test/temp_set.F90 | 3 ++- test/capgen_test/temp_set.meta | 9 +++++++++ test/capgen_test/test_capgen_host_integration.F90 | 12 ++++++++---- test/capgen_test/test_host_data.F90 | 3 ++- test/capgen_test/test_host_data.meta | 8 ++++++++ test/capgen_test/test_host_mod.F90 | 3 ++- test/capgen_test/test_host_mod.meta | 6 ++++++ 7 files changed, 37 insertions(+), 7 deletions(-) diff --git a/test/capgen_test/temp_set.F90 b/test/capgen_test/temp_set.F90 index 760fbf25..a9f4f8ac 100644 --- a/test/capgen_test/temp_set.F90 +++ b/test/capgen_test/temp_set.F90 @@ -19,7 +19,7 @@ MODULE temp_set !! \htmlinclude arg_table_temp_set_run.html !! SUBROUTINE temp_set_run(ncol, lev, timestep, temp_level, temp_diag, temp, ps, & - to_promote, promote_pcnst, slev_lbound, soil_levs, var_array, errmsg, errflg) + to_promote, promote_pcnst, slev_lbound, soil_levs, var_array, cld_frac, errmsg, errflg) !---------------------------------------------------------------- IMPLICIT NONE !---------------------------------------------------------------- @@ -36,6 +36,7 @@ SUBROUTINE temp_set_run(ncol, lev, timestep, temp_level, temp_diag, temp, ps, & real(kind_phys), intent(out) :: promote_pcnst(:) character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg + real(kind_phys), intent(in), optional :: cld_frac(:,:) !---------------------------------------------------------------- integer :: ilev diff --git a/test/capgen_test/temp_set.meta b/test/capgen_test/temp_set.meta index d709da1e..daf82eb1 100644 --- a/test/capgen_test/temp_set.meta +++ b/test/capgen_test/temp_set.meta @@ -90,6 +90,15 @@ type = real kind = kind_phys intent = inout +[ cld_frac ] + standard_name = cloud_fraction + long_name = cloud fraction + type = real + kind = kind_phys + units = Pa + dimensions = (horizontal_loop_extent, vertical_layer_dimension) + intent = in + optional = True [ errmsg ] standard_name = ccpp_error_message long_name = Error message for error handling in CCPP diff --git a/test/capgen_test/test_capgen_host_integration.F90 b/test/capgen_test/test_capgen_host_integration.F90 index 745e5678..54f79c3f 100644 --- a/test/capgen_test/test_capgen_host_integration.F90 +++ b/test/capgen_test/test_capgen_host_integration.F90 @@ -6,7 +6,7 @@ program test character(len=cs), target :: test_parts1(2) = (/ 'physics1 ', & 'physics2 ' /) character(len=cs), target :: test_parts2(1) = (/ 'data_prep ' /) - character(len=cm), target :: test_invars1(10) = (/ & + character(len=cm), target :: test_invars1(12) = (/ & 'potential_temperature ', & 'potential_temperature_at_interface ', & 'coefficients_for_interpolation ', & @@ -16,7 +16,9 @@ program test 'soil_levels ', & 'temperature_at_diagnostic_levels ', & 'time_step_for_physics ', & - 'array_variable_for_testing ' /) + 'array_variable_for_testing ', & + 'cloud_fraction ', & + 'do_cloud_fraction_adjustment '/) character(len=cm), target :: test_outvars1(10) = (/ & 'potential_temperature ', & 'potential_temperature_at_interface ', & @@ -28,7 +30,7 @@ program test 'ccpp_error_code ', & 'ccpp_error_message ', & 'array_variable_for_testing ' /) - character(len=cm), target :: test_reqvars1(12) = (/ & + character(len=cm), target :: test_reqvars1(14) = (/ & 'potential_temperature ', & 'potential_temperature_at_interface ', & 'coefficients_for_interpolation ', & @@ -40,7 +42,9 @@ program test 'temperature_at_diagnostic_levels ', & 'ccpp_error_code ', & 'ccpp_error_message ', & - 'array_variable_for_testing ' /) + 'array_variable_for_testing ', & + 'cloud_fraction ', & + 'do_cloud_fraction_adjustment '/) character(len=cm), target :: test_invars2(3) = (/ & 'model_times ', & diff --git a/test/capgen_test/test_host_data.F90 b/test/capgen_test/test_host_data.F90 index 1b0a45c1..a646eeeb 100644 --- a/test/capgen_test/test_host_data.F90 +++ b/test/capgen_test/test_host_data.F90 @@ -14,7 +14,8 @@ module test_host_data real(kind_phys), dimension(:,:), allocatable :: & u, & ! zonal wind (m/s) v, & ! meridional wind (m/s) - pmid ! midpoint pressure (Pa) + pmid, & ! midpoint pressure (Pa) + cld_frac ! cloud fraction (1) real(kind_phys), dimension(:,:,:),allocatable :: & q ! constituent mixing ratio (kg/kg moist or dry air depending on type) end type physics_state diff --git a/test/capgen_test/test_host_data.meta b/test/capgen_test/test_host_data.meta index 0e73c060..f440587d 100644 --- a/test/capgen_test/test_host_data.meta +++ b/test/capgen_test/test_host_data.meta @@ -35,6 +35,14 @@ kind = kind_phys units = Pa dimensions = (horizontal_dimension, vertical_layer_dimension) +[ cld_frac ] + standard_name = cloud_fraction + long_name = cloud fraction + type = real + kind = kind_phys + units = Pa + dimensions = (horizontal_dimension, vertical_layer_dimension) + active = (do_cloud_fraction_adjustment) [ soil_levs ] standard_name = soil_levels long_name = soil levels diff --git a/test/capgen_test/test_host_mod.F90 b/test/capgen_test/test_host_mod.F90 index f2586a77..2adbb430 100644 --- a/test/capgen_test/test_host_mod.F90 +++ b/test/capgen_test/test_host_mod.F90 @@ -36,7 +36,8 @@ module test_host_mod integer, parameter :: num_time_steps = 2 real(kind_phys), parameter :: tolerance = 1.0e-13_kind_phys real(kind_phys) :: tint_save(ncols, pverP) - + logical, parameter :: cfrac_adj = .false. + public :: init_data public :: compare_data public :: check_model_times diff --git a/test/capgen_test/test_host_mod.meta b/test/capgen_test/test_host_mod.meta index 08627af0..dc89981a 100644 --- a/test/capgen_test/test_host_mod.meta +++ b/test/capgen_test/test_host_mod.meta @@ -131,3 +131,9 @@ units = none dimensions = (horizontal_dimension,2,4,6) type = real | kind = kind_phys +[ cfrac_adj ] + standard_name = do_cloud_fraction_adjustment + long_name = control for cloud fraction adjustment + units = none + dimensions = () + type = logical From 5771816b1791a941735a4486b7ec2a4b1ce249a8 Mon Sep 17 00:00:00 2001 From: peverwhee Date: Tue, 13 Jan 2026 13:47:05 -0700 Subject: [PATCH 04/59] add parent ddt to group calling list --- scripts/ccpp_suite.py | 2 +- scripts/metavar.py | 29 ++++++++++++++--- scripts/suite_objects.py | 67 ++++++++++++++++++++++++++++++---------- 3 files changed, 75 insertions(+), 23 deletions(-) diff --git a/scripts/ccpp_suite.py b/scripts/ccpp_suite.py index d1a6e968..2b197e88 100644 --- a/scripts/ccpp_suite.py +++ b/scripts/ccpp_suite.py @@ -459,7 +459,7 @@ def analyze(self, host_model, scheme_library, ddt_library, run_env): for x in item.schemes()])) item.analyze(phase, self, scheme_library, ddt_library, self.check_suite_state(phase), - self.set_suite_state(phase)) + self.set_suite_state(phase), host_model) # Look for group variables that need to be promoted to the suite # We need to promote any variable used later to the suite, however, # we do not yet know if it will be used. diff --git a/scripts/metavar.py b/scripts/metavar.py index 62badfd7..b4a0664f 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -1825,16 +1825,35 @@ def find_variable(self, standard_name=None, source_var=None, var = CCPP_CONSTANT_VARS[standard_name] elif standard_name in self: var = self[standard_name] - elif any_scope and (self.__parent_dict is not None): - src_clist = search_call_list - var = self.__parent_dict.find_variable(standard_name=standard_name, + else: + # Look in the DDTs + var = None + for var_check in self: + var_in_object = self.find_variable(var_check) + if var_in_object.is_ddt(): + children = var_in_object.children() + if children: + for child in children: + if child.get_prop_value('standard_name') == standard_name: + var = child + # end if + # end for + # end if + # end if + # end for + if not var: + if any_scope and (self.__parent_dict is not None): + src_clist = search_call_list + var = self.__parent_dict.find_variable(standard_name=standard_name, source_var=source_var, any_scope=any_scope, clone=clone, search_call_list=src_clist, loop_subst=loop_subst) - else: - var = None + else: + var = None + # end if + # end if # end if if (var is None) and (clone is not None): lname = clone.get_prop_value['local_name'] diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index b1de44b8..4d1ccf8f 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -18,6 +18,7 @@ from parse_tools import ParseContext, ParseSource, context_string from parse_tools import ParseInternalError, CCPPError from parse_tools import init_log, set_log_to_null +from ddt_library import VarDDT from var_props import is_horizontal_dimension, find_horizontal_dimension from var_props import find_vertical_dimension from var_props import VarCompatObj @@ -839,7 +840,7 @@ def find_variable(self, standard_name=None, source_var=None, # end if return found_var - def match_variable(self, var, run_env): + def match_variable(self, var, run_env, host_dict): """Try to find a source for in this SuiteObject's dictionary tree. Several items are returned: found_var: True if a match was found @@ -875,6 +876,14 @@ def match_variable(self, var, run_env): # end if # end if + # Is this variable a member of a DDT? If so, look for the parent DDT + host_var = host_dict.find_variable(source_var=var, any_scope=True) + if host_var: + if host_var.is_ddt(): + var = host_var.var + vdims = [] + # end if + # end if # Does this variable exist in the calling tree? dict_var = self.find_variable(source_var=var, any_scope=True) if dict_var is None: @@ -1171,7 +1180,7 @@ def is_local_variable(self, var): This is an override of the SuiteObject version""" return None - def analyze(self, phase, group, scheme_library, suite_vars, level): + def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): """Analyze the scheme's interface to prepare for writing""" self.__group = group my_header = None @@ -1206,13 +1215,21 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): def_val = var.get_prop_value('default_value') vdims = var.get_dimensions() vintent = var.get_prop_value('intent') - args = self.match_variable(var, self.run_env) + args = self.match_variable(var, self.run_env, host_dict) found, dict_var, vert_dim, new_dims, missing_vert, compat_obj = args + if dict_var: + if dict_var.is_ddt(): + subst_dict = {'intent':'inout'} + clone = dict_var.clone(subst_dict) + dict_var = clone + # end if + # end if if found: if self.__group.run_env.debug: # Add variable allocation checks for group, suite and host variables if dict_var: self.add_var_debug_check(dict_var) + # end if # end if if not self.has_vertical_dim: self.__has_vertical_dimension = vert_dim is not None @@ -1220,12 +1237,20 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): # We have a match, make sure var is in call list if new_dims == vdims: self.add_call_list_variable(var, exists_ok=True, gen_unique=True) - self.update_group_call_list_variable(var) + if dict_var: + self.update_group_call_list_variable(dict_var) + else: + self.update_group_call_list_variable(var) + # end if else: subst_dict = {'dimensions':new_dims} clone = var.clone(subst_dict) self.add_call_list_variable(clone, exists_ok=True) - self.update_group_call_list_variable(clone) + if dict_var: + clone = dict_var.clone(subst_dict) + self.update_group_call_list_variable(clone) + else: + self.update_group_call_list_variable(clone) # end if else: if missing_vert is not None: @@ -1239,7 +1264,11 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): raise ParseInternalError(errmsg) # end if # The Group will manage this variable - self.__group.manage_variable(var) + if dict_var: + self.__group.manage_variable(dict_var) + else: + self.__group.manage_variable(var) + # end if self.add_call_list_variable(var) elif def_val and (vintent != 'out'): if self.__group is None: @@ -1247,7 +1276,11 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): raise ParseInternalError(errmsg) # end if # The Group will manage this variable - self.__group.manage_variable(var) + if dict_var: + self.__group.manage_variable(dict_var) + else: + self.__group.manage_variable(var) + # end if # We still need it in our call list (the group uses a clone) self.add_call_list_variable(var) else: @@ -1290,7 +1323,7 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): if isinstance(self.parent, VerticalLoop): # Restart the loop analysis scheme_mods = self.parent.analyze(phase, group, scheme_library, - suite_vars, level) + suite_vars, level, host_dict) # end if # end if return scheme_mods @@ -1978,7 +2011,7 @@ def __init__(self, index_name, context, parent, run_env, items=None): self.add_part(item) # end for - def analyze(self, phase, group, scheme_library, suite_vars, level): + def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): """Analyze the VerticalLoop's interface to prepare for writing""" # Handle all the suite objects inside of this subcycle scheme_mods = set() @@ -2012,7 +2045,7 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): # Analyze our internal items for item in self.parts: smods = item.analyze(phase, group, scheme_library, - suite_vars, level+1) + suite_vars, level+1, host_dict) for smod in smods: scheme_mods.add(smod) # end for @@ -2070,7 +2103,7 @@ def __init__(self, sub_xml, context, parent, run_env, loop_count=0): self.add_part(new_item) # end for - def analyze(self, phase, group, scheme_library, suite_vars, level): + def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): """Analyze the Subcycle's interface to prepare for writing""" if self.name is None: self.name = "subcycle_index{}".format(level) @@ -2084,7 +2117,7 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): scheme_mods = set() for item in self.parts: smods = item.analyze(phase, group, scheme_library, - suite_vars, level+1) + suite_vars, level+1, host_dict) for smod in smods: scheme_mods.add(smod) # end for @@ -2120,7 +2153,7 @@ def __init__(self, sub_xml, context, parent, run_env): self.add_part(new_item) # end for - def analyze(self, phase, group, scheme_library, suite_vars, level): + def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): # Unused arguments are for consistent analyze interface # pylint: disable=unused-argument """Analyze the TimeSplit's interface to prepare for writing""" @@ -2128,7 +2161,7 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): scheme_mods = set() for item in self.parts: smods = item.analyze(phase, group, scheme_library, - suite_vars, level+1) + suite_vars, level+1, host_dict) for smod in smods: scheme_mods.add(smod) # end for @@ -2157,7 +2190,7 @@ def __init__(self, sub_xml, context, parent, run_env): super().__init__('ProcessSplit', context, parent, run_env) raise CCPPError('ProcessSplit not yet implemented') - def analyze(self, phase, group, scheme_library, suite_vars, level): + def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): # Unused arguments are for consistent analyze interface # pylint: disable=unused-argument """Analyze the ProcessSplit's interface to prepare for writing""" @@ -2348,7 +2381,7 @@ def manage_variable(self, newvar): # end if def analyze(self, phase, suite_vars, scheme_library, ddt_library, - check_suite_state, set_suite_state): + check_suite_state, set_suite_state, host_dict): """Analyze the Group's interface to prepare for writing""" self._ddt_library = ddt_library # Sanity check for Group @@ -2361,7 +2394,7 @@ def analyze(self, phase, suite_vars, scheme_library, ddt_library, # Items can be schemes, subcycles or other objects # All have the same interface and return a set of module use # statements (lschemes) - lschemes = item.analyze(phase, self, scheme_library, suite_vars, 1) + lschemes = item.analyze(phase, self, scheme_library, suite_vars, 1, host_dict) for lscheme in lschemes: self._local_schemes.add(lscheme) # end for From e59561607584ce6085eacdef4cda91a2ca5825a4 Mon Sep 17 00:00:00 2001 From: peverwhee Date: Thu, 12 Feb 2026 15:45:05 -0700 Subject: [PATCH 05/59] find variable working for parent/children, but need to sort of const indices --- scripts/ccpp_capgen.py | 1 + scripts/ccpp_suite.py | 15 ++++--- scripts/constituents.py | 6 ++- scripts/ddt_library.py | 8 ++-- scripts/framework_env.py | 16 ++++++++ scripts/host_cap.py | 1 + scripts/host_model.py | 11 +++-- scripts/metadata_table.py | 6 ++- scripts/metavar.py | 86 +++++++++++++++++++++++++++++++-------- scripts/suite_objects.py | 61 +++++++++++++++++++-------- 10 files changed, 160 insertions(+), 51 deletions(-) diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index afd6a332..16521586 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -659,6 +659,7 @@ def capgen(run_env, return_db=False): # Handle the host files host_model = parse_host_model_files(host_files, host_name, run_env, known_ddts=scheme_ddts) + run_env.set_ddt_library(host_model.ddt_lib) # Next, parse the scheme files # We always need to parse the constituent DDTs const_prop_mod = os.path.join(src_dir, "ccpp_constituent_prop_mod.meta") diff --git a/scripts/ccpp_suite.py b/scripts/ccpp_suite.py index 2b197e88..4b051f62 100644 --- a/scripts/ccpp_suite.py +++ b/scripts/ccpp_suite.py @@ -258,7 +258,8 @@ def groups(self): def find_variable(self, standard_name=None, source_var=None, any_scope=True, clone=None, - search_call_list=False, loop_subst=False): + search_call_list=False, loop_subst=False, + check_components=True): """Attempt to return the variable matching . if is None, the standard name from is used. It is an error to pass both and if @@ -278,7 +279,8 @@ def find_variable(self, standard_name=None, source_var=None, any_scope=any_scope, clone=None, search_call_list=srch_clist, - loop_subst=loop_subst) + loop_subst=loop_subst, + check_components=check_components) if var is None: # No dice? Check for a group variable which can be promoted # Don't promote loop standard names @@ -289,7 +291,8 @@ def find_variable(self, standard_name=None, source_var=None, source_var=source_var, any_scope=False, search_call_list=srch_clist, - loop_subst=loop_subst) + loop_subst=loop_subst, + check_components=check_components) if var is not None: # Promote variable to suite level @@ -341,7 +344,8 @@ def find_variable(self, standard_name=None, source_var=None, # Guess it is time to clone a different variable var = super().find_variable(standard_name=standard_name, source_var=source_var, - any_scope=any_scope, clone=clone) + any_scope=any_scope, clone=clone, + check_components=check_components) # end if return var @@ -735,7 +739,8 @@ def __init__(self, sdfs, host_model, scheme_headers, run_env): if group.phase() == phase: self.__call_lists[phase].add_vars(group.call_list, run_env, - gen_unique=True) + gen_unique=True, + add_children=True) # end if # end for # end for diff --git a/scripts/constituents.py b/scripts/constituents.py index bb83c3c7..758ba239 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -48,7 +48,8 @@ def __init__(self, name, parent_dict, run_env, variables=None): def find_variable(self, standard_name=None, source_var=None, any_scope=True, clone=None, - search_call_list=False, loop_subst=False): + search_call_list=False, loop_subst=False, + check_components=True): """Attempt to return the variable matching . if is None, the standard name from is used. It is an error to pass both and if @@ -86,7 +87,8 @@ def find_variable(self, standard_name=None, source_var=None, source_var=source_var, any_scope=any_scope, clone=None, search_call_list=srch_clist, - loop_subst=loop_subst) + loop_subst=loop_subst, + check_components=check_components) else: var = None # end if diff --git a/scripts/ddt_library.py b/scripts/ddt_library.py index 1c362108..bdd3e8be 100644 --- a/scripts/ddt_library.py +++ b/scripts/ddt_library.py @@ -80,8 +80,8 @@ def intrinsic_elements(self, check_dict=None): # end if return pvalue - def clone(self, subst_dict, source_name=None, source_type=None, - context=None): + def clone(self, subst_dict=None, remove_intent=False, + source_name=None, source_type=None, context=None): """Create a clone of this VarDDT object's leaf Var with properties from overriding this variable's properties. may also be a string in which case only the local_name @@ -288,7 +288,7 @@ def collect_ddt_fields(self, var_dict, var, run_env, # for a DDT, the variable also cannot be in our parent # dictionaries. stdname = dvar.get_prop_value('standard_name') - pvar = var_dict.find_variable(standard_name=stdname, any_scope=True) + pvar = var_dict.find_variable(standard_name=stdname, any_scope=True, check_components=False) if pvar and (not skip_duplicates): ntx = context_string(dvar.context) ctx = context_string(pvar.context) @@ -300,6 +300,8 @@ def collect_ddt_fields(self, var_dict, var, run_env, if not pvar: var_dict.add_variable(subvar, run_env) # end if + # Add this ddt variable to the parent DDT + var.add_component(subvar) # end for def ddt_modules(self, variable_list, ddt_mods=None): diff --git a/scripts/framework_env.py b/scripts/framework_env.py index 88c9c204..fc21ba18 100644 --- a/scripts/framework_env.py +++ b/scripts/framework_env.py @@ -207,6 +207,13 @@ def __init__(self, logger, ndict=None, verbose=0, clean=False, self.__debug = debug # end if self.__logger = logger + # Set the ddt library to None + if ndict and ('ddt_library' in ndict): + self.__ddt_library = ndict['ddt_library'] + del ndict['ddt_library'] + else: + self.__ddt_library = None + # end if ## Check to see if anything is left in dictionary if ndict: for key in ndict: @@ -263,6 +270,15 @@ def host_name(self): """Return the property for this CCPPFrameworkEnv object.""" return self.__host_name + @property + def ddt_library(self): + """Return the dictionary of DDT definitions for the host model""" + return self.__ddt_library + + def set_ddt_library(self, ddt_lib): + """Set the ddt_library for the runtime environment""" + self.__ddt_library = ddt_lib + @property def generate_host_cap(self): """Return the property for this diff --git a/scripts/host_cap.py b/scripts/host_cap.py index b06906fe..5a31826e 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -445,6 +445,7 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): ind_var = Var(prop_dict, _API_SOURCE, run_env) const_dict.add_variable(ind_var, run_env) # end for + print(const_dict) # Add vertical dimensions for DDT call strings pver = host_model.find_variable(standard_name=vert_layer_dim, any_scope=False) diff --git a/scripts/host_model.py b/scripts/host_model.py index c3beb447..6d758b52 100644 --- a/scripts/host_model.py +++ b/scripts/host_model.py @@ -203,7 +203,8 @@ def variable_locations(self): def find_variable(self, standard_name=None, source_var=None, any_scope=False, clone=None, - search_call_list=False, loop_subst=False): + search_call_list=False, loop_subst=False, + check_components=True): """Return the host model variable matching or None If is True, substitute a begin:end range for an extent. """ @@ -211,7 +212,8 @@ def find_variable(self, standard_name=None, source_var=None, source_var=source_var, any_scope=any_scope, clone=clone, search_call_list=search_call_list, - loop_subst=loop_subst) + loop_subst=loop_subst, + check_components=check_components) if my_var is None: # Check our DDT library if standard_name is None: @@ -224,7 +226,8 @@ def find_variable(self, standard_name=None, source_var=None, # end if # Since we are the parent of the DDT library, only check that dict my_var = self.__ddt_dict.find_variable(standard_name=standard_name, - any_scope=False) + any_scope=False, + check_components=check_components) # End if if loop_subst: if my_var is None: @@ -257,7 +260,7 @@ def find_variable(self, standard_name=None, source_var=None, vdims = [x.strip() for x in imatch.group(2).split(',') if ':' not in x] for vname in vdims: - _ = self.find_variable(standard_name=vname) + _ = self.find_variable(standard_name=vname, check_components=check_components) # End for # End if if isinstance(my_var, VarDDT): diff --git a/scripts/metadata_table.py b/scripts/metadata_table.py index 5e4d2d7c..e0c3ac98 100755 --- a/scripts/metadata_table.py +++ b/scripts/metadata_table.py @@ -1141,13 +1141,15 @@ def variable_list(self, std_vars=True, loop_vars=True, consts=True): loop_vars=loop_vars, consts=consts) - def find_variable(self, std_name, use_local_name=False): + def find_variable(self, std_name, use_local_name=False, check_components=True): """Find a variable in this header's dictionary""" var = None if use_local_name: var = self.__variables.find_local_name(std_name) else: - var = self.__variables.find_variable(std_name, any_scope=False) + var = self.__variables.find_variable(std_name, + any_scope=False, + check_components=check_components) # end if return var diff --git a/scripts/metavar.py b/scripts/metavar.py index b4a0664f..a7ea7270 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -261,7 +261,7 @@ class Var: __var_propdict.update({p.name : p for p in __constituent_props}) # All constituent props are optional so no check - def __init__(self, prop_dict, source, run_env, context=None, + def __init__(self, prop_dict, source, run_env, is_ddt=False, components=None, context=None, clone_source=None, fortran_imports=None): """Initialize a new Var object. If is really a Var object, use that object's prop_dict. @@ -275,6 +275,11 @@ def __init__(self, prop_dict, source, run_env, context=None, """ self.__parent_var = None # for array references self.__children = list() # This Var's array references + if components: + self.__components = components + else: + self.__components = list() # This Var's DDT components + # end if self.__clone_source = clone_source self.__run_env = run_env if isinstance(prop_dict, Var): @@ -315,6 +320,9 @@ def __init__(self, prop_dict, source, run_env, context=None, else: self.__intrinsic = True # end if + if is_ddt: + self.__intrinsic = False + # end if for key in prop_dict: if Var.get_prop(key) is None: raise ParseSyntaxError("Invalid metadata variable property, '{}'".format(key), context=self.context) @@ -499,7 +507,7 @@ def clone(self, subst_dict=None, remove_intent=False, # end if psource = ParseSource(source_name, source_type, context) - return Var(cprop_dict, psource, self.run_env, clone_source=self) + return Var(cprop_dict, psource, self.run_env, is_ddt=self.is_ddt(), components=self.components, clone_source=self) def get_prop_value(self, name): """Return the value of key, if is in this variable's @@ -703,6 +711,8 @@ def call_string(self, var_dict, loop_vars=None): if item: dvar = var_dict.find_variable(standard_name=item, any_scope=False) + print(dvar) + print(item) if dvar is None: try: dval = int(item) @@ -772,7 +782,7 @@ def array_ref(self, local_name=None): match = FORTRAN_SCALAR_REF_RE.match(local_name) return match - def intrinsic_elements(self, check_dict=None, ddt_lib=None): + def intrinsic_elements(self, check_dict=None, ddt_lib=None, run_env=None): """Return a list of the standard names of this Var object's 'leaf' intrinsic elements or this Var object's standard name if it is an intrinsic 'leaf' variable. @@ -790,7 +800,14 @@ def intrinsic_elements(self, check_dict=None, ddt_lib=None): element_names = None if self.is_ddt(): dtitle = self.get_prop_value('type') - if ddt_lib and (dtitle in ddt_lib): + if not ddt_lib: + if not run_env: + errmsg = f'If ddt_lib is not supplied, run_env must be' + raise CCPPError(errmsg) + # end if + ddt_lib = run_env.ddt_library + # end if + if dtitle in ddt_lib: element_names = [] ddt_def = ddt_lib[dtitle] for dvar in ddt_def.variable_list(): @@ -883,6 +900,14 @@ def children(self): # end if return iter(children) if children else None + @property + def components(self): + """Return the list of this object's components""" + return self.__components + + def add_component(self, new_component): + self.__components.append(new_component) + @property def var(self): "Return this object (base behavior for derived classes such as VarDDT)" @@ -1604,7 +1629,7 @@ def variable_list(self, recursive=False, return vlist def add_variable(self, newvar, run_env, exists_ok=False, gen_unique=False, - adjust_intent=False): + adjust_intent=False, add_children=False): """Add if it does not conflict with existing entries If is True, attempting to add an identical copy is okay. If is True, a new local_name will be created if a @@ -1712,9 +1737,16 @@ def add_variable(self, newvar, run_env, exists_ok=False, gen_unique=False, if aref is not None: pname = aref.group(1).strip() pvar = self.find_local_name(pname) + array_pieces = aref.group(2).split(',') if pvar is not None: newvar.parent = pvar # end if + for array_piece in array_pieces: + if array_piece.strip() != ':': + pvar = self.find_variable(standard_name=array_piece.strip()) + if pvar: + newvar.add_child(pvar) + # end if # end if # If we make it to here without an exception, add the variable if standard_name not in self: @@ -1724,6 +1756,12 @@ def add_variable(self, newvar, run_env, exists_ok=False, gen_unique=False, if lname not in self.__local_names: self.__local_names[lname] = standard_name # end if + if newvar.children() and add_children: + for child in newvar.children(): + self.add_variable(child, run_env, exists_ok=exists_ok, gen_unique=gen_unique, + adjust_intent=adjust_intent) + # end for + # end if def remove_variable(self, standard_name): """Remove from the dictionary. @@ -1795,7 +1833,8 @@ def add_variable_dimensions(self, var, ignore_sources, suite_type, def find_variable(self, standard_name=None, source_var=None, any_scope=True, clone=None, - search_call_list=False, loop_subst=False): + search_call_list=False, loop_subst=False, + check_components=True): """Attempt to return the variable matching . if is None, the standard name from is used. It is an error to pass both and if @@ -1828,19 +1867,29 @@ def find_variable(self, standard_name=None, source_var=None, else: # Look in the DDTs var = None - for var_check in self: - var_in_object = self.find_variable(var_check) - if var_in_object.is_ddt(): - children = var_in_object.children() - if children: - for child in children: - if child.get_prop_value('standard_name') == standard_name: - var = child + if check_components: + for var_check in self: + var_in_object = self[var_check] + ddt_components = var_in_object.components + for component in ddt_components: + if component.get_prop_value('standard_name') == standard_name: + var = component + else: + if component.children(): + for child in component.children(): + if child.get_prop_value('standard_name') == standard_name: + var = child + # end if + # end for # end if - # end for + # end if + # end for +# if not var and var_in_object.children(): +# for child in var_in_object.children(): +# print(child) # end if - # end if - # end for + # end for + # end if if not var: if any_scope and (self.__parent_dict is not None): src_clist = search_call_list @@ -1849,7 +1898,8 @@ def find_variable(self, standard_name=None, source_var=None, any_scope=any_scope, clone=clone, search_call_list=src_clist, - loop_subst=loop_subst) + loop_subst=loop_subst, + check_components=check_components) else: var = None # end if diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 4d1ccf8f..5c03bd57 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -83,15 +83,16 @@ def __init__(self, name, run_env, routine=None): self.__routine = routine super().__init__(name, run_env) - def add_vars(self, call_list, run_env, gen_unique=False): + def add_vars(self, call_list, run_env, gen_unique=False, add_children=False): """Add new variables from another CallList ()""" for var in call_list.variable_list(): stdname = var.get_prop_value('standard_name') - self.add_variable(var, run_env, gen_unique=gen_unique, adjust_intent=True, exists_ok=True) + self.add_variable(var, run_env, gen_unique=gen_unique, adjust_intent=True, + exists_ok=True, add_children=add_children) # end for def add_variable(self, newvar, run_env, exists_ok=False, gen_unique=False, - adjust_intent=False): + adjust_intent=False, add_children=False): """Add as for VarDictionary but make sure that the variable has an intent with the default being intent(in). """ @@ -104,7 +105,8 @@ def add_variable(self, newvar, run_env, exists_ok=False, gen_unique=False, context=oldvar.context) # end if super().add_variable(newvar, run_env, exists_ok=exists_ok, - gen_unique=gen_unique, adjust_intent=adjust_intent) + gen_unique=gen_unique, adjust_intent=adjust_intent, + add_children=add_children) 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. @@ -458,7 +460,8 @@ def add_call_list_variable(self, newvar, exists_ok=False, self.call_list.add_variable(newvar, self.run_env, exists_ok=exists_ok, gen_unique=gen_unique, - adjust_intent=True) + adjust_intent=True, + add_children=True) # We need to make sure that this variable's dimensions are available for vardim in newvar.get_dim_stdnames(include_constants=False): # Unnamed dimensions are ok for allocatable variables @@ -784,7 +787,8 @@ def match_dimensions(self, need_dims, have_dims): def find_variable(self, standard_name=None, source_var=None, any_scope=True, clone=None, - search_call_list=False, loop_subst=False): + search_call_list=False, loop_subst=False, + check_components=True): """Find a matching variable to , create a local clone (if is True), or return None. First search the SuiteObject's internal dictionary, then its @@ -815,7 +819,8 @@ def find_variable(self, standard_name=None, source_var=None, source_var=source_var, any_scope=False, clone=None, search_call_list=scl, - loop_subst=loop_subst) + loop_subst=loop_subst, + check_components=check_components) if (not found_var) and (self.call_list is not None) and scl: # Don't clone yet, might find the variable further down found_var = self.call_list.find_variable(standard_name=stdname, @@ -823,7 +828,8 @@ def find_variable(self, standard_name=None, source_var=None, any_scope=False, clone=None, search_call_list=scl, - loop_subst=loop_subst) + loop_subst=loop_subst, + check_components=check_components) # end if loop_okay = VarDictionary.loop_var_okay(stdname, self.run_phase()) if not loop_okay: @@ -836,7 +842,8 @@ def find_variable(self, standard_name=None, source_var=None, any_scope=True, clone=clone, search_call_list=scl, - loop_subst=loop_subst) + loop_subst=loop_subst, + check_components=check_components) # end if return found_var @@ -851,6 +858,7 @@ def match_variable(self, var, run_env, host_dict): """ vstdname = var.get_prop_value('standard_name') vdims = var.get_dimensions() + local_var = None if (not vdims) and self.run_phase(): vmatch = VarDictionary.loop_var_match(vstdname) else: @@ -867,7 +875,7 @@ def match_variable(self, var, run_env, host_dict): if self.phase() == 'register': found_var = True new_vdims = [':'] - return found_var, dict_var, var_vdim, new_vdims, missing_vert, compat_obj + return found_var, local_var, dict_var, var_vdim, new_vdims, missing_vert, compat_obj else: errmsg = "Variables of type ccpp_constituent_properties_t only allowed in register phase: " sname = var.get_prop_value('standard_name') @@ -877,9 +885,11 @@ def match_variable(self, var, run_env, host_dict): # end if # Is this variable a member of a DDT? If so, look for the parent DDT + # and add that instead host_var = host_dict.find_variable(source_var=var, any_scope=True) if host_var: - if host_var.is_ddt(): + if isinstance(host_var, VarDDT): + local_var = host_var var = host_var.var vdims = [] # end if @@ -934,6 +944,10 @@ def match_variable(self, var, run_env, host_dict): else: sdict = {'dimensions':new_dict_dims} # end if + # Add any DDT components from the host dictionary version of the variable + for dict_var_component in dict_var.components: + var.add_component(dict_var_component) + # end if found_var = self.parent.add_variable_to_call_tree(var, subst_dict=sdict) if not match: @@ -957,7 +971,7 @@ def match_variable(self, var, run_env, host_dict): 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 + return found_var, dict_var, local_var, var_vdim, new_vdims, missing_vert, compat_obj def in_process_split(self): """Find out if we are in a process-split region""" @@ -1216,7 +1230,7 @@ def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): vdims = var.get_dimensions() vintent = var.get_prop_value('intent') args = self.match_variable(var, self.run_env, host_dict) - found, dict_var, vert_dim, new_dims, missing_vert, compat_obj = args + found, dict_var, local_var, vert_dim, new_dims, missing_vert, compat_obj = args if dict_var: if dict_var.is_ddt(): subst_dict = {'intent':'inout'} @@ -1228,7 +1242,11 @@ def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): if self.__group.run_env.debug: # Add variable allocation checks for group, suite and host variables if dict_var: - self.add_var_debug_check(dict_var) + if local_var: + self.add_var_debug_check(local_var) + else: + self.add_var_debug_check(dict_var) + # end if # end if # end if if not self.has_vertical_dim: @@ -1251,6 +1269,7 @@ def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): self.update_group_call_list_variable(clone) else: self.update_group_call_list_variable(clone) + # end if # end if else: if missing_vert is not None: @@ -1490,6 +1509,7 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er # or from the suite, not how it is called in the scheme (var) # First, check if the variable is in the call list. dvar = self.__group.call_list.find_variable(standard_name=standard_name, any_scope=False) + search_dict = self.__group.call_list if dvar: var_in_call_list = True else: @@ -1503,10 +1523,15 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er for var_dict in self.__group.suite_dicts(): dvar = var_dict.find_variable(standard_name=standard_name, any_scope=False) if dvar: + search_dict = var_dict break if not dvar: raise Exception(f"No variable with standard name '{standard_name}' in cldicts") - local_name = dvar.get_prop_value('local_name') + if dvar and isinstance(dvar, VarDDT): + local_name = dvar.call_string(search_dict) + else: + local_name = dvar.get_prop_value('local_name') + # end if # If the variable is allocatable and the intent for the scheme is 'out', # then we can't test anything because the scheme is going to allocate @@ -2440,7 +2465,8 @@ def allocate_dim_str(self, dims, context): def find_variable(self, standard_name=None, source_var=None, any_scope=True, clone=None, - search_call_list=False, loop_subst=False): + search_call_list=False, loop_subst=False, + check_components=True): """Find a matching variable to , create a local clone (if is True), or return None. This purpose of this special Group version is to record any constituent @@ -2450,7 +2476,8 @@ def find_variable(self, standard_name=None, source_var=None, source_var=source_var, any_scope=any_scope, clone=clone, search_call_list=search_call_list, - loop_subst=loop_subst) + loop_subst=loop_subst, + check_components=check_components) if fvar and fvar.is_constituent(): if fvar.source.ptype == ConstituentVarDict.constitutent_source_type(): # We found this variable in the constituent dictionary, From 3bd705702444f134df576af72251b0e36b9dfb0b Mon Sep 17 00:00:00 2001 From: peverwhee Date: Thu, 19 Feb 2026 14:35:21 -0700 Subject: [PATCH 06/59] back out ddt library addition to run env --- scripts/ccpp_capgen.py | 1 - scripts/framework_env.py | 16 ---------------- scripts/host_cap.py | 1 - scripts/metavar.py | 11 ++--------- scripts/suite_objects.py | 4 +++- 5 files changed, 5 insertions(+), 28 deletions(-) diff --git a/scripts/ccpp_capgen.py b/scripts/ccpp_capgen.py index 16521586..afd6a332 100755 --- a/scripts/ccpp_capgen.py +++ b/scripts/ccpp_capgen.py @@ -659,7 +659,6 @@ def capgen(run_env, return_db=False): # Handle the host files host_model = parse_host_model_files(host_files, host_name, run_env, known_ddts=scheme_ddts) - run_env.set_ddt_library(host_model.ddt_lib) # Next, parse the scheme files # We always need to parse the constituent DDTs const_prop_mod = os.path.join(src_dir, "ccpp_constituent_prop_mod.meta") diff --git a/scripts/framework_env.py b/scripts/framework_env.py index fc21ba18..88c9c204 100644 --- a/scripts/framework_env.py +++ b/scripts/framework_env.py @@ -207,13 +207,6 @@ def __init__(self, logger, ndict=None, verbose=0, clean=False, self.__debug = debug # end if self.__logger = logger - # Set the ddt library to None - if ndict and ('ddt_library' in ndict): - self.__ddt_library = ndict['ddt_library'] - del ndict['ddt_library'] - else: - self.__ddt_library = None - # end if ## Check to see if anything is left in dictionary if ndict: for key in ndict: @@ -270,15 +263,6 @@ def host_name(self): """Return the property for this CCPPFrameworkEnv object.""" return self.__host_name - @property - def ddt_library(self): - """Return the dictionary of DDT definitions for the host model""" - return self.__ddt_library - - def set_ddt_library(self, ddt_lib): - """Set the ddt_library for the runtime environment""" - self.__ddt_library = ddt_lib - @property def generate_host_cap(self): """Return the property for this diff --git a/scripts/host_cap.py b/scripts/host_cap.py index 5a31826e..b06906fe 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -445,7 +445,6 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): ind_var = Var(prop_dict, _API_SOURCE, run_env) const_dict.add_variable(ind_var, run_env) # end for - print(const_dict) # Add vertical dimensions for DDT call strings pver = host_model.find_variable(standard_name=vert_layer_dim, any_scope=False) diff --git a/scripts/metavar.py b/scripts/metavar.py index a7ea7270..7212dd79 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -782,7 +782,7 @@ def array_ref(self, local_name=None): match = FORTRAN_SCALAR_REF_RE.match(local_name) return match - def intrinsic_elements(self, check_dict=None, ddt_lib=None, run_env=None): + def intrinsic_elements(self, check_dict=None, ddt_lib=None): """Return a list of the standard names of this Var object's 'leaf' intrinsic elements or this Var object's standard name if it is an intrinsic 'leaf' variable. @@ -800,14 +800,7 @@ def intrinsic_elements(self, check_dict=None, ddt_lib=None, run_env=None): element_names = None if self.is_ddt(): dtitle = self.get_prop_value('type') - if not ddt_lib: - if not run_env: - errmsg = f'If ddt_lib is not supplied, run_env must be' - raise CCPPError(errmsg) - # end if - ddt_lib = run_env.ddt_library - # end if - if dtitle in ddt_lib: + if ddt_lib and (dtitle in ddt_lib): element_names = [] ddt_def = ddt_lib[dtitle] for dvar in ddt_def.variable_list(): diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 5c03bd57..5d3ae489 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -890,7 +890,9 @@ def match_variable(self, var, run_env, host_dict): if host_var: if isinstance(host_var, VarDDT): local_var = host_var - var = host_var.var + if host_var.get_prop_value('standard_name') not in ['ccpp_constituents', 'ccpp_constituent_tendencies']: + var = host_var.var + # end if vdims = [] # end if # end if From 781269a24966e84899054a1b2708b646f4991dfc Mon Sep 17 00:00:00 2001 From: peverwhee Date: Thu, 19 Feb 2026 16:16:57 -0700 Subject: [PATCH 07/59] framework generates but constituents passing individually with wrong intent --- scripts/host_cap.py | 2 +- scripts/metavar.py | 6 ------ scripts/suite_objects.py | 4 ++-- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/scripts/host_cap.py b/scripts/host_cap.py index b06906fe..9678478b 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -500,7 +500,7 @@ def suite_part_call_list(host_model, const_dict, suite_part, subst_loop_vars, var_dicts = [host_model, const_dict] # Figure out which dictionary has the variable for vdict in var_dicts: - hvar = vdict.find_variable(standard_name=stdname, any_scope=False) + hvar = vdict.find_variable(standard_name=stdname, any_scope=False, check_components=False) if hvar is not None: var_dict = vdict break diff --git a/scripts/metavar.py b/scripts/metavar.py index 7212dd79..e297c895 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -711,8 +711,6 @@ def call_string(self, var_dict, loop_vars=None): if item: dvar = var_dict.find_variable(standard_name=item, any_scope=False) - print(dvar) - print(item) if dvar is None: try: dval = int(item) @@ -1877,10 +1875,6 @@ def find_variable(self, standard_name=None, source_var=None, # end if # end if # end for -# if not var and var_in_object.children(): -# for child in var_in_object.children(): -# print(child) - # end if # end for # end if if not var: diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 5d3ae489..c6b1e5c6 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -890,8 +890,8 @@ def match_variable(self, var, run_env, host_dict): if host_var: if isinstance(host_var, VarDDT): local_var = host_var - if host_var.get_prop_value('standard_name') not in ['ccpp_constituents', 'ccpp_constituent_tendencies']: - var = host_var.var +# if host_var.get_prop_value('standard_name') not in ['ccpp_constituents', 'ccpp_constituent_tendencies']: + var = host_var.var # end if vdims = [] # end if From 71a1221ba0617c736e35f70c731363a73e519a38 Mon Sep 17 00:00:00 2001 From: peverwhee Date: Tue, 24 Feb 2026 14:45:37 -0700 Subject: [PATCH 08/59] commit latest --- scripts/metadata_table.py | 1 + scripts/metavar.py | 10 +++++++--- scripts/suite_objects.py | 34 +++++++++++++++++++++++++--------- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/scripts/metadata_table.py b/scripts/metadata_table.py index e0c3ac98..e97647b7 100755 --- a/scripts/metadata_table.py +++ b/scripts/metadata_table.py @@ -274,6 +274,7 @@ def register_ddts(file_list): """ errors = "" ddt_names = set() + in_table = False for mfile in file_list: if os.path.exists(mfile): with open(mfile, 'r') as infile: diff --git a/scripts/metavar.py b/scripts/metavar.py index e297c895..f01d0248 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -1878,9 +1878,13 @@ def find_variable(self, standard_name=None, source_var=None, # end for # end if if not var: - if any_scope and (self.__parent_dict is not None): - src_clist = search_call_list - var = self.__parent_dict.find_variable(standard_name=standard_name, + if any_scope: + if self.__parent_dict is not None: + src_clist = search_call_list + # if standard_name: + # PEVERWHEE - fix this upstream - using the parent dict for the const object creates + # an infinite loop + var = self.__parent_dict.find_variable(standard_name=standard_name, source_var=source_var, any_scope=any_scope, clone=clone, diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index c6b1e5c6..4e9cedd8 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -11,7 +11,7 @@ # CCPP framework imports from ccpp_state_machine import CCPP_STATE_MACH, RUN_PHASE_NAME from code_block import CodeBlock -from constituents import ConstituentVarDict +from constituents import ConstituentVarDict, CONST_OBJ_STDNAME from framework_env import CCPPFrameworkEnv from metavar import Var, VarDictionary, VarLoopSubst from metavar import CCPP_CONSTANT_VARS, CCPP_LOOP_VAR_STDNAMES @@ -839,7 +839,7 @@ def find_variable(self, standard_name=None, source_var=None, # We do not have the variable, look to parents. found_var = self.parent.find_variable(standard_name=stdname, source_var=source_var, - any_scope=True, + any_scope=any_scope, clone=clone, search_call_list=scl, loop_subst=loop_subst, @@ -886,14 +886,30 @@ def match_variable(self, var, run_env, host_dict): # Is this variable a member of a DDT? If so, look for the parent DDT # and add that instead - host_var = host_dict.find_variable(source_var=var, any_scope=True) - if host_var: - if isinstance(host_var, VarDDT): - local_var = host_var -# if host_var.get_prop_value('standard_name') not in ['ccpp_constituents', 'ccpp_constituent_tendencies']: - var = host_var.var + if var.is_constituent(): + # If the variable is a constituent, add the constituent object instead + host_var = host_dict.find_variable(standard_name='hi', any_scope=False) +# host_var = host_dict.find_variable(standard_name=CONST_OBJ_STDNAME, any_scope=False) + if host_var: + var = host_var +# dict_var = var + # end if + else: + host_var = host_dict.find_variable(source_var=var, any_scope=False) + if host_var: + if isinstance(host_var, VarDDT): + local_var = host_var + var = host_var.var + vdims = [] # end if - vdims = [] + # end if + # end if + #host_var = host_dict.find_variable(source_var=var, any_scope=True) + #if host_var: + # if isinstance(host_var, VarDDT): + # local_var = host_var + # var = host_var.var + # vdims = [] # end if # end if # Does this variable exist in the calling tree? From f4a05b9b8ad1936a4f525f2697d136a6e5084bb9 Mon Sep 17 00:00:00 2001 From: peverwhee Date: Tue, 24 Feb 2026 16:16:03 -0700 Subject: [PATCH 09/59] non-constituent ddts being passed through to suite cap --- scripts/metavar.py | 3 -- scripts/suite_objects.py | 42 ++++++++++--------------- test/advection_test/test_host_data.meta | 1 - 3 files changed, 16 insertions(+), 30 deletions(-) diff --git a/scripts/metavar.py b/scripts/metavar.py index f01d0248..64259849 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -1881,9 +1881,6 @@ def find_variable(self, standard_name=None, source_var=None, if any_scope: if self.__parent_dict is not None: src_clist = search_call_list - # if standard_name: - # PEVERWHEE - fix this upstream - using the parent dict for the const object creates - # an infinite loop var = self.__parent_dict.find_variable(standard_name=standard_name, source_var=source_var, any_scope=any_scope, diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 4e9cedd8..8bf8d944 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -886,15 +886,8 @@ def match_variable(self, var, run_env, host_dict): # Is this variable a member of a DDT? If so, look for the parent DDT # and add that instead - if var.is_constituent(): - # If the variable is a constituent, add the constituent object instead - host_var = host_dict.find_variable(standard_name='hi', any_scope=False) -# host_var = host_dict.find_variable(standard_name=CONST_OBJ_STDNAME, any_scope=False) - if host_var: - var = host_var -# dict_var = var - # end if - else: + constituent = var.is_constituent() + if not constituent: host_var = host_dict.find_variable(source_var=var, any_scope=False) if host_var: if isinstance(host_var, VarDDT): @@ -904,14 +897,6 @@ def match_variable(self, var, run_env, host_dict): # end if # end if # end if - #host_var = host_dict.find_variable(source_var=var, any_scope=True) - #if host_var: - # if isinstance(host_var, VarDDT): - # local_var = host_var - # var = host_var.var - # vdims = [] - # end if - # end if # Does this variable exist in the calling tree? dict_var = self.find_variable(source_var=var, any_scope=True) if dict_var is None: @@ -929,7 +914,7 @@ def match_variable(self, var, run_env, host_dict): else: # Check dimensions dict_dims = dict_var.get_dimensions() - if vdims: + if vdims and not constituent: args = self.parent.match_dimensions(vdims, dict_dims) match, new_vdims, new_dict_dims, missing_vert, perm, err = args if perm is not None: @@ -963,8 +948,10 @@ def match_variable(self, var, run_env, host_dict): sdict = {'dimensions':new_dict_dims} # end if # Add any DDT components from the host dictionary version of the variable - for dict_var_component in dict_var.components: - var.add_component(dict_var_component) + if not constituent: + for dict_var_component in dict_var.components: + var.add_component(dict_var_component) + # end for # end if found_var = self.parent.add_variable_to_call_tree(var, subst_dict=sdict) @@ -1252,9 +1239,11 @@ def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): if dict_var: if dict_var.is_ddt(): subst_dict = {'intent':'inout'} - clone = dict_var.clone(subst_dict) - dict_var = clone + else: + subst_dict = {'intent': var.get_prop_value('intent')} # end if + clone = dict_var.clone(subst_dict) + dict_var = clone # end if if found: if self.__group.run_env.debug: @@ -1545,12 +1534,10 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er break if not dvar: raise Exception(f"No variable with standard name '{standard_name}' in cldicts") - if dvar and isinstance(dvar, VarDDT): - local_name = dvar.call_string(search_dict) - else: - local_name = dvar.get_prop_value('local_name') # end if + local_name = dvar.call_string(search_dict) + # If the variable is allocatable and the intent for the scheme is 'out', # then we can't test anything because the scheme is going to allocate # the variable. We don't have this information earlier in @@ -1597,8 +1584,11 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er dvar = var_dict.find_variable(standard_name=dim, any_scope=False) if dvar is not None: break + # end if + # end for if not dvar: raise Exception(f"No variable with standard name '{dim}' in cldicts") + # end if dim_lname = dvar.get_prop_value('local_name') dim_length = 1 dim_strings.append(dim_lname) diff --git a/test/advection_test/test_host_data.meta b/test/advection_test/test_host_data.meta index a676f141..3f757264 100644 --- a/test/advection_test/test_host_data.meta +++ b/test/advection_test/test_host_data.meta @@ -60,7 +60,6 @@ long_name = Array of constituent indices units = 1 dimensions = (banana_array_dim) - protected = true type = integer [ const_index ] standard_name = test_banana_constituent_index From 413acab6e3183d994c768141a26449387bf10c36 Mon Sep 17 00:00:00 2001 From: peverwhee Date: Tue, 24 Feb 2026 16:20:41 -0700 Subject: [PATCH 10/59] cleanup --- 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 8bf8d944..2a048df6 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -886,6 +886,7 @@ def match_variable(self, var, run_env, host_dict): # Is this variable a member of a DDT? If so, look for the parent DDT # and add that instead + # PEVERWHEE - note - currently not doing this for constituents constituent = var.is_constituent() if not constituent: host_var = host_dict.find_variable(source_var=var, any_scope=False) @@ -1535,7 +1536,6 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er if not dvar: raise Exception(f"No variable with standard name '{standard_name}' in cldicts") # end if - local_name = dvar.call_string(search_dict) # If the variable is allocatable and the intent for the scheme is 'out', From 8fc24dd36ddae28309d9dbcc0d9bf22b24484049 Mon Sep 17 00:00:00 2001 From: dustinswales Date: Thu, 5 Mar 2026 13:55:33 -0700 Subject: [PATCH 11/59] Initial commit --- scripts/suite_objects.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 2a048df6..713d7401 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1659,7 +1659,7 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er outfile.write(f"if {conditional} then", indent) # end if outfile.write(f"! Check size of array {local_name}", tmp_indent) - outfile.write(f"if (size({local_name}{dim_string}) /= {array_size}) then", tmp_indent) + outfile.write(f"if (size({local_name}) /= {array_size}) then", tmp_indent) outfile.write(f"write({errmsg}, '(2(a,i8))') 'In group {self.__group.name} before "\ f"{self.__subroutine_name}: for array {local_name}, expected size ', "\ f"{array_size}, ' but got ', size({local_name})", tmp_indent+1) @@ -1688,14 +1688,7 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er # is the correct size. Skip for 1D variables. if (ndims > 1): for index, dim_length in enumerate(dim_lengths): - array_ref = '(' - # Dimension(s) before current rank to be checked. - array_ref += '1,'*(index) - # Dimension to check. - array_ref += dim_strings[index] - # Dimension(s) after current rank to be checked. - array_ref += ',1'*(ndims-(index+1)) - array_ref += ')' + array_ref = ','+str(index+1) # outfile.write(f"! Check length of {local_names[index]}{array_ref}", tmp_indent) outfile.write(f"if (size({local_names[index]}{array_ref}) /= {dim_length}) then ", \ From dae14d25fafd6b4ce66a359af7e481d953a651e8 Mon Sep 17 00:00:00 2001 From: dustinswales Date: Mon, 16 Mar 2026 09:09:35 -0600 Subject: [PATCH 12/59] Use full DDT reference in Scheme call_list --- scripts/suite_objects.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 713d7401..bb87d136 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -145,6 +145,7 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ raise CCPPError(errmsg.format(stdname, clnames)) # end if lname = dvar.get_prop_value('local_name') + lname = dvar.call_string(cldict) # Optional variables in the caps are associated with # local pointers of _ptr if dvar.get_prop_value('optional'): From 42c28986de7f58db6e51055d87f82efebc69ef51 Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Mon, 16 Mar 2026 22:04:42 +0000 Subject: [PATCH 13/59] Some more changes --- scripts/metavar.py | 29 +- scripts/suite_objects.py | 295 +++++++++++------- .../test_var_compatibility_integration.F90 | 60 +--- .../var_compatibility_test_reports.py | 36 +-- 4 files changed, 215 insertions(+), 205 deletions(-) diff --git a/scripts/metavar.py b/scripts/metavar.py index 30a84c44..3a59b7bc 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -1150,21 +1150,6 @@ def write_def(self, outfile, indent, wdict, allocatable=False, target=False, name=name, dims=dimstr, cspace=cspace, sname=stdname), indent) - def write_ptr_def(self, outfile, indent, name, kind, dimstr, vtype, extra_space=0): - """Write the definition line for local null pointer declaration to .""" - comma = ', ' - if 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}{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, - cspace=cspace, cspace2=cspace2), indent) - def is_ddt(self): """Return True iff is a DDT type.""" return not self.__intrinsic @@ -2185,6 +2170,20 @@ def new_internal_variable_name(self, prefix=None, max_len=63): # end while return newvar +def write_ptr_def(outfile, indent, name, kind, dimstr, vtype, extra_space=0): + """Write the definition line for local null pointer declaration to .""" + comma = ', ' + if 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}{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, + cspace=cspace, cspace2=cspace2), indent) ############################################################################### # List of constant variables which are universally available diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index bb87d136..ae4d37a9 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -15,6 +15,7 @@ from framework_env import CCPPFrameworkEnv from metavar import Var, VarDictionary, VarLoopSubst from metavar import CCPP_CONSTANT_VARS, CCPP_LOOP_VAR_STDNAMES +from metavar import write_ptr_def from parse_tools import ParseContext, ParseSource, context_string from parse_tools import ParseInternalError, CCPPError from parse_tools import init_log, set_log_to_null @@ -129,7 +130,7 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ if cldicts is not None: for cldict in cldicts: dvar = cldict.find_variable(standard_name=stdname, - any_scope=False) + any_scope=True) if dvar is not None: break # end if @@ -144,11 +145,10 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ clnames = [x.name for x in cldicts] raise CCPPError(errmsg.format(stdname, clnames)) # end if - lname = dvar.get_prop_value('local_name') lname = dvar.call_string(cldict) # Optional variables in the caps are associated with # local pointers of _ptr - if dvar.get_prop_value('optional'): + if var.get_prop_value('optional'): lname = dummy+'_ptr' # end if else: @@ -932,17 +932,9 @@ def match_variable(self, var, run_env, host_dict): new_dict_dims = dict_dims match = True # end if - # 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 + # Create compatability object, containing any necessary forward/reverse + # transforms from and + compat_obj = var.compatible(dict_var, run_env) # Add the variable to the parent call tree if dict_dims == new_dict_dims: sdict = {} @@ -1338,11 +1330,12 @@ def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): # end if # Is this a conditionally allocated variable? - # If so, declare localpointer variable. This is needed to - # pass inactive (not present) status through the caps. + # If so, declare local pointer variable. This is needed to + # pass inactive (not present) status through the Scheme call_lists. if var.get_prop_value('optional'): - newvar_ptr = var.clone(var.get_prop_value('local_name')+'_ptr') - self.__optional_vars.append([dict_var, var, newvar_ptr, has_transform]) + #if dict_var: + self.add_optional_var(dict_var, var, has_transform) + # end if # end if # end for @@ -1356,6 +1349,22 @@ def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): # end if return scheme_mods + def add_optional_var(self, dict_var, var, has_transform): + """Add local pointer needed for optional variable(s) in Group Cap. Also, + add any host variables from active condition that are needed to associate + the local pointer correctly.""" + + lname = var.get_prop_value('local_name') + sname = var.get_prop_value('standard_name') + lname_ptr = lname + '_ptr' + newvar_ptr = var.clone(lname_ptr) + # Group write phase needs new pointer variable for declaration step. + self.__group.optional_vars.append(newvar_ptr) + # Scheme write phase needs more info for transformations/local-pointer assignments. + self.__optional_vars.append([dict_var, var, has_transform]) + return + # end def + def add_var_debug_check(self, var): """Add a debug check for a given variable var (host model variable, suite variable or group module variable) for this scheme. @@ -1525,7 +1534,7 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er var_in_call_list = False # If it is not in the call list, try to find it # in the local variables of this group subroutine. - dvar = self.__group.find_variable(standard_name=standard_name, any_scope=False) + dvar = self.__group.find_variable(standard_name=standard_name, any_scope=True) if not dvar: # This variable is handled by the group # and is declared as a module variable @@ -1710,38 +1719,148 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er # endif # end if - def associate_optional_var(self, dict_var, var, var_ptr, has_transform, cldicts, indent, outfile): - """Write local pointer association for optional variables.""" + def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, outfile): + """Write local pointer association for optional variable.""" + # Use the local name from the Scheme call list, append "_ptr" suffix. + standard_name = var.get_prop_value('standard_name') + dvar = self.__group.call_list.find_variable(standard_name=standard_name, any_scope=False) + search_dict = self.__group.call_list + if dvar: + var_in_call_list = True + else: + var_in_call_list = False + # If it is not in the call list, try to find it + # in the local variables of this group subroutine. + dvar = self.__group.find_variable(standard_name=standard_name, any_scope=False) + if not dvar: + # This variable is handled by the group + # and is declared as a module variable + for var_dict in self.__group.suite_dicts(): + dvar = var_dict.find_variable(standard_name=standard_name, any_scope=False) + if dvar: + search_dict = var_dict + break + # end if + # end for + # end if + # end if + + # Need to use local_name in Group's call list (self.__group.call_list), not + # the local_name in var. if (dict_var): - (conditional, _) = dict_var.conditional(cldicts) + (conditional, vars_needed) = dvar.conditional(cldicts) if (has_transform): - lname = var.get_prop_value('local_name')+'_local' + lname = dvar.get_prop_value('local_name')+'_local' else: - lname = var.get_prop_value('local_name') + lname = dvar.call_string(search_dict) + # end if + lname_ptr = var.get_prop_value('local_name') + '_ptr' + # Scheme has optional varaible, host has varaible defined as Conditional (Active). + if conditional != '.true.': + outfile.write(f"if {conditional} then", indent) + outfile.write(f"{lname_ptr} => {lname}", indent+1) + outfile.write(f"end if", indent) + # Scheme has optional varaible, host has varaible defined as Mandatory. + else: + outfile.write(f"{lname_ptr} => {lname}", indent) + # end if + # end if + # end def + + def nullify_optional_var(self, dict_var, var, has_transform, cldicts, indent, outfile): + """Write local pointer nullification for optional variable.""" + + standard_name = var.get_prop_value('standard_name') + dvar = self.__group.call_list.find_variable(standard_name=standard_name, any_scope=False) + search_dict = self.__group.call_list + if dvar: + var_in_call_list = True + else: + var_in_call_list = False + # If it is not in the call list, try to find it + # in the local variables of this group subroutine. + dvar = self.__group.find_variable(standard_name=standard_name, any_scope=False) + if not dvar: + # This variable is handled by the group + # and is declared as a module variable + for var_dict in self.__group.suite_dicts(): + dvar = var_dict.find_variable(standard_name=standard_name, any_scope=False) + if dvar: + search_dict = var_dict + break + # end if + # end for + # end if + # end if + + # Need to use local_name in Group's call list (self.__group.call_list), not + # the local_name in var. + if (dict_var): + (conditional, vars_needed) = dvar.conditional(cldicts) + if (has_transform): + lname = dvar.get_prop_value('local_name')+'_local' + else: + lname = dvar.get_prop_value('local_name') + # end if + lname_ptr = var.get_prop_value('local_name') + '_ptr' + # Scheme has optional varaible, host has varaible defined as Conditional (Active). + if conditional != '.true.': + outfile.write(f"if {conditional} then", indent) + outfile.write(f"nullify({lname_ptr})", indent+1) + outfile.write(f"end if", indent) + # Scheme has optional varaible, host has varaible defined as Mandatory. + else: + outfile.write(f"nullify({lname_ptr})", indent) + # end if + # end if + # end def + + def assign_pointer_to_var(self, dict_var, var, has_transform, cldicts, indent, outfile): + """Write local pointer assignment to variable.""" + # Use the local name from the Scheme call list, append "_ptr" suffix. + standard_name = var.get_prop_value('standard_name') + dvar = self.__group.call_list.find_variable(standard_name=standard_name, any_scope=False) + search_dict = self.__group.call_list + if dvar: + var_in_call_list = True + else: + var_in_call_list = False + # If it is not in the call list, try to find it + # in the local variables of this group subroutine. + dvar = self.__group.find_variable(standard_name=standard_name, any_scope=False) + if not dvar: + # This variable is handled by the group + # and is declared as a module variable + for var_dict in self.__group.suite_dicts(): + dvar = var_dict.find_variable(standard_name=standard_name, any_scope=False) + if dvar: + search_dict = var_dict + break + # end if + # end for # 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) # 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) + (conditional, vars_needed) = dvar.conditional(cldicts) if (has_transform): - lname = var.get_prop_value('local_name')+'_local' + lname = dvar.get_prop_value('local_name')+'_local' else: - lname = var.get_prop_value('local_name') + lname = dvar.call_string(search_dict) + # end if + lname_ptr = var.get_prop_value('local_name') + '_ptr' + if conditional != '.true.': + outfile.write(f"if {conditional} then", indent) + outfile.write(f"{lname} = {lname_ptr}", indent+1) + outfile.write(f"end if", indent) + else: + outfile.write(f"{lname} = {lname_ptr}", indent) # 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 + # end def def add_var_transform(self, var, compat_obj, vert_dim): """Register any variable transformation needed by for this Scheme. @@ -1918,8 +2037,8 @@ def write(self, outfile, errcode, errmsg, indent): 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) + for (dict_var, var, has_transform) in self.__optional_vars: + tstmt = self.associate_optional_var(dict_var, var, has_transform, cldicts, indent+1, outfile) # end for # # Write the scheme call. @@ -1935,15 +2054,23 @@ def write(self, outfile, errcode, errmsg, indent): # Copy any local pointers. # first_ptr_declaration=True - for (dict_var, var, var_ptr, has_transform) in self.__optional_vars: + for (dict_var, var, has_transform) in self.__optional_vars: if first_ptr_declaration: outfile.write('! Copy any local pointers to dummy/local variables', indent+1) first_ptr_declaration=False # end if - tstmt = self.assign_pointer_to_var(dict_var, var, var_ptr, has_transform, cldicts, indent+1, outfile) + tstmt = self.assign_pointer_to_var(dict_var, var, has_transform, cldicts, indent+1, outfile) # end for outfile.write('',indent+1) # + # Nullify any local pointers. + # + if self.__optional_vars: + outfile.write('! Nullify conditional variables', indent+1) + # end if + for (dict_var, var, has_transform) in self.__optional_vars: + tstmt = self.nullify_optional_var(dict_var, var, has_transform, cldicts, indent+1, outfile) + # # Write any forward (post-Scheme) transforms. # if len(self.__forward_transforms) > 0: @@ -2081,6 +2208,10 @@ def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): def write(self, outfile, errcode, errmsg, indent): """Write code for the vertical loop, including contents, to """ + # DJS2026: The DEBUG checks are within this loop, so they check the full + # array extent at each vertical index. Redundant. Either check slice + # of vertical index, or move whole array size check outside of the Scheme + # write into the Group write phase. outfile.write('do {} = 1, {}'.format(self.name, self.dimension_name), indent) # Note that 'scheme' may be a sybcycle or other construct @@ -2305,6 +2436,7 @@ def __init__(self, group_xml, transition, parent, context, run_env): self._set_state = None self._ddt_library = None self.transform_locals = list() + self.optional_vars = list() def phase_match(self, scheme_name): """If scheme_name matches the group phase, return the group and @@ -2503,17 +2635,15 @@ def write(self, outfile, host_arglist, indent, const_mod, # end if # Collect information on local variables subpart_allocate_vars = {} - subpart_optional_vars = {} subpart_scalar_vars = {} 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') sname = var.get_prop_value('standard_name') - if (lname in subpart_allocate_vars) or (lname in subpart_optional_vars) or (lname in subpart_scalar_vars): + if (lname in subpart_allocate_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: @@ -2521,50 +2651,18 @@ def write(self, outfile, host_arglist, indent, const_mod, raise ParseInternalError(errmsg.format(lname)) # end if else: - opt_var = var.get_prop_value('optional') dims = var.get_dimensions() if (dims is not None) and dims: - if opt_var: - if (self.call_list.find_variable(standard_name=sname)): - subpart_optional_vars[lname] = (var, item, opt_var) - 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) - # end if + subpart_allocate_vars[lname] = (var, item, False) + allocatable_var_set.add(lname) else: - subpart_scalar_vars[lname] = (var, item, opt_var) + subpart_scalar_vars[lname] = (var, item, False) # end if # 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: - 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 - # 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' + # All optional variables for the Schemes need to have an associated pointer array declared. + for ivar in self.optional_vars: + name = ivar.get_prop_value('local_name') kind = ivar.get_prop_value('kind') dims = ivar.get_dimensions() if ivar.is_ddt(): @@ -2616,8 +2714,7 @@ def write(self, outfile, host_arglist, indent, const_mod, # Look for any DDT types call_vars = self.call_list.variable_list() all_vars = ([x[0] for x in subpart_allocate_vars.values()] + - [x[0] for x in subpart_scalar_vars.values()] + - [x[0] for x in subpart_optional_vars.values()]) + [x[0] for x in subpart_scalar_vars.values()]) all_vars.extend(call_vars) self._ddt_library.write_ddt_use_statements(all_vars, outfile, indent+1, pad=modmax) @@ -2631,7 +2728,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 or subpart_scalar_vars or subpart_optional_vars: + if subpart_allocate_vars or subpart_scalar_vars: outfile.write('\n! Local Variables', indent+1) # end if # Scalars @@ -2651,18 +2748,14 @@ def write(self, outfile, host_arglist, indent, const_mod, allocatable=(key in allocatable_var_set), target=target) # end for - # Target arrays. - 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 + outfile.write('', 0) + if pointer_var_set: + outfile.write('! 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) + write_ptr_def(outfile, indent+1, name, kind, dim, vtype) # end for outfile.write('', 0) # Get error variable names @@ -2718,12 +2811,6 @@ 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_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) - # end for # Allocate suite vars if allocate: outfile.write('\n! Allocate suite_vars', indent+1) @@ -2754,14 +2841,6 @@ def write(self, outfile, host_arglist, indent, const_mod, for lname in optional_var_set: 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: - #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/test_var_compatibility_integration.F90 b/test/var_compatibility_test/test_var_compatibility_integration.F90 index 1e081e10..71a3c2a5 100644 --- a/test/var_compatibility_test/test_var_compatibility_integration.F90 +++ b/test/var_compatibility_test/test_var_compatibility_integration.F90 @@ -4,67 +4,25 @@ program test_var_compatibility_integration implicit none character(len=cs), target :: test_parts1(1) = (/ 'radiation ' /) - - character(len=cm), target :: test_invars1(18) = (/ & - '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 ', & - 'cloud_graupel_number_concentration ', & - 'scalar_variable_for_testing ', & - 'turbulent_kinetic_energy ', & - 'turbulent_kinetic_energy2 ', & - 'scalar_variable_for_testing_a ', & - 'scalar_variable_for_testing_b ', & - 'scalar_variable_for_testing_c ', & - 'scheme_order_in_suite ', & + character(len=cm), target :: test_invars1(5) = (/ & 'num_subcycles_for_effr ', & 'flag_indicating_cloud_microphysics_has_graupel ', & 'flag_indicating_cloud_microphysics_has_ice ', & - 'surface_downwelling_shortwave_radiation_flux ', & - 'surface_upwelling_shortwave_radiation_flux ', & - 'longwave_radiation_fluxes '/) - - character(len=cm), target :: test_outvars1(14) = (/ & + 'effective_radius_of_stratiform_cloud_snow_particle ', & + 'physics_state_derived_type '/) + character(len=cm), target :: test_outvars1(4) = (/ & '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 ', & - 'scheme_order_in_suite ', & - 'surface_downwelling_shortwave_radiation_flux ', & - 'surface_upwelling_shortwave_radiation_flux ', & - 'turbulent_kinetic_energy ', & - 'turbulent_kinetic_energy2 ', & - 'longwave_radiation_fluxes '/) - - character(len=cm), target :: test_reqvars1(22) = (/ & + 'effective_radius_of_stratiform_cloud_snow_particle ', & + 'physics_state_derived_type '/) + character(len=cm), target :: test_reqvars1(7) = (/ & 'ccpp_error_code ', & 'ccpp_error_message ', & - 'effective_radius_of_stratiform_cloud_rain_particle ', & - '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_graupel ', & - 'cloud_graupel_number_concentration ', & - 'cloud_ice_number_concentration ', & - 'scalar_variable_for_testing ', & - 'turbulent_kinetic_energy ', & - 'turbulent_kinetic_energy2 ', & - 'scalar_variable_for_testing_a ', & - 'scalar_variable_for_testing_b ', & - 'scalar_variable_for_testing_c ', & - 'scheme_order_in_suite ', & 'num_subcycles_for_effr ', & 'flag_indicating_cloud_microphysics_has_graupel ', & 'flag_indicating_cloud_microphysics_has_ice ', & - 'surface_downwelling_shortwave_radiation_flux ', & - 'surface_upwelling_shortwave_radiation_flux ', & - 'longwave_radiation_fluxes '/) - + 'effective_radius_of_stratiform_cloud_snow_particle ', & + 'physics_state_derived_type '/) type(suite_info) :: test_suites(1) logical :: run_okay diff --git a/test/var_compatibility_test/var_compatibility_test_reports.py b/test/var_compatibility_test/var_compatibility_test_reports.py index ada612c7..795af1fc 100755 --- a/test/var_compatibility_test/var_compatibility_test_reports.py +++ b/test/var_compatibility_test/var_compatibility_test_reports.py @@ -12,7 +12,6 @@ """ import os import unittest - from test_stub import BaseTests _BUILD_DIR = os.path.join(os.path.abspath(os.environ['BUILD_DIR']), "test", "var_compatibility_test") @@ -39,38 +38,13 @@ _SUITE_LIST = ["var_compatibility_suite"] _DEPENDENCIES = [ os.path.join(_TEST_DIR, "module_rad_ddt.F90")] _INPUT_VARS_VAR_ACTION = ["horizontal_loop_begin", "horizontal_loop_end", "horizontal_dimension", "vertical_layer_dimension", - "effective_radius_of_stratiform_cloud_liquid_water_particle", - "effective_radius_of_stratiform_cloud_rain_particle", - "effective_radius_of_stratiform_cloud_snow_particle", - "effective_radius_of_stratiform_cloud_graupel", - "cloud_graupel_number_concentration", - "scalar_variable_for_testing", - "turbulent_kinetic_energy", - "turbulent_kinetic_energy2", - "scalar_variable_for_testing_a", - "scalar_variable_for_testing_b", - "scalar_variable_for_testing_c", - "scheme_order_in_suite", "flag_indicating_cloud_microphysics_has_graupel", "flag_indicating_cloud_microphysics_has_ice", - "surface_downwelling_shortwave_radiation_flux", - "surface_upwelling_shortwave_radiation_flux", - "longwave_radiation_fluxes", - "num_subcycles_for_effr"] -_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", - "cloud_ice_number_concentration", - "effective_radius_of_stratiform_cloud_rain_particle", - "turbulent_kinetic_energy", - "turbulent_kinetic_energy2", - "scalar_variable_for_testing", - "scalar_variable_for_testing", - "surface_downwelling_shortwave_radiation_flux", - "surface_upwelling_shortwave_radiation_flux", - "longwave_radiation_fluxes", - "scheme_order_in_suite"] + "num_subcycles_for_effr", + "physics_state_derived_type", + "effective_radius_of_stratiform_cloud_snow_particle"] +_OUTPUT_VARS_VAR_ACTION = ["ccpp_error_code", "ccpp_error_message","physics_state_derived_type", + "effective_radius_of_stratiform_cloud_snow_particle"] _REQUIRED_VARS_VAR_ACTION = _INPUT_VARS_VAR_ACTION + _OUTPUT_VARS_VAR_ACTION From db17baf84b8db65a4fa4e17e1b1ed1a97199c563 Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Mon, 23 Mar 2026 22:30:29 +0000 Subject: [PATCH 14/59] Add target atttribute to DDT declarations in Group cap. --- scripts/metavar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/metavar.py b/scripts/metavar.py index 3a59b7bc..aefad441 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -1122,7 +1122,7 @@ def write_def(self, outfile, indent, wdict, allocatable=False, target=False, else: comma = ' ' # end if - if self.get_prop_value('target'): + if self.components: targ = ", target" else: targ = "" From e3c45ed357c2aa4e2084d71294d0832192218cb8 Mon Sep 17 00:00:00 2001 From: Dustin Swales Date: Mon, 23 Mar 2026 22:31:13 +0000 Subject: [PATCH 15/59] Handle horizontal loop substitutions in DEBUG checks, Scheme call lists, and optional arguments. --- scripts/suite_objects.py | 214 +++++++++++++++++++++++++++++++++++---- 1 file changed, 196 insertions(+), 18 deletions(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index ae4d37a9..1e99833b 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -124,6 +124,7 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ # Do not include constants stdname = var.get_prop_value('standard_name') if stdname not in CCPP_CONSTANT_VARS: + dimensions = var.get_dimensions() # Find the dummy argument name dummy = var.get_prop_value('local_name') # Now, find the local variable name @@ -131,7 +132,19 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ for cldict in cldicts: dvar = cldict.find_variable(standard_name=stdname, any_scope=True) + host_var = False if dvar is not None: + var_in_call_list = True + if dvar.is_ddt(): + if dvar.components: + var_in_call_list = False + # end if + else: + # If we get here, the variable is explicitly in the Group call_list, + # not a DDT component. No need to modify the dimensions in the Scheme + # call list, as these were handled in the Suite Cap call_list. + host_var = True + # end if break # end if # end for @@ -145,11 +158,52 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ clnames = [x.name for x in cldicts] raise CCPPError(errmsg.format(stdname, clnames)) # end if + dimensions = dvar.get_dimensions() lname = dvar.call_string(cldict) # Optional variables in the caps are associated with # local pointers of _ptr if var.get_prop_value('optional'): lname = dummy+'_ptr' + # Finally, handle the dimensions. + else: + if dimensions and not host_var: + dimstr = '(' + for cnt,dim in enumerate(dimensions): + if is_horizontal_dimension(dim): + if self.routine.run_phase(): + if var_in_call_list and \ + self.find_variable(standard_name="horizontal_loop_extent"): + ldim = "ccpp_constant_one" + udim = "horizontal_loop_extent" + else: + ldim = "horizontal_loop_begin" + udim = "horizontal_loop_end" + # endif + else: + ldim = "ccpp_constant_one" + udim = "horizontal_dimension" + # endif + # Get dimension for lower bound + lvar = cldict.find_variable(standard_name=ldim, any_scope=True) + if not lvar: + raise Exception(f"No variable with standard name '{ldim}' in cldict") + # end if + ldim_lname = lvar.get_prop_value('local_name') + # Get dimension for upper bound + uvar = cldict.find_variable(standard_name=udim, any_scope=True) + if not uvar: + raise Exception(f"No variable with standard name '{udim}' in cldict") + # end if + udim_lname = uvar.get_prop_value('local_name') + dimstr = dimstr + ldim_lname + ':' + udim_lname + else: + dimstr = dimstr + ':' + # endif + if cnt < len(dimensions)-1: dimstr = dimstr+',' + # end for + dimstr = dimstr+')' + lname = lname + dimstr + # end if # end if else: cldict = None @@ -1522,14 +1576,18 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er allocatable = var.get_prop_value('allocatable') vtype = var.get_prop_value('type') - # Need the local name from the group call list, - # from the locally-defined variables of the group, - # or from the suite, not how it is called in the scheme (var) - # First, check if the variable is in the call list. - dvar = self.__group.call_list.find_variable(standard_name=standard_name, any_scope=False) + # Need the local_name from the Scheme call list (i.e. dvar.call_string(var_dict)) + dvar = self.__group.call_list.find_variable(standard_name=standard_name, any_scope=True) search_dict = self.__group.call_list if dvar: var_in_call_list = True + # If we find a call_list variable that is a DDT, check to see if it has components. + # Variables that are components of a DDT are NOT part of the call_list. + if dvar.is_ddt(): + if dvar.components: + var_in_call_list = False + # end if + # end if else: var_in_call_list = False # If it is not in the call list, try to find it @@ -1668,11 +1726,11 @@ 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"! Check size of array {local_name}", tmp_indent) - outfile.write(f"if (size({local_name}) /= {array_size}) then", tmp_indent) + outfile.write(f"! Check size of array {local_name+dim_string}", tmp_indent) + outfile.write(f"if (size({local_name+dim_string}) /= {array_size}) then", tmp_indent) outfile.write(f"write({errmsg}, '(2(a,i8))') 'In group {self.__group.name} before "\ - f"{self.__subroutine_name}: for array {local_name}, expected size ', "\ - f"{array_size}, ' but got ', size({local_name})", tmp_indent+1) + f"{self.__subroutine_name}: for array {local_name+dim_string}, expected size ', "\ + f"{array_size}, ' but got ', size({local_name+dim_string})", tmp_indent+1) outfile.write(f"{errcode} = 1", tmp_indent+1) outfile.write(f"return", tmp_indent+1) outfile.write(f"end if", tmp_indent) @@ -1700,13 +1758,13 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er for index, dim_length in enumerate(dim_lengths): array_ref = ','+str(index+1) # - outfile.write(f"! Check length of {local_names[index]}{array_ref}", tmp_indent) - outfile.write(f"if (size({local_names[index]}{array_ref}) /= {dim_length}) then ", \ + outfile.write(f"! Check length of {local_names[index]+dim_string}{array_ref}", tmp_indent) + outfile.write(f"if (size({local_names[index]+dim_string}{array_ref}) /= {dim_length}) then ", \ tmp_indent) outfile.write(f"write({errmsg}, '(2(a,i8))') 'In group {self.__group.name} before " \ - f"{self.__subroutine_name}: for array {local_names[index]}{array_ref}, "\ + f"{self.__subroutine_name}: for array {local_names[index]+dim_string}{array_ref}, "\ f"expected size ', {dim_length}, ' but got ', " \ - f"size({local_names[index]}{array_ref})", tmp_indent+1) + f"size({local_names[index]+dim_string}{array_ref})", tmp_indent+1) outfile.write(f"{errcode} = 1", tmp_indent+1) outfile.write(f"return", tmp_indent+1) outfile.write(f"end if", tmp_indent) @@ -1727,6 +1785,13 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, search_dict = self.__group.call_list if dvar: var_in_call_list = True + # If we find a call_list variable that is a DDT, check to see if it has components. + # Variables that are components of a DDT are NOT part of the call_list. + if dvar.is_ddt(): + if dvar.components: + var_in_call_list = False + # end if + # end if else: var_in_call_list = False # If it is not in the call list, try to find it @@ -1744,6 +1809,60 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, # end for # end if # end if + if not dvar: + raise Exception(f"No variable with standard name '{standard_name}' in cldicts") + # end if + # Handle the dimensions... + dimensions = dvar.get_dimensions() + dimstr = '' + if dimensions: + dimstr = dimstr + '(' + for cnt,dim in enumerate(dimensions): + if is_horizontal_dimension(dim): + if self.run_phase(): + if var_in_call_list and \ + self.find_variable(standard_name="horizontal_loop_extent"): + ldim = "ccpp_constant_one" + udim = "horizontal_loop_extent" + else: + ldim = "horizontal_loop_begin" + udim = "horizontal_loop_end" + # endif + else: + ldim = "ccpp_constant_one" + udim = "horizontal_dimension" + # endif + # Get dimension for lower bound + #lvar = self.__group.call_list.find_variable(standard_name=ldim, any_scope=True) + for var_dict in cldicts: + lvar = var_dict.find_variable(standard_name=ldim, any_scope=False) + if lvar is not None: + break + # end if + # end for + if not lvar: + raise Exception(f"No variable with standard name '{ldim}' in cldicts") + # end if + ldim_lname = lvar.get_prop_value('local_name') + # Get dimension for upper bound + for var_dict in cldicts: + uvar = var_dict.find_variable(standard_name=udim, any_scope=False) + if uvar is not None: + break + # end if + # end for + if not uvar: + raise Exception(f"No variable with standard name '{udim}' in cldicts") + # end if + udim_lname = uvar.get_prop_value('local_name') + dimstr = dimstr + ldim_lname + ':' + udim_lname + else: + dimstr = dimstr + ':' + # end if + if cnt < len(dimensions)-1: dimstr = dimstr+',' + # end for + dimstr = dimstr+')' + # end if # Need to use local_name in Group's call list (self.__group.call_list), not # the local_name in var. @@ -1758,11 +1877,11 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, # Scheme has optional varaible, host has varaible defined as Conditional (Active). if conditional != '.true.': outfile.write(f"if {conditional} then", indent) - outfile.write(f"{lname_ptr} => {lname}", indent+1) + outfile.write(f"{lname_ptr} => {lname+dimstr}", indent+1) outfile.write(f"end if", indent) # Scheme has optional varaible, host has varaible defined as Mandatory. else: - outfile.write(f"{lname_ptr} => {lname}", indent) + outfile.write(f"{lname_ptr} => {lname+dimstr}", indent) # end if # end if # end def @@ -1823,6 +1942,13 @@ def assign_pointer_to_var(self, dict_var, var, has_transform, cldicts, indent, o search_dict = self.__group.call_list if dvar: var_in_call_list = True + # If we find a call_list variable that is a DDT, check to see if it has components. + # Variables that are components of a DDT are NOT part of the call_list. + if dvar.is_ddt(): + if dvar.components: + var_in_call_list = False + # end if + # end if else: var_in_call_list = False # If it is not in the call list, try to find it @@ -1840,7 +1966,59 @@ def assign_pointer_to_var(self, dict_var, var, has_transform, cldicts, indent, o # end for # end if # end if - + if not dvar: + raise Exception(f"No variable with standard name '{standard_name}' in cldicts") + # end if + # Handle the dimensions... + dimensions = dvar.get_dimensions() + dimstr = '' + if dimensions: + dimstr = dimstr + '(' + for cnt,dim in enumerate(dimensions): + if is_horizontal_dimension(dim): + if self.run_phase(): + if var_in_call_list and \ + self.find_variable(standard_name="horizontal_loop_extent"): + ldim = "ccpp_constant_one" + udim = "horizontal_loop_extent" + else: + ldim = "horizontal_loop_begin" + udim = "horizontal_loop_end" + # endif + else: + ldim = "ccpp_constant_one" + udim = "horizontal_dimension" + # endif + # Get dimension for lower bound + for var_dict in cldicts: + lvar = var_dict.find_variable(standard_name=ldim, any_scope=False) + if lvar is not None: + break + # end if + # end for + if not lvar: + raise Exception(f"No variable with standard name '{ldim}' in cldicts") + # end if + ldim_lname = lvar.get_prop_value('local_name') + # Get dimension for upper bound + for var_dict in cldicts: + uvar = var_dict.find_variable(standard_name=udim, any_scope=False) + if uvar is not None: + break + # end if + # end for + if not uvar: + raise Exception(f"No variable with standard name '{udim}' in cldicts") + # end if + udim_lname = uvar.get_prop_value('local_name') + dimstr = dimstr + ldim_lname + ':' + udim_lname + else: + dimstr = dimstr + ':' + # end if + if cnt < len(dimensions)-1: dimstr = dimstr+',' + # end for + dimstr = dimstr+')' + # end if if (dict_var): intent = var.get_prop_value('intent') if (intent == 'out' or intent == 'inout'): @@ -1853,10 +2031,10 @@ def assign_pointer_to_var(self, dict_var, var, has_transform, cldicts, indent, o lname_ptr = var.get_prop_value('local_name') + '_ptr' if conditional != '.true.': outfile.write(f"if {conditional} then", indent) - outfile.write(f"{lname} = {lname_ptr}", indent+1) + outfile.write(f"{lname+dimstr} = {lname_ptr}", indent+1) outfile.write(f"end if", indent) else: - outfile.write(f"{lname} = {lname_ptr}", indent) + outfile.write(f"{lname+dimstr} = {lname_ptr}", indent) # end if # end if # end if From 98764c92e305200a8d10cd96efbdb2e7db102c9c Mon Sep 17 00:00:00 2001 From: dustinswales Date: Mon, 23 Mar 2026 20:45:03 -0600 Subject: [PATCH 16/59] Variable transforms. Work in progress. --- scripts/suite_objects.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 1e99833b..332dcda9 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -926,6 +926,7 @@ def match_variable(self, var, run_env, host_dict): var_vdim = var.has_vertical_dimension(dims=vdims) compat_obj = None dict_var = None + scheme_var = None if var.get_prop_value('type') == 'ccpp_constituent_properties_t': if self.phase() == 'register': found_var = True @@ -948,6 +949,7 @@ def match_variable(self, var, run_env, host_dict): if host_var: if isinstance(host_var, VarDDT): local_var = host_var + scheme_var = var # Save Scheme for transform var = host_var.var vdims = [] # end if @@ -1022,9 +1024,13 @@ def match_variable(self, var, run_env, host_dict): # 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) + if scheme_var is not None: + compat_obj = var.compatible(scheme_var, run_env) + else: + compat_obj = var.compatible(dict_var, run_env) + # end if # end if - return found_var, dict_var, local_var, var_vdim, new_vdims, missing_vert, compat_obj + return found_var, dict_var, local_var, var_vdim, new_vdims, missing_vert, compat_obj, scheme_var def in_process_split(self): """Find out if we are in a process-split region""" @@ -1283,7 +1289,7 @@ def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): vdims = var.get_dimensions() vintent = var.get_prop_value('intent') args = self.match_variable(var, self.run_env, host_dict) - found, dict_var, local_var, vert_dim, new_dims, missing_vert, compat_obj = args + found, dict_var, local_var, vert_dim, new_dims, missing_vert, compat_obj, scheme_var = args if dict_var: if dict_var.is_ddt(): subst_dict = {'intent':'inout'} @@ -1379,7 +1385,12 @@ def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): 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) + if scheme_var is not None: + print("SWALES scheme_var for transform",scheme_var.get_prop_value('local_name')) + self.add_var_transform(scheme_var, compat_obj, vert_dim) + else: + self.add_var_transform(var, compat_obj, vert_dim) + # end if has_transform = True # end if @@ -1869,7 +1880,7 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, if (dict_var): (conditional, vars_needed) = dvar.conditional(cldicts) if (has_transform): - lname = dvar.get_prop_value('local_name')+'_local' + lname = var.get_prop_value('local_name')+'_local' else: lname = dvar.call_string(search_dict) # end if @@ -2205,7 +2216,7 @@ def write(self, outfile, errcode, errmsg, indent): # 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') + lvar_lname = lvar.call_string(self.__group.call_list) tstmt = self.write_var_transform(lvar_lname, dummy, rindices, lindices, compat_obj, outfile, indent+1, False) # end for outfile.write('',indent+1) @@ -2261,7 +2272,7 @@ def write(self, outfile, errcode, errmsg, indent): # 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') + lvar_lname = lvar.call_string(self.__group.call_list) tstmt = self.write_var_transform(lvar_lname, dummy, rindices, lindices, compat_obj, outfile, indent+1, True) # end for outfile.write('', indent) From 5f187edb1776047c54f2bd5b0f1d2e6ebd19f9bd Mon Sep 17 00:00:00 2001 From: dustinswales Date: Fri, 17 Apr 2026 09:02:49 -0600 Subject: [PATCH 17/59] Optional args. wip --- scripts/suite_objects.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 332dcda9..5af300b6 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -222,6 +222,11 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ for (var_trans_local, var_lname, sname, rindices, lindices, compat_obj) in sub_lname_list: if (sname == stdname): lname = var_trans_local + # For the case of optional argument with a variable transform, + # any loop substitution are handled during the pointer assignment (earlier) + if var.get_prop_value('optional'): + lname = var_lname+'_ptr' + # end if # end if # end for # end if @@ -1882,17 +1887,17 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, if (has_transform): lname = var.get_prop_value('local_name')+'_local' else: - lname = dvar.call_string(search_dict) + lname = dvar.call_string(search_dict)+dimstr # end if lname_ptr = var.get_prop_value('local_name') + '_ptr' # Scheme has optional varaible, host has varaible defined as Conditional (Active). if conditional != '.true.': outfile.write(f"if {conditional} then", indent) - outfile.write(f"{lname_ptr} => {lname+dimstr}", indent+1) + outfile.write(f"{lname_ptr} => {lname}", indent+1) outfile.write(f"end if", indent) # Scheme has optional varaible, host has varaible defined as Mandatory. else: - outfile.write(f"{lname_ptr} => {lname+dimstr}", indent) + outfile.write(f"{lname_ptr} => {lname}", indent) # end if # end if # end def @@ -2035,17 +2040,17 @@ def assign_pointer_to_var(self, dict_var, var, has_transform, cldicts, indent, o if (intent == 'out' or intent == 'inout'): (conditional, vars_needed) = dvar.conditional(cldicts) if (has_transform): - lname = dvar.get_prop_value('local_name')+'_local' + lname = var.get_prop_value('local_name')+'_local' else: - lname = dvar.call_string(search_dict) + lname = dvar.call_string(search_dict)+dimstr # end if lname_ptr = var.get_prop_value('local_name') + '_ptr' if conditional != '.true.': outfile.write(f"if {conditional} then", indent) - outfile.write(f"{lname+dimstr} = {lname_ptr}", indent+1) + outfile.write(f"{lname} = {lname_ptr}", indent+1) outfile.write(f"end if", indent) else: - outfile.write(f"{lname+dimstr} = {lname_ptr}", indent) + outfile.write(f"{lname} = {lname_ptr}", indent) # end if # end if # end if From 8e2d34a33199978f01e9c660417286c3e43d9455 Mon Sep 17 00:00:00 2001 From: dustinswales Date: Fri, 17 Apr 2026 09:05:58 -0600 Subject: [PATCH 18/59] Revert "Optional args. wip" This reverts commit 5f187edb1776047c54f2bd5b0f1d2e6ebd19f9bd. --- scripts/suite_objects.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 5af300b6..332dcda9 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -222,11 +222,6 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ for (var_trans_local, var_lname, sname, rindices, lindices, compat_obj) in sub_lname_list: if (sname == stdname): lname = var_trans_local - # For the case of optional argument with a variable transform, - # any loop substitution are handled during the pointer assignment (earlier) - if var.get_prop_value('optional'): - lname = var_lname+'_ptr' - # end if # end if # end for # end if @@ -1887,17 +1882,17 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, if (has_transform): lname = var.get_prop_value('local_name')+'_local' else: - lname = dvar.call_string(search_dict)+dimstr + lname = dvar.call_string(search_dict) # end if lname_ptr = var.get_prop_value('local_name') + '_ptr' # Scheme has optional varaible, host has varaible defined as Conditional (Active). if conditional != '.true.': outfile.write(f"if {conditional} then", indent) - outfile.write(f"{lname_ptr} => {lname}", indent+1) + outfile.write(f"{lname_ptr} => {lname+dimstr}", indent+1) outfile.write(f"end if", indent) # Scheme has optional varaible, host has varaible defined as Mandatory. else: - outfile.write(f"{lname_ptr} => {lname}", indent) + outfile.write(f"{lname_ptr} => {lname+dimstr}", indent) # end if # end if # end def @@ -2040,17 +2035,17 @@ def assign_pointer_to_var(self, dict_var, var, has_transform, cldicts, indent, o if (intent == 'out' or intent == 'inout'): (conditional, vars_needed) = dvar.conditional(cldicts) if (has_transform): - lname = var.get_prop_value('local_name')+'_local' + lname = dvar.get_prop_value('local_name')+'_local' else: - lname = dvar.call_string(search_dict)+dimstr + lname = dvar.call_string(search_dict) # end if lname_ptr = var.get_prop_value('local_name') + '_ptr' if conditional != '.true.': outfile.write(f"if {conditional} then", indent) - outfile.write(f"{lname} = {lname_ptr}", indent+1) + outfile.write(f"{lname+dimstr} = {lname_ptr}", indent+1) outfile.write(f"end if", indent) else: - outfile.write(f"{lname} = {lname_ptr}", indent) + outfile.write(f"{lname+dimstr} = {lname_ptr}", indent) # end if # end if # end if From 2826979a81ecac08fb4992f21027a23e3596525b Mon Sep 17 00:00:00 2001 From: dustinswales Date: Fri, 17 Apr 2026 09:26:51 -0600 Subject: [PATCH 19/59] Update --- scripts/suite_objects.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 27e66c11..e6fa1fca 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1104,7 +1104,7 @@ def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): vdims = var.get_dimensions() vintent = var.get_prop_value('intent') args = self.match_variable(var, self.run_env, host_dict) - found, dict_var, local_var, vert_dim, new_dims, compat_obj, scheme_var = args + found, dict_var, local_var, var_vdim, new_dims, compat_obj, scheme_var = args if dict_var: if dict_var.is_ddt(): subst_dict = {'intent':'inout'} @@ -1125,8 +1125,8 @@ def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): # end if # end if # end if - if not self.has_vertical_dim: - self.__has_vertical_dimension = vert_dim is not None +# if not self.has_vertical_dim: +# self.__has_vertical_dimension = vert_dim is not None # end if # We have a match, make sure var is in call list if new_dims == vdims: @@ -1197,9 +1197,9 @@ def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): compat_obj.has_kind_transforms): if scheme_var is not None: print("SWALES scheme_var for transform",scheme_var.get_prop_value('local_name')) - self.add_var_transform(scheme_var, compat_obj, vert_dim) + self.add_var_transform(scheme_var, compat_obj) else: - self.add_var_transform(var, compat_obj, vert_dim) + self.add_var_transform(var, compat_obj) # end if has_transform = True # end if @@ -1853,7 +1853,7 @@ def assign_pointer_to_var(self, dict_var, var, has_transform, cldicts, indent, o # end if # end def - def add_var_transform(self, var, compat_obj, vert_dim): + def add_var_transform(self, var, compat_obj): """Register any variable transformation needed by for this Scheme. For any transformation identified in , create dummy variable from to perform the transformation. Determine the indices needed @@ -1886,7 +1886,7 @@ def add_var_transform(self, var, compat_obj, vert_dim): # If needed, modify vertical dimension for vertical orientation flipping _, vdim = find_vertical_dimension(var.get_dimensions()) if vdim >= 0: - vdims = vert_dim.split(':') + vdims = vdim.split(':') vdim_name = vdims[-1] group_vvar = self.__group.call_list.find_variable(vdim_name) if group_vvar is None: From 0380abb17d52552c3e781702f40f21929c3d192a Mon Sep 17 00:00:00 2001 From: peverwhee Date: Fri, 17 Apr 2026 10:19:26 -0600 Subject: [PATCH 20/59] fix index not found issue --- scripts/metavar.py | 22 ++++++++++++++-------- scripts/suite_objects.py | 4 ++-- test/README.md | 1 + 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/scripts/metavar.py b/scripts/metavar.py index aefad441..d6cfff53 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -674,13 +674,16 @@ def call_dimstring(self, var_dicts=None, # end if return dimstr - def call_string(self, var_dict, loop_vars=None): + def call_string(self, var_dicts, loop_vars=None): """Construct the actual argument string for this Var by translating standard names to local names. String includes array bounds unless loop_vars is None. if is not None, look there first for array bounds, even if usage requires a loop substitution. """ + if not isinstance(var_dicts, list): + var_dicts = [var_dicts] + # end if if loop_vars is None: call_str = self.get_prop_value('local_name') # Look for dims in case this is an array selection variable @@ -709,8 +712,15 @@ def call_string(self, var_dict, loop_vars=None): lname = "" for item in dim.split(':'): if item: - dvar = var_dict.find_variable(standard_name=item, - any_scope=False) + for var_dict in var_dicts: + dvar = var_dict.find_variable(standard_name=item, + any_scope=False) + if dvar is not None: + iname = dvar.call_string(var_dict, + loop_vars=loop_vars) + break + # end if + # end for if dvar is None: try: dval = int(item) @@ -718,9 +728,6 @@ def call_string(self, var_dict, loop_vars=None): except ValueError: iname = None # end try - else: - iname = dvar.call_string(var_dict, - loop_vars=loop_vars) # end if else: iname = '' @@ -729,9 +736,8 @@ def call_string(self, var_dict, loop_vars=None): lname = lname + isep + iname isep = ':' else: - errmsg = 'No local variable {} in {}{}' + errmsg = 'No local variable {} in variable dictionaries' ctx = context_string(self.context) - dname = var_dict.name raise CCPPError(errmsg.format(item, dname, ctx)) # end if # end for diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index e6fa1fca..a4ba2348 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -159,7 +159,7 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ raise CCPPError(errmsg.format(stdname, clnames)) # end if dimensions = dvar.get_dimensions() - lname = dvar.call_string(cldict) + lname = dvar.call_string(cldicts) # Optional variables in the caps are associated with # local pointers of _ptr if var.get_prop_value('optional'): @@ -770,7 +770,7 @@ def match_variable(self, var, run_env, host_dict): if self.phase() == 'register': found_var = True new_vdims = [':'] - return found_var, local_var, dict_var, var_vdim, new_vdims, compat_obj + return found_var, local_var, dict_var, var_vdim, new_vdims, compat_obj, scheme_var else: errmsg = "Variables of type ccpp_constituent_properties_t only allowed in register phase: " sname = var.get_prop_value('standard_name') diff --git a/test/README.md b/test/README.md index ba5978aa..d780efd7 100644 --- a/test/README.md +++ b/test/README.md @@ -49,6 +49,7 @@ There are several `...` to enable tests: 3) `-DCCPP_RUN_CAPGEN_TEST=ON` Turns on only the capgen test 4) `-DCCPP_RUN_DDT_HOST_TEST=ON` Turns on only the ddt host test 5) `-DCCPP_RUN_VAR_COMPATIBILITY_TEST=ON` Turns on only the variable compatibility test +5) `-DCCPP_RUN_NESTED_SUITE_TEST=ON` Turns on only the nested suite test By default, the tests will build in release mode. To enable debug mode, you will need to set the build type: `-DCMAKE_BUILD_TYPE=Release` (or if you want release with debug symbols: `-DCMAKE_BUILD_TYPE=RelWithDebInfo`). From 323aae2fc31607a90a352c51fa136d9f95c12c83 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Fri, 17 Apr 2026 10:02:23 -0600 Subject: [PATCH 21/59] Remove variable debug checks --- cmake/ccpp_capgen.cmake | 6 +- scripts/framework_env.py | 21 +- scripts/suite_objects.py | 380 --------------------- test/README.md | 2 +- test/advection_test/CMakeLists.txt | 4 +- test/capgen_test/CMakeLists.txt | 3 +- test/ddthost_test/CMakeLists.txt | 3 +- test/nested_suite_test/CMakeLists.txt | 3 +- test/var_compatibility_test/CMakeLists.txt | 3 +- 9 files changed, 9 insertions(+), 416 deletions(-) diff --git a/cmake/ccpp_capgen.cmake b/cmake/ccpp_capgen.cmake index 0d7afb91..599a28ff 100644 --- a/cmake/ccpp_capgen.cmake +++ b/cmake/ccpp_capgen.cmake @@ -1,7 +1,6 @@ # CMake wrapper for ccpp_capgen.py # Currently meant to be a CMake API needed for generating caps for regression tests. # -# CAPGEN_DEBUG - ON/OFF (Default: OFF) - Enables debug capability through ccpp_capgen.py # CAPGEN_EXPECT_THROW_ERROR - ON/OFF (Default: OFF) - Scans ccpp_capgen.py log for error string and errors if not found. # HOST_NAME - String name of host # OUTPUT_ROOT - String path to put generated caps @@ -10,7 +9,7 @@ # SCHEMEFILES - CMake list of scheme metadata files # SUITES - CMake list of suite xml files function(ccpp_capgen) - set(optionalArgs CAPGEN_DEBUG CAPGEN_EXPECT_THROW_ERROR) + set(optionalArgs CAPGEN_EXPECT_THROW_ERROR) set(oneValueArgs HOST_NAME OUTPUT_ROOT VERBOSITY KIND_SPECS) set(multi_value_keywords HOSTFILES SCHEMEFILES SUITES) @@ -23,9 +22,6 @@ function(ccpp_capgen) endif() # Interpret parsed arguments - if(DEFINED arg_CAPGEN_DEBUG) - list(APPEND CCPP_CAPGEN_CMD_LIST "--debug") - endif() if(DEFINED arg_HOSTFILES) list(JOIN arg_HOSTFILES "," HOSTFILES_SEPARATED) list(APPEND CCPP_CAPGEN_CMD_LIST "--host-files" "${HOSTFILES_SEPARATED}") diff --git a/scripts/framework_env.py b/scripts/framework_env.py index 582358f8..2237db3e 100644 --- a/scripts/framework_env.py +++ b/scripts/framework_env.py @@ -29,8 +29,7 @@ def __init__(self, logger, ndict=None, verbose=0, clean=False, host_files=None, scheme_files=None, suites=None, preproc_directives=[], generate_docfiles=False, host_name='', kind_types=[], use_error_obj=False, force_overwrite=False, - output_root=os.getcwd(), ccpp_datafile="datatable.xml", - debug=False): + output_root=os.getcwd(), ccpp_datafile="datatable.xml"): """Initialize a new CCPPFrameworkEnv object from the input arguments. is a dict with the parsed command-line arguments (or a dictionary created with the necessary arguments). @@ -230,13 +229,6 @@ def __init__(self, logger, ndict=None, verbose=0, clean=False, self.__datatable_file = os.path.join(self.output_dir, self.datatable_file) # end if - # Enable or disable variable allocation checks - if ndict and ('debug' in ndict): - self.__debug = ndict['debug'] - del ndict['debug'] - else: - self.__debug = debug - # end if self.__logger = logger ## Check to see if anything is left in dictionary if ndict: @@ -369,7 +361,7 @@ def kind_types(self): @property def verbose(self): - """Return true if debug enabled for the CCPPFrameworkEnv's + """Return true if verbose enabled for the CCPPFrameworkEnv's logger object.""" return (self.logger and verbose(self.logger)) @@ -402,12 +394,6 @@ def datatable_file(self): CCPPFrameworkEnv object.""" return self.__datatable_file - @property - def debug(self): - """Return the property for this - CCPPFrameworkEnv object.""" - return self.__debug - @property def logger(self): """Return the property for this CCPPFrameworkEnv object.""" @@ -483,9 +469,6 @@ def parse_command_line(args, description, logger=None): help="""Overwrite all CCPP-generated files, even if unmodified""") - parser.add_argument("--debug", action='store_true', default=False, - help="Add variable allocation checks to assist debugging") - parser.add_argument("--verbose", action='count', default=0, help="Log more activity, repeat for increased output") diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 1305953d..7fbd1353 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -946,7 +946,6 @@ def __init__(self, scheme_xml, context, parent, run_env): self.__lib = scheme_xml.get('lib', None) self.__has_vertical_dimension = False self.__group = None - self.__var_debug_checks = list() self.__forward_transforms = list() self.__reverse_transforms = list() self._has_run_phase = True @@ -1024,11 +1023,6 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): args = self.match_variable(var, self.run_env) found, dict_var, vert_dim, new_dims, compat_obj = args if found: - if self.__group.run_env.debug: - # Add variable allocation checks for group, suite and host variables - if dict_var: - self.add_var_debug_check(dict_var) - # end if if not self.has_vertical_dim: self.__has_vertical_dimension = vert_dim is not None # end if @@ -1097,361 +1091,6 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): # end for return scheme_mods - def add_var_debug_check(self, var): - """Add a debug check for a given variable var (host model variable, - suite variable or group module variable) for this scheme. - Return the variable and an associated dummy variable that is - managed by the group subroutine that calls the scheme, and - which is used to assign the scalar or the lower and upper bounds - of the array to if the intent is 'inout' or 'out'. - """ - # Get the basic attributes that decide whether we need - # to check the variable when we write the group - standard_name = var.get_prop_value('standard_name') - dimensions = var.get_dimensions() - active = var.get_prop_value('active') - var_dicts = [ self.__group.call_list ] + self.__group.suite_dicts() - - # If the variable isn't active, skip it - if active.lower() =='.false.': - return - # Also, if the variable is one of the CCPP error handling messages, skip it - # since it is defined as intent(out) and we can't do meaningful checks on it - elif standard_name == 'ccpp_error_code' or standard_name == 'ccpp_error_message': - return - # To perform allocation checks, we need to know all variables - # that are part of the 'active' attribute conditional and add - # it to the group's call list. - else: - (_, vars_needed) = var.conditional(var_dicts) - for var_needed in vars_needed: - self.update_group_call_list_variable(var_needed) - - # For scalars and arrays, need an internal_var variable (same kind and type) - # that we can assign the scalar or the lbound/ubound of the array to. - # We need to treat DDTs and variables with kind attributes slightly - # differently, and make sure there are no duplicate variables. We - # also need to assign a bogus standard name to these local variables. - vtype = var.get_prop_value('type') - if var.is_ddt(): - vkind = '' - units = '' - else: - vkind = var.get_prop_value('kind') - units = var.get_prop_value('units') - if vkind: - internal_var_lname = f'internal_var_{vtype.replace("=","_")}_{vkind.replace("=","_")}' - else: - internal_var_lname = f'internal_var_{vtype.replace("=","_")}' - if var.is_ddt(): - internal_var = Var({'local_name':internal_var_lname, 'standard_name':f'{internal_var_lname}_local', - 'ddt_type':vtype, 'kind':vkind, 'units':units, 'dimensions':'()'}, - _API_LOCAL, self.run_env) - else: - internal_var = Var({'local_name':internal_var_lname, 'standard_name':f'{internal_var_lname}_local', - 'type':vtype, 'kind':vkind, 'units':units, 'dimensions':'()'}, - _API_LOCAL, self.run_env) - found = self.__group.find_variable(source_var=internal_var, any_scope=False) - if not found: - self.__group.manage_variable(internal_var) - - # For arrays, we need to get information on the dimensions and add it to - # the group's call list so that we can test for the correct size later on - if dimensions: - for dim in dimensions: - if not ':' in dim: - dim_var = self.find_variable(standard_name=dim) - if not dim_var: - # To allow for numerical dimensions in metadata. - if not dim.isnumeric(): - raise Exception(f"No dimension with standard name '{dim}'") - # end if - else: - self.update_group_call_list_variable(dim_var) - # end if - else: - (ldim, udim) = dim.split(":") - ldim_var = self.find_variable(standard_name=ldim) - if not ldim_var: - # To allow for numerical dimensions in metadata. - if not ldim.isnumeric(): - raise Exception(f"No dimension with standard name '{ldim}'") - # end if - # end if - self.update_group_call_list_variable(ldim_var) - udim_var = self.find_variable(standard_name=udim) - if not udim_var: - # To allow for numerical dimensions in metadata. - if not udim.isnumeric(): - raise Exception(f"No dimension with standard name '{udim}'") - # end if - else: - self.update_group_call_list_variable(udim_var) - # end if - - # Add the variable to the list of variables to check. Record which internal_var to use. - self.__var_debug_checks.append([var, internal_var]) - - def replace_horiz_dim_debug_check(self, dim, cldicts, var_in_call_list): - """Determine the correct horizontal dimension to use for a given variable, - depending on the CCPP phase and origin of the variable (from the host/suite - or defined as a module variable for the parent group, or local to the group. - Return the dimension length and other properties needed for debug checks.""" - if not is_horizontal_dimension(dim): - raise Exception(f"Dimension {dim} is not a horizontal dimension") - if self.run_phase(): - if var_in_call_list and \ - self.find_variable(standard_name="horizontal_loop_extent"): - ldim = "ccpp_constant_one" - udim = "horizontal_loop_extent" - else: - ldim = "horizontal_loop_begin" - udim = "horizontal_loop_end" - else: - ldim = "ccpp_constant_one" - udim = "horizontal_dimension" - # Get dimension for lower bound - for var_dict in cldicts: - dvar = var_dict.find_variable(standard_name=ldim, any_scope=False) - if dvar is not None: - break - if not dvar: - raise Exception(f"No variable with standard name '{ldim}' in cldicts") - ldim_lname = dvar.get_prop_value('local_name') - # Get dimension for upper bound - for var_dict in cldicts: - dvar = var_dict.find_variable(standard_name=udim, any_scope=False) - if dvar is not None: - break - if not dvar: - raise Exception(f"No variable with standard name '{udim}' in cldicts") - udim_lname = dvar.get_prop_value('local_name') - # Assemble dimensions and bounds for size checking - dim_length = f'{udim_lname}-{ldim_lname}+1' - # If the variable that uses these dimensions is not in the group's call - # list, then it is defined as a module variable for this group and the - # dimensions run from ldim to udim, otherwise from 1:dim_length. - if not var_in_call_list: - dim_string = f"{ldim_lname}:{udim_lname}" - lbound_string = ldim_lname - ubound_string = udim_lname - else: - dim_string = ":" - lbound_string = '1' - ubound_string = f'{udim_lname}-{ldim_lname}+1' - return (dim_length, dim_string, lbound_string, ubound_string) - - def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, errmsg, indent): - """Write the variable debug check for the given variable, as determined - in a previous step (add_var_debug_check). Assign the scalar or lower and - upper bounds of the array to the internal_var variable, and for arrays also check - that the size of the array matches the dimensions from the metadata. - """ - # Get the basic attributes for writing the check - standard_name = var.get_prop_value('standard_name') - dimensions = var.get_dimensions() - active = var.get_prop_value('active') - allocatable = var.get_prop_value('allocatable') - vtype = var.get_prop_value('type') - - # Need the local name from the group call list, - # from the locally-defined variables of the group, - # or from the suite, not how it is called in the scheme (var) - # First, check if the variable is in the call list. - dvar = self.__group.call_list.find_variable(standard_name=standard_name, any_scope=False) - if dvar: - var_in_call_list = True - else: - var_in_call_list = False - # If it is not in the call list, try to find it - # in the local variables of this group subroutine. - dvar = self.__group.find_variable(standard_name=standard_name, any_scope=False) - if not dvar: - # This variable is handled by the group - # and is declared as a module variable - for var_dict in self.__group.suite_dicts(): - dvar = var_dict.find_variable(standard_name=standard_name, any_scope=False) - if dvar: - break - if not dvar: - raise Exception(f"No variable with standard name '{standard_name}' in cldicts") - local_name = dvar.get_prop_value('local_name') - - # If the variable is allocatable and the intent for the scheme is 'out', - # then we can't test anything because the scheme is going to allocate - # the variable. We don't have this information earlier in - # add_var_debug_check, therefore need to back out here, - # using the information from the scheme variable (call list). - svar = self.call_list.find_variable(standard_name=standard_name, any_scope=False) - intent = svar.get_prop_value('intent') - if intent == 'out' and allocatable: - return - # end if - - # Get the condition on which the variable is active - (conditional, _) = var.conditional(cldicts) - - # For scalars, assign to internal_var variable if the variable intent is in/inout - if not dimensions: - if not intent == 'out': - internal_var_lname = internal_var.get_prop_value('local_name') - tmp_indent = indent - if conditional != '.true.': - tmp_indent = indent + 1 - outfile.write(f"if {conditional} then", indent) - # end if - 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('',tmp_indent) - if conditional != '.true.': - outfile.write(f"end if", indent) - # end if - # For arrays, check size of array against dimensions in metadata, then assign - # the lower and upper bounds to the internal_var variable if the intent is in/inout - else: - array_size = 1 - dim_strings = [] - lbound_strings = [] - ubound_strings = [] - dim_lengths = [] - local_names = [] - for dim in dimensions: - if not ':' in dim: - # In capgen, any true dimension (that is not a single index) does - # have a colon (:) in the dimension, therefore this is an index - for var_dict in cldicts: - dvar = var_dict.find_variable(standard_name=dim, any_scope=False) - if dvar is not None: - break - if not dvar: - raise Exception(f"No variable with standard name '{dim}' in cldicts") - dim_lname = dvar.get_prop_value('local_name') - dim_length = 1 - dim_strings.append(dim_lname) - lbound_strings.append(dim_lname) - ubound_strings.append(dim_lname) - else: - # Horizontal dimension needs to be dealt with separately, because it - # depends on the CCPP phase, whether the variable is a host/suite - # variable or locally defined on the group level. - if is_horizontal_dimension(dim): - (dim_length, dim_string, lbound_string, ubound_string) = \ - self.replace_horiz_dim_debug_check(dim, cldicts, var_in_call_list) - else: - (ldim, udim) = dim.split(":") - # Get dimension for lower bound - for var_dict in cldicts: - dvar = var_dict.find_variable(standard_name=ldim, any_scope=False) - if dvar is not None: - ldim_lname = dvar.get_prop_value('local_name') - break - if not dvar: - # To allow for numerical dimensions in metadata. - if ldim.isnumeric(): - ldim_lname = ldim - else: - raise Exception(f"No variable with standard name '{ldim}' in cldicts") - # endif - # endif - # Get dimension for upper bound - for var_dict in cldicts: - dvar = var_dict.find_variable(standard_name=udim, any_scope=False) - if dvar is not None: - udim_lname = dvar.get_prop_value('local_name') - break - if not dvar: - # To allow for numerical dimensions in metadata. - if udim.isnumeric(): - udim_lname = udim - else: - raise Exception(f"No variable with standard name '{udim}' in cldicts") - # end if - # end if - # Assemble dimensions and bounds for size checking - dim_length = f'{udim_lname}-{ldim_lname}+1' - dim_string = ":" - lbound_string = ldim_lname - ubound_string = udim_lname - # end if - dim_strings.append(dim_string) - lbound_strings.append(lbound_string) - ubound_strings.append(ubound_string) - array_size = f'{array_size}*({dim_length})' - dim_lengths.append(dim_length) - local_names.append(local_name) - # end for - # Various strings needed to get the right size - # and lower/upper bound of the array - dim_string = '(' + ','.join(dim_strings) + ')' - lbound_string = '(' + ','.join(lbound_strings) + ')' - ubound_string = '(' + ','.join(ubound_strings) + ')' - - # Write size check - # - Only for types int and real. - if (vtype == "integer") or (vtype == "real"): - tmp_indent = indent - if conditional != '.true.': - tmp_indent = indent + 1 - outfile.write(f"if {conditional} then", indent) - # end if - outfile.write(f"! Check size of array {local_name}", tmp_indent) - outfile.write(f"if (size({local_name}{dim_string}) /= {array_size}) then", tmp_indent) - outfile.write(f"write({errmsg}, '(2(a,i8))') 'In group {self.__group.name} before "\ - f"{self.__subroutine_name}: for array {local_name}, expected size ', "\ - f"{array_size}, ' but got ', size({local_name})", tmp_indent+1) - outfile.write(f"{errcode} = 1", tmp_indent+1) - outfile.write(f"return", tmp_indent+1) - outfile.write(f"end if", tmp_indent) - if conditional != '.true.': - outfile.write(f"end if", indent) - # end if - outfile.write('',indent) - # end if - - # Write size check for each dimension in array. - # - If intent is not out. - # - Only for types int and real. - if (vtype == "integer") or (vtype == "real"): - if not intent == 'out': - tmp_indent = indent - if conditional != '.true.': - tmp_indent = indent + 1 - outfile.write(f"if {conditional} then", indent) - # end if - ndims = len(dim_lengths) - - # Loop through dimensions in var and check if length of each dimension - # is the correct size. Skip for 1D variables. - if (ndims > 1): - for index, dim_length in enumerate(dim_lengths): - array_ref = '(' - # Dimension(s) before current rank to be checked. - array_ref += '1,'*(index) - # Dimension to check. - array_ref += dim_strings[index] - # Dimension(s) after current rank to be checked. - array_ref += ',1'*(ndims-(index+1)) - array_ref += ')' - # - outfile.write(f"! Check length of {local_names[index]}{array_ref}", tmp_indent) - outfile.write(f"if (size({local_names[index]}{array_ref}) /= {dim_length}) then ", \ - tmp_indent) - outfile.write(f"write({errmsg}, '(2(a,i8))') 'In group {self.__group.name} before " \ - f"{self.__subroutine_name}: for array {local_names[index]}{array_ref}, "\ - f"expected size ', {dim_length}, ' but got ', " \ - f"size({local_names[index]}{array_ref})", tmp_indent+1) - outfile.write(f"{errcode} = 1", tmp_indent+1) - outfile.write(f"return", tmp_indent+1) - outfile.write(f"end if", tmp_indent) - # end for - #end if - if conditional != '.true.': - outfile.write(f"end if", indent) - # end if - outfile.write('',indent) - # endif - # end if - def associate_optional_var(self, dict_var, var, var_ptr, has_transform, cldicts, indent, outfile): """Write local pointer association for optional variables.""" if (dict_var): @@ -1620,25 +1259,6 @@ def write(self, outfile, errcode, errmsg, indent): 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('! ##################################################################', indent+1) - outfile.comment('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.comment('End debug tests', indent+1) - outfile.write('! ##################################################################', indent+1) - outfile.write('', indent+1) - # end if - # # Write any reverse (pre-Scheme) transforms. if len(self.__reverse_transforms) > 0: outfile.comment('Compute reverse (pre-scheme) transforms', indent+1) diff --git a/test/README.md b/test/README.md index ba5978aa..03363532 100644 --- a/test/README.md +++ b/test/README.md @@ -50,7 +50,7 @@ There are several `...` to enable tests: 4) `-DCCPP_RUN_DDT_HOST_TEST=ON` Turns on only the ddt host test 5) `-DCCPP_RUN_VAR_COMPATIBILITY_TEST=ON` Turns on only the variable compatibility test -By default, the tests will build in release mode. To enable debug mode, you will need to set the build type: `-DCMAKE_BUILD_TYPE=Release` (or if you want release with debug symbols: `-DCMAKE_BUILD_TYPE=RelWithDebInfo`). +By default, the tests will build in debug mode. To enable release mode, you will need to set the build type: `-DCMAKE_BUILD_TYPE=Release` (or if you want release with debug symbols: `-DCMAKE_BUILD_TYPE=RelWithDebInfo`). To enable more verbose output for `ccpp_capgen.py`, add `-DCCPP_VERBOSITY=` to the `cmake` command line arguments where `n={1,2,3}` (`n=0` or no verbosity by default). diff --git a/test/advection_test/CMakeLists.txt b/test/advection_test/CMakeLists.txt index 0f1be200..4c20835b 100644 --- a/test/advection_test/CMakeLists.txt +++ b/test/advection_test/CMakeLists.txt @@ -22,7 +22,6 @@ list(APPEND ADVECTION_HOST_METADATA_FILES "${HOST}.meta") # Run ccpp_capgen that we expect to fail ccpp_capgen(CAPGEN_EXPECT_THROW_ERROR ON - CAPGEN_DEBUG ON VERBOSITY ${CCPP_VERBOSITY} HOSTFILES ${ADVECTION_HOST_METADATA_FILES} SCHEMEFILES ${SCHEME_ERROR_META_FILES} @@ -31,8 +30,7 @@ ccpp_capgen(CAPGEN_EXPECT_THROW_ERROR ON OUTPUT_ROOT "${CCPP_CAP_FILES}") # Run ccpp_capgen -ccpp_capgen(CAPGEN_DEBUG ON - VERBOSITY ${CCPP_VERBOSITY} +ccpp_capgen(VERBOSITY ${CCPP_VERBOSITY} HOSTFILES ${ADVECTION_HOST_METADATA_FILES} SCHEMEFILES ${SCHEME_META_FILES} SUITES ${SUITE_FILES} diff --git a/test/capgen_test/CMakeLists.txt b/test/capgen_test/CMakeLists.txt index 3a0e1405..49c75842 100644 --- a/test/capgen_test/CMakeLists.txt +++ b/test/capgen_test/CMakeLists.txt @@ -51,8 +51,7 @@ list(TRANSFORM HOST_FILES APPEND ".meta" OUTPUT_VARIABLE CAPGEN_HOST_MET list(APPEND CAPGEN_HOST_METADATA_FILES "${HOST}.meta") -ccpp_capgen(CAPGEN_DEBUG ON - VERBOSITY ${CCPP_VERBOSITY} +ccpp_capgen(VERBOSITY ${CCPP_VERBOSITY} HOSTFILES ${CAPGEN_HOST_METADATA_FILES} SCHEMEFILES ${SCHEME_META_FILES} ${SUITE_SCHEME_META_FILES} SUITES ${SUITE_FILES} diff --git a/test/ddthost_test/CMakeLists.txt b/test/ddthost_test/CMakeLists.txt index cc257619..5516277b 100644 --- a/test/ddthost_test/CMakeLists.txt +++ b/test/ddthost_test/CMakeLists.txt @@ -27,8 +27,7 @@ list(TRANSFORM HOST_FILES APPEND ".meta" OUTPUT_VARIABLE DDT_HOST_METADA list(APPEND DDT_HOST_METADATA_FILES "${HOST}.meta") # Run ccpp_capgen -ccpp_capgen(CAPGEN_DEBUG ON - VERBOSITY ${CCPP_VERBOSITY} +ccpp_capgen(VERBOSITY ${CCPP_VERBOSITY} HOSTFILES ${DDT_HOST_METADATA_FILES} SCHEMEFILES ${SCHEME_META_FILES} ${SUITE_SCHEME_META_FILES} SUITES ${SUITE_FILES} diff --git a/test/nested_suite_test/CMakeLists.txt b/test/nested_suite_test/CMakeLists.txt index 491a8fb4..c55d9bed 100644 --- a/test/nested_suite_test/CMakeLists.txt +++ b/test/nested_suite_test/CMakeLists.txt @@ -24,8 +24,7 @@ list(TRANSFORM HOST_FILES APPEND ".meta" OUTPUT_VARIABLE NESTED_SUITE_HOST_MET list(APPEND NESTED_SUITE_HOST_METADATA_FILES "${HOST}.meta") # Run ccpp_capgen -ccpp_capgen(CAPGEN_DEBUG ON - VERBOSITY ${CCPP_VERBOSITY} +ccpp_capgen(VERBOSITY ${CCPP_VERBOSITY} HOSTFILES ${NESTED_SUITE_HOST_METADATA_FILES} SCHEMEFILES ${SCHEME_META_FILES} SUITES ${SUITE_FILES} diff --git a/test/var_compatibility_test/CMakeLists.txt b/test/var_compatibility_test/CMakeLists.txt index 79ca1b3e..2938c2d0 100644 --- a/test/var_compatibility_test/CMakeLists.txt +++ b/test/var_compatibility_test/CMakeLists.txt @@ -24,8 +24,7 @@ list(TRANSFORM HOST_FILES APPEND ".meta" OUTPUT_VARIABLE VAR_COMPATIBILITY_HOS list(APPEND VAR_COMPATIBILITY_HOST_METADATA_FILES "${HOST}.meta") # Run ccpp_capgen -ccpp_capgen(CAPGEN_DEBUG ON - VERBOSITY ${CCPP_VERBOSITY} +ccpp_capgen(VERBOSITY ${CCPP_VERBOSITY} HOSTFILES ${VAR_COMPATIBILITY_HOST_METADATA_FILES} SCHEMEFILES ${SCHEME_META_FILES} SUITES ${SUITE_FILES} From ed6edc1ef01f0baaa9c9952099f9a0256d8d0154 Mon Sep 17 00:00:00 2001 From: dustinswales Date: Fri, 17 Apr 2026 10:47:49 -0600 Subject: [PATCH 22/59] Remove pointer assignment. Remove indexing in Suite cap args. --- scripts/host_cap.py | 2 +- scripts/suite_objects.py | 123 +-------------------------------------- 2 files changed, 4 insertions(+), 121 deletions(-) diff --git a/scripts/host_cap.py b/scripts/host_cap.py index 1b33d076..b86450a3 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -511,7 +511,7 @@ def suite_part_call_list(host_model, const_dict, suite_part, subst_loop_vars, raise CCPPError(errmsg) # End if if stdname not in CCPP_CONSTANT_VARS: - lname = var_dict.var_call_string(hvar, loop_vars=loop_vars) + lname = var_dict.var_call_string(hvar) hmvars.append(f"{sp_lname}={lname}") # End if # End for diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index e6fa1fca..f2164ac2 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1747,112 +1747,6 @@ def nullify_optional_var(self, dict_var, var, has_transform, cldicts, indent, ou # end if # end def - def assign_pointer_to_var(self, dict_var, var, has_transform, cldicts, indent, outfile): - """Write local pointer assignment to variable.""" - # Use the local name from the Scheme call list, append "_ptr" suffix. - standard_name = var.get_prop_value('standard_name') - dvar = self.__group.call_list.find_variable(standard_name=standard_name, any_scope=False) - search_dict = self.__group.call_list - if dvar: - var_in_call_list = True - # If we find a call_list variable that is a DDT, check to see if it has components. - # Variables that are components of a DDT are NOT part of the call_list. - if dvar.is_ddt(): - if dvar.components: - var_in_call_list = False - # end if - # end if - else: - var_in_call_list = False - # If it is not in the call list, try to find it - # in the local variables of this group subroutine. - dvar = self.__group.find_variable(standard_name=standard_name, any_scope=False) - if not dvar: - # This variable is handled by the group - # and is declared as a module variable - for var_dict in self.__group.suite_dicts(): - dvar = var_dict.find_variable(standard_name=standard_name, any_scope=False) - if dvar: - search_dict = var_dict - break - # end if - # end for - # end if - # end if - if not dvar: - raise Exception(f"No variable with standard name '{standard_name}' in cldicts") - # end if - # Handle the dimensions... - dimensions = dvar.get_dimensions() - dimstr = '' - if dimensions: - dimstr = dimstr + '(' - for cnt,dim in enumerate(dimensions): - if is_horizontal_dimension(dim): - if self.run_phase(): - if var_in_call_list and \ - self.find_variable(standard_name="horizontal_loop_extent"): - ldim = "ccpp_constant_one" - udim = "horizontal_loop_extent" - else: - ldim = "horizontal_loop_begin" - udim = "horizontal_loop_end" - # endif - else: - ldim = "ccpp_constant_one" - udim = "horizontal_dimension" - # endif - # Get dimension for lower bound - for var_dict in cldicts: - lvar = var_dict.find_variable(standard_name=ldim, any_scope=False) - if lvar is not None: - break - # end if - # end for - if not lvar: - raise Exception(f"No variable with standard name '{ldim}' in cldicts") - # end if - ldim_lname = lvar.get_prop_value('local_name') - # Get dimension for upper bound - for var_dict in cldicts: - uvar = var_dict.find_variable(standard_name=udim, any_scope=False) - if uvar is not None: - break - # end if - # end for - if not uvar: - raise Exception(f"No variable with standard name '{udim}' in cldicts") - # end if - udim_lname = uvar.get_prop_value('local_name') - dimstr = dimstr + ldim_lname + ':' + udim_lname - else: - dimstr = dimstr + ':' - # end if - if cnt < len(dimensions)-1: dimstr = dimstr+',' - # end for - dimstr = dimstr+')' - # end if - if (dict_var): - intent = var.get_prop_value('intent') - if (intent == 'out' or intent == 'inout'): - (conditional, vars_needed) = dvar.conditional(cldicts) - if (has_transform): - lname = dvar.get_prop_value('local_name')+'_local' - else: - lname = dvar.call_string(search_dict) - # end if - lname_ptr = var.get_prop_value('local_name') + '_ptr' - if conditional != '.true.': - outfile.write(f"if {conditional} then", indent) - outfile.write(f"{lname+dimstr} = {lname_ptr}", indent+1) - outfile.write(f"end if", indent) - else: - outfile.write(f"{lname+dimstr} = {lname_ptr}", indent) - # end if - # end if - # end if - # end def - def add_var_transform(self, var, compat_obj): """Register any variable transformation needed by for this Scheme. For any transformation identified in , create dummy variable @@ -1884,9 +1778,9 @@ def add_var_transform(self, var, compat_obj): rindices = [':']*var.get_rank() # If needed, modify vertical dimension for vertical orientation flipping - _, vdim = find_vertical_dimension(var.get_dimensions()) + var_vdim, vdim = find_vertical_dimension(var.get_dimensions()) if vdim >= 0: - vdims = vdim.split(':') + vdims = var_vdim.split(':') vdim_name = vdims[-1] group_vvar = self.__group.call_list.find_variable(vdim_name) if group_vvar is None: @@ -2041,18 +1935,7 @@ def write(self, outfile, errcode, errmsg, indent): outfile.write(stmt.format(self.subroutine_name, my_args), indent+1) outfile.write('',indent+1) # end if - # - # Copy any local pointers. - # - first_ptr_declaration=True - for (dict_var, var, has_transform) in self.__optional_vars: - if first_ptr_declaration: - outfile.write('! Copy any local pointers to dummy/local variables', indent+1) - first_ptr_declaration=False - # end if - tstmt = self.assign_pointer_to_var(dict_var, var, has_transform, cldicts, indent+1, outfile) - # end for - outfile.write('',indent+1) + # # Nullify any local pointers. # From 356e2797f6d867c27c10fd498248840fb12ab25a Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Fri, 17 Apr 2026 11:29:53 -0600 Subject: [PATCH 23/59] Add 'add_var_debug_checks' back in --- scripts/suite_objects.py | 97 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 7fbd1353..d968f3e8 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1023,6 +1023,11 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): args = self.match_variable(var, self.run_env) found, dict_var, vert_dim, new_dims, compat_obj = args if found: + # Hack to get the missing dimensions promoted to the right place + # Add variable allocation checks for group, suite and host variables + if dict_var: + self.add_var_debug_check(dict_var) + # end if if not self.has_vertical_dim: self.__has_vertical_dimension = vert_dim is not None # end if @@ -1091,6 +1096,98 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): # end for return scheme_mods + def add_var_debug_check(self, var): + """Add a debug check for a given variable var (host model variable, + suite variable or group module variable) for this scheme. + Return the variable and an associated dummy variable that is + managed by the group subroutine that calls the scheme, and + which is used to assign the scalar or the lower and upper bounds + of the array to if the intent is 'inout' or 'out'. + """ + # Get the basic attributes that decide whether we need + # to check the variable when we write the group + standard_name = var.get_prop_value('standard_name') + dimensions = var.get_dimensions() + active = var.get_prop_value('active') + var_dicts = [ self.__group.call_list ] + self.__group.suite_dicts() + + # If the variable isn't active, skip it + if active.lower() =='.false.': + return + # Also, if the variable is one of the CCPP error handling messages, skip it + # since it is defined as intent(out) and we can't do meaningful checks on it + elif standard_name == 'ccpp_error_code' or standard_name == 'ccpp_error_message': + return + # To perform allocation checks, we need to know all variables + # that are part of the 'active' attribute conditional and add + # it to the group's call list. + else: + (_, vars_needed) = var.conditional(var_dicts) + for var_needed in vars_needed: + self.update_group_call_list_variable(var_needed) + + # For scalars and arrays, need an internal_var variable (same kind and type) + # that we can assign the scalar or the lbound/ubound of the array to. + # We need to treat DDTs and variables with kind attributes slightly + # differently, and make sure there are no duplicate variables. We + # also need to assign a bogus standard name to these local variables. + vtype = var.get_prop_value('type') + if var.is_ddt(): + vkind = '' + units = '' + else: + vkind = var.get_prop_value('kind') + units = var.get_prop_value('units') + if vkind: + internal_var_lname = f'internal_var_{vtype.replace("=","_")}_{vkind.replace("=","_")}' + else: + internal_var_lname = f'internal_var_{vtype.replace("=","_")}' + if var.is_ddt(): + internal_var = Var({'local_name':internal_var_lname, 'standard_name':f'{internal_var_lname}_local', + 'ddt_type':vtype, 'kind':vkind, 'units':units, 'dimensions':'()'}, + _API_LOCAL, self.run_env) + else: + internal_var = Var({'local_name':internal_var_lname, 'standard_name':f'{internal_var_lname}_local', + 'type':vtype, 'kind':vkind, 'units':units, 'dimensions':'()'}, + _API_LOCAL, self.run_env) + found = self.__group.find_variable(source_var=internal_var, any_scope=False) + if not found: + self.__group.manage_variable(internal_var) + + # For arrays, we need to get information on the dimensions and add it to + # the group's call list so that we can test for the correct size later on + if dimensions: + for dim in dimensions: + if not ':' in dim: + dim_var = self.find_variable(standard_name=dim) + if not dim_var: + # To allow for numerical dimensions in metadata. + if not dim.isnumeric(): + raise Exception(f"No dimension with standard name '{dim}'") + # end if + else: + self.update_group_call_list_variable(dim_var) + # end if + else: + (ldim, udim) = dim.split(":") + ldim_var = self.find_variable(standard_name=ldim) + if not ldim_var: + # To allow for numerical dimensions in metadata. + if not ldim.isnumeric(): + raise Exception(f"No dimension with standard name '{ldim}'") + # end if + # end if + self.update_group_call_list_variable(ldim_var) + udim_var = self.find_variable(standard_name=udim) + if not udim_var: + # To allow for numerical dimensions in metadata. + if not udim.isnumeric(): + raise Exception(f"No dimension with standard name '{udim}'") + # end if + else: + self.update_group_call_list_variable(udim_var) + # end if + def associate_optional_var(self, dict_var, var, var_ptr, has_transform, cldicts, indent, outfile): """Write local pointer association for optional variables.""" if (dict_var): From 454733680639add040c8ee7623210424c6d99d73 Mon Sep 17 00:00:00 2001 From: peverwhee Date: Fri, 17 Apr 2026 11:42:41 -0600 Subject: [PATCH 24/59] rename add_var_debug_check and use for handling dimension and active vars --- scripts/suite_objects.py | 40 +++------------------------------------- 1 file changed, 3 insertions(+), 37 deletions(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index d968f3e8..b4526cbb 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1026,7 +1026,7 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): # Hack to get the missing dimensions promoted to the right place # Add variable allocation checks for group, suite and host variables if dict_var: - self.add_var_debug_check(dict_var) + self.handle_downstream_variables(dict_var) # end if if not self.has_vertical_dim: self.__has_vertical_dimension = vert_dim is not None @@ -1096,14 +1096,8 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): # end for return scheme_mods - def add_var_debug_check(self, var): - """Add a debug check for a given variable var (host model variable, - suite variable or group module variable) for this scheme. - Return the variable and an associated dummy variable that is - managed by the group subroutine that calls the scheme, and - which is used to assign the scalar or the lower and upper bounds - of the array to if the intent is 'inout' or 'out'. - """ + def handle_downstream_variables(self, var): + """Ensure all dimension and optional variable arguments are available""" # Get the basic attributes that decide whether we need # to check the variable when we write the group standard_name = var.get_prop_value('standard_name') @@ -1126,34 +1120,6 @@ def add_var_debug_check(self, var): for var_needed in vars_needed: self.update_group_call_list_variable(var_needed) - # For scalars and arrays, need an internal_var variable (same kind and type) - # that we can assign the scalar or the lbound/ubound of the array to. - # We need to treat DDTs and variables with kind attributes slightly - # differently, and make sure there are no duplicate variables. We - # also need to assign a bogus standard name to these local variables. - vtype = var.get_prop_value('type') - if var.is_ddt(): - vkind = '' - units = '' - else: - vkind = var.get_prop_value('kind') - units = var.get_prop_value('units') - if vkind: - internal_var_lname = f'internal_var_{vtype.replace("=","_")}_{vkind.replace("=","_")}' - else: - internal_var_lname = f'internal_var_{vtype.replace("=","_")}' - if var.is_ddt(): - internal_var = Var({'local_name':internal_var_lname, 'standard_name':f'{internal_var_lname}_local', - 'ddt_type':vtype, 'kind':vkind, 'units':units, 'dimensions':'()'}, - _API_LOCAL, self.run_env) - else: - internal_var = Var({'local_name':internal_var_lname, 'standard_name':f'{internal_var_lname}_local', - 'type':vtype, 'kind':vkind, 'units':units, 'dimensions':'()'}, - _API_LOCAL, self.run_env) - found = self.__group.find_variable(source_var=internal_var, any_scope=False) - if not found: - self.__group.manage_variable(internal_var) - # For arrays, we need to get information on the dimensions and add it to # the group's call list so that we can test for the correct size later on if dimensions: From dc1ebf5ba88d757dbeb1d6e6644d3b1f95b9c0a3 Mon Sep 17 00:00:00 2001 From: dustinswales Date: Fri, 17 Apr 2026 11:54:24 -0600 Subject: [PATCH 25/59] Add logic to optional variable transforms in Cap --- scripts/suite_objects.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 3fa9fbcb..825125f0 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -164,9 +164,10 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ # local pointers of _ptr if var.get_prop_value('optional'): lname = dummy+'_ptr' + # end if # Finally, handle the dimensions. else: - if dimensions and not host_var: + if dimensions:# and not host_var: dimstr = '(' for cnt,dim in enumerate(dimensions): if is_horizontal_dimension(dim): @@ -1684,17 +1685,17 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, if (has_transform): lname = var.get_prop_value('local_name')+'_local' else: - lname = dvar.call_string(search_dict) + lname = dvar.call_string(search_dict)+dimstr # end if lname_ptr = var.get_prop_value('local_name') + '_ptr' # Scheme has optional varaible, host has varaible defined as Conditional (Active). if conditional != '.true.': outfile.write(f"if {conditional} then", indent) - outfile.write(f"{lname_ptr} => {lname+dimstr}", indent+1) + outfile.write(f"{lname_ptr} => {lname}", indent+1) outfile.write(f"end if", indent) # Scheme has optional varaible, host has varaible defined as Mandatory. else: - outfile.write(f"{lname_ptr} => {lname+dimstr}", indent) + outfile.write(f"{lname_ptr} => {lname}", indent) # end if # end if # end def @@ -1835,8 +1836,8 @@ def add_var_transform(self, var, compat_obj): 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): + def write_var_transform(self, var, var_name, dummy, rindices, lindices, compat_obj, + outfile, indent, forward, cldicts): """Write variable transformation needed to call this Scheme in . is the variable that needs transformation before and after calling Scheme. is the local variable needed for the transformation.. @@ -1851,7 +1852,7 @@ def write_var_transform(self, var, dummy, rindices, lindices, compat_obj, if not forward: # dummy(lindices) = var(rindices) stmt = compat_obj.reverse_transform(lvar_lname=dummy, - rvar_lname=var, + rvar_lname=var_name, lvar_indices=lindices, rvar_indices=rindices) # @@ -1859,12 +1860,21 @@ def write_var_transform(self, var, dummy, rindices, lindices, compat_obj, # else: # var(lindices) = dummy(rindices) - stmt = compat_obj.forward_transform(lvar_lname=var, + stmt = compat_obj.forward_transform(lvar_lname=var_name, rvar_lname=dummy, lvar_indices=rindices, rvar_indices=lindices) # end if - outfile.write(stmt, indent) + + (conditional, vars_needed) = var.conditional(cldicts) + if conditional != '.true.': + outfile.write(f"if {conditional} then", indent) + outfile.write(stmt, indent+1) + outfile.write(f"end if", indent) + # Scheme has optional varaible, host has varaible defined as Mandatory. + else: + outfile.write(stmt, indent) + # end if def write(self, outfile, errcode, errmsg, indent): # Unused arguments are for consistent write interface @@ -1913,7 +1923,7 @@ def write(self, outfile, errcode, errmsg, indent): # Group's call_list. lvar = self.__group.call_list.find_variable(standard_name=var_sname) lvar_lname = lvar.call_string(self.__group.call_list) - tstmt = self.write_var_transform(lvar_lname, dummy, rindices, lindices, compat_obj, outfile, indent+1, False) + tstmt = self.write_var_transform(lvar, lvar_lname, dummy, rindices, lindices, compat_obj, outfile, indent+1, False, cldicts) # end for outfile.write('',indent+1) # @@ -1958,7 +1968,7 @@ def write(self, outfile, errcode, errmsg, indent): # Group's call_list. lvar = self.__group.call_list.find_variable(standard_name=var_sname) lvar_lname = lvar.call_string(self.__group.call_list) - tstmt = self.write_var_transform(lvar_lname, dummy, rindices, lindices, compat_obj, outfile, indent+1, True) + tstmt = self.write_var_transform(lvar, lvar_lname, dummy, rindices, lindices, compat_obj, outfile, indent+1, True, cldicts) # end for outfile.write('', indent) outfile.write('end if', indent) From 6d67364ebdc4fdb4526d7816eeddc5ab0ebc3f6b Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Fri, 17 Apr 2026 12:11:03 -0600 Subject: [PATCH 26/59] Clean up --- scripts/suite_objects.py | 138 +++++---------------------------------- 1 file changed, 16 insertions(+), 122 deletions(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index b21fe207..86013e88 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1211,6 +1211,22 @@ def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): # end for return scheme_mods + def add_optional_var(self, dict_var, var, has_transform): + """Add local pointer needed for optional variable(s) in Group Cap. Also, + add any host variables from active condition that are needed to associate + the local pointer correctly.""" + + lname = var.get_prop_value('local_name') + sname = var.get_prop_value('standard_name') + lname_ptr = lname + '_ptr' + newvar_ptr = var.clone(lname_ptr) + # Group write phase needs new pointer variable for declaration step. + self.__group.optional_vars.append(newvar_ptr) + # Scheme write phase needs more info for transformations/local-pointer assignments. + self.__optional_vars.append([dict_var, var, has_transform]) + return + # end def + def handle_downstream_variables(self, var): """Ensure all dimension and optional variable arguments are available""" # Get the basic attributes that decide whether we need @@ -1269,22 +1285,6 @@ def handle_downstream_variables(self, var): self.update_group_call_list_variable(udim_var) # end if - def add_optional_var(self, dict_var, var, has_transform): - """Add local pointer needed for optional variable(s) in Group Cap. Also, - add any host variables from active condition that are needed to associate - the local pointer correctly.""" - - lname = var.get_prop_value('local_name') - sname = var.get_prop_value('standard_name') - lname_ptr = lname + '_ptr' - newvar_ptr = var.clone(lname_ptr) - # Group write phase needs new pointer variable for declaration step. - self.__group.optional_vars.append(newvar_ptr) - # Scheme write phase needs more info for transformations/local-pointer assignments. - self.__optional_vars.append([dict_var, var, has_transform]) - return - # end def - def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, outfile): """Write local pointer association for optional variable.""" # Use the local name from the Scheme call list, append "_ptr" suffix. @@ -1442,112 +1442,6 @@ def nullify_optional_var(self, dict_var, var, has_transform, cldicts, indent, ou # end if # end def - def assign_pointer_to_var(self, dict_var, var, has_transform, cldicts, indent, outfile): - """Write local pointer assignment to variable.""" - # Use the local name from the Scheme call list, append "_ptr" suffix. - standard_name = var.get_prop_value('standard_name') - dvar = self.__group.call_list.find_variable(standard_name=standard_name, any_scope=False) - search_dict = self.__group.call_list - if dvar: - var_in_call_list = True - # If we find a call_list variable that is a DDT, check to see if it has components. - # Variables that are components of a DDT are NOT part of the call_list. - if dvar.is_ddt(): - if dvar.components: - var_in_call_list = False - # end if - # end if - else: - var_in_call_list = False - # If it is not in the call list, try to find it - # in the local variables of this group subroutine. - dvar = self.__group.find_variable(standard_name=standard_name, any_scope=False) - if not dvar: - # This variable is handled by the group - # and is declared as a module variable - for var_dict in self.__group.suite_dicts(): - dvar = var_dict.find_variable(standard_name=standard_name, any_scope=False) - if dvar: - search_dict = var_dict - break - # end if - # end for - # end if - # end if - if not dvar: - raise Exception(f"No variable with standard name '{standard_name}' in cldicts") - # end if - # Handle the dimensions... - dimensions = dvar.get_dimensions() - dimstr = '' - if dimensions: - dimstr = dimstr + '(' - for cnt,dim in enumerate(dimensions): - if is_horizontal_dimension(dim): - if self.run_phase(): - if var_in_call_list and \ - self.find_variable(standard_name="horizontal_loop_extent"): - ldim = "ccpp_constant_one" - udim = "horizontal_loop_extent" - else: - ldim = "horizontal_loop_begin" - udim = "horizontal_loop_end" - # endif - else: - ldim = "ccpp_constant_one" - udim = "horizontal_dimension" - # endif - # Get dimension for lower bound - for var_dict in cldicts: - lvar = var_dict.find_variable(standard_name=ldim, any_scope=False) - if lvar is not None: - break - # end if - # end for - if not lvar: - raise Exception(f"No variable with standard name '{ldim}' in cldicts") - # end if - ldim_lname = lvar.get_prop_value('local_name') - # Get dimension for upper bound - for var_dict in cldicts: - uvar = var_dict.find_variable(standard_name=udim, any_scope=False) - if uvar is not None: - break - # end if - # end for - if not uvar: - raise Exception(f"No variable with standard name '{udim}' in cldicts") - # end if - udim_lname = uvar.get_prop_value('local_name') - dimstr = dimstr + ldim_lname + ':' + udim_lname - else: - dimstr = dimstr + ':' - # end if - if cnt < len(dimensions)-1: dimstr = dimstr+',' - # end for - dimstr = dimstr+')' - # end if - if (dict_var): - intent = var.get_prop_value('intent') - if (intent == 'out' or intent == 'inout'): - (conditional, vars_needed) = dvar.conditional(cldicts) - if (has_transform): - lname = dvar.get_prop_value('local_name')+'_local' - else: - lname = dvar.call_string(search_dict) - # end if - lname_ptr = var.get_prop_value('local_name') + '_ptr' - if conditional != '.true.': - outfile.write(f"if {conditional} then", indent) - outfile.write(f"{lname+dimstr} = {lname_ptr}", indent+1) - outfile.write(f"end if", indent) - else: - outfile.write(f"{lname+dimstr} = {lname_ptr}", indent) - # end if - # end if - # end if - # end def - def add_var_transform(self, var, compat_obj): """Register any variable transformation needed by for this Scheme. For any transformation identified in , create dummy variable From a5ec9ba16d6dde138d477f50e1dd5af505cb9914 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Fri, 17 Apr 2026 12:50:03 -0600 Subject: [PATCH 27/59] Update test/nested_suite_test/test_nested_suite_integration.F90 --- .../test_nested_suite_integration.F90 | 80 +++++-------------- 1 file changed, 21 insertions(+), 59 deletions(-) diff --git a/test/nested_suite_test/test_nested_suite_integration.F90 b/test/nested_suite_test/test_nested_suite_integration.F90 index 09dfea10..d987e38a 100644 --- a/test/nested_suite_test/test_nested_suite_integration.F90 +++ b/test/nested_suite_test/test_nested_suite_integration.F90 @@ -8,65 +8,27 @@ program test_nested_suite_integration 'rad_lw_group ', & 'rad_sw_group '/) - character(len=cm), target :: test_invars1(18) = (/ & - '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 ', & - 'cloud_graupel_number_concentration ', & - 'scalar_variable_for_testing ', & - 'turbulent_kinetic_energy ', & - 'turbulent_kinetic_energy2 ', & - 'scalar_variable_for_testing_a ', & - 'scalar_variable_for_testing_b ', & - 'scalar_variable_for_testing_c ', & - 'scheme_order_in_suite ', & - 'num_subcycles_for_effr ', & - 'flag_indicating_cloud_microphysics_has_graupel ', & - 'flag_indicating_cloud_microphysics_has_ice ', & - 'surface_downwelling_shortwave_radiation_flux ', & - 'surface_upwelling_shortwave_radiation_flux ', & - 'longwave_radiation_fluxes '/) - - character(len=cm), target :: test_outvars1(14) = (/ & - '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 ', & - 'scheme_order_in_suite ', & - 'surface_downwelling_shortwave_radiation_flux ', & - 'surface_upwelling_shortwave_radiation_flux ', & - 'turbulent_kinetic_energy ', & - 'turbulent_kinetic_energy2 ', & - 'longwave_radiation_fluxes '/) - - character(len=cm), target :: test_reqvars1(22) = (/ & - 'ccpp_error_code ', & - 'ccpp_error_message ', & - 'effective_radius_of_stratiform_cloud_rain_particle ', & - '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_graupel ', & - 'cloud_graupel_number_concentration ', & - 'cloud_ice_number_concentration ', & - 'scalar_variable_for_testing ', & - 'turbulent_kinetic_energy ', & - 'turbulent_kinetic_energy2 ', & - 'scalar_variable_for_testing_a ', & - 'scalar_variable_for_testing_b ', & - 'scalar_variable_for_testing_c ', & - 'scheme_order_in_suite ', & - 'num_subcycles_for_effr ', & - 'flag_indicating_cloud_microphysics_has_graupel ', & - 'flag_indicating_cloud_microphysics_has_ice ', & - 'surface_downwelling_shortwave_radiation_flux ', & - 'surface_upwelling_shortwave_radiation_flux ', & - 'longwave_radiation_fluxes '/) + character(len=cm), target :: test_invars1(5) = (/ & + 'effective_radius_of_stratiform_cloud_snow_particle', & + 'physics_state_derived_type ', & + 'flag_indicating_cloud_microphysics_has_graupel ', & + 'flag_indicating_cloud_microphysics_has_ice ', & + 'num_subcycles_for_effr '/) + + character(len=cm), target :: test_outvars1(4) = (/ & + 'effective_radius_of_stratiform_cloud_snow_particle', & + 'physics_state_derived_type ', & + 'ccpp_error_code ', & + 'ccpp_error_message '/) + + character(len=cm), target :: test_reqvars1(7) = (/ & + 'effective_radius_of_stratiform_cloud_snow_particle', & + 'physics_state_derived_type ', & + 'flag_indicating_cloud_microphysics_has_graupel ', & + 'flag_indicating_cloud_microphysics_has_ice ', & + 'num_subcycles_for_effr ', & + 'ccpp_error_code ', & + 'ccpp_error_message '/) type(suite_info) :: test_suites(1) logical :: run_okay From 7df19165908963473a6f1aa21b36f082c98053ef Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Fri, 17 Apr 2026 12:53:50 -0600 Subject: [PATCH 28/59] Remove logging/logging.F90 --- logging/logging.F90 | 406 -------------------------------------------- 1 file changed, 406 deletions(-) delete mode 100644 logging/logging.F90 diff --git a/logging/logging.F90 b/logging/logging.F90 deleted file mode 100644 index 59e82786..00000000 --- a/logging/logging.F90 +++ /dev/null @@ -1,406 +0,0 @@ -module marbl_logging - -! ============ -! Module Usage -! ============ -! -! Assume a variable named StatusLog (as appears in the marbl_interface_class) -! -! ----------------------------------------------- -! Use the following routines to write log entries -! ----------------------------------------------- -! -! (1) StatusLog%log_noerror -- this stores a log message in StatusLog that does -! not contain a fatal error -! (2) StatusLog%log_header -- this stores a log message in StatusLog that is -! meant to be read as a section header; e.g. StatusLog%log_header('HEADER',...) -! writes the following (including blank lines) -! -! ------ -! HEADER -! ------ -! -! (3) StatusLog%log_error -- this stores a log message in StatusLog that DOES -! contain a fatal error. It does this by setting StatusLog%labort_marbl = -! .true.; when a call from the GCM to MARBL returns, it is important for the -! GCM to check the value of StatusLog%labort_marbl and abort the run if an -! error has been reported. -! (4) StatusLog%log_error_trace -- this stores a log message in StatusLog -! detailing what subroutine was just called and where it was called from. It -! is meant to provide more information when trying to trace the path through -! the code that resulted in an error. -! -! ----------------------------------------------- -! Pseudo-code for writing StatusLog in the driver -! ----------------------------------------------- -! -! type(marbl_status_log_entry_type), pointer :: LogEntry -! -! ! Set pointer to first entry of the log -! LogEntry => StatusLog%FullLog -! -! do while (associated(LogEntry)) -! ! If running in parallel, you may want to check if you are the master -! ! task or if LogEntry%lalltasks = .true. -! write(stdout,*) trim(LogEntry%LogMessage) -! LogEntry => LogEntry%next -! end do -! -! ! Erase contents of log now that they have been written out -! call StatusLog%erase() -! -! if (StatusLog%labort_marbl) then -! [GCM abort call: "error found in MARBL"] -! end if -! - - use marbl_kinds_mod, only : char_len - - implicit none - private - save - - integer, parameter, private :: marbl_log_len = 2*char_len - - !**************************************************************************** - - type, public :: marbl_status_log_entry_type - integer :: ElementInd = -1 ! ElementInd < 0 implies no location data - logical :: lonly_master_writes ! True => message should be written to stdout - ! master task; False => all tasks - character(len=marbl_log_len) :: LogMessage ! Message text - character(len=char_len) :: CodeLocation ! Information on where log was written - - type(marbl_status_log_entry_type), pointer :: next - end type marbl_status_log_entry_type - - !**************************************************************************** - - ! Note: this data type is not in use at the moment, but it is included as an - ! initial step towards allowing the user some control over what types - ! of messages are added to the log. For example, if you do not want - ! the contents of namelists written to the log, you would simply set - ! - ! lLogNamelist = .false. - ! - ! In the future we hope to be able to set these options via namelist, - ! but for now lLogNamelist, lLogGeneral, lLogWarning, and lLogError are - ! all set to .true. and can not be changed without modifying the source - ! code in this file. - type, private :: marbl_log_output_options_type - logical :: labort_on_warning ! True => elevate Warnings to Errors - logical :: lLogVerbose ! Debugging output should be given Verbose label - logical :: lLogNamelist ! Write namelists to log? - logical :: lLogGeneral ! General diagnostic output - logical :: lLogWarning ! Warnings (can be elevated to errors via labort_on_warning) - logical :: lLogError ! Errors (will toggle labort_marbl whether log - ! is written or not) - contains - procedure :: construct => marbl_output_options_constructor - end type marbl_log_output_options_type - - !**************************************************************************** - - type, public :: marbl_log_type - logical, private :: lconstructed = .false. ! True => constructor was already called - logical, public :: labort_marbl = .false. ! True => driver should abort GCM - logical, public :: lwarning = .false. ! True => warnings are present - type(marbl_log_output_options_type) :: OutputOptions - type(marbl_status_log_entry_type), pointer :: FullLog - type(marbl_status_log_entry_type), pointer :: LastEntry - contains - procedure, public :: construct => marbl_log_constructor - procedure, public :: log_header => marbl_log_header - procedure, public :: log_error => marbl_log_error - procedure, public :: log_warning => marbl_log_warning - procedure, public :: log_noerror => marbl_log_noerror - procedure, public :: log_error_trace => marbl_log_error_trace - procedure, public :: log_warning_trace => marbl_log_warning_trace - procedure, public :: erase => marbl_log_erase - procedure, private :: append_to_log - end type marbl_log_type - - !**************************************************************************** - -contains - - !**************************************************************************** - - subroutine marbl_output_options_constructor(this, labort_on_warning, LogVerbose, LogNamelist, & - LogGeneral, LogWarning, LogError) - - class(marbl_log_output_options_type), intent(inout) :: this - logical, intent(in), optional :: labort_on_warning, LogVerbose, LogNamelist - logical, intent(in), optional :: LogGeneral, LogWarning, LogError - - if (present(labort_on_warning)) then - this%labort_on_warning = labort_on_warning - else - this%labort_on_warning = .false. - end if - - if (present(LogVerbose)) then - this%lLogVerbose = LogVerbose - else - this%lLogVerbose = .false. - end if - - if (present(LogNamelist)) then - this%lLogNamelist = LogNamelist - else - this%lLogNamelist = .true. - end if - - if (present(LogGeneral)) then - this%lLogGeneral = LogGeneral - else - this%lLogGeneral = .true. - end if - - if (present(LogWarning)) then - this%lLogWarning = LogWarning - else - this%lLogWarning = .true. - end if - - if (present(LogError)) then - this%lLogError = LogError - else - this%lLogError = .true. - end if - - end subroutine marbl_output_options_constructor - - !**************************************************************************** - - subroutine marbl_log_constructor(this) - - class(marbl_log_type), intent(inout) :: this - - if (this%lconstructed) return - this%lconstructed = .true. - nullify(this%FullLog) - nullify(this%LastEntry) - call this%OutputOptions%construct() - - end subroutine marbl_log_constructor - - !**************************************************************************** - - subroutine marbl_log_header(this, HeaderMsg, CodeLoc) - - class(marbl_log_type), intent(inout) :: this - ! StatusMsg is the message to be printed in the log; it does not need to - ! contain the name of the module or subroutine producing the log message - ! CodeLoc is the name of the subroutine that is calling StatusLog%log_noerror - character(len=*), intent(in) :: HeaderMsg, CodeLoc - - character(len=len_trim(HeaderMsg)) :: dashes - integer :: n - - do n=1, len(dashes) - dashes(n:n) = '-' - end do - call this%log_noerror('', CodeLoc) - call this%log_noerror(dashes, CodeLoc) - call this%log_noerror(HeaderMsg, CodeLoc) - call this%log_noerror(dashes, CodeLoc) - call this%log_noerror('', CodeLoc) - - end subroutine marbl_log_header - - !**************************************************************************** - - subroutine marbl_log_error(this, ErrorMsg, CodeLoc, ElemInd) - - class(marbl_log_type), intent(inout) :: this - ! ErrorMsg is the error message to be printed in the log; it does not need - ! to contain the name of the module or subroutine triggering the error - ! CodeLoc is the name of the subroutine that is calling StatusLog%log_error - character(len=*), intent(in) :: ErrorMsg, CodeLoc - integer, optional, intent(in) :: ElemInd - - character(len=marbl_log_len) :: ErrorMsg_loc ! Message text - - this%labort_marbl = .true. - - ! Only allocate memory and add entry if we want to log full namelist! - if (.not.this%OutputOptions%lLogError) then - return - end if - - write(ErrorMsg_loc, "(4A)") "MARBL ERROR (", trim(CodeLoc), "): ", & - trim(ErrorMsg) - - call this%append_to_log(ErrorMsg_loc, CodeLoc, ElemInd, lonly_master_writes=.false.) - - end subroutine marbl_log_error - - !**************************************************************************** - - subroutine marbl_log_warning(this, WarningMsg, CodeLoc, ElemInd) - - class(marbl_log_type), intent(inout) :: this - ! WarningMsg is the message to be printed in the log; it does not need to - ! contain the name of the module or subroutine producing the log message - ! CodeLoc is the name of the subroutine that is calling StatusLog%log_warning - character(len=*), intent(in) :: WarningMsg, CodeLoc - integer, optional, intent(in) :: ElemInd - - character(len=marbl_log_len) :: WarningMsg_loc ! Message text - - this%lwarning = .true. - - ! Only allocate memory and add entry if we want to log full namelist! - if (.not.this%OutputOptions%lLogWarning) then - return - end if - - write(WarningMsg_loc, "(4A)") "MARBL WARNING (", trim(CodeLoc), "): ", & - trim(WarningMsg) - - call this%append_to_log(WarningMsg_loc, CodeLoc, ElemInd, lonly_master_writes=.false.) - - end subroutine marbl_log_warning - - !**************************************************************************** - - subroutine marbl_log_noerror(this, StatusMsg, CodeLoc, ElemInd, lonly_master_writes) - - class(marbl_log_type), intent(inout) :: this - ! StatusMsg is the message to be printed in the log; it does not need to - ! contain the name of the module or subroutine producing the log message - ! CodeLoc is the name of the subroutine that is calling StatusLog%log_noerror - character(len=*), intent(in) :: StatusMsg, CodeLoc - integer, optional, intent(in) :: ElemInd - ! If lonly_master_writes is .false., then this is a message that should be - ! printed out regardless of which task produced it. By default, MARBL assumes - ! that only the master task needs to print a message - logical, optional, intent(in) :: lonly_master_writes - - ! Only allocate memory and add entry if we want to log full namelist! - if (.not.this%OutputOptions%lLogGeneral) then - return - end if - - call this%append_to_log(StatusMsg, CodeLoc, ElemInd, lonly_master_writes) - - end subroutine marbl_log_noerror - - !**************************************************************************** - - subroutine append_to_log(this, StatusMsg, CodeLoc, ElemInd, lonly_master_writes) - - class(marbl_log_type), intent(inout) :: this - ! StatusMsg is the message to be printed in the log; it does not need to - ! contain the name of the module or subroutine producing the log message - ! CodeLoc is the name of the subroutine that is calling StatusLog%log_noerror - character(len=*), intent(in) :: StatusMsg, CodeLoc - integer, optional, intent(in) :: ElemInd - ! If lonly_master_writes is .false., then this is a message that should be - ! printed out regardless of which task produced it. By default, MARBL assumes - ! that only the master task needs to print a message - logical, optional, intent(in) :: lonly_master_writes - type(marbl_status_log_entry_type), pointer :: new_entry - - allocate(new_entry) - nullify(new_entry%next) - if (present(ElemInd)) then - new_entry%ElementInd = ElemInd - else - new_entry%ElementInd = -1 - end if - new_entry%LogMessage = trim(StatusMsg) - new_entry%CodeLocation = trim(CodeLoc) - if (present(lonly_master_writes)) then - new_entry%lonly_master_writes = lonly_master_writes - else - new_entry%lonly_master_writes = .true. - end if - - if (associated(this%FullLog)) then - ! Append new entry to last entry in the log - this%LastEntry%next => new_entry - else - this%FullLog => new_entry - end if - ! Update LastEntry attribute of linked list - this%LastEntry => new_entry - - end subroutine append_to_log - - !**************************************************************************** - - subroutine marbl_log_error_trace(this, RoutineName, CodeLoc, ElemInd) - - ! This routine should only be called if another subroutine has returned and - ! StatusLog%labort_marbl = .true. - - class(marbl_log_type), intent(inout) :: this - ! RoutineName is the name of the subroutine that returned with - ! labort_marbl = .true. - ! CodeLoc is the name of the subroutine that is calling StatusLog%log_error_trace - ! - ! Log will contain a message along the lines of - ! - ! "(CodeLoc) Error reported from RoutineName" - ! - ! When the log is printed, this will provide a traceback through the sequence - ! of calls that led to the original error message. - character(len=*), intent(in) :: RoutineName, CodeLoc - integer, optional, intent(in) :: ElemInd - character(len=char_len) :: log_message - - write(log_message, "(2A)") "Error reported from ", trim(RoutineName) - call this%log_error(log_message, CodeLoc, ElemInd) - - end subroutine marbl_log_error_trace - - !**************************************************************************** - - subroutine marbl_log_warning_trace(this, RoutineName, CodeLoc, ElemInd) - - ! This routine should only be called if another subroutine has returned and - ! StatusLog%lwarning = .true. - - class(marbl_log_type), intent(inout) :: this - ! RoutineName is the name of the subroutine that returned with - ! lwarning = .true. - ! CodeLoc is the name of the subroutine that is calling StatusLog%log_warning_trace - ! - ! Log will contain a message along the lines of - ! - ! "(CodeLoc) Warning reported from RoutineName" - ! - ! When the log is printed, this will provide a traceback through the sequence - ! of calls that led to the original warning message. - character(len=*), intent(in) :: RoutineName, CodeLoc - integer, optional, intent(in) :: ElemInd - character(len=char_len) :: log_message - - write(log_message, "(2A)") "Warning reported from ", trim(RoutineName) - call this%log_warning(log_message, CodeLoc, ElemInd) - this%lwarning = .false. - - end subroutine marbl_log_warning_trace - - !**************************************************************************** - - subroutine marbl_log_erase(this) - - class(marbl_log_type), intent(inout) :: this - type(marbl_status_log_entry_type), pointer :: tmp - - do while (associated(this%FullLog)) - tmp => this%FullLog%next - deallocate(this%FullLog) - this%FullLog => tmp - end do - nullify(this%FullLog) - nullify(this%LastEntry) - - this%lwarning = .false. - - end subroutine marbl_log_erase - -end module marbl_logging From 88e62163ac1cf5e5e969a61430c9ab6b51459de1 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Fri, 17 Apr 2026 12:57:12 -0600 Subject: [PATCH 29/59] Remove stub/* --- stub/CMakeLists.txt | 84 ------------------------------------ stub/README.md | 13 ------ stub/ccpp_prebuild_config.py | 78 --------------------------------- stub/data.F90 | 17 -------- stub/data.meta | 14 ------ stub/stub.F90 | 35 --------------- stub/stub.meta | 45 ------------------- stub/suite_stub.xml | 9 ---- 8 files changed, 295 deletions(-) delete mode 100644 stub/CMakeLists.txt delete mode 100644 stub/README.md delete mode 100755 stub/ccpp_prebuild_config.py delete mode 100644 stub/data.F90 delete mode 100644 stub/data.meta delete mode 100644 stub/stub.F90 delete mode 100644 stub/stub.meta delete mode 100644 stub/suite_stub.xml diff --git a/stub/CMakeLists.txt b/stub/CMakeLists.txt deleted file mode 100644 index 4550036f..00000000 --- a/stub/CMakeLists.txt +++ /dev/null @@ -1,84 +0,0 @@ -#------------------------------------------------------------------------------ -cmake_minimum_required(VERSION 3.0) - -project(ccppstub - VERSION 1.0.0 - LANGUAGES Fortran) - -#------------------------------------------------------------------------------ -# Request a static build -option(BUILD_SHARED_LIBS "Build a shared library" OFF) - -#------------------------------------------------------------------------------ -# Set the sources: physics type definitions -set(TYPEDEFS $ENV{CCPP_TYPEDEFS}) -if(TYPEDEFS) - message(STATUS "Got CCPP TYPEDEFS from environment variable: ${TYPEDEFS}") -else(TYPEDEFS) - include(${CMAKE_CURRENT_BINARY_DIR}/CCPP_TYPEDEFS.cmake) - message(STATUS "Got CCPP TYPEDEFS from cmakefile include file: ${TYPEDEFS}") -endif(TYPEDEFS) - -# Generate list of Fortran modules from the CCPP type -# definitions that need need to be installed -foreach(typedef_module ${TYPEDEFS}) - list(APPEND MODULES_F90 ${CMAKE_CURRENT_BINARY_DIR}/${typedef_module}) -endforeach() - -#------------------------------------------------------------------------------ -# Set the sources: physics schemes -set(SCHEMES $ENV{CCPP_SCHEMES}) -if(SCHEMES) - message(STATUS "Got CCPP SCHEMES from environment variable: ${SCHEMES}") -else(SCHEMES) - include(${CMAKE_CURRENT_BINARY_DIR}/CCPP_SCHEMES.cmake) - message(STATUS "Got CCPP SCHEMES from cmakefile include file: ${SCHEMES}") -endif(SCHEMES) - -# Set the sources: physics scheme caps -set(CAPS $ENV{CCPP_CAPS}) -if(CAPS) - message(STATUS "Got CCPP CAPS from environment variable: ${CAPS}") -else(CAPS) - include(${CMAKE_CURRENT_BINARY_DIR}/CCPP_CAPS.cmake) - message(STATUS "Got CCPP CAPS from cmakefile include file: ${CAPS}") -endif(CAPS) - -# Set the sources: physics scheme caps -set(API $ENV{CCPP_API}) -if(API) - message(STATUS "Got CCPP API from environment variable: ${API}") -else(API) - include(${CMAKE_CURRENT_BINARY_DIR}/CCPP_API.cmake) - message(STATUS "Got CCPP API from cmakefile include file: ${API}") -endif(API) - -#------------------------------------------------------------------------------ -add_library(ccppstub STATIC ${SCHEMES} ${CAPS} ${API}) -# Generate list of Fortran modules from defined sources -foreach(source_f90 ${CAPS} ${API}) - get_filename_component(tmp_source_f90 ${source_f90} NAME) - string(REGEX REPLACE ".F90" ".mod" tmp_module_f90 ${tmp_source_f90}) - string(TOLOWER ${tmp_module_f90} module_f90) - list(APPEND MODULES_F90 ${CMAKE_CURRENT_BINARY_DIR}/${module_f90}) -endforeach() - -set_target_properties(ccppstub PROPERTIES VERSION ${PROJECT_VERSION} - SOVERSION ${PROJECT_VERSION_MAJOR}) - -# Define where to install the library -install(TARGETS ccppstub - EXPORT ccppstub-targets - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib - RUNTIME DESTINATION lib -) -# Export our configuration -install(EXPORT ccppstub-targets - FILE ccppstub-config.cmake - DESTINATION lib/cmake -) -# Define where to install the C headers and Fortran modules -#install(FILES ${HEADERS_C} DESTINATION include) -install(FILES ${MODULES_F90} DESTINATION include) - diff --git a/stub/README.md b/stub/README.md deleted file mode 100644 index 19c47c25..00000000 --- a/stub/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# How to build the stub - -1. Set compiler environment as appropriate for your system -2. Run the following commands: -``` -cd stub -rm -fr build -mkdir build -../scripts/ccpp_prebuild.py --config=ccpp_prebuild_config.py --builddir=build -cd build -cmake .. 2>&1 | tee log.cmake -make 2>&1 | tee log.make -``` diff --git a/stub/ccpp_prebuild_config.py b/stub/ccpp_prebuild_config.py deleted file mode 100755 index c95a41f6..00000000 --- a/stub/ccpp_prebuild_config.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python - -# CCPP prebuild config for GFDL Finite-Volume Cubed-Sphere Model (FV3) - - -############################################################################### -# Definitions # -############################################################################### - -HOST_MODEL_IDENTIFIER = "FV3" - -# Add all files with metadata tables on the host model side and in CCPP, -# relative to basedir = top-level directory of host model. This includes -# kind and type definitions used in CCPP physics. Also add any internal -# dependencies of these files to the list. -VARIABLE_DEFINITION_FILES = [ - # actual variable definition files - '../src/ccpp_types.F90', - 'data.F90', - ] - -TYPEDEFS_NEW_METADATA = { - 'ccpp_types' : { - 'ccpp_types' : '', - 'ccpp_t' : 'ccpp_data', - }, - } - -# Add all physics scheme files relative to basedir -SCHEME_FILES = [ - 'stub.F90', - ] - -# Default build dir, relative to current working directory, -# if not specified as command-line argument -DEFAULT_BUILD_DIR = 'build' - -# Auto-generated makefile/cmakefile snippets that contain all type definitions -TYPEDEFS_MAKEFILE = '{build_dir}/CCPP_TYPEDEFS.mk' -TYPEDEFS_CMAKEFILE = '{build_dir}/CCPP_TYPEDEFS.cmake' -TYPEDEFS_SOURCEFILE = '{build_dir}/CCPP_TYPEDEFS.sh' - -# Auto-generated makefile/cmakefile snippets that contain all schemes -SCHEMES_MAKEFILE = '{build_dir}/CCPP_SCHEMES.mk' -SCHEMES_CMAKEFILE = '{build_dir}/CCPP_SCHEMES.cmake' -SCHEMES_SOURCEFILE = '{build_dir}/CCPP_SCHEMES.sh' - -# Auto-generated makefile/cmakefile snippets that contain all caps -CAPS_MAKEFILE = '{build_dir}/CCPP_CAPS.mk' -CAPS_CMAKEFILE = '{build_dir}/CCPP_CAPS.cmake' -CAPS_SOURCEFILE = '{build_dir}/CCPP_CAPS.sh' - -# Directory where to put all auto-generated physics caps -CAPS_DIR = '{build_dir}' - -# Directory where the suite definition files are stored -SUITES_DIR = '.' - -# Optional arguments - only required for schemes that use -# optional arguments. ccpp_prebuild.py will throw an exception -# if it encounters a scheme subroutine with optional arguments -# if no entry is made here. Possible values are: 'all', 'none', -# or a list of standard_names: [ 'var1', 'var3' ]. -OPTIONAL_ARGUMENTS = {} - -# Directory where to write static API to -STATIC_API_DIR = '{build_dir}' -STATIC_API_CMAKEFILE = '{build_dir}/CCPP_API.cmake' -STATIC_API_SOURCEFILE = '{build_dir}/CCPP_API.sh' - -# Directory for writing HTML pages generated from metadata files -METADATA_HTML_OUTPUT_DIR = '{build_dir}' - -# HTML document containing the model-defined CCPP variables -HTML_VARTABLE_FILE = '{build_dir}/CCPP_VARIABLES_STUB.html' - -# LaTeX document containing the provided vs requested CCPP variables -LATEX_VARTABLE_FILE = '{build_dir}/CCPP_VARIABLES_STUB.tex' diff --git a/stub/data.F90 b/stub/data.F90 deleted file mode 100644 index d2a21c15..00000000 --- a/stub/data.F90 +++ /dev/null @@ -1,17 +0,0 @@ -module data - -!! \section arg_table_data Argument Table -!! \htmlinclude data.html -!! - - use ccpp_types, only: ccpp_t - - implicit none - - private - - public ccpp_data - - type(ccpp_t), save, target :: ccpp_data - -end module data diff --git a/stub/data.meta b/stub/data.meta deleted file mode 100644 index 55600b1a..00000000 --- a/stub/data.meta +++ /dev/null @@ -1,14 +0,0 @@ -[ccpp-table-properties] - name = data - type = module - dependencies = - -[ccpp-arg-table] - name = data - type = module -[ccpp_data] - standard_name = ccpp_t_instance - long_name = instance of derived data type ccpp_t - units = DDT - dimensions = () - type = ccpp_t diff --git a/stub/stub.F90 b/stub/stub.F90 deleted file mode 100644 index 0b392daa..00000000 --- a/stub/stub.F90 +++ /dev/null @@ -1,35 +0,0 @@ -!>\file stub.F90 -!! This file contains a stub CCPP scheme that does nothing -!! except requesting the minimum, mandatory variables. - -module stub - - implicit none - private - public :: stub_init, stub_finalize - - contains - -!! \section arg_table_stub_init Argument Table -!! \htmlinclude stub_init.html -!! - subroutine stub_init(errmsg, errflg) - character(len=*), intent(out) :: errmsg - integer, intent(out) :: errflg - ! Initialize CCPP error handling variables - errmsg = '' - errflg = 0 - end subroutine stub_init - -!! \section arg_table_stub_finalize Argument Table -!! \htmlinclude stub_finalize.html -!! - subroutine stub_finalize(errmsg, errflg) - character(len=*), intent(out) :: errmsg - integer, intent(out) :: errflg - ! Initialize CCPP error handling variables - errmsg = '' - errflg = 0 - end subroutine stub_finalize - -end module stub diff --git a/stub/stub.meta b/stub/stub.meta deleted file mode 100644 index 3cc30d59..00000000 --- a/stub/stub.meta +++ /dev/null @@ -1,45 +0,0 @@ -[ccpp-table-properties] - name = stub - type = scheme - dependencies = - -######################################################################## -[ccpp-arg-table] - name = stub_init - type = scheme -[errmsg] - standard_name = ccpp_error_message - long_name = error message for error handling in CCPP - units = none - dimensions = () - type = character - kind = len=* - intent = out -[errflg] - standard_name = ccpp_error_code - long_name = error code for error handling in CCPP - units = 1 - dimensions = () - type = integer - intent = out - -######################################################################## -[ccpp-arg-table] - name = stub_finalize - type = scheme -[errmsg] - standard_name = ccpp_error_message - long_name = error message for error handling in CCPP - units = none - dimensions = () - type = character - kind = len=* - intent = out -[errflg] - standard_name = ccpp_error_code - long_name = error code for error handling in CCPP - units = 1 - dimensions = () - type = integer - intent = out - diff --git a/stub/suite_stub.xml b/stub/suite_stub.xml deleted file mode 100644 index 46d25f9f..00000000 --- a/stub/suite_stub.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - stub - - - From 685e165444262dc76466328a50392d05d8b1c3a1 Mon Sep 17 00:00:00 2001 From: dustinswales Date: Fri, 17 Apr 2026 12:58:20 -0600 Subject: [PATCH 30/59] Stash --- scripts/suite_objects.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 0563cc9c..5360b185 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1192,7 +1192,7 @@ def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): compat_obj.has_unit_transforms or compat_obj.has_kind_transforms): if scheme_var is not None: - print("SWALES scheme_var for transform",scheme_var.get_prop_value('local_name')) + #print("SWALES scheme_var for transform",scheme_var.get_prop_value('local_name')) self.add_var_transform(scheme_var, compat_obj) else: self.add_var_transform(var, compat_obj) @@ -1503,8 +1503,9 @@ def add_var_transform(self, var, compat_obj): # If needed, modify horizontal dimension for loop substitution. # NOT YET IMPLEMENTED - #hdim = find_horizontal_dimension(var.get_dimensions()) + hdim = find_horizontal_dimension(var.get_dimensions()) #if compat_obj.has_dim_transforms: + print("SWALES ",hdim,var.get_prop_value('local_name')) # Register any reverse (pre-Scheme) transforms. Also, save local_name used in # transform (used in write stage). From 40a19c63004b1ec877cc613763e1a32edd8f1b15 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Fri, 17 Apr 2026 13:16:39 -0600 Subject: [PATCH 31/59] Fix bug in scripts/metavar.py: dname --> lname --- scripts/metavar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/metavar.py b/scripts/metavar.py index d6cfff53..f096e7c7 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -738,7 +738,7 @@ def call_string(self, var_dicts, loop_vars=None): else: errmsg = 'No local variable {} in variable dictionaries' ctx = context_string(self.context) - raise CCPPError(errmsg.format(item, dname, ctx)) + raise CCPPError(errmsg.format(item, lname, ctx)) # end if # end for # end if From a1c86969554551eee4d23ef2eca03030c3fade87 Mon Sep 17 00:00:00 2001 From: dustinswales Date: Fri, 17 Apr 2026 14:04:15 -0600 Subject: [PATCH 32/59] col_start:col_end in var transforms --- scripts/suite_objects.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 5360b185..5c73c09f 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1192,7 +1192,6 @@ def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): compat_obj.has_unit_transforms or compat_obj.has_kind_transforms): if scheme_var is not None: - #print("SWALES scheme_var for transform",scheme_var.get_prop_value('local_name')) self.add_var_transform(scheme_var, compat_obj) else: self.add_var_transform(var, compat_obj) @@ -1502,10 +1501,27 @@ def add_var_transform(self, var, compat_obj): # end if # If needed, modify horizontal dimension for loop substitution. + cldicts = [self]#.__group, self.__group.call_list] + #cldicts.extend(self.__group.suite_dicts()) # NOT YET IMPLEMENTED - hdim = find_horizontal_dimension(var.get_dimensions()) - #if compat_obj.has_dim_transforms: - print("SWALES ",hdim,var.get_prop_value('local_name')) + var_hdim,hdim = find_horizontal_dimension(var.get_dimensions()) + if var_hdim: + if self.run_phase(): + ldim = "horizontal_loop_begin" + udim = "horizontal_loop_end" + else: + ldim = "ccpp_constant_one" + udim = "horizontal_dimension" + # end if + lvar = self.find_variable(ldim) + if lvar is None: + raise CCPPError(f"add_var_transform: Cannot find dimension variable, {ldim}") + # end if + uvar = self.find_variable(udim) + if uvar is None: + raise CCPPError(f"add_var_transform: Cannot find dimension variable, {udim}") + # end if + rindices[hdim] = lvar.get_prop_value('local_name')+':'+uvar.get_prop_value('local_name') # Register any reverse (pre-Scheme) transforms. Also, save local_name used in # transform (used in write stage). @@ -1542,6 +1558,7 @@ def write_var_transform(self, var, var_name, dummy, rindices, lindices, compat_o are the LHS indices of for forward transforms (after Scheme). are the RHS indices of for forward transforms (after Scheme). """ + # # Write reverse (pre-Scheme) transform. # @@ -1561,7 +1578,7 @@ def write_var_transform(self, var, var_name, dummy, rindices, lindices, compat_o lvar_indices=rindices, rvar_indices=lindices) # end if - + (conditional, vars_needed) = var.conditional(cldicts) if conditional != '.true.': outfile.write(f"if {conditional} then", indent) From 1df6662aa86236113ffda87c3ee5b824d0b36d52 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Thu, 23 Apr 2026 08:39:04 -0600 Subject: [PATCH 33/59] Stage progress --- scripts/suite_objects.py | 55 +- scripts/suite_objects.py.dom | 2354 ++++++++++++++++++++++++++++++++++ test/utils/test_utils.F90 | 1 + 3 files changed, 2386 insertions(+), 24 deletions(-) create mode 100755 scripts/suite_objects.py.dom diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 5c73c09f..6c58745a 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -131,7 +131,7 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ if cldicts is not None: for cldict in cldicts: dvar = cldict.find_variable(standard_name=stdname, - any_scope=True) + any_scope=False) host_var = False if dvar is not None: var_in_call_list = True @@ -160,6 +160,13 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ # end if dimensions = dvar.get_dimensions() lname = dvar.call_string(cldicts) + # DH* + abort_me = False + if True: # lname == "o3": + print(f"DH XXX: GOT YOU: {lname}") + print(f"DH XXX: {dimensions}") + abort_me = True + dimstr = None # Optional variables in the caps are associated with # local pointers of _ptr if var.get_prop_value('optional'): @@ -167,9 +174,11 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ # end if # Finally, handle the dimensions. else: - if dimensions:# and not host_var: + print(f"DH AAA host_var? {host_var}") + if dimensions and not host_var: dimstr = '(' for cnt,dim in enumerate(dimensions): + print(f"DH ZZZ: {cnt} / {dim} / {is_horizontal_dimension(dim)}") if is_horizontal_dimension(dim): if self.routine.run_phase(): if var_in_call_list and \ @@ -206,6 +215,12 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ lname = lname + dimstr # end if # end if + # DH* + if abort_me: + print(f"DH YYY: {lname}") + if dimstr: + print(f"DH YYY: {dimstr}") + #raise Exception("XXX") else: cldict = None aref = var.array_ref(local_name=dummy) @@ -1307,7 +1322,7 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, dvar = self.__group.find_variable(standard_name=standard_name, any_scope=False) if not dvar: # This variable is handled by the group - # and is declared as a module variable + # and is declared as a module variable for var_dict in self.__group.suite_dicts(): dvar = var_dict.find_variable(standard_name=standard_name, any_scope=False) if dvar: @@ -1320,6 +1335,9 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, if not dvar: raise Exception(f"No variable with standard name '{standard_name}' in cldicts") # end if + # DH* + print(f"DH BBB GOT YOU! {standard_name} / {var.get_prop_value('local_name')} / / {dvar.get_prop_value('local_name')} % .") + # *DH # Handle the dimensions... dimensions = dvar.get_dimensions() dimstr = '' @@ -1392,6 +1410,11 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, outfile.write(f"{lname_ptr} => {lname}", indent) # end if # end if + + if lname_ptr and lname_ptr=="qv_ptr": + raise Exception(f"DH CCC: {lname_ptr} => {lname}") + + # end def def nullify_optional_var(self, dict_var, var, has_transform, cldicts, indent, outfile): @@ -1501,27 +1524,12 @@ def add_var_transform(self, var, compat_obj): # end if # If needed, modify horizontal dimension for loop substitution. - cldicts = [self]#.__group, self.__group.call_list] + #cldicts = [self]#.__group, self.__group.call_list] #cldicts.extend(self.__group.suite_dicts()) # NOT YET IMPLEMENTED - var_hdim,hdim = find_horizontal_dimension(var.get_dimensions()) - if var_hdim: - if self.run_phase(): - ldim = "horizontal_loop_begin" - udim = "horizontal_loop_end" - else: - ldim = "ccpp_constant_one" - udim = "horizontal_dimension" - # end if - lvar = self.find_variable(ldim) - if lvar is None: - raise CCPPError(f"add_var_transform: Cannot find dimension variable, {ldim}") - # end if - uvar = self.find_variable(udim) - if uvar is None: - raise CCPPError(f"add_var_transform: Cannot find dimension variable, {udim}") - # end if - rindices[hdim] = lvar.get_prop_value('local_name')+':'+uvar.get_prop_value('local_name') + hdim = find_horizontal_dimension(var.get_dimensions()) + #if compat_obj.has_dim_transforms: + print("SWALES ",hdim,var.get_prop_value('local_name')) # Register any reverse (pre-Scheme) transforms. Also, save local_name used in # transform (used in write stage). @@ -1558,7 +1566,6 @@ def write_var_transform(self, var, var_name, dummy, rindices, lindices, compat_o are the LHS indices of for forward transforms (after Scheme). are the RHS indices of for forward transforms (after Scheme). """ - # # Write reverse (pre-Scheme) transform. # @@ -1578,7 +1585,7 @@ def write_var_transform(self, var, var_name, dummy, rindices, lindices, compat_o lvar_indices=rindices, rvar_indices=lindices) # end if - + (conditional, vars_needed) = var.conditional(cldicts) if conditional != '.true.': outfile.write(f"if {conditional} then", indent) diff --git a/scripts/suite_objects.py.dom b/scripts/suite_objects.py.dom new file mode 100755 index 00000000..b1a4bbb5 --- /dev/null +++ b/scripts/suite_objects.py.dom @@ -0,0 +1,2354 @@ +#!/usr/bin/env python3 +# + +"""Classes and methods to create a Fortran suite-implementation file +to implement calls to a set of suites for a given host model.""" + +# Python library imports +import logging +import re +import xml.etree.ElementTree as ET +# CCPP framework imports +from ccpp_state_machine import CCPP_STATE_MACH, RUN_PHASE_NAME +from code_block import CodeBlock +from constituents import ConstituentVarDict, CONST_OBJ_STDNAME +from framework_env import CCPPFrameworkEnv +from metavar import Var, VarDictionary, VarLoopSubst +from metavar import CCPP_CONSTANT_VARS, CCPP_LOOP_VAR_STDNAMES +from metavar import write_ptr_def +from parse_tools import ParseContext, ParseSource, context_string +from parse_tools import ParseInternalError, CCPPError +from parse_tools import init_log, set_log_to_null +from ddt_library import VarDDT +from var_props import is_horizontal_dimension, find_horizontal_dimension +from var_props import find_vertical_dimension +from var_props import VarCompatObj + +# pylint: disable=too-many-lines + +############################################################################### +# Module (global) variables +############################################################################### + +_OBJ_LOC_RE = re.compile(r"(0x[0-9A-Fa-f]+)>") +_BLANK_DIMS_RE = re.compile(r"[(][:](,:)*[)]$") + +# Source for internally generated variables. +_API_SOURCE_NAME = "CCPP_API" +# Use the constituent source type for consistency +_API_SUITE_VAR_NAME = ConstituentVarDict.constitutent_source_type() +_API_GROUP_VAR_NAME = "group" +_API_SCHEME_VAR_NAME = "scheme" +_API_LOCAL_VAR_NAME = "local" +_API_LOCAL_VAR_TYPES = [_API_LOCAL_VAR_NAME, _API_SUITE_VAR_NAME] +_API_CONTEXT = ParseContext(filename="ccpp_suite.py") +_API_SOURCE = ParseSource(_API_SOURCE_NAME, _API_SCHEME_VAR_NAME, _API_CONTEXT) +_API_LOCAL = ParseSource(_API_SOURCE_NAME, _API_LOCAL_VAR_NAME, _API_CONTEXT) +_API_TIMESPLIT_TAG = 'time_split' +_API_PROCESSSPLIT_TAG = 'process_split' +_API_LOGGING = init_log('ccpp_suite') +set_log_to_null(_API_LOGGING) +_API_DUMMY_RUN_ENV = CCPPFrameworkEnv(_API_LOGGING, + ndict={'host_files':'', + 'scheme_files':'', + 'suites':''}) + +############################################################################### +def new_suite_object(item, context, parent, run_env, loop_count=0): +############################################################################### + "'Factory' method to create the appropriate suite object from XML" + new_item = None + if item.tag == 'subcycle': + new_item = Subcycle(item, context, parent, run_env, loop_count=loop_count) + elif item.tag == 'scheme': + new_item = Scheme(item, context, parent, run_env) + elif item.tag == _API_TIMESPLIT_TAG: + new_item = TimeSplit(item, context, parent, run_env) + else: + emsg = "Unknown CCPP suite element type, '{}'" + raise CCPPError(emsg.format(item.tag)) + # end if + return new_item + +############################################################################### + +class CallList(VarDictionary): + """A simple class to hold a routine's call list (dummy arguments)""" + + def __init__(self, name, run_env, routine=None): + """Initialize this call list. + is the name of this dictionary. + is a pointer to the routine for which this is a call list + or None for a routine that is not a SuiteObject. + """ + self.__routine = routine + super().__init__(name, run_env) + + def add_vars(self, call_list, run_env, gen_unique=False, add_children=False): + """Add new variables from another CallList ()""" + for var in call_list.variable_list(): + stdname = var.get_prop_value('standard_name') + self.add_variable(var, run_env, gen_unique=gen_unique, adjust_intent=True, + exists_ok=True, add_children=add_children) + # end for + + def add_variable(self, newvar, run_env, exists_ok=False, gen_unique=False, + adjust_intent=False, add_children=False): + """Add as for VarDictionary but make sure that the variable + has an intent with the default being intent(in). + """ + # We really need an intent on a dummy argument + if newvar.get_prop_value("intent") is None: + subst_dict = {'intent' : 'in'} + oldvar = newvar + newvar = oldvar.clone(subst_dict, source_name=self.name, + source_type=_API_GROUP_VAR_NAME, + context=oldvar.context) + # end if + super().add_variable(newvar, run_env, exists_ok=exists_ok, + gen_unique=gen_unique, adjust_intent=adjust_intent, + add_children=add_children) + + 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 = "" + for var in self.variable_list(): + # Do not include constants + stdname = var.get_prop_value('standard_name') + if stdname not in CCPP_CONSTANT_VARS: + dimensions = var.get_dimensions() + # Find the dummy argument name + dummy = var.get_prop_value('local_name') + # Now, find the local variable name + if cldicts is not None: + for cldict in cldicts: + dvar = cldict.find_variable(standard_name=stdname, + any_scope=False) + host_var = False + if dvar is not None: + var_in_call_list = True + if dvar.is_ddt(): + if dvar.components: + var_in_call_list = False + # end if + else: + # If we get here, the variable is explicitly in the Group call_list, + # not a DDT component. No need to modify the dimensions in the Scheme + # call list, as these were handled in the Suite Cap call_list. + host_var = True + # end if + break + # end if + # end for + if dvar is None: + if subname is not None: + errmsg = "{}: ".format(subname) + else: + errmsg = "" + # end if + errmsg += "'{}', not found in call list for '{}'" + clnames = [x.name for x in cldicts] + raise CCPPError(errmsg.format(stdname, clnames)) + # end if + dimensions = dvar.get_dimensions() + lname = dvar.call_string(cldicts) + # DH* + abort_me = False + if True: # lname == "o3": + print(f"DH XXX: GOT YOU: {lname}") + print(f"DH XXX: {dimensions}") + abort_me = True + dimstr = None + # Optional variables in the caps are associated with + # local pointers of _ptr + if var.get_prop_value('optional'): + lname = dummy+'_ptr' + # end if + # Finally, handle the dimensions. + else: + print(f"DH AAA host_var? {host_var}") + if dimensions and not host_var: + dimstr = '(' + for cnt,dim in enumerate(dimensions): + print(f"DH ZZZ: {cnt} / {dim} / {is_horizontal_dimension(dim)}") + if is_horizontal_dimension(dim): + if self.routine.run_phase(): + if var_in_call_list and \ + self.find_variable(standard_name="horizontal_loop_extent"): + ldim = "ccpp_constant_one" + udim = "horizontal_loop_extent" + else: + ldim = "horizontal_loop_begin" + udim = "horizontal_loop_end" + # endif + else: + ldim = "ccpp_constant_one" + udim = "horizontal_dimension" + # endif + # Get dimension for lower bound + lvar = cldict.find_variable(standard_name=ldim, any_scope=True) + if not lvar: + raise Exception(f"No variable with standard name '{ldim}' in cldict") + # end if + ldim_lname = lvar.get_prop_value('local_name') + # Get dimension for upper bound + uvar = cldict.find_variable(standard_name=udim, any_scope=True) + if not uvar: + raise Exception(f"No variable with standard name '{udim}' in cldict") + # end if + udim_lname = uvar.get_prop_value('local_name') + dimstr = dimstr + ldim_lname + ':' + udim_lname + else: + dimstr = dimstr + ':' + # endif + if cnt < len(dimensions)-1: dimstr = dimstr+',' + # end for + dimstr = dimstr+')' + lname = lname + dimstr + # end if + # end if + # DH* + if abort_me: + print(f"DH YYY: {lname}") + if dimstr: + print(f"DH YYY: {dimstr}") + #raise Exception("XXX") + else: + cldict = None + aref = var.array_ref(local_name=dummy) + if aref is not None: + lname = aref.group(1) + else: + 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 + else: + use_dicts = [self] + # end if + run_phase = self.routine.run_phase() + # We only need dimensions for suite variables in run phase + need_dims = SuiteObject.is_suite_variable(dvar) and run_phase + vdims = var.call_dimstring(var_dicts=use_dicts, + explicit_dims=need_dims, + loop_subst=run_phase) + if _BLANK_DIMS_RE.match(vdims) is None: + lname = lname + vdims + # end if + # end if + if is_func_call: + arg_str += "{}{}={}".format(arg_sep, dummy, lname) + else: + arg_str += "{}{}".format(arg_sep, lname) + # end if + arg_sep = ", " + # end if + # end for + return arg_str + + @property + def routine(self): + """Return the routine for this call list (or None)""" + return self.__routine + +############################################################################### + +class SuiteObject(VarDictionary): + """Base class for all CCPP Suite objects (e.g., Scheme, Subcycle) + SuiteObjects have an internal dictionary for variables created for + execution of the SuiteObject. These variables will be allocated and + managed at the Group level (unless cross-group usage or persistence + requires handling at the Suite level). + SuiteObjects also have a call list which is a list of variables which + are passed to callable SuiteObjects (e.g., Scheme). + """ + + def __init__(self, name, context, parent, run_env, + active_call_list=False, variables=None, phase_type=None): + # pylint: disable=too-many-arguments + self.__name = name + self.__context = context + self.__parent = parent + self.__run_env = run_env + if active_call_list: + self.__call_list = CallList(name + '_call_list', run_env, + routine=self) + else: + self.__call_list = None + # end if + self.__parts = list() + self.__needs_horizontal = None + self.__phase_type = phase_type + # Initialize our dictionary + super().__init__(self.name, run_env, + variables=variables, parent_dict=parent) + + def declarations(self): + """Return a list of local variables to be declared in parent Group + or Suite. By default, this list is the object's embedded VarDictionary. + """ + return self.variable_list() + + def add_part(self, item, replace=False): + """Add an object (e.g., Scheme, Subcycle) to this SuiteObject. + if is True, replace in its current position in self. + """ + if replace: + if item in self.__parts: + index = self.__parts.index(item) + else: + emsg = 'Cannot replace {} in {}, not a member' + raise ParseInternalError(emsg.format(item.name, self.name)) + # end if + else: + if item in self.__parts: + emsg = 'Cannot add {} to {}, already a member' + raise ParseInternalError(emsg.format(item.name, self.name)) + # end if + index = len(self.__parts) + # end if + # Just add + self.__parts.insert(index, item) + item.reset_parent(self) + + def schemes(self): + """Return a flattened list of schemes for this SuiteObject""" + schemes = list() + for item in self.__parts: + schemes.extend(item.schemes()) + # end for + return schemes + + def reset_parent(self, new_parent): + """Reset the parent of this SuiteObject (which has been moved)""" + self.__parent = new_parent + + def phase(self): + """Return the CCPP state phase_type for this SuiteObject""" + trans = self.phase_type + if trans is None: + if self.parent is not None: + trans = self.parent.phase() + else: + trans = False + # end if + # end if + return trans + + def run_phase(self): + """Return True iff this SuiteObject is in a run phase group""" + return self.phase() == RUN_PHASE_NAME + + def timestep_phase(self): + '''Return True iff this SuiteObject is in a timestep initial or + timestep final phase group''' + phase = self.phase() + return (phase is not None) and ('timestep' in phase) + + def register_action(self, vaction): + """Register (i.e., save information for processing during write stage) + and return True or pass up to the parent of + . Return True if any level registers , False otherwise. + The base class will not register any action, it must be registered in + an override of this method. + """ + if self.parent is not None: + return self.parent.register_action(vaction) + # end if + return False + + @classmethod + def is_suite_variable(cls, var): + """Return True iff belongs to our Suite""" + return var and (var.source.ptype == _API_SUITE_VAR_NAME) + + def is_local_variable(self, var): + """Return the local variable matching if one is found belonging + to this object or any of its SuiteObject parents.""" + stdname = var.get_prop_value('standard_name') + lvar = None + obj = self + while (not lvar) and (obj is not None) and isinstance(obj, SuiteObject): + lvar = obj.find_variable(standard_name=stdname, any_scope=False, + search_call_list=False) + if not lvar: + obj = obj.parent + # end if + # end while + return lvar + + def add_call_list_variable(self, newvar, exists_ok=False, + gen_unique=False, subst_dict=None): + """Add to this SuiteObject's call_list. If this SuiteObject + does not have a call list, recursively try the SuiteObject's parent + If is not None, create a clone using that as a dictionary + of substitutions. + Do not add if it exists as a local variable. + Do not add if it is a suite variable""" + stdname = newvar.get_prop_value('standard_name') + if self.parent: + pvar = self.parent.find_variable(standard_name=stdname, + source_var=newvar, + any_scope=False) + else: + pvar = None + # end if + if SuiteObject.is_suite_variable(pvar): + pass # Do not add suite variable to a call list + elif self.is_local_variable(newvar): + pass # Do not add to call list, it is owned by a SuiteObject + elif self.call_list is not None: + if (stdname in CCPP_LOOP_VAR_STDNAMES) and (not self.run_phase()): + errmsg = 'Attempting to use loop variable {} in {} phase' + raise CCPPError(errmsg.format(stdname, self.phase())) + # end if + # Do we need a clone? + if isinstance(self, Group): + stype = _API_GROUP_VAR_NAME + else: + stype = None + # end if + if stype or subst_dict: + oldvar = newvar + if subst_dict is None: + subst_dict = {} + # end if + # Make sure that this variable has an intent + if ((oldvar.get_prop_value("intent") is None) and + ("intent" not in subst_dict)): + subst_dict["intent"] = "in" + # end if + newvar = oldvar.clone(subst_dict, source_name=self.name, + source_type=stype, context=self.context) + # end if + self.call_list.add_variable(newvar, self.run_env, + exists_ok=exists_ok, + gen_unique=gen_unique, + adjust_intent=True, + add_children=True) + # We need to make sure that this variable's dimensions are available + for vardim in newvar.get_dim_stdnames(include_constants=False): + # Unnamed dimensions are ok for allocatable variables + if vardim == '' and newvar.get_prop_value('allocatable'): + continue + elif vardim == '': + emsg = f"{self.name}: Cannot have unnamed/empty string dimension" + raise ParseInternalError(emsg) + # end if + dvar = self.find_variable(standard_name=vardim, + any_scope=True) + if dvar is None: + emsg = "{}: Could not find dimension {} in {}" + raise ParseInternalError(emsg.format(self.name, + vardim, stdname)) + # end if + elif self.parent is None: + errmsg = 'No call_list found for {}'.format(newvar) + raise ParseInternalError(errmsg) + elif pvar: + # Check for call list incompatibility + if pvar is not None: + compat, reason = pvar.compatible(newvar, self.run_env) + if not compat: + emsg = 'Attempt to add incompatible variable to call list:' + emsg += '\n{} from {} is not compatible with {} from {}' + nlreason = newvar.get_prop_value(reason) + plreason = pvar.get_prop_value(reason) + emsg += '\nreason = {} ({} != {})'.format(reason, + nlreason, + plreason) + nlname = newvar.get_prop_value('local_name') + plname = pvar.get_prop_value('local_name') + raise CCPPError(emsg.format(nlname, newvar.source.name, + plname, pvar.source.name)) + # end if + # end if (no else, variable already in call list) + else: + self.parent.add_call_list_variable(newvar, exists_ok=exists_ok, + gen_unique=gen_unique, + subst_dict=subst_dict) + # end if + + def add_variable_to_call_tree(self, var, vmatch=None, subst_dict=None): + """Add to 's call_list (or a parent if does not + have an active call_list). + If is not None, also add the loop substitution variables + which must be present. + If is not None, create a clone using that as a dictionary + of substitutions. + """ + found_dims = False + if var is not None: + self.add_call_list_variable(var, exists_ok=True, + gen_unique=True, subst_dict=subst_dict) + found_dims = True + # end if + if vmatch is not None: + svars = vmatch.has_subst(self, any_scope=True) + if svars is None: + found_dims = False + else: + found_dims = True + for svar in svars: + self.add_call_list_variable(svar, exists_ok=True) + # end for + # Register the action (probably at Group level) + self.register_action(vmatch) + # end if + # end if + return found_dims + + def horiz_dim_match(self, ndim, hdim, nloop_subst): + """Find a match between and , if they are both + horizontal dimensions. + If == , return . + If is not None and its required standard names exist + in our extended dictionary, return them. + Otherwise, return None. + NB: Loop substitutions are only allowed during the run phase but in + other phases, horizontal_dimension and horizontal_loop_extent + are the same. + """ + dim_match = None + nis_hdim = is_horizontal_dimension(ndim) + his_hdim = is_horizontal_dimension(hdim) + if nis_hdim and his_hdim: + if ndim == hdim: + dim_match = ndim + elif self.run_phase() and (nloop_subst is not None): + svars = nloop_subst.has_subst(self, any_scope=True) + match = svars is not None + if match: + if isinstance(self, Scheme): + obj = self.parent + else: + obj = self + # end if + for svar in svars: + obj.add_call_list_variable(svar, exists_ok=True) + # end for + dim_match = ':'.join(nloop_subst.required_stdnames) + # end if + elif not self.run_phase(): + if ((hdim == 'ccpp_constant_one:horizontal_dimension') and + (ndim == 'ccpp_constant_one:horizontal_loop_extent')): + dim_match = hdim + elif ((hdim == 'ccpp_constant_one:horizontal_dimension') and + (ndim == 'horizontal_loop_begin:horizontal_loop_end')): + dim_match = hdim + # end if (no else, there is no non-run-phase match) + # end if (no else, there is no match) + # end if (no else, there is no match) + return dim_match + + @staticmethod + def dim_match(need_dim, have_dim): + """Test whether matches . + If they match, return the matching dimension (which may be + modified by, e.g., a loop substitution). + If they do not match, return None. + """ + match = None + # First, try for all the marbles + if need_dim == have_dim: + match = need_dim + # end if + # Is one side missing a one start? + if not match: + ndims = need_dim.split(':') + hdims = have_dim.split(':') + if len(ndims) > len(hdims): + if ndims[0].lower == 'ccpp_constant_one': + ndims = ndims[1:] + elif hdims[0].lower == 'ccpp_constant_one': + hdims = hdims[1:] + # end if (no else) + # Last try + match = ndims == hdims + # end if + # end if + + return match + + def match_dimensions(self, need_dims, have_dims): + """Compare dimensions between and . + Return 6 items: + 1) Return True if all dims match. + If has a vertical dimension and does not + but all other dimensions match, return False but include the + missing dimension index as the third return value. + 2) Return modified, if necessary to + reflect the available limits. + 3) Return have_dims modified, if necessary to reflect + any loop substitutions. If no substitutions, return None + This is done so that the correct dimensions are used in the host cap. + 4) Return the name of the missing vertical index, or None + 5) Return a permutation array if the dimension ordering is + different (or None if the ordering is the same). Each element of the + permutation array is the index in for that dimension of + . + 6) Finally, return a 'reason' string. If match (first return value) is + False, this string will contain information about the reason for + the match failure. + >>> SuiteObject('foo', _API_CONTEXT, None, _API_DUMMY_RUN_ENV).match_dimensions(['horizontal_loop_extent'], ['horizontal_loop_extent']) + (True, ['horizontal_loop_extent'], ['horizontal_loop_extent'], None, '') + >>> SuiteObject('foo', _API_CONTEXT,None,_API_DUMMY_RUN_ENV,variables=[Var({'local_name':'beg','standard_name':'horizontal_loop_begin','units':'count','dimensions':'()','type':'integer'}, _API_LOCAL,_API_DUMMY_RUN_ENV),Var({'local_name':'end','standard_name':'horizontal_loop_end','units':'count','dimensions':'()','type':'integer'}, _API_LOCAL, _API_DUMMY_RUN_ENV)],active_call_list=True,phase_type='initialize').match_dimensions(['ccpp_constant_one:horizontal_loop_extent'], ['ccpp_constant_one:horizontal_dimension']) + (True, ['ccpp_constant_one:horizontal_dimension'], ['ccpp_constant_one:horizontal_dimension'], None, '') + >>> SuiteObject('foo', _API_CONTEXT,None,_API_DUMMY_RUN_ENV,variables=[Var({'local_name':'beg','standard_name':'horizontal_loop_begin','units':'count','dimensions':'()','type':'integer'}, _API_LOCAL, _API_DUMMY_RUN_ENV),Var({'local_name':'end','standard_name':'horizontal_loop_end','units':'count','dimensions':'()','type':'integer'}, _API_LOCAL, _API_DUMMY_RUN_ENV)],active_call_list=True,phase_type=RUN_PHASE_NAME).match_dimensions(['ccpp_constant_one:horizontal_loop_extent'], ['horizontal_loop_begin:horizontal_loop_end']) + (True, ['horizontal_loop_begin:horizontal_loop_end'], ['horizontal_loop_begin:horizontal_loop_end'], None, '') + >>> SuiteObject('foo', _API_CONTEXT,None,_API_DUMMY_RUN_ENV,variables=[Var({'local_name':'beg','standard_name':'horizontal_loop_begin','units':'count','dimensions':'()','type':'integer'}, _API_LOCAL, _API_DUMMY_RUN_ENV),Var({'local_name':'end','standard_name':'horizontal_loop_end','units':'count','dimensions':'()','type':'integer'}, _API_LOCAL, _API_DUMMY_RUN_ENV),Var({'local_name':'lev','standard_name':'vertical_layer_dimension','units':'count','dimensions':'()','type':'integer'}, _API_LOCAL, _API_DUMMY_RUN_ENV)],active_call_list=True,phase_type=RUN_PHASE_NAME).match_dimensions(['ccpp_constant_one:horizontal_loop_extent','ccpp_constant_one:vertical_layer_dimension'], ['horizontal_loop_begin:horizontal_loop_end','ccpp_constant_one:vertical_layer_dimension']) + (True, ['horizontal_loop_begin:horizontal_loop_end', 'ccpp_constant_one:vertical_layer_dimension'], ['horizontal_loop_begin:horizontal_loop_end', 'ccpp_constant_one:vertical_layer_dimension'], None, '') + >>> SuiteObject('foo', _API_CONTEXT,None,_API_DUMMY_RUN_ENV,variables=[Var({'local_name':'beg','standard_name':'horizontal_loop_begin','units':'count','dimensions':'()','type':'integer'}, _API_LOCAL, _API_DUMMY_RUN_ENV),Var({'local_name':'end','standard_name':'horizontal_loop_end','units':'count','dimensions':'()','type':'integer'}, _API_LOCAL, _API_DUMMY_RUN_ENV),Var({'local_name':'lev','standard_name':'vertical_layer_dimension','units':'count','dimensions':'()','type':'integer'}, _API_LOCAL, _API_DUMMY_RUN_ENV)],active_call_list=True,phase_type=RUN_PHASE_NAME).match_dimensions(['ccpp_constant_one:horizontal_loop_extent','ccpp_constant_one:vertical_layer_dimension'], ['ccpp_constant_one:vertical_layer_dimension','horizontal_loop_begin:horizontal_loop_end']) + (True, ['horizontal_loop_begin:horizontal_loop_end', 'ccpp_constant_one:vertical_layer_dimension'], ['ccpp_constant_one:vertical_layer_dimension', 'horizontal_loop_begin:horizontal_loop_end'], [1, 0], '') + """ + new_need_dims = [] + new_have_dims = list(have_dims) + perm = [] + match = True + reason = '' + nlen = len(need_dims) + hlen = len(have_dims) + _, nvdim_index = find_vertical_dimension(need_dims) + _, hvdim_index = find_vertical_dimension(have_dims) + _, nhdim_index = find_horizontal_dimension(need_dims) + _, hhdim_index = find_horizontal_dimension(have_dims) + if hhdim_index < 0 <= nhdim_index: + match = False + nlen = 0 # To skip logic below + hlen = 0 # To skip logic below + reason = '{hname}{hctx} is missing a horizontal dimension ' + reason += 'required by {nname}{nctx}' + # end if + for nindex in range(nlen): + neddim = need_dims[nindex] + if nindex == nhdim_index: + # Look for a horizontal dimension match + vmatch = VarDictionary.loop_var_match(neddim) + hmatch = self.horiz_dim_match(neddim, have_dims[hhdim_index], + vmatch) + if hmatch: + perm.append(hhdim_index) + new_need_dims.append(hmatch) + new_have_dims[hhdim_index] = hmatch + found_ndim = True + else: + found_ndim = False + # end if + else: + # Find the first dimension in have_dims that matches neddim + found_ndim = False + if nvdim_index < 0 <= hvdim_index: + skip = hvdim_index + else: + skip = -1 + # end if + hdim_indices = [x for x in range(hlen) + if (x not in perm) and (x != skip)] + for hindex in hdim_indices: + if (hindex != hvdim_index) or (nvdim_index >= 0): + hmatch = self.dim_match(neddim, have_dims[hindex]) + if hmatch: + perm.append(hindex) + new_need_dims.append(hmatch) + new_have_dims[hindex] = hmatch + found_ndim = True + break + # end if + # end if + # end if + # end for + if not found_ndim: + match = False + reason = 'Could not find dimension, ' + neddim + ', in ' + reason += '{hname}{hctx}. Needed by {nname}{nctx}' + break + # end if (no else, we are still okay) + # end for + perm_test = list(range(hlen)) + # If no permutation is found, reset to None + if perm == perm_test: + perm = None + elif (not match): + perm = None + # end if (else, return perm as is) + if new_have_dims == have_dims: + have_dims = None # Do not make any substitutions + # end if + return match, new_need_dims, new_have_dims, perm, reason + + def find_variable(self, standard_name=None, source_var=None, + any_scope=True, clone=None, + search_call_list=False, loop_subst=False, + check_components=True): + """Find a matching variable to , create a local clone (if + is True), or return None. + First search the SuiteObject's internal dictionary, then its + call list (unless is True, then any parent + dictionary (if is True). + can be a Var object or a standard_name string. + is not used by this version of . + """ + # First, search our local dictionary + if standard_name is None: + if source_var is None: + emsg = "One of or must be passed." + raise ParseInternalError(emsg) + # end if + standard_name = source_var.get_prop_value('standard_name') + elif source_var is not None: + stest = source_var.get_prop_value('standard_name') + if stest != standard_name: + emsg = (" and must match if " + + "both are passed.") + raise ParseInternalError(emsg) + # end if + # end if + scl = search_call_list + stdname = standard_name + # Don't clone yet, might find the variable further down + found_var = super().find_variable(standard_name=stdname, + source_var=source_var, + any_scope=False, clone=None, + search_call_list=scl, + loop_subst=loop_subst, + check_components=check_components) + if (not found_var) and (self.call_list is not None) and scl: + # Don't clone yet, might find the variable further down + found_var = self.call_list.find_variable(standard_name=stdname, + source_var=source_var, + any_scope=False, + clone=None, + search_call_list=scl, + loop_subst=loop_subst, + check_components=check_components) + # end if + loop_okay = VarDictionary.loop_var_okay(stdname, self.run_phase()) + if not loop_okay: + loop_subst = False + # end if + if (found_var is None) and any_scope and (self.parent is not None): + # We do not have the variable, look to parents. + found_var = self.parent.find_variable(standard_name=stdname, + source_var=source_var, + any_scope=any_scope, + clone=clone, + search_call_list=scl, + loop_subst=loop_subst, + check_components=check_components) + # end if + return found_var + + def match_variable(self, var, run_env, host_dict): + """Try to find a source for in this SuiteObject's dictionary + tree. Several items are returned: + found_var: True if a match was found + vert_dim: The vertical dimension in , or None + call_dims: How this variable should be called (or None if no match) + perm: Permutation (XXgoldyXX: Not yet implemented) + """ + vstdname = var.get_prop_value('standard_name') + vdims = var.get_dimensions() + local_var = None + if (not vdims) and self.run_phase(): + vmatch = VarDictionary.loop_var_match(vstdname) + else: + vmatch = None + # end if + + found_var = False + new_vdims = list() + var_vdim = var.has_vertical_dimension(dims=vdims) + compat_obj = None + dict_var = None + scheme_var = None + if var.get_prop_value('type') == 'ccpp_constituent_properties_t': + if self.phase() == 'register': + found_var = True + new_vdims = [':'] + return found_var, local_var, dict_var, var_vdim, new_vdims, compat_obj, scheme_var + else: + errmsg = "Variables of type ccpp_constituent_properties_t only allowed in register phase: " + sname = var.get_prop_value('standard_name') + errmsg += f"'{sname}' found in {self.phase()} phase" + raise CCPPError(errmsg) + # end if + # end if + + # Is this variable a member of a DDT? If so, look for the parent DDT + # and add that instead + # PEVERWHEE - note - currently not doing this for constituents + constituent = var.is_constituent() + if not constituent: + host_var = host_dict.find_variable(source_var=var, any_scope=False) + if host_var: + if isinstance(host_var, VarDDT): + local_var = host_var + scheme_var = var # Save Scheme for transform + var = host_var.var + vdims = [] + # end if + # end if + # end if + # Does this variable exist in the calling tree? + dict_var = self.find_variable(source_var=var, any_scope=True) + if dict_var is None: + # No existing variable but add loop var match to call tree + found_var = self.parent.add_variable_to_call_tree(dict_var, + vmatch=vmatch) + new_vdims = vdims + elif dict_var.source.ptype in _API_LOCAL_VAR_TYPES: + # We cannot change the dimensions of locally-declared variables + # Using a loop substitution is invalid because the loop variable + # value has not yet been set. + # Therefore, we have to use the declaration dimensions in the call. + found_var = True + new_vdims = dict_var.get_dimensions() + else: + # Check dimensions + dict_dims = dict_var.get_dimensions() + if vdims and not constituent: + args = self.parent.match_dimensions(vdims, dict_dims) + match, new_vdims, new_dict_dims, perm, err = args + if perm is not None: + errmsg = "Permuted indices are not yet supported" + lname = var.get_prop_value('local_name') + dstr = ', '.join(vdims) + ctx = context_string(var.context) + errmsg += ", var = {}({}){}".format(lname, dstr, ctx) + raise CCPPError(errmsg) + # end if + else: + 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) + # Add the variable to the parent call tree + if dict_dims == new_dict_dims: + sdict = {} + else: + sdict = {'dimensions':new_dict_dims} + # end if + # Add any DDT components from the host dictionary version of the variable + if not constituent: + for dict_var_component in dict_var.components: + var.add_component(dict_var_component) + # end for + # end if + found_var = self.parent.add_variable_to_call_tree(var, + subst_dict=sdict) + if not match: + found_var = False + nctx = context_string(var.context) + nname = var.get_prop_value('local_name') + hctx = context_string(dict_var.context) + hname = dict_var.get_prop_value('local_name') + raise CCPPError(err.format(nname=nname, nctx=nctx, + hname=hname, hctx=hctx)) + # 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) + if scheme_var is not None: + compat_obj = var.compatible(scheme_var, run_env) + else: + compat_obj = var.compatible(dict_var, run_env) + # end if + # end if + return found_var, dict_var, local_var, var_vdim, new_vdims, compat_obj, scheme_var + + def in_process_split(self): + """Find out if we are in a process-split region""" + proc_split = False + obj = self + while obj is not None: + if isinstance(obj, ProcessSplit): + proc_split = True + break + # end if + if isinstance(obj, TimeSplit): + break + # end if (other object types do not change status) + obj = obj.parent + # end while + return proc_split + + def part(self, index, error=True): + """Return one of this SuiteObject's parts raise an exception, or, + if is False, just return None""" + plen = len(self.__parts) + if (0 <= index < plen) or (abs(index) <= plen): + return self.__parts[index] + # end if + if error: + errmsg = 'No part {} in {} {}'.format(index, + self.__class__.__name__, + self.name) + raise ParseInternalError(errmsg) + # end if + return None + + def has_item(self, item_name): + """Return True iff item, , is already in this SuiteObject""" + has = False + for item in self.__parts: + if item.name == item_name: + has = True + else: + has = item.has_item(item_name) + # end if + if has: + break + # end if + # end for + return has + + @property + def name(self): + """Return the name of the element""" + return self.__name + + @name.setter + def name(self, value): + """Set the name of the element if it has not been set""" + if self.__name is None: + self.__name = value + else: + errmsg = 'Attempt to change name of {} to {}' + raise ParseInternalError(errmsg.format(self, value)) + # end if + + @property + def parent(self): + """This SuiteObject's parent (or none)""" + return self.__parent + + @property + def call_list(self): + """Return the SuiteObject's call_list""" + return self.__call_list + + @property + def phase_type(self): + """Return the phase_type of this suite_object""" + return self.__phase_type + + @property + def parts(self): + """Return a copy the component parts of this SuiteObject. + Returning a copy allows for the part list to be changed during + processing of the return value""" + return self.__parts[:] + + @property + def context(self): + """Return the context of this SuiteObject""" + return self.__context + + @property + def run_env(self): + """Return the CCPPFrameworkEnv runtime object for this SuiteObject""" + return self.__run_env + + def __repr__(self): + """Create a unique readable string for this Object""" + so_repr = super().__repr__() + olmatch = _OBJ_LOC_RE.search(so_repr) + if olmatch is not None: + loc = ' at {}'.format(olmatch.group(1)) + else: + loc = "" + # end if + return '<{} {}{}>'.format(self.__class__.__name__, self.name, loc) + + def __format__(self, spec): + """Return a string representing the SuiteObject, including its children. + is used between subitems. + is the indent level for multi-line output. + """ + if spec: + sep = spec[0] + else: + sep = '\n' + # end if + try: + ind_level = int(spec[1:]) + except (ValueError, IndexError): + ind_level = 0 + # end try + if sep == '\n': + indent = " " + else: + indent = "" + # end if + if self.name == self.__class__.__name__: + # This object does not have separate name + nstr = self.name + else: + nstr = "{}: {}".format(self.__class__.__name__, self.name) + # end if + output = "{}<{}>".format(indent*ind_level, nstr) + subspec = "{}{}".format(sep, ind_level + 1) + substr = "{o}{s}{p:" + subspec + "}" + subout = "" + for part in self.parts: + subout = substr.format(o=subout, s=sep, p=part) + # end for + if subout: + output = "{}{}{}{}".format(output, subout, sep, + indent*ind_level, + self.__class__.__name__) + else: + output = "{}".format(output, self.__class__.__name__) + # end if + return output + +############################################################################### + +class Scheme(SuiteObject): + """A single scheme in a suite (e.g., init method)""" + + def __init__(self, scheme_xml, context, parent, run_env): + """Initialize this physics Scheme""" + name = scheme_xml.text + self.__subroutine_name = None + self.__context = context + self.__version = scheme_xml.get('version', None) + self.__lib = scheme_xml.get('lib', None) + self.__has_vertical_dimension = False + self.__group = None + self.__forward_transforms = list() + self.__reverse_transforms = list() + self._has_run_phase = True + self.__optional_vars = list() + super().__init__(name, context, parent, run_env, active_call_list=True) + + def update_group_call_list_variable(self, var): + """If is in our group's call list, update its intent. + Add to our group's call list unless: + - is in our group's call list + - is in our group's dictionary, + - is a suite variable""" + stdname = var.get_prop_value('standard_name') + my_group = self.__group + gvar = my_group.call_list.find_variable(standard_name=stdname, + any_scope=False) + if gvar: + gvar.adjust_intent(var) + else: + gvar = my_group.find_variable(standard_name=stdname, + any_scope=False) + if gvar is None: + # Check for suite variable + gvar = my_group.find_variable(standard_name=stdname, + any_scope=True) + if gvar and (not SuiteObject.is_suite_variable(gvar)): + gvar = None + # end if + if gvar is None: + my_group.add_call_list_variable(var, gen_unique=True) + # end if + # end if + + def is_local_variable(self, var): + """Return None as we never consider to be in our local + dictionary. + This is an override of the SuiteObject version""" + return None + + def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): + """Analyze the scheme's interface to prepare for writing""" + self.__group = group + my_header = None + if self.name in scheme_library: + func = scheme_library[self.name] + if phase in func: + my_header = func[phase] + self.__subroutine_name = my_header.title + else: + self._has_run_phase = False + return set() + # end if + else: + estr = 'No schemes found for {}' + raise ParseInternalError(estr.format(self.name), + context=self.__context) + # end if + if my_header is None: + estr = 'No {} header found for scheme, {}' + raise ParseInternalError(estr.format(phase, self.name), + context=self.__context) + # end if + if my_header.module is None: + estr = 'No module found for subroutine, {}' + raise ParseInternalError(estr.format(self.subroutine_name), + context=self.__context) + # end if + scheme_mods = set() + scheme_mods.add((my_header.module, self.subroutine_name)) + for var in my_header.variable_list(): + vstdname = var.get_prop_value('standard_name') + def_val = var.get_prop_value('default_value') + vdims = var.get_dimensions() + vintent = var.get_prop_value('intent') + args = self.match_variable(var, self.run_env, host_dict) + found, dict_var, local_var, var_vdim, new_dims, compat_obj, scheme_var = args + if dict_var: + if dict_var.is_ddt(): + subst_dict = {'intent':'inout'} + else: + subst_dict = {'intent': var.get_prop_value('intent')} + # end if + clone = dict_var.clone(subst_dict) + dict_var = clone + # end if + if found: + # Hack to get the missing dimensions promoted to the right place + # Add variable allocation checks for group, suite and host variables + if dict_var: + if local_var: + self.handle_downstream_variables(local_var) + else: + self.handle_downstream_variables(dict_var) + # end if + # end if + # We have a match, make sure var is in call list + if new_dims == vdims: + self.add_call_list_variable(var, exists_ok=True, gen_unique=True) + if dict_var: + self.update_group_call_list_variable(dict_var) + else: + self.update_group_call_list_variable(var) + # end if + else: + subst_dict = {'dimensions':new_dims} + clone = var.clone(subst_dict) + self.add_call_list_variable(clone, exists_ok=True) + if dict_var: + clone = dict_var.clone(subst_dict) + self.update_group_call_list_variable(clone) + else: + self.update_group_call_list_variable(clone) + # end if + # end if + else: + if vintent == 'out': + if self.__group is None: + errmsg = 'Group not defined for {}'.format(self.name) + raise ParseInternalError(errmsg) + # end if + # The Group will manage this variable + if dict_var: + self.__group.manage_variable(dict_var) + else: + self.__group.manage_variable(var) + # end if + self.add_call_list_variable(var) + elif def_val and (vintent != 'out'): + if self.__group is None: + errmsg = 'Group not defined for {}'.format(self.name) + raise ParseInternalError(errmsg) + # end if + # The Group will manage this variable + if dict_var: + self.__group.manage_variable(dict_var) + else: + self.__group.manage_variable(var) + # end if + # We still need it in our call list (the group uses a clone) + self.add_call_list_variable(var) + else: + errmsg = 'Input argument for {}, {}, not found.' + if self.find_variable(source_var=var) is not None: + # The variable exists, maybe it is dim mismatch + lname = var.get_prop_value('local_name') + emsg = '\nCheck for dimension mismatch in {}' + errmsg += emsg.format(lname) + # end if + if ((not self.run_phase()) and + (vstdname in CCPP_LOOP_VAR_STDNAMES)): + emsg = '\nLoop variables not allowed in {} phase.' + errmsg += emsg.format(self.phase()) + # end if + raise CCPPError(errmsg.format(self.subroutine_name, + vstdname)) + # 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): + if scheme_var is not None: + #print("SWALES scheme_var for transform",scheme_var.get_prop_value('local_name')) + self.add_var_transform(scheme_var, compat_obj) + else: + self.add_var_transform(var, compat_obj) + # end if + has_transform = True + # end if + + # Is this a conditionally allocated variable? + # If so, declare local pointer variable. This is needed to + # pass inactive (not present) status through the Scheme call_lists. + if var.get_prop_value('optional'): + #if dict_var: + self.add_optional_var(dict_var, var, has_transform) + # end if + # end if + + # end for + return scheme_mods + + def add_optional_var(self, dict_var, var, has_transform): + """Add local pointer needed for optional variable(s) in Group Cap. Also, + add any host variables from active condition that are needed to associate + the local pointer correctly.""" + + lname = var.get_prop_value('local_name') + sname = var.get_prop_value('standard_name') + lname_ptr = lname + '_ptr' + newvar_ptr = var.clone(lname_ptr) + # Group write phase needs new pointer variable for declaration step. + self.__group.optional_vars.append(newvar_ptr) + # Scheme write phase needs more info for transformations/local-pointer assignments. + self.__optional_vars.append([dict_var, var, has_transform]) + return + # end def + + def handle_downstream_variables(self, var): + """Ensure all dimension and optional variable arguments are available""" + # Get the basic attributes that decide whether we need + # to check the variable when we write the group + standard_name = var.get_prop_value('standard_name') + dimensions = var.get_dimensions() + active = var.get_prop_value('active') + var_dicts = [ self.__group.call_list ] + self.__group.suite_dicts() + + # If the variable isn't active, skip it + if active.lower() =='.false.': + return + # Also, if the variable is one of the CCPP error handling messages, skip it + # since it is defined as intent(out) and we can't do meaningful checks on it + elif standard_name == 'ccpp_error_code' or standard_name == 'ccpp_error_message': + return + # To perform allocation checks, we need to know all variables + # that are part of the 'active' attribute conditional and add + # it to the group's call list. + else: + (_, vars_needed) = var.conditional(var_dicts) + for var_needed in vars_needed: + self.update_group_call_list_variable(var_needed) + + # For arrays, we need to get information on the dimensions and add it to + # the group's call list so that we can test for the correct size later on + if dimensions: + for dim in dimensions: + if not ':' in dim: + dim_var = self.find_variable(standard_name=dim) + if not dim_var: + # To allow for numerical dimensions in metadata. + if not dim.isnumeric(): + raise Exception(f"No dimension with standard name '{dim}'") + # end if + else: + self.update_group_call_list_variable(dim_var) + # end if + else: + (ldim, udim) = dim.split(":") + ldim_var = self.find_variable(standard_name=ldim) + if not ldim_var: + # To allow for numerical dimensions in metadata. + if not ldim.isnumeric(): + raise Exception(f"No dimension with standard name '{ldim}'") + # end if + # end if + self.update_group_call_list_variable(ldim_var) + udim_var = self.find_variable(standard_name=udim) + if not udim_var: + # To allow for numerical dimensions in metadata. + if not udim.isnumeric(): + raise Exception(f"No dimension with standard name '{udim}'") + # end if + else: + self.update_group_call_list_variable(udim_var) + # end if + + def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, outfile): + """Write local pointer association for optional variable.""" + # Use the local name from the Scheme call list, append "_ptr" suffix. + standard_name = var.get_prop_value('standard_name') + dvar = self.__group.call_list.find_variable(standard_name=standard_name, any_scope=False) + search_dict = self.__group.call_list + if dvar: + var_in_call_list = True + # If we find a call_list variable that is a DDT, check to see if it has components. + # Variables that are components of a DDT are NOT part of the call_list. + if dvar.is_ddt(): + if dvar.components: + var_in_call_list = False + # end if + # end if + else: + var_in_call_list = False + # If it is not in the call list, try to find it + # in the local variables of this group subroutine. + dvar = self.__group.find_variable(standard_name=standard_name, any_scope=False) + if not dvar: + # This variable is handled by the group + # and is declared as a module variable + for var_dict in self.__group.suite_dicts(): + dvar = var_dict.find_variable(standard_name=standard_name, any_scope=False) + if dvar: + search_dict = var_dict + break + # end if + # end for + # end if + # end if + if not dvar: + raise Exception(f"No variable with standard name '{standard_name}' in cldicts") + # end if + # Handle the dimensions... + dimensions = dvar.get_dimensions() + dimstr = '' + if dimensions: + dimstr = dimstr + '(' + for cnt,dim in enumerate(dimensions): + if is_horizontal_dimension(dim): + if self.run_phase(): + if var_in_call_list and \ + self.find_variable(standard_name="horizontal_loop_extent"): + ldim = "ccpp_constant_one" + udim = "horizontal_loop_extent" + else: + ldim = "horizontal_loop_begin" + udim = "horizontal_loop_end" + # endif + else: + ldim = "ccpp_constant_one" + udim = "horizontal_dimension" + # endif + # Get dimension for lower bound + #lvar = self.__group.call_list.find_variable(standard_name=ldim, any_scope=True) + for var_dict in cldicts: + lvar = var_dict.find_variable(standard_name=ldim, any_scope=False) + if lvar is not None: + break + # end if + # end for + if not lvar: + raise Exception(f"No variable with standard name '{ldim}' in cldicts") + # end if + ldim_lname = lvar.get_prop_value('local_name') + # Get dimension for upper bound + for var_dict in cldicts: + uvar = var_dict.find_variable(standard_name=udim, any_scope=False) + if uvar is not None: + break + # end if + # end for + if not uvar: + raise Exception(f"No variable with standard name '{udim}' in cldicts") + # end if + udim_lname = uvar.get_prop_value('local_name') + dimstr = dimstr + ldim_lname + ':' + udim_lname + else: + dimstr = dimstr + ':' + # end if + if cnt < len(dimensions)-1: dimstr = dimstr+',' + # end for + dimstr = dimstr+')' + # end if + + # Need to use local_name in Group's call list (self.__group.call_list), not + # the local_name in var. + if (dict_var): + (conditional, vars_needed) = dvar.conditional(cldicts) + if (has_transform): + lname = var.get_prop_value('local_name')+'_local' + else: + lname = dvar.call_string(search_dict)+dimstr + # end if + lname_ptr = var.get_prop_value('local_name') + '_ptr' + # Scheme has optional varaible, host has varaible defined as Conditional (Active). + if conditional != '.true.': + outfile.write(f"if {conditional} then", indent) + outfile.write(f"{lname_ptr} => {lname}", indent+1) + outfile.write(f"end if", indent) + # Scheme has optional varaible, host has varaible defined as Mandatory. + else: + outfile.write(f"{lname_ptr} => {lname}", indent) + # end if + # end if + # end def + + def nullify_optional_var(self, dict_var, var, has_transform, cldicts, indent, outfile): + """Write local pointer nullification for optional variable.""" + + standard_name = var.get_prop_value('standard_name') + dvar = self.__group.call_list.find_variable(standard_name=standard_name, any_scope=False) + search_dict = self.__group.call_list + if dvar: + var_in_call_list = True + else: + var_in_call_list = False + # If it is not in the call list, try to find it + # in the local variables of this group subroutine. + dvar = self.__group.find_variable(standard_name=standard_name, any_scope=False) + if not dvar: + # This variable is handled by the group + # and is declared as a module variable + for var_dict in self.__group.suite_dicts(): + dvar = var_dict.find_variable(standard_name=standard_name, any_scope=False) + if dvar: + search_dict = var_dict + break + # end if + # end for + # end if + # end if + + # Need to use local_name in Group's call list (self.__group.call_list), not + # the local_name in var. + if (dict_var): + (conditional, vars_needed) = dvar.conditional(cldicts) + if (has_transform): + lname = dvar.get_prop_value('local_name')+'_local' + else: + lname = dvar.get_prop_value('local_name') + # end if + lname_ptr = var.get_prop_value('local_name') + '_ptr' + # Scheme has optional varaible, host has varaible defined as Conditional (Active). + if conditional != '.true.': + outfile.write(f"if {conditional} then", indent) + outfile.write(f"nullify({lname_ptr})", indent+1) + outfile.write(f"end if", indent) + # Scheme has optional varaible, host has varaible defined as Mandatory. + else: + outfile.write(f"nullify({lname_ptr})", indent) + # end if + # end if + # end def + + def add_var_transform(self, var, compat_obj): + """Register any variable transformation needed by for this Scheme. + For any transformation identified in , create dummy variable + from to perform the transformation. Determine the indices needed + for the transform and save for use during write stage""" + + # 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() + rindices = [':']*var.get_rank() + + # If needed, modify vertical dimension for vertical orientation flipping + var_vdim, vdim = find_vertical_dimension(var.get_dimensions()) + if vdim >= 0: + vdims = var_vdim.split(':') + vdim_name = vdims[-1] + group_vvar = self.__group.call_list.find_variable(vdim_name) + if group_vvar is None: + raise CCPPError(f"add_var_transform: Cannot find dimension variable, {vdim_name}") + # end if + vname = group_vvar.get_prop_value('local_name') + if len(vdims) == 2: + sdim_name = vdims[0] + group_vvar = self.find_variable(sdim_name) + if group_vvar is None: + raise CCPPError(f"add_var_transform: Cannot find dimension variable, {sdim_name}") + # end if + sname = group_vvar.get_prop_value('local_name') + else: + sname = '1' + # end if + lindices[vdim] = sname+':'+vname + if compat_obj.has_vert_transforms: + rindices[vdim] = vname+':'+sname+':-1' + else: + rindices[vdim] = sname+':'+vname + # end if + # end if + + # If needed, modify horizontal dimension for loop substitution. + # NOT YET IMPLEMENTED + hdim = find_horizontal_dimension(var.get_dimensions()) + #if compat_obj.has_dim_transforms: + print("SWALES ",hdim,var.get_prop_value('local_name')) + + # Register any reverse (pre-Scheme) transforms. Also, save local_name used in + # transform (used in write stage). + if (var.get_prop_value('intent') != 'out'): + 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, + self.__subroutine_name)) + 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, + self.__subroutine_name)) + self.__forward_transforms.append([var.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, var_name, dummy, rindices, lindices, compat_obj, + outfile, indent, forward, cldicts): + """Write variable transformation needed to call this Scheme in . + 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). + are the LHS indices of for forward transforms (after Scheme). + are the RHS indices of for forward transforms (after Scheme). + """ + # + # Write reverse (pre-Scheme) transform. + # + if not forward: + # dummy(lindices) = var(rindices) + stmt = compat_obj.reverse_transform(lvar_lname=dummy, + rvar_lname=var_name, + lvar_indices=lindices, + rvar_indices=rindices) + # + # Write forward (post-Scheme) transform. + # + else: + # var(lindices) = dummy(rindices) + stmt = compat_obj.forward_transform(lvar_lname=var_name, + rvar_lname=dummy, + lvar_indices=rindices, + rvar_indices=lindices) + # end if + + (conditional, vars_needed) = var.conditional(cldicts) + if conditional != '.true.': + outfile.write(f"if {conditional} then", indent) + outfile.write(stmt, indent+1) + outfile.write(f"end if", indent) + # Scheme has optional varaible, host has varaible defined as Mandatory. + else: + outfile.write(stmt, indent) + # end if + + def write(self, outfile, errcode, errmsg, indent): + # Unused arguments are for consistent write interface + # pylint: disable=unused-argument + """Write code to call this Scheme to """ + # Dictionaries to try are our group, the group's call list, + # or our module + cldicts = [self.__group, self.__group.call_list] + cldicts.extend(self.__group.suite_dicts()) + my_args = self.call_list.call_string(cldicts=cldicts, + is_func_call=True, + subname=self.subroutine_name, + sub_lname_list = self.__reverse_transforms) + # + outfile.write('', indent) + outfile.write('if ({} == 0) then'.format(errcode), indent) + # + # Write any reverse (pre-Scheme) transforms. + if len(self.__reverse_transforms) > 0: + outfile.comment('Compute reverse (pre-scheme) transforms', indent+1) + # end if + 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.call_string(self.__group.call_list) + tstmt = self.write_var_transform(lvar, lvar_lname, dummy, rindices, lindices, compat_obj, outfile, indent+1, False, cldicts) + # end for + outfile.write('',indent+1) + # + # Associate any conditionally allocated variables. + # + if self.__optional_vars: + outfile.write('! Associate conditional variables', indent+1) + # end if + for (dict_var, var, has_transform) in self.__optional_vars: + tstmt = self.associate_optional_var(dict_var, var, has_transform, cldicts, indent+1, outfile) + # end for + # + # Write the scheme call. + # + if self._has_run_phase: + stmt = 'call {}({})' + outfile.write('',indent+1) + outfile.write('! Call scheme', indent+1) + outfile.write(stmt.format(self.subroutine_name, my_args), indent+1) + outfile.write('',indent+1) + # end if + + # + # Nullify any local pointers. + # + if self.__optional_vars: + outfile.write('! Nullify conditional variables', indent+1) + # end if + for (dict_var, var, has_transform) in self.__optional_vars: + tstmt = self.nullify_optional_var(dict_var, var, has_transform, cldicts, indent+1, outfile) + # + # Write any forward (post-Scheme) transforms. + # + if len(self.__forward_transforms) > 0: + outfile.comment('Compute forward (post-scheme) transforms', indent+1) + # end if + 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.call_string(self.__group.call_list) + tstmt = self.write_var_transform(lvar, lvar_lname, dummy, rindices, lindices, compat_obj, outfile, indent+1, True, cldicts) + # end for + outfile.write('', indent) + outfile.write('end if', indent) + + def schemes(self): + """Return self as a list for consistency with subcycle""" + return [self] + + def variable_list(self, recursive=False, + std_vars=True, loop_vars=True, consts=True): + """Return a list of all variables for this Scheme. + Because Schemes do not have any variables, return a list + of this object's CallList variables instead. + Note that because of this, is not allowed.""" + if recursive: + raise ParseInternalError("recursive=True not allowed for Schemes") + # end if + return self.call_list.variable_list(recursive=recursive, + std_vars=std_vars, + loop_vars=loop_vars, consts=consts) + + @property + def subroutine_name(self): + """Return this scheme's actual subroutine name""" + return self.__subroutine_name + + @property + def has_vertical_dim(self): + """Return True if at least one of this Scheme's variables has + a vertical dimension (vertical_layer_dimension or + vertical_interface_dimension) + """ + return self.__has_vertical_dimension + + def __str__(self): + """Create a readable string for this Scheme""" + return ''.format(self.name, self.subroutine_name) + +############################################################################### + +class Subcycle(SuiteObject): + """Class to represent a subcycled group of schemes or scheme collections""" + + def __init__(self, sub_xml, context, parent, run_env, loop_count=0): + self._loop_extent = sub_xml.get('loop', "1") # Number of iterations + self._loop = None + # See if our loop variable is an integer or a variable + try: + _ = int(self._loop_extent) + self._loop = self._loop_extent + self._loop_var_int = True + name = f"loop{loop_count}" + super().__init__(name, context, parent, run_env, active_call_list=False) + loop_count = loop_count + 1 + except ValueError: + self._loop_var_int = False + lvar = parent.find_variable(standard_name=self._loop_extent, any_scope=True) + if lvar is None: + emsg = "Subcycle, {}, specifies {} iterations, variable not found" + raise CCPPError(emsg.format(name, self._loop_extent)) + else: + self._loop_var_int = False + self._loop = lvar.get_prop_value('local_name') + # end if + name = f"loop{loop_count}_{self._loop_extent}"[0:63] + super().__init__(name, context, parent, run_env, active_call_list=True) + parent.add_call_list_variable(lvar, exists_ok=True) + loop_count = loop_count + 1 + # end try + for item in sub_xml: + new_item = new_suite_object(item, context, self, run_env, loop_count=loop_count) + self.add_part(new_item) + # end for + + def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): + """Analyze the Subcycle's interface to prepare for writing""" + if self.name is None: + self.name = "subcycle_index{}".format(level) + # end if + # Create a Group variable for the subcycle index. + newvar = Var({'local_name':self.name, 'standard_name':self.name, + 'type':'integer', 'units':'count', 'dimensions':'()'}, + _API_LOCAL, self.run_env) + group.manage_variable(newvar) + # Handle all the suite objects inside of this subcycle + scheme_mods = set() + for item in self.parts: + smods = item.analyze(phase, group, scheme_library, + suite_vars, level+1, host_dict) + for smod in smods: + scheme_mods.add(smod) + # end for + # end for + return scheme_mods + + def write(self, outfile, errcode, errmsg, indent): + """Write code for the subcycle loop, including contents, to """ + outfile.write('do {} = 1, {}'.format(self.name, self._loop), indent) + # Note that 'scheme' may be a sybcycle or other construct + for item in self.parts: + item.write(outfile, errcode, errmsg, indent+1) + # end for + outfile.write('end do', 2) + + @property + def loop(self): + """Return the loop value or variable local_name""" + return self._loop + +############################################################################### + +class TimeSplit(SuiteObject): + """Class to represent a group of processes to be computed in a time-split + manner -- each parameterization or other construct is called with an + state which has been updated from the previous step. + """ + + def __init__(self, sub_xml, context, parent, run_env): + super().__init__('TimeSplit', context, parent, run_env) + for part in sub_xml: + new_item = new_suite_object(part, context, self, run_env) + self.add_part(new_item) + # end for + + def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): + # Unused arguments are for consistent analyze interface + # pylint: disable=unused-argument + """Analyze the TimeSplit's interface to prepare for writing""" + # Handle all the suite objects inside of this group + scheme_mods = set() + for item in self.parts: + smods = item.analyze(phase, group, scheme_library, + suite_vars, level+1, host_dict) + for smod in smods: + scheme_mods.add(smod) + # end for + # end for + return scheme_mods + + def write(self, outfile, errcode, errmsg, indent): + """Write code for this TimeSplit section, including contents, + to """ + for item in self.parts: + item.write(outfile, errcode, errmsg, indent) + # end for + +############################################################################### + +class ProcessSplit(SuiteObject): + """Class to represent a group of processes to be computed in a + process-split manner -- all parameterizations or other constructs are + called with the same state. + NOTE: Currently a stub + """ + + def __init__(self, sub_xml, context, parent, run_env): + # Unused arguments are for consistent __init__ interface + # pylint: disable=unused-argument + super().__init__('ProcessSplit', context, parent, run_env) + raise CCPPError('ProcessSplit not yet implemented') + + def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): + # Unused arguments are for consistent analyze interface + # pylint: disable=unused-argument + """Analyze the ProcessSplit's interface to prepare for writing""" + # Handle all the suite objects inside of this group + raise CCPPError('ProcessSplit not yet implemented') + + def write(self, outfile, errcode, errmsg, indent): + """Write code for this ProcessSplit section, including contents, + to """ + raise CCPPError('ProcessSplit not yet implemented') + +############################################################################### + +class Group(SuiteObject): + """Class to represent a grouping of schemes in a suite + A Group object is implemented as a subroutine callable by the API. + The main arguments to a group are the host model variables. + Additional output arguments are generated from schemes with intent(out) + arguments. + Additional input or inout arguments are generated for inputs needed by + schemes which are produced (intent(out)) by other groups. + """ + + __subhead = ''' + subroutine {subname}({args}) +''' + + __subend = ''' + end subroutine {subname} + +! ======================================================================== +''' + + __thread_check = CodeBlock([('#ifdef _OPENMP', -1), + ('if (omp_get_thread_num() > 1) then', 1), + ('{errcode} = 1', 2), + (('{errmsg} = "Cannot call {phase} routine ' + 'from a threaded region"'), 2), + ('return', 2), + ('end if', 1), + ('#endif', -1)]) + + __process_types = [_API_TIMESPLIT_TAG, _API_PROCESSSPLIT_TAG] + + __process_xml = {} + for gptype in __process_types: + __process_xml[gptype] = '<{ptype}>'.format(ptype=gptype) + # end for + + def __init__(self, group_xml, transition, parent, context, run_env): + """Initialize this Group object from . + is the group's phase, is the group's suite. + """ + name = parent.name + '_' + group_xml.get('name') + if transition not in CCPP_STATE_MACH.transitions(): + errmsg = "Bad transition argument to Group, '{}'" + raise ParseInternalError(errmsg.format(transition)) + # end if + # Initialize the dictionary of variables internal to group + super().__init__(name, context, parent, run_env, + active_call_list=True, phase_type=transition) + # Add the items but first make sure we know the process type for + # the group (e.g., TimeSplit or ProcessSplit). + if (transition == RUN_PHASE_NAME) and ((not group_xml) or + (group_xml[0].tag not in + Group.__process_types)): + # Default is TimeSplit + tsxml = ET.fromstring(Group.__process_xml[_API_TIMESPLIT_TAG]) + time_split = new_suite_object(tsxml, context, self, run_env) + add_to = time_split + self.add_part(time_split) + else: + add_to = self + # end if + # Add the sub objects either directly to the Group or to the TimeSplit + for item in group_xml: + new_item = new_suite_object(item, context, add_to, run_env) + add_to.add_part(new_item) + # end for + self._local_schemes = set() + self._host_vars = None + self._host_ddts = None + self._loop_var_matches = list() + self._phase_check_stmts = list() + self._set_state = None + self._ddt_library = None + self.transform_locals = list() + self.optional_vars = list() + + def phase_match(self, scheme_name): + """If scheme_name matches the group phase, return the group and + function ID. Otherwise, return None + """ + fid, tid, _ = CCPP_STATE_MACH.transition_match(scheme_name, + transition=self.phase()) + if tid is not None: + return self, fid + # end if + return None, None + + def move_to_call_list(self, standard_name): + """Move a variable from the group internal dictionary to the call list. + This is done when the variable, , will be allocated by + the suite. + """ + gvar = self.find_variable(standard_name=standard_name, any_scope=False) + if gvar is None: + errmsg = "Group {}, cannot move {}, variable not found" + raise ParseInternalError(errmsg.format(self.name, standard_name)) + # end if + self.add_call_list_variable(gvar, exists_ok=True) + self.remove_variable(standard_name) + + def register_action(self, vaction): + """Register any recognized type for use during self.write. + Return True iff is handled. + """ + if isinstance(vaction, VarLoopSubst): + self._loop_var_matches = vaction.add_to_list(self._loop_var_matches) + # Add the missing dim + vaction.add_local(self, _API_LOCAL, self.run_env) + return True + # end if + return False + + def manage_variable(self, newvar): + """Add to our local dictionary making necessary + modifications to the variable properties so that it is + allocated appropriately""" + # Need new prop dict to eliminate unwanted properties (e.g., intent) + vdims = newvar.get_dimensions() + # Look for dimensions where we have a loop substitution and replace + # with the correct size + if self.run_phase(): + hdims = [x.missing_stdname for x in self._loop_var_matches] + else: + # Do not do loop substitutions in full phases + hdims = list() + # end if + for index, dim in enumerate(vdims): + newdim = None + for subdim in dim.split(':'): + if subdim in hdims: + # We have a loop substitution, find and replace + hindex = hdims.index(subdim) + names = self._loop_var_matches[hindex].required_stdnames + newdim = ':'.join(names) + break + # end if + if ('vertical' in subdim) and ('index' in subdim): + # We have a vertical index, replace with correct dimension + errmsg = "vertical index replace not implemented" + raise ParseInternalError(errmsg) + # end if + # end for + if newdim is not None: + vdims[index] = newdim + # end if + # end for + if self.timestep_phase(): + persist = 'timestep' + else: + persist = 'run' + # end if + # Start with an official copy of 's prop_dict with + # corrected dimensions + subst_dict = {'dimensions':vdims} + prop_dict = newvar.copy_prop_dict(subst_dict=subst_dict) + # Add the allocatable items + prop_dict['allocatable'] = len(vdims) > 0 # No need to allocate scalar + prop_dict['persistence'] = persist + # This is a local variable + if 'intent' in prop_dict: + del prop_dict['intent'] + # end if + # Create a new variable, save the original context + local_var = Var(prop_dict, + ParseSource(_API_SOURCE_NAME, + _API_LOCAL_VAR_NAME, newvar.context), + self.run_env) + self.add_variable(local_var, self.run_env, exists_ok=True, gen_unique=True) + # Finally, make sure all dimensions are accounted for + emsg = self.add_variable_dimensions(local_var, [_API_LOCAL_VAR_NAME], + _API_SUITE_VAR_NAME, + adjust_intent=True, + to_dict=self.call_list) + if emsg: + raise CCPPError(emsg) + # end if + + def analyze(self, phase, suite_vars, scheme_library, ddt_library, + check_suite_state, set_suite_state, host_dict): + """Analyze the Group's interface to prepare for writing""" + self._ddt_library = ddt_library + # Sanity check for Group + if phase != self.phase(): + errmsg = 'Group {} has phase {} but analyze is phase {}' + raise ParseInternalError(errmsg.format(self.name, + self.phase(), phase)) + # end if + for item in self.parts: + # Items can be schemes, subcycles or other objects + # All have the same interface and return a set of module use + # statements (lschemes) + lschemes = item.analyze(phase, self, scheme_library, suite_vars, 1, host_dict) + for lscheme in lschemes: + self._local_schemes.add(lscheme) + # end for + # end for + self._phase_check_stmts = check_suite_state + self._set_state = set_suite_state + if (self.run_env.logger and + self.run_env.logger.isEnabledFor(logging.DEBUG)): + self.run_env.logger.debug("{}".format(self)) + # end if + + def allocate_dim_str(self, dims, context): + """Create the dimension string for an allocate statement""" + rdims = list() + for dim in dims: + rdparts = list() + dparts = dim.split(':') + for dpart in dparts: + dvar = self.find_variable(standard_name=dpart, any_scope=False) + if dvar is None: + dvar = self.call_list.find_variable(standard_name=dpart, + any_scope=False) + # end if + if dvar is None: + # Check if it's a module-level variable + dvar = self.find_variable(standard_name=dpart, any_scope=True) + # end if + if dvar is None: + emsg = "Dimension variable, '{}', not found{}" + lvar = self.find_local_name(dpart, any_scope=True) + if lvar is not None: + emsg += "\nBe sure to use standard names!" + # end if + ctx = context_string(context) + raise CCPPError(emsg.format(dpart, ctx)) + # end if + lname = dvar.get_prop_value('local_name') + rdparts.append(lname) + # end for + rdims.append(':'.join(rdparts)) + # end for + return ', '.join(rdims) + + def find_variable(self, standard_name=None, source_var=None, + any_scope=True, clone=None, + search_call_list=False, loop_subst=False, + check_components=True): + """Find a matching variable to , create a local clone (if + is True), or return None. + This purpose of this special Group version is to record any constituent + variable found for processing during the write phase. + """ + fvar = super().find_variable(standard_name=standard_name, + source_var=source_var, + any_scope=any_scope, clone=clone, + search_call_list=search_call_list, + loop_subst=loop_subst, + check_components=check_components) + if fvar and fvar.is_constituent(): + if fvar.source.ptype == ConstituentVarDict.constitutent_source_type(): + # We found this variable in the constituent dictionary, + # add it to our call list + self.add_call_list_variable(fvar, exists_ok=True) + # end if + # end if + return fvar + + def write(self, outfile, host_arglist, indent, const_mod, + suite_vars=None, allocate=False, deallocate=False): + """Write code for this subroutine (Group), including contents, + to """ + # Unused arguments are for consistent write interface + # pylint: disable=unused-argument + # group type for (de)allocation + if self.timestep_phase(): + group_type = 'timestep' # Just allocate for the timestep + else: + group_type = 'run' # Allocate for entire run + # end if + # Collect information on local variables + subpart_allocate_vars = {} + subpart_scalar_vars = {} + allocatable_var_set = set() + optional_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') + sname = var.get_prop_value('standard_name') + if (lname in subpart_allocate_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, {}" + raise ParseInternalError(errmsg.format(lname)) + # end if + else: + dims = var.get_dimensions() + if (dims is not None) and dims: + subpart_allocate_vars[lname] = (var, item, False) + allocatable_var_set.add(lname) + else: + subpart_scalar_vars[lname] = (var, item, False) + # end if + # end if + # end for + # All optional variables for the Schemes need to have an associated pointer array declared. + for ivar in self.optional_vars: + name = ivar.get_prop_value('local_name') + 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 + # 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 + subname = self.name + call_list = self.call_list.call_string() + outfile.write(Group.__subhead.format(subname=subname, args=call_list), + indent) + # Write out any use statements + if self._local_schemes: + modmax = max([len(s[0]) for s in self._local_schemes]) + else: + modmax = 0 + # end if + # Write out the scheme use statements + scheme_use = 'use {},{} only: {}' + for scheme in sorted(self._local_schemes): + smod = scheme[0] + sname = scheme[1] + slen = ' '*(modmax - len(smod)) + outfile.write(scheme_use.format(smod, slen, sname), indent+1) + # end for + # Look for any DDT types + call_vars = self.call_list.variable_list() + all_vars = ([x[0] for x in subpart_allocate_vars.values()] + + [x[0] for x in subpart_scalar_vars.values()]) + all_vars.extend(call_vars) + self._ddt_library.write_ddt_use_statements(all_vars, outfile, + indent+1, pad=modmax) + outfile.write('', 0) + # Write out dummy arguments + outfile.write('! Dummy arguments', indent+1) + msg = 'Variables for {}: ({})' + if (self.run_env.logger and + self.run_env.logger.isEnabledFor(logging.DEBUG)): + 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_allocate_vars or subpart_scalar_vars: + outfile.write('\n! Local Variables', indent+1) + # end if + # Scalars + 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_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 + # end for + # Pointer variables + outfile.write('', 0) + if pointer_var_set: + outfile.write('! Local pointer variables', indent+1) + # end if + for (name, kind, dim, vtype) in pointer_var_set: + 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: + raise ParseInternalError("Error object not supported") + else: + verrcode = self.call_list.find_variable(standard_name='ccpp_error_code') + if verrcode is not None: + errcode = verrcode.get_prop_value('local_name') + else: + errmsg = "No ccpp_error_code variable for group, {}" + raise CCPPError(errmsg.format(self.name)) + # end if + verrmsg = self.call_list.find_variable(standard_name='ccpp_error_message') + if verrmsg is not None: + errmsg = verrmsg.get_prop_value('local_name') + else: + errmsg = "No ccpp_error_message variable for group, {}" + 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}) + # 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({}({}))" + for lname in sorted(allocatable_var_set): + 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 + # 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: + timestep_var = svar.get_prop_value('persistence') + if group_type == timestep_var: + alloc_str = self.allocate_dim_str(dims, svar.context) + lname = svar.get_prop_value('local_name') + outfile.write(alloc_stmt.format(lname, alloc_str), + indent+1) + # end if (do not allocate in this phase) + # end if dims (do not allocate scalars) + # end for + # end if + # Write the scheme and subcycle calls + for item in self.parts: + 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 sorted(allocatable_var_set): + outfile.write('if (allocated({})) {} deallocate({})'.format(lname,' '*(20-len(lname)),lname), indent+1) + # end for + for lname in optional_var_set: + outfile.write('if (allocated({})) {} deallocate({})'.format(lname,' '*(20-len(lname)),lname), indent+1) + # end for + # Deallocate suite vars + if deallocate: + for svar in suite_vars.variable_list(): + dims = svar.get_dimensions() + if dims: + timestep_var = svar.get_prop_value('persistence') + if group_type == timestep_var: + lname = svar.get_prop_value('local_name') + outfile.write('deallocate({})'.format(lname), indent+1) + # end if + # end if (no else, do not deallocate scalars) + # end for + # end if + self._set_state.write(outfile, indent, {}) + # end if + outfile.write(Group.__subend.format(subname=subname), indent) + + @property + def suite(self): + """Return this Group's suite""" + return self.parent + + def suite_dicts(self): + """Return a list of this Group's Suite's dictionaries""" + return self.suite.suite_dicts() + +############################################################################### + +if __name__ == "__main__": + # First, run doctest + # pylint: disable=ungrouped-imports + import doctest + import sys + # pylint: enable=ungrouped-imports + fail, _ = doctest.testmod() + sys.exit(fail) +# end if diff --git a/test/utils/test_utils.F90 b/test/utils/test_utils.F90 index 088c347d..8179ce1d 100644 --- a/test/utils/test_utils.F90 +++ b/test/utils/test_utils.F90 @@ -34,6 +34,7 @@ logical function check_list(test_list, chk_list, list_desc, suite_name) end if write(errmsg(len_trim(errmsg)+1:), '(a,i0)') ', should be ', num_items write(6, *) trim(errmsg) + write(6,*) test_list errmsg = '' check_list = .false. end if From 20f0ea3a0c5d447b5a8bd4ffe48558db006d9bf5 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Thu, 23 Apr 2026 16:28:53 -0600 Subject: [PATCH 34/59] Saving progress, capgen test compiles --- scripts/ddt_library.py | 6 +- scripts/metavar.py | 12 +- scripts/suite_objects.py | 197 +- scripts/suite_objects.py.dom | 2354 ----------------- .../test_capgen_host_integration.F90 | 75 +- 5 files changed, 214 insertions(+), 2430 deletions(-) delete mode 100755 scripts/suite_objects.py.dom diff --git a/scripts/ddt_library.py b/scripts/ddt_library.py index bdd3e8be..90571978 100644 --- a/scripts/ddt_library.py +++ b/scripts/ddt_library.py @@ -101,14 +101,16 @@ def clone(self, subst_dict=None, remove_intent=False, # end if return clone_var - def call_string(self, var_dict, loop_vars=None): + def call_string(self, var_dict, loop_vars=None, dhdebug=False): """Return a legal call string of this VarDDT's local name sequence. """ # XXgoldyXX: Need to add dimensions to this call_str = super().get_prop_value('local_name') + if dhdebug: + print(f"DH DEBUG VarDDT.call_string: '{call_str}', '{self.field}'") if self.field is not None: call_str += '%' + self.field.call_string(var_dict, - loop_vars=loop_vars) + loop_vars=loop_vars, dhdebug=dhdebug) # end if return call_str diff --git a/scripts/metavar.py b/scripts/metavar.py index f096e7c7..6289b2c1 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -674,19 +674,29 @@ def call_dimstring(self, var_dicts=None, # end if return dimstr - def call_string(self, var_dicts, loop_vars=None): + def call_string(self, var_dicts, loop_vars=None, dhdebug=False): """Construct the actual argument string for this Var by translating standard names to local names. String includes array bounds unless loop_vars is None. if is not None, look there first for array bounds, even if usage requires a loop substitution. """ + # DH* + if loop_vars: + raise Exception(f"DH DEBUG: WE DO USE loop_vars in call_string! '{loop_vars}'") + # *DH if not isinstance(var_dicts, list): var_dicts = [var_dicts] # end if if loop_vars is None: call_str = self.get_prop_value('local_name') + if dhdebug: + print(f"DH call_string: '{call_str}'") # Look for dims in case this is an array selection variable + # DH* + # Will this work with something like ddt1(some_index)%nested_ddt(other_index)%myvar(dimstring)? + # Is local_name guaranteed to be just the member of the innermost DDT? + # *DH dind = call_str.find('(') if dind > 0: dimstr = call_str[dind+1:].rstrip()[:-1] diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 6c58745a..0f60ce08 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -53,6 +53,58 @@ 'scheme_files':'', 'suites':''}) +# DH* MOVE THIS ELSEWHERE +def split_dims_from_name(expr: str): + """Split the full dimension specifier of the innermost variable + of a potentially nested derived data type from the variable name: + + foo%bar(idx1)%baz(idx2, idx3(idx4), idx5a(idx6):idx5b(idx6)) + --> + foo%bar(idx1)%baz AND (idx2, idx3(idx4), idx5a(idx6):idx5b(idx6)) + + Walk the string from right to left, and find the outermost ( that + starts the final argument list at depth 0.""" + name = expr + depth = 0 + dimstring = None + for i in range(len(expr)-1, -1, -1): + c = expr[i] + if c == ')': + depth += 1 + elif c == '(': + depth -= 1 + if depth == 0: + # Found the outermost opening parenthesis + name = expr[:i] + dimstring = expr[i:] + break + if not dimstring: + return name, None + + # ... + dimstring = dimstring.lstrip('(').rstrip(')') + dims = [] + current = [] + depth = 0 + for c in dimstring: + if c == '(': + depth += 1 + current.append(c) + elif c == ')': + depth -= 1 + current.append(c) + elif c == ',' and depth == 0: + dims.append(''.join(current).strip()) + current = [] + else: + current.append(c) + # Add last piece + if current: + dims.append(''.join(current).strip()) + + return name, dims +# *DH + ############################################################################### def new_suite_object(item, context, parent, run_env, loop_count=0): ############################################################################### @@ -1336,30 +1388,89 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, raise Exception(f"No variable with standard name '{standard_name}' in cldicts") # end if # DH* - print(f"DH BBB GOT YOU! {standard_name} / {var.get_prop_value('local_name')} / / {dvar.get_prop_value('local_name')} % .") + print(f"DH BBB GOT YOU! {standard_name} / {var.get_prop_value('local_name')} / {dvar.get_prop_value('local_name')}") # *DH # Handle the dimensions... dimensions = dvar.get_dimensions() - dimstr = '' - if dimensions: - dimstr = dimstr + '(' - for cnt,dim in enumerate(dimensions): - if is_horizontal_dimension(dim): - if self.run_phase(): - if var_in_call_list and \ - self.find_variable(standard_name="horizontal_loop_extent"): - ldim = "ccpp_constant_one" - udim = "horizontal_loop_extent" + print(f"DH CCC '{dimensions}'") + #dimstr = '' + #if dimensions: + # dimstr = dimstr + '(' + # for cnt,dim in enumerate(dimensions): + # if is_horizontal_dimension(dim): + # if self.run_phase(): + # if var_in_call_list and \ + # self.find_variable(standard_name="horizontal_loop_extent"): + # ldim = "ccpp_constant_one" + # udim = "horizontal_loop_extent" + # else: + # ldim = "horizontal_loop_begin" + # udim = "horizontal_loop_end" + # # endif + # else: + # ldim = "ccpp_constant_one" + # udim = "horizontal_dimension" + # # endif + # # Get dimension for lower bound + # #lvar = self.__group.call_list.find_variable(standard_name=ldim, any_scope=True) + # for var_dict in cldicts: + # lvar = var_dict.find_variable(standard_name=ldim, any_scope=False) + # if lvar is not None: + # break + # # end if + # # end for + # if not lvar: + # raise Exception(f"No variable with standard name '{ldim}' in cldicts") + # # end if + # ldim_lname = lvar.get_prop_value('local_name') + # # Get dimension for upper bound + # for var_dict in cldicts: + # uvar = var_dict.find_variable(standard_name=udim, any_scope=False) + # if uvar is not None: + # break + # # end if + # # end for + # if not uvar: + # raise Exception(f"No variable with standard name '{udim}' in cldicts") + # # end if + # udim_lname = uvar.get_prop_value('local_name') + # dimstr = dimstr + ldim_lname + ':' + udim_lname + # else: + # dimstr = dimstr + ':' + # # end if + # if cnt < len(dimensions)-1: dimstr = dimstr+',' + # # end for + # dimstr = dimstr+')' + ## end if + + # Need to use local_name in Group's call list (self.__group.call_list), + # not the local_name in var. + if (dict_var): + (conditional, _) = dvar.conditional(cldicts) + if (has_transform): + lname = var.get_prop_value('local_name')+'_local' + else: + ddims = dvar.get_dimensions() + for i in range(len(ddims)): + dim = ddims[i] + if is_horizontal_dimension(dim): + if self.run_phase(): + if var_in_call_list and \ + self.find_variable(standard_name="horizontal_loop_extent"): + ldim = "ccpp_constant_one" + udim = "horizontal_loop_extent" + else: + ldim = "horizontal_loop_begin" + udim = "horizontal_loop_end" + # endif else: - ldim = "horizontal_loop_begin" - udim = "horizontal_loop_end" + ldim = "ccpp_constant_one" + udim = "horizontal_dimension" # endif else: - ldim = "ccpp_constant_one" - udim = "horizontal_dimension" - # endif + ldim, udim = dim.split(':') + # end if # Get dimension for lower bound - #lvar = self.__group.call_list.find_variable(standard_name=ldim, any_scope=True) for var_dict in cldicts: lvar = var_dict.find_variable(standard_name=ldim, any_scope=False) if lvar is not None: @@ -1381,23 +1492,40 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, raise Exception(f"No variable with standard name '{udim}' in cldicts") # end if udim_lname = uvar.get_prop_value('local_name') - dimstr = dimstr + ldim_lname + ':' + udim_lname + ddims[i] = ldim_lname + ':' + udim_lname + # end for + print(f"DH ddims: '{ddims}'") + lname, ldims = split_dims_from_name(dvar.call_string(search_dict)) + print(f"DH lname: '{lname}'") + print(f"DH ldims: '{ldims}'") + # This is where it gets tricky. We have a dimension specifier + # as part of the local name, and we have a dimstr. The latter + # contains the correct horizontal and vertical extents, the former + # does not - they can be ranges (containing ":") or indices + # (that is, the variable is a slice of another variable). + # We need to walk the lim specifier left to right and for each + # dimension we need to check if it is a range or not. If it is + # a range, we insert the first range from dimstr, then we remove + # this range from dimstr and move on to the next. + #if ldim: + # ldim_list = ldim.lstrip('(').rstrip(')').split(',') + # dimstr_list = dimstr.split(',') + # print(f"DH DEBUG lists: '{ldim_list}' and '{dimstr_list}'") + if ldims: + # Consistency check: + if len(ldims) < len(ddims): + raise Exception("Logic error in associate_optional_var: len({ldims}) < len({ddims})") + for i in range(len(ldims)): + if ':' in ldims[i]: + ldims[i] = ddims.pop(0) + # At the end ddims must be empty + if ddims: + raise Exception("Logic error in associate_optional_var: after filling ldims='{ldims}' from ddims, ddims is not empty: '{ddims}'") + dimstr = '(' + ','.join(ldims) + ')' + #raise Exception(f"UUU: {ldims}") else: - dimstr = dimstr + ':' - # end if - if cnt < len(dimensions)-1: dimstr = dimstr+',' - # end for - dimstr = dimstr+')' - # end if - - # Need to use local_name in Group's call list (self.__group.call_list), not - # the local_name in var. - if (dict_var): - (conditional, vars_needed) = dvar.conditional(cldicts) - if (has_transform): - lname = var.get_prop_value('local_name')+'_local' - else: - lname = dvar.call_string(search_dict)+dimstr + dimstr = '(' + ','.join(ddims) + ')' + lname += dimstr # end if lname_ptr = var.get_prop_value('local_name') + '_ptr' # Scheme has optional varaible, host has varaible defined as Conditional (Active). @@ -1412,7 +1540,7 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, # end if if lname_ptr and lname_ptr=="qv_ptr": - raise Exception(f"DH CCC: {lname_ptr} => {lname}") + print(f"DH TTT: {lname_ptr} => {lname}") # end def @@ -1556,6 +1684,7 @@ def add_var_transform(self, var, compat_obj): local_trans_var.get_prop_value('local_name'), lindices, rindices, compat_obj]) # end if + def write_var_transform(self, var, var_name, dummy, rindices, lindices, compat_obj, outfile, indent, forward, cldicts): """Write variable transformation needed to call this Scheme in . diff --git a/scripts/suite_objects.py.dom b/scripts/suite_objects.py.dom deleted file mode 100755 index b1a4bbb5..00000000 --- a/scripts/suite_objects.py.dom +++ /dev/null @@ -1,2354 +0,0 @@ -#!/usr/bin/env python3 -# - -"""Classes and methods to create a Fortran suite-implementation file -to implement calls to a set of suites for a given host model.""" - -# Python library imports -import logging -import re -import xml.etree.ElementTree as ET -# CCPP framework imports -from ccpp_state_machine import CCPP_STATE_MACH, RUN_PHASE_NAME -from code_block import CodeBlock -from constituents import ConstituentVarDict, CONST_OBJ_STDNAME -from framework_env import CCPPFrameworkEnv -from metavar import Var, VarDictionary, VarLoopSubst -from metavar import CCPP_CONSTANT_VARS, CCPP_LOOP_VAR_STDNAMES -from metavar import write_ptr_def -from parse_tools import ParseContext, ParseSource, context_string -from parse_tools import ParseInternalError, CCPPError -from parse_tools import init_log, set_log_to_null -from ddt_library import VarDDT -from var_props import is_horizontal_dimension, find_horizontal_dimension -from var_props import find_vertical_dimension -from var_props import VarCompatObj - -# pylint: disable=too-many-lines - -############################################################################### -# Module (global) variables -############################################################################### - -_OBJ_LOC_RE = re.compile(r"(0x[0-9A-Fa-f]+)>") -_BLANK_DIMS_RE = re.compile(r"[(][:](,:)*[)]$") - -# Source for internally generated variables. -_API_SOURCE_NAME = "CCPP_API" -# Use the constituent source type for consistency -_API_SUITE_VAR_NAME = ConstituentVarDict.constitutent_source_type() -_API_GROUP_VAR_NAME = "group" -_API_SCHEME_VAR_NAME = "scheme" -_API_LOCAL_VAR_NAME = "local" -_API_LOCAL_VAR_TYPES = [_API_LOCAL_VAR_NAME, _API_SUITE_VAR_NAME] -_API_CONTEXT = ParseContext(filename="ccpp_suite.py") -_API_SOURCE = ParseSource(_API_SOURCE_NAME, _API_SCHEME_VAR_NAME, _API_CONTEXT) -_API_LOCAL = ParseSource(_API_SOURCE_NAME, _API_LOCAL_VAR_NAME, _API_CONTEXT) -_API_TIMESPLIT_TAG = 'time_split' -_API_PROCESSSPLIT_TAG = 'process_split' -_API_LOGGING = init_log('ccpp_suite') -set_log_to_null(_API_LOGGING) -_API_DUMMY_RUN_ENV = CCPPFrameworkEnv(_API_LOGGING, - ndict={'host_files':'', - 'scheme_files':'', - 'suites':''}) - -############################################################################### -def new_suite_object(item, context, parent, run_env, loop_count=0): -############################################################################### - "'Factory' method to create the appropriate suite object from XML" - new_item = None - if item.tag == 'subcycle': - new_item = Subcycle(item, context, parent, run_env, loop_count=loop_count) - elif item.tag == 'scheme': - new_item = Scheme(item, context, parent, run_env) - elif item.tag == _API_TIMESPLIT_TAG: - new_item = TimeSplit(item, context, parent, run_env) - else: - emsg = "Unknown CCPP suite element type, '{}'" - raise CCPPError(emsg.format(item.tag)) - # end if - return new_item - -############################################################################### - -class CallList(VarDictionary): - """A simple class to hold a routine's call list (dummy arguments)""" - - def __init__(self, name, run_env, routine=None): - """Initialize this call list. - is the name of this dictionary. - is a pointer to the routine for which this is a call list - or None for a routine that is not a SuiteObject. - """ - self.__routine = routine - super().__init__(name, run_env) - - def add_vars(self, call_list, run_env, gen_unique=False, add_children=False): - """Add new variables from another CallList ()""" - for var in call_list.variable_list(): - stdname = var.get_prop_value('standard_name') - self.add_variable(var, run_env, gen_unique=gen_unique, adjust_intent=True, - exists_ok=True, add_children=add_children) - # end for - - def add_variable(self, newvar, run_env, exists_ok=False, gen_unique=False, - adjust_intent=False, add_children=False): - """Add as for VarDictionary but make sure that the variable - has an intent with the default being intent(in). - """ - # We really need an intent on a dummy argument - if newvar.get_prop_value("intent") is None: - subst_dict = {'intent' : 'in'} - oldvar = newvar - newvar = oldvar.clone(subst_dict, source_name=self.name, - source_type=_API_GROUP_VAR_NAME, - context=oldvar.context) - # end if - super().add_variable(newvar, run_env, exists_ok=exists_ok, - gen_unique=gen_unique, adjust_intent=adjust_intent, - add_children=add_children) - - 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 = "" - for var in self.variable_list(): - # Do not include constants - stdname = var.get_prop_value('standard_name') - if stdname not in CCPP_CONSTANT_VARS: - dimensions = var.get_dimensions() - # Find the dummy argument name - dummy = var.get_prop_value('local_name') - # Now, find the local variable name - if cldicts is not None: - for cldict in cldicts: - dvar = cldict.find_variable(standard_name=stdname, - any_scope=False) - host_var = False - if dvar is not None: - var_in_call_list = True - if dvar.is_ddt(): - if dvar.components: - var_in_call_list = False - # end if - else: - # If we get here, the variable is explicitly in the Group call_list, - # not a DDT component. No need to modify the dimensions in the Scheme - # call list, as these were handled in the Suite Cap call_list. - host_var = True - # end if - break - # end if - # end for - if dvar is None: - if subname is not None: - errmsg = "{}: ".format(subname) - else: - errmsg = "" - # end if - errmsg += "'{}', not found in call list for '{}'" - clnames = [x.name for x in cldicts] - raise CCPPError(errmsg.format(stdname, clnames)) - # end if - dimensions = dvar.get_dimensions() - lname = dvar.call_string(cldicts) - # DH* - abort_me = False - if True: # lname == "o3": - print(f"DH XXX: GOT YOU: {lname}") - print(f"DH XXX: {dimensions}") - abort_me = True - dimstr = None - # Optional variables in the caps are associated with - # local pointers of _ptr - if var.get_prop_value('optional'): - lname = dummy+'_ptr' - # end if - # Finally, handle the dimensions. - else: - print(f"DH AAA host_var? {host_var}") - if dimensions and not host_var: - dimstr = '(' - for cnt,dim in enumerate(dimensions): - print(f"DH ZZZ: {cnt} / {dim} / {is_horizontal_dimension(dim)}") - if is_horizontal_dimension(dim): - if self.routine.run_phase(): - if var_in_call_list and \ - self.find_variable(standard_name="horizontal_loop_extent"): - ldim = "ccpp_constant_one" - udim = "horizontal_loop_extent" - else: - ldim = "horizontal_loop_begin" - udim = "horizontal_loop_end" - # endif - else: - ldim = "ccpp_constant_one" - udim = "horizontal_dimension" - # endif - # Get dimension for lower bound - lvar = cldict.find_variable(standard_name=ldim, any_scope=True) - if not lvar: - raise Exception(f"No variable with standard name '{ldim}' in cldict") - # end if - ldim_lname = lvar.get_prop_value('local_name') - # Get dimension for upper bound - uvar = cldict.find_variable(standard_name=udim, any_scope=True) - if not uvar: - raise Exception(f"No variable with standard name '{udim}' in cldict") - # end if - udim_lname = uvar.get_prop_value('local_name') - dimstr = dimstr + ldim_lname + ':' + udim_lname - else: - dimstr = dimstr + ':' - # endif - if cnt < len(dimensions)-1: dimstr = dimstr+',' - # end for - dimstr = dimstr+')' - lname = lname + dimstr - # end if - # end if - # DH* - if abort_me: - print(f"DH YYY: {lname}") - if dimstr: - print(f"DH YYY: {dimstr}") - #raise Exception("XXX") - else: - cldict = None - aref = var.array_ref(local_name=dummy) - if aref is not None: - lname = aref.group(1) - else: - 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 - else: - use_dicts = [self] - # end if - run_phase = self.routine.run_phase() - # We only need dimensions for suite variables in run phase - need_dims = SuiteObject.is_suite_variable(dvar) and run_phase - vdims = var.call_dimstring(var_dicts=use_dicts, - explicit_dims=need_dims, - loop_subst=run_phase) - if _BLANK_DIMS_RE.match(vdims) is None: - lname = lname + vdims - # end if - # end if - if is_func_call: - arg_str += "{}{}={}".format(arg_sep, dummy, lname) - else: - arg_str += "{}{}".format(arg_sep, lname) - # end if - arg_sep = ", " - # end if - # end for - return arg_str - - @property - def routine(self): - """Return the routine for this call list (or None)""" - return self.__routine - -############################################################################### - -class SuiteObject(VarDictionary): - """Base class for all CCPP Suite objects (e.g., Scheme, Subcycle) - SuiteObjects have an internal dictionary for variables created for - execution of the SuiteObject. These variables will be allocated and - managed at the Group level (unless cross-group usage or persistence - requires handling at the Suite level). - SuiteObjects also have a call list which is a list of variables which - are passed to callable SuiteObjects (e.g., Scheme). - """ - - def __init__(self, name, context, parent, run_env, - active_call_list=False, variables=None, phase_type=None): - # pylint: disable=too-many-arguments - self.__name = name - self.__context = context - self.__parent = parent - self.__run_env = run_env - if active_call_list: - self.__call_list = CallList(name + '_call_list', run_env, - routine=self) - else: - self.__call_list = None - # end if - self.__parts = list() - self.__needs_horizontal = None - self.__phase_type = phase_type - # Initialize our dictionary - super().__init__(self.name, run_env, - variables=variables, parent_dict=parent) - - def declarations(self): - """Return a list of local variables to be declared in parent Group - or Suite. By default, this list is the object's embedded VarDictionary. - """ - return self.variable_list() - - def add_part(self, item, replace=False): - """Add an object (e.g., Scheme, Subcycle) to this SuiteObject. - if is True, replace in its current position in self. - """ - if replace: - if item in self.__parts: - index = self.__parts.index(item) - else: - emsg = 'Cannot replace {} in {}, not a member' - raise ParseInternalError(emsg.format(item.name, self.name)) - # end if - else: - if item in self.__parts: - emsg = 'Cannot add {} to {}, already a member' - raise ParseInternalError(emsg.format(item.name, self.name)) - # end if - index = len(self.__parts) - # end if - # Just add - self.__parts.insert(index, item) - item.reset_parent(self) - - def schemes(self): - """Return a flattened list of schemes for this SuiteObject""" - schemes = list() - for item in self.__parts: - schemes.extend(item.schemes()) - # end for - return schemes - - def reset_parent(self, new_parent): - """Reset the parent of this SuiteObject (which has been moved)""" - self.__parent = new_parent - - def phase(self): - """Return the CCPP state phase_type for this SuiteObject""" - trans = self.phase_type - if trans is None: - if self.parent is not None: - trans = self.parent.phase() - else: - trans = False - # end if - # end if - return trans - - def run_phase(self): - """Return True iff this SuiteObject is in a run phase group""" - return self.phase() == RUN_PHASE_NAME - - def timestep_phase(self): - '''Return True iff this SuiteObject is in a timestep initial or - timestep final phase group''' - phase = self.phase() - return (phase is not None) and ('timestep' in phase) - - def register_action(self, vaction): - """Register (i.e., save information for processing during write stage) - and return True or pass up to the parent of - . Return True if any level registers , False otherwise. - The base class will not register any action, it must be registered in - an override of this method. - """ - if self.parent is not None: - return self.parent.register_action(vaction) - # end if - return False - - @classmethod - def is_suite_variable(cls, var): - """Return True iff belongs to our Suite""" - return var and (var.source.ptype == _API_SUITE_VAR_NAME) - - def is_local_variable(self, var): - """Return the local variable matching if one is found belonging - to this object or any of its SuiteObject parents.""" - stdname = var.get_prop_value('standard_name') - lvar = None - obj = self - while (not lvar) and (obj is not None) and isinstance(obj, SuiteObject): - lvar = obj.find_variable(standard_name=stdname, any_scope=False, - search_call_list=False) - if not lvar: - obj = obj.parent - # end if - # end while - return lvar - - def add_call_list_variable(self, newvar, exists_ok=False, - gen_unique=False, subst_dict=None): - """Add to this SuiteObject's call_list. If this SuiteObject - does not have a call list, recursively try the SuiteObject's parent - If is not None, create a clone using that as a dictionary - of substitutions. - Do not add if it exists as a local variable. - Do not add if it is a suite variable""" - stdname = newvar.get_prop_value('standard_name') - if self.parent: - pvar = self.parent.find_variable(standard_name=stdname, - source_var=newvar, - any_scope=False) - else: - pvar = None - # end if - if SuiteObject.is_suite_variable(pvar): - pass # Do not add suite variable to a call list - elif self.is_local_variable(newvar): - pass # Do not add to call list, it is owned by a SuiteObject - elif self.call_list is not None: - if (stdname in CCPP_LOOP_VAR_STDNAMES) and (not self.run_phase()): - errmsg = 'Attempting to use loop variable {} in {} phase' - raise CCPPError(errmsg.format(stdname, self.phase())) - # end if - # Do we need a clone? - if isinstance(self, Group): - stype = _API_GROUP_VAR_NAME - else: - stype = None - # end if - if stype or subst_dict: - oldvar = newvar - if subst_dict is None: - subst_dict = {} - # end if - # Make sure that this variable has an intent - if ((oldvar.get_prop_value("intent") is None) and - ("intent" not in subst_dict)): - subst_dict["intent"] = "in" - # end if - newvar = oldvar.clone(subst_dict, source_name=self.name, - source_type=stype, context=self.context) - # end if - self.call_list.add_variable(newvar, self.run_env, - exists_ok=exists_ok, - gen_unique=gen_unique, - adjust_intent=True, - add_children=True) - # We need to make sure that this variable's dimensions are available - for vardim in newvar.get_dim_stdnames(include_constants=False): - # Unnamed dimensions are ok for allocatable variables - if vardim == '' and newvar.get_prop_value('allocatable'): - continue - elif vardim == '': - emsg = f"{self.name}: Cannot have unnamed/empty string dimension" - raise ParseInternalError(emsg) - # end if - dvar = self.find_variable(standard_name=vardim, - any_scope=True) - if dvar is None: - emsg = "{}: Could not find dimension {} in {}" - raise ParseInternalError(emsg.format(self.name, - vardim, stdname)) - # end if - elif self.parent is None: - errmsg = 'No call_list found for {}'.format(newvar) - raise ParseInternalError(errmsg) - elif pvar: - # Check for call list incompatibility - if pvar is not None: - compat, reason = pvar.compatible(newvar, self.run_env) - if not compat: - emsg = 'Attempt to add incompatible variable to call list:' - emsg += '\n{} from {} is not compatible with {} from {}' - nlreason = newvar.get_prop_value(reason) - plreason = pvar.get_prop_value(reason) - emsg += '\nreason = {} ({} != {})'.format(reason, - nlreason, - plreason) - nlname = newvar.get_prop_value('local_name') - plname = pvar.get_prop_value('local_name') - raise CCPPError(emsg.format(nlname, newvar.source.name, - plname, pvar.source.name)) - # end if - # end if (no else, variable already in call list) - else: - self.parent.add_call_list_variable(newvar, exists_ok=exists_ok, - gen_unique=gen_unique, - subst_dict=subst_dict) - # end if - - def add_variable_to_call_tree(self, var, vmatch=None, subst_dict=None): - """Add to 's call_list (or a parent if does not - have an active call_list). - If is not None, also add the loop substitution variables - which must be present. - If is not None, create a clone using that as a dictionary - of substitutions. - """ - found_dims = False - if var is not None: - self.add_call_list_variable(var, exists_ok=True, - gen_unique=True, subst_dict=subst_dict) - found_dims = True - # end if - if vmatch is not None: - svars = vmatch.has_subst(self, any_scope=True) - if svars is None: - found_dims = False - else: - found_dims = True - for svar in svars: - self.add_call_list_variable(svar, exists_ok=True) - # end for - # Register the action (probably at Group level) - self.register_action(vmatch) - # end if - # end if - return found_dims - - def horiz_dim_match(self, ndim, hdim, nloop_subst): - """Find a match between and , if they are both - horizontal dimensions. - If == , return . - If is not None and its required standard names exist - in our extended dictionary, return them. - Otherwise, return None. - NB: Loop substitutions are only allowed during the run phase but in - other phases, horizontal_dimension and horizontal_loop_extent - are the same. - """ - dim_match = None - nis_hdim = is_horizontal_dimension(ndim) - his_hdim = is_horizontal_dimension(hdim) - if nis_hdim and his_hdim: - if ndim == hdim: - dim_match = ndim - elif self.run_phase() and (nloop_subst is not None): - svars = nloop_subst.has_subst(self, any_scope=True) - match = svars is not None - if match: - if isinstance(self, Scheme): - obj = self.parent - else: - obj = self - # end if - for svar in svars: - obj.add_call_list_variable(svar, exists_ok=True) - # end for - dim_match = ':'.join(nloop_subst.required_stdnames) - # end if - elif not self.run_phase(): - if ((hdim == 'ccpp_constant_one:horizontal_dimension') and - (ndim == 'ccpp_constant_one:horizontal_loop_extent')): - dim_match = hdim - elif ((hdim == 'ccpp_constant_one:horizontal_dimension') and - (ndim == 'horizontal_loop_begin:horizontal_loop_end')): - dim_match = hdim - # end if (no else, there is no non-run-phase match) - # end if (no else, there is no match) - # end if (no else, there is no match) - return dim_match - - @staticmethod - def dim_match(need_dim, have_dim): - """Test whether matches . - If they match, return the matching dimension (which may be - modified by, e.g., a loop substitution). - If they do not match, return None. - """ - match = None - # First, try for all the marbles - if need_dim == have_dim: - match = need_dim - # end if - # Is one side missing a one start? - if not match: - ndims = need_dim.split(':') - hdims = have_dim.split(':') - if len(ndims) > len(hdims): - if ndims[0].lower == 'ccpp_constant_one': - ndims = ndims[1:] - elif hdims[0].lower == 'ccpp_constant_one': - hdims = hdims[1:] - # end if (no else) - # Last try - match = ndims == hdims - # end if - # end if - - return match - - def match_dimensions(self, need_dims, have_dims): - """Compare dimensions between and . - Return 6 items: - 1) Return True if all dims match. - If has a vertical dimension and does not - but all other dimensions match, return False but include the - missing dimension index as the third return value. - 2) Return modified, if necessary to - reflect the available limits. - 3) Return have_dims modified, if necessary to reflect - any loop substitutions. If no substitutions, return None - This is done so that the correct dimensions are used in the host cap. - 4) Return the name of the missing vertical index, or None - 5) Return a permutation array if the dimension ordering is - different (or None if the ordering is the same). Each element of the - permutation array is the index in for that dimension of - . - 6) Finally, return a 'reason' string. If match (first return value) is - False, this string will contain information about the reason for - the match failure. - >>> SuiteObject('foo', _API_CONTEXT, None, _API_DUMMY_RUN_ENV).match_dimensions(['horizontal_loop_extent'], ['horizontal_loop_extent']) - (True, ['horizontal_loop_extent'], ['horizontal_loop_extent'], None, '') - >>> SuiteObject('foo', _API_CONTEXT,None,_API_DUMMY_RUN_ENV,variables=[Var({'local_name':'beg','standard_name':'horizontal_loop_begin','units':'count','dimensions':'()','type':'integer'}, _API_LOCAL,_API_DUMMY_RUN_ENV),Var({'local_name':'end','standard_name':'horizontal_loop_end','units':'count','dimensions':'()','type':'integer'}, _API_LOCAL, _API_DUMMY_RUN_ENV)],active_call_list=True,phase_type='initialize').match_dimensions(['ccpp_constant_one:horizontal_loop_extent'], ['ccpp_constant_one:horizontal_dimension']) - (True, ['ccpp_constant_one:horizontal_dimension'], ['ccpp_constant_one:horizontal_dimension'], None, '') - >>> SuiteObject('foo', _API_CONTEXT,None,_API_DUMMY_RUN_ENV,variables=[Var({'local_name':'beg','standard_name':'horizontal_loop_begin','units':'count','dimensions':'()','type':'integer'}, _API_LOCAL, _API_DUMMY_RUN_ENV),Var({'local_name':'end','standard_name':'horizontal_loop_end','units':'count','dimensions':'()','type':'integer'}, _API_LOCAL, _API_DUMMY_RUN_ENV)],active_call_list=True,phase_type=RUN_PHASE_NAME).match_dimensions(['ccpp_constant_one:horizontal_loop_extent'], ['horizontal_loop_begin:horizontal_loop_end']) - (True, ['horizontal_loop_begin:horizontal_loop_end'], ['horizontal_loop_begin:horizontal_loop_end'], None, '') - >>> SuiteObject('foo', _API_CONTEXT,None,_API_DUMMY_RUN_ENV,variables=[Var({'local_name':'beg','standard_name':'horizontal_loop_begin','units':'count','dimensions':'()','type':'integer'}, _API_LOCAL, _API_DUMMY_RUN_ENV),Var({'local_name':'end','standard_name':'horizontal_loop_end','units':'count','dimensions':'()','type':'integer'}, _API_LOCAL, _API_DUMMY_RUN_ENV),Var({'local_name':'lev','standard_name':'vertical_layer_dimension','units':'count','dimensions':'()','type':'integer'}, _API_LOCAL, _API_DUMMY_RUN_ENV)],active_call_list=True,phase_type=RUN_PHASE_NAME).match_dimensions(['ccpp_constant_one:horizontal_loop_extent','ccpp_constant_one:vertical_layer_dimension'], ['horizontal_loop_begin:horizontal_loop_end','ccpp_constant_one:vertical_layer_dimension']) - (True, ['horizontal_loop_begin:horizontal_loop_end', 'ccpp_constant_one:vertical_layer_dimension'], ['horizontal_loop_begin:horizontal_loop_end', 'ccpp_constant_one:vertical_layer_dimension'], None, '') - >>> SuiteObject('foo', _API_CONTEXT,None,_API_DUMMY_RUN_ENV,variables=[Var({'local_name':'beg','standard_name':'horizontal_loop_begin','units':'count','dimensions':'()','type':'integer'}, _API_LOCAL, _API_DUMMY_RUN_ENV),Var({'local_name':'end','standard_name':'horizontal_loop_end','units':'count','dimensions':'()','type':'integer'}, _API_LOCAL, _API_DUMMY_RUN_ENV),Var({'local_name':'lev','standard_name':'vertical_layer_dimension','units':'count','dimensions':'()','type':'integer'}, _API_LOCAL, _API_DUMMY_RUN_ENV)],active_call_list=True,phase_type=RUN_PHASE_NAME).match_dimensions(['ccpp_constant_one:horizontal_loop_extent','ccpp_constant_one:vertical_layer_dimension'], ['ccpp_constant_one:vertical_layer_dimension','horizontal_loop_begin:horizontal_loop_end']) - (True, ['horizontal_loop_begin:horizontal_loop_end', 'ccpp_constant_one:vertical_layer_dimension'], ['ccpp_constant_one:vertical_layer_dimension', 'horizontal_loop_begin:horizontal_loop_end'], [1, 0], '') - """ - new_need_dims = [] - new_have_dims = list(have_dims) - perm = [] - match = True - reason = '' - nlen = len(need_dims) - hlen = len(have_dims) - _, nvdim_index = find_vertical_dimension(need_dims) - _, hvdim_index = find_vertical_dimension(have_dims) - _, nhdim_index = find_horizontal_dimension(need_dims) - _, hhdim_index = find_horizontal_dimension(have_dims) - if hhdim_index < 0 <= nhdim_index: - match = False - nlen = 0 # To skip logic below - hlen = 0 # To skip logic below - reason = '{hname}{hctx} is missing a horizontal dimension ' - reason += 'required by {nname}{nctx}' - # end if - for nindex in range(nlen): - neddim = need_dims[nindex] - if nindex == nhdim_index: - # Look for a horizontal dimension match - vmatch = VarDictionary.loop_var_match(neddim) - hmatch = self.horiz_dim_match(neddim, have_dims[hhdim_index], - vmatch) - if hmatch: - perm.append(hhdim_index) - new_need_dims.append(hmatch) - new_have_dims[hhdim_index] = hmatch - found_ndim = True - else: - found_ndim = False - # end if - else: - # Find the first dimension in have_dims that matches neddim - found_ndim = False - if nvdim_index < 0 <= hvdim_index: - skip = hvdim_index - else: - skip = -1 - # end if - hdim_indices = [x for x in range(hlen) - if (x not in perm) and (x != skip)] - for hindex in hdim_indices: - if (hindex != hvdim_index) or (nvdim_index >= 0): - hmatch = self.dim_match(neddim, have_dims[hindex]) - if hmatch: - perm.append(hindex) - new_need_dims.append(hmatch) - new_have_dims[hindex] = hmatch - found_ndim = True - break - # end if - # end if - # end if - # end for - if not found_ndim: - match = False - reason = 'Could not find dimension, ' + neddim + ', in ' - reason += '{hname}{hctx}. Needed by {nname}{nctx}' - break - # end if (no else, we are still okay) - # end for - perm_test = list(range(hlen)) - # If no permutation is found, reset to None - if perm == perm_test: - perm = None - elif (not match): - perm = None - # end if (else, return perm as is) - if new_have_dims == have_dims: - have_dims = None # Do not make any substitutions - # end if - return match, new_need_dims, new_have_dims, perm, reason - - def find_variable(self, standard_name=None, source_var=None, - any_scope=True, clone=None, - search_call_list=False, loop_subst=False, - check_components=True): - """Find a matching variable to , create a local clone (if - is True), or return None. - First search the SuiteObject's internal dictionary, then its - call list (unless is True, then any parent - dictionary (if is True). - can be a Var object or a standard_name string. - is not used by this version of . - """ - # First, search our local dictionary - if standard_name is None: - if source_var is None: - emsg = "One of or must be passed." - raise ParseInternalError(emsg) - # end if - standard_name = source_var.get_prop_value('standard_name') - elif source_var is not None: - stest = source_var.get_prop_value('standard_name') - if stest != standard_name: - emsg = (" and must match if " + - "both are passed.") - raise ParseInternalError(emsg) - # end if - # end if - scl = search_call_list - stdname = standard_name - # Don't clone yet, might find the variable further down - found_var = super().find_variable(standard_name=stdname, - source_var=source_var, - any_scope=False, clone=None, - search_call_list=scl, - loop_subst=loop_subst, - check_components=check_components) - if (not found_var) and (self.call_list is not None) and scl: - # Don't clone yet, might find the variable further down - found_var = self.call_list.find_variable(standard_name=stdname, - source_var=source_var, - any_scope=False, - clone=None, - search_call_list=scl, - loop_subst=loop_subst, - check_components=check_components) - # end if - loop_okay = VarDictionary.loop_var_okay(stdname, self.run_phase()) - if not loop_okay: - loop_subst = False - # end if - if (found_var is None) and any_scope and (self.parent is not None): - # We do not have the variable, look to parents. - found_var = self.parent.find_variable(standard_name=stdname, - source_var=source_var, - any_scope=any_scope, - clone=clone, - search_call_list=scl, - loop_subst=loop_subst, - check_components=check_components) - # end if - return found_var - - def match_variable(self, var, run_env, host_dict): - """Try to find a source for in this SuiteObject's dictionary - tree. Several items are returned: - found_var: True if a match was found - vert_dim: The vertical dimension in , or None - call_dims: How this variable should be called (or None if no match) - perm: Permutation (XXgoldyXX: Not yet implemented) - """ - vstdname = var.get_prop_value('standard_name') - vdims = var.get_dimensions() - local_var = None - if (not vdims) and self.run_phase(): - vmatch = VarDictionary.loop_var_match(vstdname) - else: - vmatch = None - # end if - - found_var = False - new_vdims = list() - var_vdim = var.has_vertical_dimension(dims=vdims) - compat_obj = None - dict_var = None - scheme_var = None - if var.get_prop_value('type') == 'ccpp_constituent_properties_t': - if self.phase() == 'register': - found_var = True - new_vdims = [':'] - return found_var, local_var, dict_var, var_vdim, new_vdims, compat_obj, scheme_var - else: - errmsg = "Variables of type ccpp_constituent_properties_t only allowed in register phase: " - sname = var.get_prop_value('standard_name') - errmsg += f"'{sname}' found in {self.phase()} phase" - raise CCPPError(errmsg) - # end if - # end if - - # Is this variable a member of a DDT? If so, look for the parent DDT - # and add that instead - # PEVERWHEE - note - currently not doing this for constituents - constituent = var.is_constituent() - if not constituent: - host_var = host_dict.find_variable(source_var=var, any_scope=False) - if host_var: - if isinstance(host_var, VarDDT): - local_var = host_var - scheme_var = var # Save Scheme for transform - var = host_var.var - vdims = [] - # end if - # end if - # end if - # Does this variable exist in the calling tree? - dict_var = self.find_variable(source_var=var, any_scope=True) - if dict_var is None: - # No existing variable but add loop var match to call tree - found_var = self.parent.add_variable_to_call_tree(dict_var, - vmatch=vmatch) - new_vdims = vdims - elif dict_var.source.ptype in _API_LOCAL_VAR_TYPES: - # We cannot change the dimensions of locally-declared variables - # Using a loop substitution is invalid because the loop variable - # value has not yet been set. - # Therefore, we have to use the declaration dimensions in the call. - found_var = True - new_vdims = dict_var.get_dimensions() - else: - # Check dimensions - dict_dims = dict_var.get_dimensions() - if vdims and not constituent: - args = self.parent.match_dimensions(vdims, dict_dims) - match, new_vdims, new_dict_dims, perm, err = args - if perm is not None: - errmsg = "Permuted indices are not yet supported" - lname = var.get_prop_value('local_name') - dstr = ', '.join(vdims) - ctx = context_string(var.context) - errmsg += ", var = {}({}){}".format(lname, dstr, ctx) - raise CCPPError(errmsg) - # end if - else: - 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) - # Add the variable to the parent call tree - if dict_dims == new_dict_dims: - sdict = {} - else: - sdict = {'dimensions':new_dict_dims} - # end if - # Add any DDT components from the host dictionary version of the variable - if not constituent: - for dict_var_component in dict_var.components: - var.add_component(dict_var_component) - # end for - # end if - found_var = self.parent.add_variable_to_call_tree(var, - subst_dict=sdict) - if not match: - found_var = False - nctx = context_string(var.context) - nname = var.get_prop_value('local_name') - hctx = context_string(dict_var.context) - hname = dict_var.get_prop_value('local_name') - raise CCPPError(err.format(nname=nname, nctx=nctx, - hname=hname, hctx=hctx)) - # 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) - if scheme_var is not None: - compat_obj = var.compatible(scheme_var, run_env) - else: - compat_obj = var.compatible(dict_var, run_env) - # end if - # end if - return found_var, dict_var, local_var, var_vdim, new_vdims, compat_obj, scheme_var - - def in_process_split(self): - """Find out if we are in a process-split region""" - proc_split = False - obj = self - while obj is not None: - if isinstance(obj, ProcessSplit): - proc_split = True - break - # end if - if isinstance(obj, TimeSplit): - break - # end if (other object types do not change status) - obj = obj.parent - # end while - return proc_split - - def part(self, index, error=True): - """Return one of this SuiteObject's parts raise an exception, or, - if is False, just return None""" - plen = len(self.__parts) - if (0 <= index < plen) or (abs(index) <= plen): - return self.__parts[index] - # end if - if error: - errmsg = 'No part {} in {} {}'.format(index, - self.__class__.__name__, - self.name) - raise ParseInternalError(errmsg) - # end if - return None - - def has_item(self, item_name): - """Return True iff item, , is already in this SuiteObject""" - has = False - for item in self.__parts: - if item.name == item_name: - has = True - else: - has = item.has_item(item_name) - # end if - if has: - break - # end if - # end for - return has - - @property - def name(self): - """Return the name of the element""" - return self.__name - - @name.setter - def name(self, value): - """Set the name of the element if it has not been set""" - if self.__name is None: - self.__name = value - else: - errmsg = 'Attempt to change name of {} to {}' - raise ParseInternalError(errmsg.format(self, value)) - # end if - - @property - def parent(self): - """This SuiteObject's parent (or none)""" - return self.__parent - - @property - def call_list(self): - """Return the SuiteObject's call_list""" - return self.__call_list - - @property - def phase_type(self): - """Return the phase_type of this suite_object""" - return self.__phase_type - - @property - def parts(self): - """Return a copy the component parts of this SuiteObject. - Returning a copy allows for the part list to be changed during - processing of the return value""" - return self.__parts[:] - - @property - def context(self): - """Return the context of this SuiteObject""" - return self.__context - - @property - def run_env(self): - """Return the CCPPFrameworkEnv runtime object for this SuiteObject""" - return self.__run_env - - def __repr__(self): - """Create a unique readable string for this Object""" - so_repr = super().__repr__() - olmatch = _OBJ_LOC_RE.search(so_repr) - if olmatch is not None: - loc = ' at {}'.format(olmatch.group(1)) - else: - loc = "" - # end if - return '<{} {}{}>'.format(self.__class__.__name__, self.name, loc) - - def __format__(self, spec): - """Return a string representing the SuiteObject, including its children. - is used between subitems. - is the indent level for multi-line output. - """ - if spec: - sep = spec[0] - else: - sep = '\n' - # end if - try: - ind_level = int(spec[1:]) - except (ValueError, IndexError): - ind_level = 0 - # end try - if sep == '\n': - indent = " " - else: - indent = "" - # end if - if self.name == self.__class__.__name__: - # This object does not have separate name - nstr = self.name - else: - nstr = "{}: {}".format(self.__class__.__name__, self.name) - # end if - output = "{}<{}>".format(indent*ind_level, nstr) - subspec = "{}{}".format(sep, ind_level + 1) - substr = "{o}{s}{p:" + subspec + "}" - subout = "" - for part in self.parts: - subout = substr.format(o=subout, s=sep, p=part) - # end for - if subout: - output = "{}{}{}{}".format(output, subout, sep, - indent*ind_level, - self.__class__.__name__) - else: - output = "{}".format(output, self.__class__.__name__) - # end if - return output - -############################################################################### - -class Scheme(SuiteObject): - """A single scheme in a suite (e.g., init method)""" - - def __init__(self, scheme_xml, context, parent, run_env): - """Initialize this physics Scheme""" - name = scheme_xml.text - self.__subroutine_name = None - self.__context = context - self.__version = scheme_xml.get('version', None) - self.__lib = scheme_xml.get('lib', None) - self.__has_vertical_dimension = False - self.__group = None - self.__forward_transforms = list() - self.__reverse_transforms = list() - self._has_run_phase = True - self.__optional_vars = list() - super().__init__(name, context, parent, run_env, active_call_list=True) - - def update_group_call_list_variable(self, var): - """If is in our group's call list, update its intent. - Add to our group's call list unless: - - is in our group's call list - - is in our group's dictionary, - - is a suite variable""" - stdname = var.get_prop_value('standard_name') - my_group = self.__group - gvar = my_group.call_list.find_variable(standard_name=stdname, - any_scope=False) - if gvar: - gvar.adjust_intent(var) - else: - gvar = my_group.find_variable(standard_name=stdname, - any_scope=False) - if gvar is None: - # Check for suite variable - gvar = my_group.find_variable(standard_name=stdname, - any_scope=True) - if gvar and (not SuiteObject.is_suite_variable(gvar)): - gvar = None - # end if - if gvar is None: - my_group.add_call_list_variable(var, gen_unique=True) - # end if - # end if - - def is_local_variable(self, var): - """Return None as we never consider to be in our local - dictionary. - This is an override of the SuiteObject version""" - return None - - def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): - """Analyze the scheme's interface to prepare for writing""" - self.__group = group - my_header = None - if self.name in scheme_library: - func = scheme_library[self.name] - if phase in func: - my_header = func[phase] - self.__subroutine_name = my_header.title - else: - self._has_run_phase = False - return set() - # end if - else: - estr = 'No schemes found for {}' - raise ParseInternalError(estr.format(self.name), - context=self.__context) - # end if - if my_header is None: - estr = 'No {} header found for scheme, {}' - raise ParseInternalError(estr.format(phase, self.name), - context=self.__context) - # end if - if my_header.module is None: - estr = 'No module found for subroutine, {}' - raise ParseInternalError(estr.format(self.subroutine_name), - context=self.__context) - # end if - scheme_mods = set() - scheme_mods.add((my_header.module, self.subroutine_name)) - for var in my_header.variable_list(): - vstdname = var.get_prop_value('standard_name') - def_val = var.get_prop_value('default_value') - vdims = var.get_dimensions() - vintent = var.get_prop_value('intent') - args = self.match_variable(var, self.run_env, host_dict) - found, dict_var, local_var, var_vdim, new_dims, compat_obj, scheme_var = args - if dict_var: - if dict_var.is_ddt(): - subst_dict = {'intent':'inout'} - else: - subst_dict = {'intent': var.get_prop_value('intent')} - # end if - clone = dict_var.clone(subst_dict) - dict_var = clone - # end if - if found: - # Hack to get the missing dimensions promoted to the right place - # Add variable allocation checks for group, suite and host variables - if dict_var: - if local_var: - self.handle_downstream_variables(local_var) - else: - self.handle_downstream_variables(dict_var) - # end if - # end if - # We have a match, make sure var is in call list - if new_dims == vdims: - self.add_call_list_variable(var, exists_ok=True, gen_unique=True) - if dict_var: - self.update_group_call_list_variable(dict_var) - else: - self.update_group_call_list_variable(var) - # end if - else: - subst_dict = {'dimensions':new_dims} - clone = var.clone(subst_dict) - self.add_call_list_variable(clone, exists_ok=True) - if dict_var: - clone = dict_var.clone(subst_dict) - self.update_group_call_list_variable(clone) - else: - self.update_group_call_list_variable(clone) - # end if - # end if - else: - if vintent == 'out': - if self.__group is None: - errmsg = 'Group not defined for {}'.format(self.name) - raise ParseInternalError(errmsg) - # end if - # The Group will manage this variable - if dict_var: - self.__group.manage_variable(dict_var) - else: - self.__group.manage_variable(var) - # end if - self.add_call_list_variable(var) - elif def_val and (vintent != 'out'): - if self.__group is None: - errmsg = 'Group not defined for {}'.format(self.name) - raise ParseInternalError(errmsg) - # end if - # The Group will manage this variable - if dict_var: - self.__group.manage_variable(dict_var) - else: - self.__group.manage_variable(var) - # end if - # We still need it in our call list (the group uses a clone) - self.add_call_list_variable(var) - else: - errmsg = 'Input argument for {}, {}, not found.' - if self.find_variable(source_var=var) is not None: - # The variable exists, maybe it is dim mismatch - lname = var.get_prop_value('local_name') - emsg = '\nCheck for dimension mismatch in {}' - errmsg += emsg.format(lname) - # end if - if ((not self.run_phase()) and - (vstdname in CCPP_LOOP_VAR_STDNAMES)): - emsg = '\nLoop variables not allowed in {} phase.' - errmsg += emsg.format(self.phase()) - # end if - raise CCPPError(errmsg.format(self.subroutine_name, - vstdname)) - # 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): - if scheme_var is not None: - #print("SWALES scheme_var for transform",scheme_var.get_prop_value('local_name')) - self.add_var_transform(scheme_var, compat_obj) - else: - self.add_var_transform(var, compat_obj) - # end if - has_transform = True - # end if - - # Is this a conditionally allocated variable? - # If so, declare local pointer variable. This is needed to - # pass inactive (not present) status through the Scheme call_lists. - if var.get_prop_value('optional'): - #if dict_var: - self.add_optional_var(dict_var, var, has_transform) - # end if - # end if - - # end for - return scheme_mods - - def add_optional_var(self, dict_var, var, has_transform): - """Add local pointer needed for optional variable(s) in Group Cap. Also, - add any host variables from active condition that are needed to associate - the local pointer correctly.""" - - lname = var.get_prop_value('local_name') - sname = var.get_prop_value('standard_name') - lname_ptr = lname + '_ptr' - newvar_ptr = var.clone(lname_ptr) - # Group write phase needs new pointer variable for declaration step. - self.__group.optional_vars.append(newvar_ptr) - # Scheme write phase needs more info for transformations/local-pointer assignments. - self.__optional_vars.append([dict_var, var, has_transform]) - return - # end def - - def handle_downstream_variables(self, var): - """Ensure all dimension and optional variable arguments are available""" - # Get the basic attributes that decide whether we need - # to check the variable when we write the group - standard_name = var.get_prop_value('standard_name') - dimensions = var.get_dimensions() - active = var.get_prop_value('active') - var_dicts = [ self.__group.call_list ] + self.__group.suite_dicts() - - # If the variable isn't active, skip it - if active.lower() =='.false.': - return - # Also, if the variable is one of the CCPP error handling messages, skip it - # since it is defined as intent(out) and we can't do meaningful checks on it - elif standard_name == 'ccpp_error_code' or standard_name == 'ccpp_error_message': - return - # To perform allocation checks, we need to know all variables - # that are part of the 'active' attribute conditional and add - # it to the group's call list. - else: - (_, vars_needed) = var.conditional(var_dicts) - for var_needed in vars_needed: - self.update_group_call_list_variable(var_needed) - - # For arrays, we need to get information on the dimensions and add it to - # the group's call list so that we can test for the correct size later on - if dimensions: - for dim in dimensions: - if not ':' in dim: - dim_var = self.find_variable(standard_name=dim) - if not dim_var: - # To allow for numerical dimensions in metadata. - if not dim.isnumeric(): - raise Exception(f"No dimension with standard name '{dim}'") - # end if - else: - self.update_group_call_list_variable(dim_var) - # end if - else: - (ldim, udim) = dim.split(":") - ldim_var = self.find_variable(standard_name=ldim) - if not ldim_var: - # To allow for numerical dimensions in metadata. - if not ldim.isnumeric(): - raise Exception(f"No dimension with standard name '{ldim}'") - # end if - # end if - self.update_group_call_list_variable(ldim_var) - udim_var = self.find_variable(standard_name=udim) - if not udim_var: - # To allow for numerical dimensions in metadata. - if not udim.isnumeric(): - raise Exception(f"No dimension with standard name '{udim}'") - # end if - else: - self.update_group_call_list_variable(udim_var) - # end if - - def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, outfile): - """Write local pointer association for optional variable.""" - # Use the local name from the Scheme call list, append "_ptr" suffix. - standard_name = var.get_prop_value('standard_name') - dvar = self.__group.call_list.find_variable(standard_name=standard_name, any_scope=False) - search_dict = self.__group.call_list - if dvar: - var_in_call_list = True - # If we find a call_list variable that is a DDT, check to see if it has components. - # Variables that are components of a DDT are NOT part of the call_list. - if dvar.is_ddt(): - if dvar.components: - var_in_call_list = False - # end if - # end if - else: - var_in_call_list = False - # If it is not in the call list, try to find it - # in the local variables of this group subroutine. - dvar = self.__group.find_variable(standard_name=standard_name, any_scope=False) - if not dvar: - # This variable is handled by the group - # and is declared as a module variable - for var_dict in self.__group.suite_dicts(): - dvar = var_dict.find_variable(standard_name=standard_name, any_scope=False) - if dvar: - search_dict = var_dict - break - # end if - # end for - # end if - # end if - if not dvar: - raise Exception(f"No variable with standard name '{standard_name}' in cldicts") - # end if - # Handle the dimensions... - dimensions = dvar.get_dimensions() - dimstr = '' - if dimensions: - dimstr = dimstr + '(' - for cnt,dim in enumerate(dimensions): - if is_horizontal_dimension(dim): - if self.run_phase(): - if var_in_call_list and \ - self.find_variable(standard_name="horizontal_loop_extent"): - ldim = "ccpp_constant_one" - udim = "horizontal_loop_extent" - else: - ldim = "horizontal_loop_begin" - udim = "horizontal_loop_end" - # endif - else: - ldim = "ccpp_constant_one" - udim = "horizontal_dimension" - # endif - # Get dimension for lower bound - #lvar = self.__group.call_list.find_variable(standard_name=ldim, any_scope=True) - for var_dict in cldicts: - lvar = var_dict.find_variable(standard_name=ldim, any_scope=False) - if lvar is not None: - break - # end if - # end for - if not lvar: - raise Exception(f"No variable with standard name '{ldim}' in cldicts") - # end if - ldim_lname = lvar.get_prop_value('local_name') - # Get dimension for upper bound - for var_dict in cldicts: - uvar = var_dict.find_variable(standard_name=udim, any_scope=False) - if uvar is not None: - break - # end if - # end for - if not uvar: - raise Exception(f"No variable with standard name '{udim}' in cldicts") - # end if - udim_lname = uvar.get_prop_value('local_name') - dimstr = dimstr + ldim_lname + ':' + udim_lname - else: - dimstr = dimstr + ':' - # end if - if cnt < len(dimensions)-1: dimstr = dimstr+',' - # end for - dimstr = dimstr+')' - # end if - - # Need to use local_name in Group's call list (self.__group.call_list), not - # the local_name in var. - if (dict_var): - (conditional, vars_needed) = dvar.conditional(cldicts) - if (has_transform): - lname = var.get_prop_value('local_name')+'_local' - else: - lname = dvar.call_string(search_dict)+dimstr - # end if - lname_ptr = var.get_prop_value('local_name') + '_ptr' - # Scheme has optional varaible, host has varaible defined as Conditional (Active). - if conditional != '.true.': - outfile.write(f"if {conditional} then", indent) - outfile.write(f"{lname_ptr} => {lname}", indent+1) - outfile.write(f"end if", indent) - # Scheme has optional varaible, host has varaible defined as Mandatory. - else: - outfile.write(f"{lname_ptr} => {lname}", indent) - # end if - # end if - # end def - - def nullify_optional_var(self, dict_var, var, has_transform, cldicts, indent, outfile): - """Write local pointer nullification for optional variable.""" - - standard_name = var.get_prop_value('standard_name') - dvar = self.__group.call_list.find_variable(standard_name=standard_name, any_scope=False) - search_dict = self.__group.call_list - if dvar: - var_in_call_list = True - else: - var_in_call_list = False - # If it is not in the call list, try to find it - # in the local variables of this group subroutine. - dvar = self.__group.find_variable(standard_name=standard_name, any_scope=False) - if not dvar: - # This variable is handled by the group - # and is declared as a module variable - for var_dict in self.__group.suite_dicts(): - dvar = var_dict.find_variable(standard_name=standard_name, any_scope=False) - if dvar: - search_dict = var_dict - break - # end if - # end for - # end if - # end if - - # Need to use local_name in Group's call list (self.__group.call_list), not - # the local_name in var. - if (dict_var): - (conditional, vars_needed) = dvar.conditional(cldicts) - if (has_transform): - lname = dvar.get_prop_value('local_name')+'_local' - else: - lname = dvar.get_prop_value('local_name') - # end if - lname_ptr = var.get_prop_value('local_name') + '_ptr' - # Scheme has optional varaible, host has varaible defined as Conditional (Active). - if conditional != '.true.': - outfile.write(f"if {conditional} then", indent) - outfile.write(f"nullify({lname_ptr})", indent+1) - outfile.write(f"end if", indent) - # Scheme has optional varaible, host has varaible defined as Mandatory. - else: - outfile.write(f"nullify({lname_ptr})", indent) - # end if - # end if - # end def - - def add_var_transform(self, var, compat_obj): - """Register any variable transformation needed by for this Scheme. - For any transformation identified in , create dummy variable - from to perform the transformation. Determine the indices needed - for the transform and save for use during write stage""" - - # 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() - rindices = [':']*var.get_rank() - - # If needed, modify vertical dimension for vertical orientation flipping - var_vdim, vdim = find_vertical_dimension(var.get_dimensions()) - if vdim >= 0: - vdims = var_vdim.split(':') - vdim_name = vdims[-1] - group_vvar = self.__group.call_list.find_variable(vdim_name) - if group_vvar is None: - raise CCPPError(f"add_var_transform: Cannot find dimension variable, {vdim_name}") - # end if - vname = group_vvar.get_prop_value('local_name') - if len(vdims) == 2: - sdim_name = vdims[0] - group_vvar = self.find_variable(sdim_name) - if group_vvar is None: - raise CCPPError(f"add_var_transform: Cannot find dimension variable, {sdim_name}") - # end if - sname = group_vvar.get_prop_value('local_name') - else: - sname = '1' - # end if - lindices[vdim] = sname+':'+vname - if compat_obj.has_vert_transforms: - rindices[vdim] = vname+':'+sname+':-1' - else: - rindices[vdim] = sname+':'+vname - # end if - # end if - - # If needed, modify horizontal dimension for loop substitution. - # NOT YET IMPLEMENTED - hdim = find_horizontal_dimension(var.get_dimensions()) - #if compat_obj.has_dim_transforms: - print("SWALES ",hdim,var.get_prop_value('local_name')) - - # Register any reverse (pre-Scheme) transforms. Also, save local_name used in - # transform (used in write stage). - if (var.get_prop_value('intent') != 'out'): - 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, - self.__subroutine_name)) - 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, - self.__subroutine_name)) - self.__forward_transforms.append([var.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, var_name, dummy, rindices, lindices, compat_obj, - outfile, indent, forward, cldicts): - """Write variable transformation needed to call this Scheme in . - 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). - are the LHS indices of for forward transforms (after Scheme). - are the RHS indices of for forward transforms (after Scheme). - """ - # - # Write reverse (pre-Scheme) transform. - # - if not forward: - # dummy(lindices) = var(rindices) - stmt = compat_obj.reverse_transform(lvar_lname=dummy, - rvar_lname=var_name, - lvar_indices=lindices, - rvar_indices=rindices) - # - # Write forward (post-Scheme) transform. - # - else: - # var(lindices) = dummy(rindices) - stmt = compat_obj.forward_transform(lvar_lname=var_name, - rvar_lname=dummy, - lvar_indices=rindices, - rvar_indices=lindices) - # end if - - (conditional, vars_needed) = var.conditional(cldicts) - if conditional != '.true.': - outfile.write(f"if {conditional} then", indent) - outfile.write(stmt, indent+1) - outfile.write(f"end if", indent) - # Scheme has optional varaible, host has varaible defined as Mandatory. - else: - outfile.write(stmt, indent) - # end if - - def write(self, outfile, errcode, errmsg, indent): - # Unused arguments are for consistent write interface - # pylint: disable=unused-argument - """Write code to call this Scheme to """ - # Dictionaries to try are our group, the group's call list, - # or our module - cldicts = [self.__group, self.__group.call_list] - cldicts.extend(self.__group.suite_dicts()) - my_args = self.call_list.call_string(cldicts=cldicts, - is_func_call=True, - subname=self.subroutine_name, - sub_lname_list = self.__reverse_transforms) - # - outfile.write('', indent) - outfile.write('if ({} == 0) then'.format(errcode), indent) - # - # Write any reverse (pre-Scheme) transforms. - if len(self.__reverse_transforms) > 0: - outfile.comment('Compute reverse (pre-scheme) transforms', indent+1) - # end if - 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.call_string(self.__group.call_list) - tstmt = self.write_var_transform(lvar, lvar_lname, dummy, rindices, lindices, compat_obj, outfile, indent+1, False, cldicts) - # end for - outfile.write('',indent+1) - # - # Associate any conditionally allocated variables. - # - if self.__optional_vars: - outfile.write('! Associate conditional variables', indent+1) - # end if - for (dict_var, var, has_transform) in self.__optional_vars: - tstmt = self.associate_optional_var(dict_var, var, has_transform, cldicts, indent+1, outfile) - # end for - # - # Write the scheme call. - # - if self._has_run_phase: - stmt = 'call {}({})' - outfile.write('',indent+1) - outfile.write('! Call scheme', indent+1) - outfile.write(stmt.format(self.subroutine_name, my_args), indent+1) - outfile.write('',indent+1) - # end if - - # - # Nullify any local pointers. - # - if self.__optional_vars: - outfile.write('! Nullify conditional variables', indent+1) - # end if - for (dict_var, var, has_transform) in self.__optional_vars: - tstmt = self.nullify_optional_var(dict_var, var, has_transform, cldicts, indent+1, outfile) - # - # Write any forward (post-Scheme) transforms. - # - if len(self.__forward_transforms) > 0: - outfile.comment('Compute forward (post-scheme) transforms', indent+1) - # end if - 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.call_string(self.__group.call_list) - tstmt = self.write_var_transform(lvar, lvar_lname, dummy, rindices, lindices, compat_obj, outfile, indent+1, True, cldicts) - # end for - outfile.write('', indent) - outfile.write('end if', indent) - - def schemes(self): - """Return self as a list for consistency with subcycle""" - return [self] - - def variable_list(self, recursive=False, - std_vars=True, loop_vars=True, consts=True): - """Return a list of all variables for this Scheme. - Because Schemes do not have any variables, return a list - of this object's CallList variables instead. - Note that because of this, is not allowed.""" - if recursive: - raise ParseInternalError("recursive=True not allowed for Schemes") - # end if - return self.call_list.variable_list(recursive=recursive, - std_vars=std_vars, - loop_vars=loop_vars, consts=consts) - - @property - def subroutine_name(self): - """Return this scheme's actual subroutine name""" - return self.__subroutine_name - - @property - def has_vertical_dim(self): - """Return True if at least one of this Scheme's variables has - a vertical dimension (vertical_layer_dimension or - vertical_interface_dimension) - """ - return self.__has_vertical_dimension - - def __str__(self): - """Create a readable string for this Scheme""" - return ''.format(self.name, self.subroutine_name) - -############################################################################### - -class Subcycle(SuiteObject): - """Class to represent a subcycled group of schemes or scheme collections""" - - def __init__(self, sub_xml, context, parent, run_env, loop_count=0): - self._loop_extent = sub_xml.get('loop', "1") # Number of iterations - self._loop = None - # See if our loop variable is an integer or a variable - try: - _ = int(self._loop_extent) - self._loop = self._loop_extent - self._loop_var_int = True - name = f"loop{loop_count}" - super().__init__(name, context, parent, run_env, active_call_list=False) - loop_count = loop_count + 1 - except ValueError: - self._loop_var_int = False - lvar = parent.find_variable(standard_name=self._loop_extent, any_scope=True) - if lvar is None: - emsg = "Subcycle, {}, specifies {} iterations, variable not found" - raise CCPPError(emsg.format(name, self._loop_extent)) - else: - self._loop_var_int = False - self._loop = lvar.get_prop_value('local_name') - # end if - name = f"loop{loop_count}_{self._loop_extent}"[0:63] - super().__init__(name, context, parent, run_env, active_call_list=True) - parent.add_call_list_variable(lvar, exists_ok=True) - loop_count = loop_count + 1 - # end try - for item in sub_xml: - new_item = new_suite_object(item, context, self, run_env, loop_count=loop_count) - self.add_part(new_item) - # end for - - def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): - """Analyze the Subcycle's interface to prepare for writing""" - if self.name is None: - self.name = "subcycle_index{}".format(level) - # end if - # Create a Group variable for the subcycle index. - newvar = Var({'local_name':self.name, 'standard_name':self.name, - 'type':'integer', 'units':'count', 'dimensions':'()'}, - _API_LOCAL, self.run_env) - group.manage_variable(newvar) - # Handle all the suite objects inside of this subcycle - scheme_mods = set() - for item in self.parts: - smods = item.analyze(phase, group, scheme_library, - suite_vars, level+1, host_dict) - for smod in smods: - scheme_mods.add(smod) - # end for - # end for - return scheme_mods - - def write(self, outfile, errcode, errmsg, indent): - """Write code for the subcycle loop, including contents, to """ - outfile.write('do {} = 1, {}'.format(self.name, self._loop), indent) - # Note that 'scheme' may be a sybcycle or other construct - for item in self.parts: - item.write(outfile, errcode, errmsg, indent+1) - # end for - outfile.write('end do', 2) - - @property - def loop(self): - """Return the loop value or variable local_name""" - return self._loop - -############################################################################### - -class TimeSplit(SuiteObject): - """Class to represent a group of processes to be computed in a time-split - manner -- each parameterization or other construct is called with an - state which has been updated from the previous step. - """ - - def __init__(self, sub_xml, context, parent, run_env): - super().__init__('TimeSplit', context, parent, run_env) - for part in sub_xml: - new_item = new_suite_object(part, context, self, run_env) - self.add_part(new_item) - # end for - - def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): - # Unused arguments are for consistent analyze interface - # pylint: disable=unused-argument - """Analyze the TimeSplit's interface to prepare for writing""" - # Handle all the suite objects inside of this group - scheme_mods = set() - for item in self.parts: - smods = item.analyze(phase, group, scheme_library, - suite_vars, level+1, host_dict) - for smod in smods: - scheme_mods.add(smod) - # end for - # end for - return scheme_mods - - def write(self, outfile, errcode, errmsg, indent): - """Write code for this TimeSplit section, including contents, - to """ - for item in self.parts: - item.write(outfile, errcode, errmsg, indent) - # end for - -############################################################################### - -class ProcessSplit(SuiteObject): - """Class to represent a group of processes to be computed in a - process-split manner -- all parameterizations or other constructs are - called with the same state. - NOTE: Currently a stub - """ - - def __init__(self, sub_xml, context, parent, run_env): - # Unused arguments are for consistent __init__ interface - # pylint: disable=unused-argument - super().__init__('ProcessSplit', context, parent, run_env) - raise CCPPError('ProcessSplit not yet implemented') - - def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): - # Unused arguments are for consistent analyze interface - # pylint: disable=unused-argument - """Analyze the ProcessSplit's interface to prepare for writing""" - # Handle all the suite objects inside of this group - raise CCPPError('ProcessSplit not yet implemented') - - def write(self, outfile, errcode, errmsg, indent): - """Write code for this ProcessSplit section, including contents, - to """ - raise CCPPError('ProcessSplit not yet implemented') - -############################################################################### - -class Group(SuiteObject): - """Class to represent a grouping of schemes in a suite - A Group object is implemented as a subroutine callable by the API. - The main arguments to a group are the host model variables. - Additional output arguments are generated from schemes with intent(out) - arguments. - Additional input or inout arguments are generated for inputs needed by - schemes which are produced (intent(out)) by other groups. - """ - - __subhead = ''' - subroutine {subname}({args}) -''' - - __subend = ''' - end subroutine {subname} - -! ======================================================================== -''' - - __thread_check = CodeBlock([('#ifdef _OPENMP', -1), - ('if (omp_get_thread_num() > 1) then', 1), - ('{errcode} = 1', 2), - (('{errmsg} = "Cannot call {phase} routine ' - 'from a threaded region"'), 2), - ('return', 2), - ('end if', 1), - ('#endif', -1)]) - - __process_types = [_API_TIMESPLIT_TAG, _API_PROCESSSPLIT_TAG] - - __process_xml = {} - for gptype in __process_types: - __process_xml[gptype] = '<{ptype}>'.format(ptype=gptype) - # end for - - def __init__(self, group_xml, transition, parent, context, run_env): - """Initialize this Group object from . - is the group's phase, is the group's suite. - """ - name = parent.name + '_' + group_xml.get('name') - if transition not in CCPP_STATE_MACH.transitions(): - errmsg = "Bad transition argument to Group, '{}'" - raise ParseInternalError(errmsg.format(transition)) - # end if - # Initialize the dictionary of variables internal to group - super().__init__(name, context, parent, run_env, - active_call_list=True, phase_type=transition) - # Add the items but first make sure we know the process type for - # the group (e.g., TimeSplit or ProcessSplit). - if (transition == RUN_PHASE_NAME) and ((not group_xml) or - (group_xml[0].tag not in - Group.__process_types)): - # Default is TimeSplit - tsxml = ET.fromstring(Group.__process_xml[_API_TIMESPLIT_TAG]) - time_split = new_suite_object(tsxml, context, self, run_env) - add_to = time_split - self.add_part(time_split) - else: - add_to = self - # end if - # Add the sub objects either directly to the Group or to the TimeSplit - for item in group_xml: - new_item = new_suite_object(item, context, add_to, run_env) - add_to.add_part(new_item) - # end for - self._local_schemes = set() - self._host_vars = None - self._host_ddts = None - self._loop_var_matches = list() - self._phase_check_stmts = list() - self._set_state = None - self._ddt_library = None - self.transform_locals = list() - self.optional_vars = list() - - def phase_match(self, scheme_name): - """If scheme_name matches the group phase, return the group and - function ID. Otherwise, return None - """ - fid, tid, _ = CCPP_STATE_MACH.transition_match(scheme_name, - transition=self.phase()) - if tid is not None: - return self, fid - # end if - return None, None - - def move_to_call_list(self, standard_name): - """Move a variable from the group internal dictionary to the call list. - This is done when the variable, , will be allocated by - the suite. - """ - gvar = self.find_variable(standard_name=standard_name, any_scope=False) - if gvar is None: - errmsg = "Group {}, cannot move {}, variable not found" - raise ParseInternalError(errmsg.format(self.name, standard_name)) - # end if - self.add_call_list_variable(gvar, exists_ok=True) - self.remove_variable(standard_name) - - def register_action(self, vaction): - """Register any recognized type for use during self.write. - Return True iff is handled. - """ - if isinstance(vaction, VarLoopSubst): - self._loop_var_matches = vaction.add_to_list(self._loop_var_matches) - # Add the missing dim - vaction.add_local(self, _API_LOCAL, self.run_env) - return True - # end if - return False - - def manage_variable(self, newvar): - """Add to our local dictionary making necessary - modifications to the variable properties so that it is - allocated appropriately""" - # Need new prop dict to eliminate unwanted properties (e.g., intent) - vdims = newvar.get_dimensions() - # Look for dimensions where we have a loop substitution and replace - # with the correct size - if self.run_phase(): - hdims = [x.missing_stdname for x in self._loop_var_matches] - else: - # Do not do loop substitutions in full phases - hdims = list() - # end if - for index, dim in enumerate(vdims): - newdim = None - for subdim in dim.split(':'): - if subdim in hdims: - # We have a loop substitution, find and replace - hindex = hdims.index(subdim) - names = self._loop_var_matches[hindex].required_stdnames - newdim = ':'.join(names) - break - # end if - if ('vertical' in subdim) and ('index' in subdim): - # We have a vertical index, replace with correct dimension - errmsg = "vertical index replace not implemented" - raise ParseInternalError(errmsg) - # end if - # end for - if newdim is not None: - vdims[index] = newdim - # end if - # end for - if self.timestep_phase(): - persist = 'timestep' - else: - persist = 'run' - # end if - # Start with an official copy of 's prop_dict with - # corrected dimensions - subst_dict = {'dimensions':vdims} - prop_dict = newvar.copy_prop_dict(subst_dict=subst_dict) - # Add the allocatable items - prop_dict['allocatable'] = len(vdims) > 0 # No need to allocate scalar - prop_dict['persistence'] = persist - # This is a local variable - if 'intent' in prop_dict: - del prop_dict['intent'] - # end if - # Create a new variable, save the original context - local_var = Var(prop_dict, - ParseSource(_API_SOURCE_NAME, - _API_LOCAL_VAR_NAME, newvar.context), - self.run_env) - self.add_variable(local_var, self.run_env, exists_ok=True, gen_unique=True) - # Finally, make sure all dimensions are accounted for - emsg = self.add_variable_dimensions(local_var, [_API_LOCAL_VAR_NAME], - _API_SUITE_VAR_NAME, - adjust_intent=True, - to_dict=self.call_list) - if emsg: - raise CCPPError(emsg) - # end if - - def analyze(self, phase, suite_vars, scheme_library, ddt_library, - check_suite_state, set_suite_state, host_dict): - """Analyze the Group's interface to prepare for writing""" - self._ddt_library = ddt_library - # Sanity check for Group - if phase != self.phase(): - errmsg = 'Group {} has phase {} but analyze is phase {}' - raise ParseInternalError(errmsg.format(self.name, - self.phase(), phase)) - # end if - for item in self.parts: - # Items can be schemes, subcycles or other objects - # All have the same interface and return a set of module use - # statements (lschemes) - lschemes = item.analyze(phase, self, scheme_library, suite_vars, 1, host_dict) - for lscheme in lschemes: - self._local_schemes.add(lscheme) - # end for - # end for - self._phase_check_stmts = check_suite_state - self._set_state = set_suite_state - if (self.run_env.logger and - self.run_env.logger.isEnabledFor(logging.DEBUG)): - self.run_env.logger.debug("{}".format(self)) - # end if - - def allocate_dim_str(self, dims, context): - """Create the dimension string for an allocate statement""" - rdims = list() - for dim in dims: - rdparts = list() - dparts = dim.split(':') - for dpart in dparts: - dvar = self.find_variable(standard_name=dpart, any_scope=False) - if dvar is None: - dvar = self.call_list.find_variable(standard_name=dpart, - any_scope=False) - # end if - if dvar is None: - # Check if it's a module-level variable - dvar = self.find_variable(standard_name=dpart, any_scope=True) - # end if - if dvar is None: - emsg = "Dimension variable, '{}', not found{}" - lvar = self.find_local_name(dpart, any_scope=True) - if lvar is not None: - emsg += "\nBe sure to use standard names!" - # end if - ctx = context_string(context) - raise CCPPError(emsg.format(dpart, ctx)) - # end if - lname = dvar.get_prop_value('local_name') - rdparts.append(lname) - # end for - rdims.append(':'.join(rdparts)) - # end for - return ', '.join(rdims) - - def find_variable(self, standard_name=None, source_var=None, - any_scope=True, clone=None, - search_call_list=False, loop_subst=False, - check_components=True): - """Find a matching variable to , create a local clone (if - is True), or return None. - This purpose of this special Group version is to record any constituent - variable found for processing during the write phase. - """ - fvar = super().find_variable(standard_name=standard_name, - source_var=source_var, - any_scope=any_scope, clone=clone, - search_call_list=search_call_list, - loop_subst=loop_subst, - check_components=check_components) - if fvar and fvar.is_constituent(): - if fvar.source.ptype == ConstituentVarDict.constitutent_source_type(): - # We found this variable in the constituent dictionary, - # add it to our call list - self.add_call_list_variable(fvar, exists_ok=True) - # end if - # end if - return fvar - - def write(self, outfile, host_arglist, indent, const_mod, - suite_vars=None, allocate=False, deallocate=False): - """Write code for this subroutine (Group), including contents, - to """ - # Unused arguments are for consistent write interface - # pylint: disable=unused-argument - # group type for (de)allocation - if self.timestep_phase(): - group_type = 'timestep' # Just allocate for the timestep - else: - group_type = 'run' # Allocate for entire run - # end if - # Collect information on local variables - subpart_allocate_vars = {} - subpart_scalar_vars = {} - allocatable_var_set = set() - optional_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') - sname = var.get_prop_value('standard_name') - if (lname in subpart_allocate_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, {}" - raise ParseInternalError(errmsg.format(lname)) - # end if - else: - dims = var.get_dimensions() - if (dims is not None) and dims: - subpart_allocate_vars[lname] = (var, item, False) - allocatable_var_set.add(lname) - else: - subpart_scalar_vars[lname] = (var, item, False) - # end if - # end if - # end for - # All optional variables for the Schemes need to have an associated pointer array declared. - for ivar in self.optional_vars: - name = ivar.get_prop_value('local_name') - 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 - # 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 - subname = self.name - call_list = self.call_list.call_string() - outfile.write(Group.__subhead.format(subname=subname, args=call_list), - indent) - # Write out any use statements - if self._local_schemes: - modmax = max([len(s[0]) for s in self._local_schemes]) - else: - modmax = 0 - # end if - # Write out the scheme use statements - scheme_use = 'use {},{} only: {}' - for scheme in sorted(self._local_schemes): - smod = scheme[0] - sname = scheme[1] - slen = ' '*(modmax - len(smod)) - outfile.write(scheme_use.format(smod, slen, sname), indent+1) - # end for - # Look for any DDT types - call_vars = self.call_list.variable_list() - all_vars = ([x[0] for x in subpart_allocate_vars.values()] + - [x[0] for x in subpart_scalar_vars.values()]) - all_vars.extend(call_vars) - self._ddt_library.write_ddt_use_statements(all_vars, outfile, - indent+1, pad=modmax) - outfile.write('', 0) - # Write out dummy arguments - outfile.write('! Dummy arguments', indent+1) - msg = 'Variables for {}: ({})' - if (self.run_env.logger and - self.run_env.logger.isEnabledFor(logging.DEBUG)): - 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_allocate_vars or subpart_scalar_vars: - outfile.write('\n! Local Variables', indent+1) - # end if - # Scalars - 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_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 - # end for - # Pointer variables - outfile.write('', 0) - if pointer_var_set: - outfile.write('! Local pointer variables', indent+1) - # end if - for (name, kind, dim, vtype) in pointer_var_set: - 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: - raise ParseInternalError("Error object not supported") - else: - verrcode = self.call_list.find_variable(standard_name='ccpp_error_code') - if verrcode is not None: - errcode = verrcode.get_prop_value('local_name') - else: - errmsg = "No ccpp_error_code variable for group, {}" - raise CCPPError(errmsg.format(self.name)) - # end if - verrmsg = self.call_list.find_variable(standard_name='ccpp_error_message') - if verrmsg is not None: - errmsg = verrmsg.get_prop_value('local_name') - else: - errmsg = "No ccpp_error_message variable for group, {}" - 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}) - # 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({}({}))" - for lname in sorted(allocatable_var_set): - 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 - # 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: - timestep_var = svar.get_prop_value('persistence') - if group_type == timestep_var: - alloc_str = self.allocate_dim_str(dims, svar.context) - lname = svar.get_prop_value('local_name') - outfile.write(alloc_stmt.format(lname, alloc_str), - indent+1) - # end if (do not allocate in this phase) - # end if dims (do not allocate scalars) - # end for - # end if - # Write the scheme and subcycle calls - for item in self.parts: - 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 sorted(allocatable_var_set): - outfile.write('if (allocated({})) {} deallocate({})'.format(lname,' '*(20-len(lname)),lname), indent+1) - # end for - for lname in optional_var_set: - outfile.write('if (allocated({})) {} deallocate({})'.format(lname,' '*(20-len(lname)),lname), indent+1) - # end for - # Deallocate suite vars - if deallocate: - for svar in suite_vars.variable_list(): - dims = svar.get_dimensions() - if dims: - timestep_var = svar.get_prop_value('persistence') - if group_type == timestep_var: - lname = svar.get_prop_value('local_name') - outfile.write('deallocate({})'.format(lname), indent+1) - # end if - # end if (no else, do not deallocate scalars) - # end for - # end if - self._set_state.write(outfile, indent, {}) - # end if - outfile.write(Group.__subend.format(subname=subname), indent) - - @property - def suite(self): - """Return this Group's suite""" - return self.parent - - def suite_dicts(self): - """Return a list of this Group's Suite's dictionaries""" - return self.suite.suite_dicts() - -############################################################################### - -if __name__ == "__main__": - # First, run doctest - # pylint: disable=ungrouped-imports - import doctest - import sys - # pylint: enable=ungrouped-imports - fail, _ = doctest.testmod() - sys.exit(fail) -# end if diff --git a/test/capgen_test/test_capgen_host_integration.F90 b/test/capgen_test/test_capgen_host_integration.F90 index f9cb1410..aa4328b6 100644 --- a/test/capgen_test/test_capgen_host_integration.F90 +++ b/test/capgen_test/test_capgen_host_integration.F90 @@ -5,45 +5,42 @@ program test character(len=cs), target :: test_parts1(2) = (/ 'physics1 ', & 'physics2 ' /) character(len=cs), target :: test_parts2(1) = (/ 'data_prep ' /) - character(len=cm), target :: test_invars1(12) = (/ & - 'potential_temperature ', & - 'potential_temperature_at_interface ', & - 'coefficients_for_interpolation ', & - 'surface_air_pressure ', & - 'water_vapor_specific_humidity ', & - 'potential_temperature_increment ', & - 'soil_levels ', & - 'temperature_at_diagnostic_levels ', & - 'time_step_for_physics ', & - 'array_variable_for_testing ', & - 'cloud_fraction ', & - 'do_cloud_fraction_adjustment '/) - character(len=cm), target :: test_outvars1(10) = (/ & - 'potential_temperature ', & - 'potential_temperature_at_interface ', & - 'coefficients_for_interpolation ', & - 'surface_air_pressure ', & - 'water_vapor_specific_humidity ', & - 'soil_levels ', & - 'temperature_at_diagnostic_levels ', & - 'ccpp_error_code ', & - 'ccpp_error_message ', & - 'array_variable_for_testing ' /) - character(len=cm), target :: test_reqvars1(14) = (/ & - 'potential_temperature ', & - 'potential_temperature_at_interface ', & - 'coefficients_for_interpolation ', & - 'surface_air_pressure ', & - 'water_vapor_specific_humidity ', & - 'potential_temperature_increment ', & - 'time_step_for_physics ', & - 'soil_levels ', & - 'temperature_at_diagnostic_levels ', & - 'ccpp_error_code ', & - 'ccpp_error_message ', & - 'array_variable_for_testing ', & - 'cloud_fraction ', & - 'do_cloud_fraction_adjustment '/) + character(len=cm), target :: test_invars1(10) = (/ & + 'array_variable_for_testing ', & + 'coefficients_for_interpolation ', & + 'physics_state_derived_type ', & + 'potential_temperature ', & + 'potential_temperature_at_interface ', & + 'temperature_at_diagnostic_levels ', & + 'index_of_water_vapor_specific_humidity', & + 'do_cloud_fraction_adjustment ', & + 'potential_temperature_increment ', & + 'time_step_for_physics '/) + + character(len=cm), target :: test_outvars1(9) = (/ & + 'array_variable_for_testing ', & + 'coefficients_for_interpolation ', & + 'physics_state_derived_type ', & + 'potential_temperature ', & + 'potential_temperature_at_interface ', & + 'temperature_at_diagnostic_levels ', & + 'index_of_water_vapor_specific_humidity', & + 'ccpp_error_code ', & + 'ccpp_error_message '/) + + character(len=cm), target :: test_reqvars1(12) = (/ & + 'array_variable_for_testing ', & + 'coefficients_for_interpolation ', & + 'physics_state_derived_type ', & + 'potential_temperature ', & + 'potential_temperature_at_interface ', & + 'temperature_at_diagnostic_levels ', & + 'index_of_water_vapor_specific_humidity', & + 'do_cloud_fraction_adjustment ', & + 'potential_temperature_increment ', & + 'time_step_for_physics ', & + 'ccpp_error_code ', & + 'ccpp_error_message '/) character(len=cm), target :: test_invars2(3) = (/ & 'model_times ', & From df3be4344b3e452892fce717cfd4130b85848cfd Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Thu, 23 Apr 2026 20:19:21 -0600 Subject: [PATCH 35/59] Pointer assignment now ok, but results are off because temp doesn't have the correct indices --- .../test_capgen_host_integration.F90 | 89 ++++++++++--------- test/capgen_test/test_host.F90 | 2 +- 2 files changed, 46 insertions(+), 45 deletions(-) diff --git a/test/capgen_test/test_capgen_host_integration.F90 b/test/capgen_test/test_capgen_host_integration.F90 index aa4328b6..b8fdb3fc 100644 --- a/test/capgen_test/test_capgen_host_integration.F90 +++ b/test/capgen_test/test_capgen_host_integration.F90 @@ -6,60 +6,61 @@ program test 'physics2 ' /) character(len=cs), target :: test_parts2(1) = (/ 'data_prep ' /) character(len=cm), target :: test_invars1(10) = (/ & - 'array_variable_for_testing ', & - 'coefficients_for_interpolation ', & - 'physics_state_derived_type ', & - 'potential_temperature ', & - 'potential_temperature_at_interface ', & - 'temperature_at_diagnostic_levels ', & - 'index_of_water_vapor_specific_humidity', & - 'do_cloud_fraction_adjustment ', & - 'potential_temperature_increment ', & - 'time_step_for_physics '/) + 'array_variable_for_testing ', & + 'coefficients_for_interpolation ', & + 'physics_state_derived_type ', & + 'potential_temperature ', & + 'potential_temperature_at_interface ', & + 'temperature_at_diagnostic_levels ', & + 'index_of_water_vapor_specific_humidity ', & + 'do_cloud_fraction_adjustment ', & + 'potential_temperature_increment ', & + 'time_step_for_physics '/) character(len=cm), target :: test_outvars1(9) = (/ & - 'array_variable_for_testing ', & - 'coefficients_for_interpolation ', & - 'physics_state_derived_type ', & - 'potential_temperature ', & - 'potential_temperature_at_interface ', & - 'temperature_at_diagnostic_levels ', & - 'index_of_water_vapor_specific_humidity', & - 'ccpp_error_code ', & - 'ccpp_error_message '/) + 'array_variable_for_testing ', & + 'coefficients_for_interpolation ', & + 'physics_state_derived_type ', & + 'potential_temperature ', & + 'potential_temperature_at_interface ', & + 'temperature_at_diagnostic_levels ', & + 'index_of_water_vapor_specific_humidity ', & + 'ccpp_error_code ', & + 'ccpp_error_message '/) character(len=cm), target :: test_reqvars1(12) = (/ & - 'array_variable_for_testing ', & - 'coefficients_for_interpolation ', & - 'physics_state_derived_type ', & - 'potential_temperature ', & - 'potential_temperature_at_interface ', & - 'temperature_at_diagnostic_levels ', & - 'index_of_water_vapor_specific_humidity', & - 'do_cloud_fraction_adjustment ', & - 'potential_temperature_increment ', & - 'time_step_for_physics ', & - 'ccpp_error_code ', & - 'ccpp_error_message '/) + 'array_variable_for_testing ', & + 'coefficients_for_interpolation ', & + 'physics_state_derived_type ', & + 'potential_temperature ', & + 'potential_temperature_at_interface ', & + 'temperature_at_diagnostic_levels ', & + 'index_of_water_vapor_specific_humidity ', & + 'do_cloud_fraction_adjustment ', & + 'potential_temperature_increment ', & + 'time_step_for_physics ', & + 'ccpp_error_code ', & + 'ccpp_error_message '/) character(len=cm), target :: test_invars2(3) = (/ & - 'model_times ', & - 'number_of_model_times ', & - 'surface_air_pressure ' /) + 'model_times ', & + 'number_of_model_times ', & + 'physics_state_derived_type ' /) character(len=cm), target :: test_outvars2(5) = (/ & - 'ccpp_error_code ', & - 'ccpp_error_message ', & - 'model_times ', & - 'surface_air_pressure ', & - 'number_of_model_times ' /) + 'number_of_model_times ', & + 'physics_state_derived_type ', & + 'ccpp_error_code ', & + 'ccpp_error_message ', & + 'model_times ' /) character(len=cm), target :: test_reqvars2(5) = (/ & - 'model_times ', & - 'number_of_model_times ', & - 'surface_air_pressure ', & - 'ccpp_error_code ', & - 'ccpp_error_message ' /) + 'number_of_model_times ', & + 'physics_state_derived_type ', & + 'model_times ', & + 'ccpp_error_code ', & + 'ccpp_error_message ' /) + type(suite_info) :: test_suites(2) logical :: run_okay diff --git a/test/capgen_test/test_host.F90 b/test/capgen_test/test_host.F90 index 6e39c787..3a95d879 100644 --- a/test/capgen_test/test_host.F90 +++ b/test/capgen_test/test_host.F90 @@ -9,7 +9,7 @@ module test_prog ! Public data and interfaces integer, public, parameter :: cs = 16 - integer, public, parameter :: cm = 36 + integer, public, parameter :: cm = 64 !> \section arg_table_suite_info Argument Table !! \htmlinclude arg_table_suite_info.html From 57d55bea2d170850eb768149243cb97876ecce36 Mon Sep 17 00:00:00 2001 From: dustinswales Date: Mon, 27 Apr 2026 11:09:49 -0600 Subject: [PATCH 36/59] var_compatibility_test passing --- scripts/metavar.py | 48 +++++++++++++++++++++++++++------------- scripts/suite_objects.py | 7 ++---- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/scripts/metavar.py b/scripts/metavar.py index f096e7c7..e4c2f3ad 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -375,26 +375,44 @@ def __init__(self, prop_dict, source, run_env, is_ddt=False, components=None, co # end if # end try - def compatible(self, other, run_env, is_tend=False): + def compatible(self, other, run_env, is_tend=False, is_host_var=False): """Return a VarCompatObj object which describes the equivalence, compatibility, or incompatibility between and . + If , reverse the variable properties. """ # We accept character(len=*) as compatible with # character(len=INTEGER_VALUE) - stype = self.get_prop_value('type') - skind = self.get_prop_value('kind') - sunits = self.get_prop_value('units') - sstd_name = self.get_prop_value('standard_name') - sloc_name = self.get_prop_value('local_name') - stopp = self.get_prop_value('top_at_one') - sdims = self.get_dimensions() - otype = other.get_prop_value('type') - okind = other.get_prop_value('kind') - ounits = other.get_prop_value('units') - ostd_name = other.get_prop_value('standard_name') - oloc_name = other.get_prop_value('local_name') - otopp = other.get_prop_value('top_at_one') - odims = other.get_dimensions() + if not is_host_var: + stype = self.get_prop_value('type') + skind = self.get_prop_value('kind') + sstd_name = self.get_prop_value('standard_name') + sloc_name = self.get_prop_value('local_name') + stopp = self.get_prop_value('top_at_one') + sdims = self.get_dimensions() + sunits = self.get_prop_value('units') + otype = other.get_prop_value('type') + okind = other.get_prop_value('kind') + ostd_name = other.get_prop_value('standard_name') + oloc_name = other.get_prop_value('local_name') + otopp = other.get_prop_value('top_at_one') + odims = other.get_dimensions() + ounits = other.get_prop_value('units') + else: + otype = self.get_prop_value('type') + okind = self.get_prop_value('kind') + ostd_name = self.get_prop_value('standard_name') + oloc_name = self.get_prop_value('local_name') + otopp = self.get_prop_value('top_at_one') + odims = self.get_dimensions() + ounits = self.get_prop_value('units') + stype = other.get_prop_value('type') + skind = other.get_prop_value('kind') + sstd_name = other.get_prop_value('standard_name') + sloc_name = other.get_prop_value('local_name') + stopp = other.get_prop_value('top_at_one') + sdims = other.get_dimensions() + sunits = other.get_prop_value('units') + # end if compat = VarCompatObj(sstd_name, stype, skind, sunits, sdims, sloc_name, stopp, ostd_name, otype, okind, ounits, odims, oloc_name, otopp, run_env, diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 5c73c09f..dfbc8dd0 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -830,7 +830,7 @@ def match_variable(self, var, run_env, host_dict): # end if # Create compatability object, containing any necessary forward/reverse # transforms from and - compat_obj = var.compatible(dict_var, run_env) + #compat_obj = var.compatible(dict_var, run_env) # Add the variable to the parent call tree if dict_dims == new_dict_dims: sdict = {} @@ -862,7 +862,7 @@ def match_variable(self, var, run_env, host_dict): if dict_var is not None: dict_var = self.parent.find_variable(source_var=var, any_scope=True) if scheme_var is not None: - compat_obj = var.compatible(scheme_var, run_env) + compat_obj = var.compatible(scheme_var, run_env, is_host_var=True) else: compat_obj = var.compatible(dict_var, run_env) # end if @@ -1501,9 +1501,6 @@ def add_var_transform(self, var, compat_obj): # end if # If needed, modify horizontal dimension for loop substitution. - cldicts = [self]#.__group, self.__group.call_list] - #cldicts.extend(self.__group.suite_dicts()) - # NOT YET IMPLEMENTED var_hdim,hdim = find_horizontal_dimension(var.get_dimensions()) if var_hdim: if self.run_phase(): From e76739d8f06ad3d8fde4114a80087fa78592ff74 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Mon, 27 Apr 2026 11:31:06 -0600 Subject: [PATCH 37/59] Saving progress --- scripts/suite_objects.py | 102 +++++++++++++++++++++++++-------------- 1 file changed, 66 insertions(+), 36 deletions(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 0f60ce08..effd36c1 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -191,11 +191,11 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ if dvar.components: var_in_call_list = False # end if - else: - # If we get here, the variable is explicitly in the Group call_list, - # not a DDT component. No need to modify the dimensions in the Scheme - # call list, as these were handled in the Suite Cap call_list. - host_var = True + #else: + # # If we get here, the variable is explicitly in the Group call_list, + # # not a DDT component. No need to modify the dimensions in the Scheme + # # call list, as these were handled in the Suite Cap call_list. + # host_var = True # end if break # end if @@ -223,36 +223,47 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ # local pointers of _ptr if var.get_prop_value('optional'): lname = dummy+'_ptr' - # end if # Finally, handle the dimensions. - else: + elif not var.get_prop_value('allocatable'): + #else: print(f"DH AAA host_var? {host_var}") + print(f"DH AAA allocatable? {var.get_prop_value('allocatable')}") if dimensions and not host_var: dimstr = '(' for cnt,dim in enumerate(dimensions): print(f"DH ZZZ: {cnt} / {dim} / {is_horizontal_dimension(dim)}") if is_horizontal_dimension(dim): if self.routine.run_phase(): - if var_in_call_list and \ - self.find_variable(standard_name="horizontal_loop_extent"): - ldim = "ccpp_constant_one" - udim = "horizontal_loop_extent" - else: - ldim = "horizontal_loop_begin" - udim = "horizontal_loop_end" + #if var_in_call_list and \ + # self.find_variable(standard_name="horizontal_loop_extent"): + # ldim = "ccpp_constant_one" + # udim = "horizontal_loop_extent" + #else: + # ldim = "horizontal_loop_begin" + # udim = "horizontal_loop_end" + ldim = "horizontal_loop_begin" + udim = "horizontal_loop_end" # endif else: ldim = "ccpp_constant_one" udim = "horizontal_dimension" # endif # Get dimension for lower bound - lvar = cldict.find_variable(standard_name=ldim, any_scope=True) + for cldict in cldicts: + #lvar = cldict.find_variable(standard_name=ldim, any_scope=True) + lvar = cldict.find_variable(standard_name=ldim, any_scope=False) + if lvar: + break if not lvar: raise Exception(f"No variable with standard name '{ldim}' in cldict") # end if ldim_lname = lvar.get_prop_value('local_name') # Get dimension for upper bound - uvar = cldict.find_variable(standard_name=udim, any_scope=True) + for cldict in cldicts: + #uvar = cldict.find_variable(standard_name=udim, any_scope=True) + uvar = cldict.find_variable(standard_name=udim, any_scope=False) + if uvar: + break if not uvar: raise Exception(f"No variable with standard name '{udim}' in cldict") # end if @@ -293,22 +304,22 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ # end if # end for # end if - if is_func_call: - if cldicts is not None: - use_dicts = cldicts - else: - use_dicts = [self] - # end if - run_phase = self.routine.run_phase() - # We only need dimensions for suite variables in run phase - need_dims = SuiteObject.is_suite_variable(dvar) and run_phase - vdims = var.call_dimstring(var_dicts=use_dicts, - explicit_dims=need_dims, - loop_subst=run_phase) - if _BLANK_DIMS_RE.match(vdims) is None: - lname = lname + vdims - # end if - # end if + #if is_func_call: + # if cldicts is not None: + # use_dicts = cldicts + # else: + # use_dicts = [self] + # # end if + # run_phase = self.routine.run_phase() + # # We only need dimensions for suite variables in run phase + # need_dims = SuiteObject.is_suite_variable(dvar) and run_phase + # vdims = var.call_dimstring(var_dicts=use_dicts, + # explicit_dims=need_dims, + # loop_subst=run_phase) + # if _BLANK_DIMS_RE.match(vdims) is None: + # lname = lname + vdims + # # end if + ## end if if is_func_call: arg_str += "{}{}={}".format(arg_sep, dummy, lname) else: @@ -1172,6 +1183,10 @@ def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): vintent = var.get_prop_value('intent') args = self.match_variable(var, self.run_env, host_dict) found, dict_var, local_var, var_vdim, new_dims, compat_obj, scheme_var = args + # DH* + print(f"DH DEBUG ABC: {self.subroutine_name} / {vstdname} / {vdims} / {found} / {dict_var} / {local_var} ") + #if vstdname == "ozone": + # raise Exception("XXX") if dict_var: if dict_var.is_ddt(): subst_dict = {'intent':'inout'} @@ -1652,12 +1667,27 @@ def add_var_transform(self, var, compat_obj): # end if # If needed, modify horizontal dimension for loop substitution. - #cldicts = [self]#.__group, self.__group.call_list] + cldicts = [self]#.__group, self.__group.call_list] #cldicts.extend(self.__group.suite_dicts()) # NOT YET IMPLEMENTED - hdim = find_horizontal_dimension(var.get_dimensions()) - #if compat_obj.has_dim_transforms: - print("SWALES ",hdim,var.get_prop_value('local_name')) + var_hdim,hdim = find_horizontal_dimension(var.get_dimensions()) + if var_hdim: + if self.run_phase(): + ldim = "horizontal_loop_begin" + udim = "horizontal_loop_end" + else: + ldim = "ccpp_constant_one" + udim = "horizontal_dimension" + # end if + lvar = self.find_variable(ldim) + if lvar is None: + raise CCPPError(f"add_var_transform: Cannot find dimension variable, {ldim}") + # end if + uvar = self.find_variable(udim) + if uvar is None: + raise CCPPError(f"add_var_transform: Cannot find dimension variable, {udim}") + # end if + rindices[hdim] = lvar.get_prop_value('local_name')+':'+uvar.get_prop_value('local_name') # Register any reverse (pre-Scheme) transforms. Also, save local_name used in # transform (used in write stage). From fafe414467c8e830743a1ba4abb861266e4af43a Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Mon, 27 Apr 2026 12:30:05 -0600 Subject: [PATCH 38/59] Cleanup --- scripts/ddt_library.py | 6 +-- scripts/metavar.py | 7 ++- scripts/suite_objects.py | 95 +++------------------------------------- 3 files changed, 11 insertions(+), 97 deletions(-) diff --git a/scripts/ddt_library.py b/scripts/ddt_library.py index 90571978..bdd3e8be 100644 --- a/scripts/ddt_library.py +++ b/scripts/ddt_library.py @@ -101,16 +101,14 @@ def clone(self, subst_dict=None, remove_intent=False, # end if return clone_var - def call_string(self, var_dict, loop_vars=None, dhdebug=False): + def call_string(self, var_dict, loop_vars=None): """Return a legal call string of this VarDDT's local name sequence. """ # XXgoldyXX: Need to add dimensions to this call_str = super().get_prop_value('local_name') - if dhdebug: - print(f"DH DEBUG VarDDT.call_string: '{call_str}', '{self.field}'") if self.field is not None: call_str += '%' + self.field.call_string(var_dict, - loop_vars=loop_vars, dhdebug=dhdebug) + loop_vars=loop_vars) # end if return call_str diff --git a/scripts/metavar.py b/scripts/metavar.py index 5d270888..331fe0be 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -692,7 +692,7 @@ def call_dimstring(self, var_dicts=None, # end if return dimstr - def call_string(self, var_dicts, loop_vars=None, dhdebug=False): + def call_string(self, var_dicts, loop_vars=None): """Construct the actual argument string for this Var by translating standard names to local names. String includes array bounds unless loop_vars is None. @@ -708,13 +708,11 @@ def call_string(self, var_dicts, loop_vars=None, dhdebug=False): # end if if loop_vars is None: call_str = self.get_prop_value('local_name') - if dhdebug: - print(f"DH call_string: '{call_str}'") # Look for dims in case this is an array selection variable # DH* # Will this work with something like ddt1(some_index)%nested_ddt(other_index)%myvar(dimstring)? # Is local_name guaranteed to be just the member of the innermost DDT? - # *DH + # Better to use split_dims_from_name ... dind = call_str.find('(') if dind > 0: dimstr = call_str[dind+1:].rstrip()[:-1] @@ -723,6 +721,7 @@ def call_string(self, var_dicts, loop_vars=None, dhdebug=False): else: dims = None # end if + # *DH else: call_str, dims = self.handle_array_ref() # end if diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 1fb90453..1c21feaf 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -81,7 +81,9 @@ def split_dims_from_name(expr: str): if not dimstring: return name, None - # ... + # Now turn the dimstring into a list of dimensions, + # taking into account that a dimension might have + # parentheses: (idx2, idx3(idx4), idx5a(idx6):idx5b(idx6)) dimstring = dimstring.lstrip('(').rstrip(')') dims = [] current = [] @@ -212,22 +214,13 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ # end if dimensions = dvar.get_dimensions() lname = dvar.call_string(cldicts) - # DH* - abort_me = False - if True: # lname == "o3": - print(f"DH XXX: GOT YOU: {lname}") - print(f"DH XXX: {dimensions}") - abort_me = True - dimstr = None # Optional variables in the caps are associated with # local pointers of _ptr if var.get_prop_value('optional'): lname = dummy+'_ptr' - # Finally, handle the dimensions. + # Finally, handle the dimensions unless it's an allocatable var elif not var.get_prop_value('allocatable'): - #else: - print(f"DH AAA host_var? {host_var}") - print(f"DH AAA allocatable? {var.get_prop_value('allocatable')}") + # DH* host_var still needed? if dimensions and not host_var: dimstr = '(' for cnt,dim in enumerate(dimensions): @@ -278,12 +271,6 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ lname = lname + dimstr # end if # end if - # DH* - if abort_me: - print(f"DH YYY: {lname}") - if dimstr: - print(f"DH YYY: {dimstr}") - #raise Exception("XXX") else: cldict = None aref = var.array_ref(local_name=dummy) @@ -1183,10 +1170,6 @@ def analyze(self, phase, group, scheme_library, suite_vars, level, host_dict): vintent = var.get_prop_value('intent') args = self.match_variable(var, self.run_env, host_dict) found, dict_var, local_var, var_vdim, new_dims, compat_obj, scheme_var = args - # DH* - print(f"DH DEBUG ABC: {self.subroutine_name} / {vstdname} / {vdims} / {found} / {dict_var} / {local_var} ") - #if vstdname == "ozone": - # raise Exception("XXX") if dict_var: if dict_var.is_ddt(): subst_dict = {'intent':'inout'} @@ -1402,62 +1385,9 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, if not dvar: raise Exception(f"No variable with standard name '{standard_name}' in cldicts") # end if - # DH* - print(f"DH BBB GOT YOU! {standard_name} / {var.get_prop_value('local_name')} / {dvar.get_prop_value('local_name')}") - # *DH + # Handle the dimensions... dimensions = dvar.get_dimensions() - print(f"DH CCC '{dimensions}'") - #dimstr = '' - #if dimensions: - # dimstr = dimstr + '(' - # for cnt,dim in enumerate(dimensions): - # if is_horizontal_dimension(dim): - # if self.run_phase(): - # if var_in_call_list and \ - # self.find_variable(standard_name="horizontal_loop_extent"): - # ldim = "ccpp_constant_one" - # udim = "horizontal_loop_extent" - # else: - # ldim = "horizontal_loop_begin" - # udim = "horizontal_loop_end" - # # endif - # else: - # ldim = "ccpp_constant_one" - # udim = "horizontal_dimension" - # # endif - # # Get dimension for lower bound - # #lvar = self.__group.call_list.find_variable(standard_name=ldim, any_scope=True) - # for var_dict in cldicts: - # lvar = var_dict.find_variable(standard_name=ldim, any_scope=False) - # if lvar is not None: - # break - # # end if - # # end for - # if not lvar: - # raise Exception(f"No variable with standard name '{ldim}' in cldicts") - # # end if - # ldim_lname = lvar.get_prop_value('local_name') - # # Get dimension for upper bound - # for var_dict in cldicts: - # uvar = var_dict.find_variable(standard_name=udim, any_scope=False) - # if uvar is not None: - # break - # # end if - # # end for - # if not uvar: - # raise Exception(f"No variable with standard name '{udim}' in cldicts") - # # end if - # udim_lname = uvar.get_prop_value('local_name') - # dimstr = dimstr + ldim_lname + ':' + udim_lname - # else: - # dimstr = dimstr + ':' - # # end if - # if cnt < len(dimensions)-1: dimstr = dimstr+',' - # # end for - # dimstr = dimstr+')' - ## end if - # Need to use local_name in Group's call list (self.__group.call_list), # not the local_name in var. if (dict_var): @@ -1509,10 +1439,7 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, udim_lname = uvar.get_prop_value('local_name') ddims[i] = ldim_lname + ':' + udim_lname # end for - print(f"DH ddims: '{ddims}'") lname, ldims = split_dims_from_name(dvar.call_string(search_dict)) - print(f"DH lname: '{lname}'") - print(f"DH ldims: '{ldims}'") # This is where it gets tricky. We have a dimension specifier # as part of the local name, and we have a dimstr. The latter # contains the correct horizontal and vertical extents, the former @@ -1522,10 +1449,6 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, # dimension we need to check if it is a range or not. If it is # a range, we insert the first range from dimstr, then we remove # this range from dimstr and move on to the next. - #if ldim: - # ldim_list = ldim.lstrip('(').rstrip(')').split(',') - # dimstr_list = dimstr.split(',') - # print(f"DH DEBUG lists: '{ldim_list}' and '{dimstr_list}'") if ldims: # Consistency check: if len(ldims) < len(ddims): @@ -1537,7 +1460,6 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, if ddims: raise Exception("Logic error in associate_optional_var: after filling ldims='{ldims}' from ddims, ddims is not empty: '{ddims}'") dimstr = '(' + ','.join(ldims) + ')' - #raise Exception(f"UUU: {ldims}") else: dimstr = '(' + ','.join(ddims) + ')' lname += dimstr @@ -1553,11 +1475,6 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, outfile.write(f"{lname_ptr} => {lname}", indent) # end if # end if - - if lname_ptr and lname_ptr=="qv_ptr": - print(f"DH TTT: {lname_ptr} => {lname}") - - # end def def nullify_optional_var(self, dict_var, var, has_transform, cldicts, indent, outfile): From 0ccbd6088bef6850c8a91bb7d74d4d59008db153 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Mon, 27 Apr 2026 12:32:36 -0600 Subject: [PATCH 39/59] Cleanup --- scripts/suite_objects.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 1c21feaf..c6bad633 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -224,7 +224,6 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ if dimensions and not host_var: dimstr = '(' for cnt,dim in enumerate(dimensions): - print(f"DH ZZZ: {cnt} / {dim} / {is_horizontal_dimension(dim)}") if is_horizontal_dimension(dim): if self.routine.run_phase(): #if var_in_call_list and \ @@ -1390,6 +1389,7 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, dimensions = dvar.get_dimensions() # Need to use local_name in Group's call list (self.__group.call_list), # not the local_name in var. + # DH* a lot of repeated logic here ... consolidate! if (dict_var): (conditional, _) = dvar.conditional(cldicts) if (has_transform): @@ -1445,10 +1445,10 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, # contains the correct horizontal and vertical extents, the former # does not - they can be ranges (containing ":") or indices # (that is, the variable is a slice of another variable). - # We need to walk the lim specifier left to right and for each + # We need to walk the ldims list left to right and for each # dimension we need to check if it is a range or not. If it is - # a range, we insert the first range from dimstr, then we remove - # this range from dimstr and move on to the next. + # a range, we insert the first element from ddims, then we remove + # this range from ddims and move on to the next. if ldims: # Consistency check: if len(ldims) < len(ddims): @@ -1475,6 +1475,7 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, outfile.write(f"{lname_ptr} => {lname}", indent) # end if # end if + # *DH # end def def nullify_optional_var(self, dict_var, var, has_transform, cldicts, indent, outfile): From 670ba5efd444d6c59f97f6bf65fe1b6e47c6d0f5 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Mon, 27 Apr 2026 12:36:38 -0600 Subject: [PATCH 40/59] Cleanup --- scripts/suite_objects.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index c6bad633..7ef04e68 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -226,6 +226,14 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ for cnt,dim in enumerate(dimensions): if is_horizontal_dimension(dim): if self.routine.run_phase(): + # DH* Using this produces out of range exceptions. Begs a + # larger question of we should - internally - *always* use + # horizontal_loop_begin:horizontal_loop_end, regardless + # of how it is defined in the metadata (e.g. convert + # ccpp_constant_one:horizontal_loop_extent + # to + # horizontal_loop_begin:horizontal_loop_end + # for the run phase when parsing the metadata ? #if var_in_call_list and \ # self.find_variable(standard_name="horizontal_loop_extent"): # ldim = "ccpp_constant_one" @@ -233,9 +241,10 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ #else: # ldim = "horizontal_loop_begin" # udim = "horizontal_loop_end" + # endif ldim = "horizontal_loop_begin" udim = "horizontal_loop_end" - # endif + # *DH else: ldim = "ccpp_constant_one" udim = "horizontal_dimension" From 41dedc8ab4d8a0b760272e8c719c8e84fab50aed Mon Sep 17 00:00:00 2001 From: dustinswales Date: Mon, 27 Apr 2026 13:07:30 -0600 Subject: [PATCH 41/59] Revert "Merge pull request #10 from climbfuji/bug/unalloc_var_to_Group_TMP" This reverts commit ba5ca8b5f8896d7bd4ddb1d30b9191ba42c7e872, reversing changes made to 57d55bea2d170850eb768149243cb97876ecce36. --- scripts/metavar.py | 9 - scripts/suite_objects.py | 233 ++++++------------ .../test_capgen_host_integration.F90 | 102 ++++---- test/capgen_test/test_host.F90 | 2 +- test/utils/test_utils.F90 | 1 - 5 files changed, 123 insertions(+), 224 deletions(-) diff --git a/scripts/metavar.py b/scripts/metavar.py index 331fe0be..e4c2f3ad 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -699,20 +699,12 @@ def call_string(self, var_dicts, loop_vars=None): if is not None, look there first for array bounds, even if usage requires a loop substitution. """ - # DH* - if loop_vars: - raise Exception(f"DH DEBUG: WE DO USE loop_vars in call_string! '{loop_vars}'") - # *DH if not isinstance(var_dicts, list): var_dicts = [var_dicts] # end if if loop_vars is None: call_str = self.get_prop_value('local_name') # Look for dims in case this is an array selection variable - # DH* - # Will this work with something like ddt1(some_index)%nested_ddt(other_index)%myvar(dimstring)? - # Is local_name guaranteed to be just the member of the innermost DDT? - # Better to use split_dims_from_name ... dind = call_str.find('(') if dind > 0: dimstr = call_str[dind+1:].rstrip()[:-1] @@ -721,7 +713,6 @@ def call_string(self, var_dicts, loop_vars=None): else: dims = None # end if - # *DH else: call_str, dims = self.handle_array_ref() # end if diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 7ef04e68..dfbc8dd0 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -53,60 +53,6 @@ 'scheme_files':'', 'suites':''}) -# DH* MOVE THIS ELSEWHERE -def split_dims_from_name(expr: str): - """Split the full dimension specifier of the innermost variable - of a potentially nested derived data type from the variable name: - - foo%bar(idx1)%baz(idx2, idx3(idx4), idx5a(idx6):idx5b(idx6)) - --> - foo%bar(idx1)%baz AND (idx2, idx3(idx4), idx5a(idx6):idx5b(idx6)) - - Walk the string from right to left, and find the outermost ( that - starts the final argument list at depth 0.""" - name = expr - depth = 0 - dimstring = None - for i in range(len(expr)-1, -1, -1): - c = expr[i] - if c == ')': - depth += 1 - elif c == '(': - depth -= 1 - if depth == 0: - # Found the outermost opening parenthesis - name = expr[:i] - dimstring = expr[i:] - break - if not dimstring: - return name, None - - # Now turn the dimstring into a list of dimensions, - # taking into account that a dimension might have - # parentheses: (idx2, idx3(idx4), idx5a(idx6):idx5b(idx6)) - dimstring = dimstring.lstrip('(').rstrip(')') - dims = [] - current = [] - depth = 0 - for c in dimstring: - if c == '(': - depth += 1 - current.append(c) - elif c == ')': - depth -= 1 - current.append(c) - elif c == ',' and depth == 0: - dims.append(''.join(current).strip()) - current = [] - else: - current.append(c) - # Add last piece - if current: - dims.append(''.join(current).strip()) - - return name, dims -# *DH - ############################################################################### def new_suite_object(item, context, parent, run_env, loop_count=0): ############################################################################### @@ -185,7 +131,7 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ if cldicts is not None: for cldict in cldicts: dvar = cldict.find_variable(standard_name=stdname, - any_scope=False) + any_scope=True) host_var = False if dvar is not None: var_in_call_list = True @@ -193,11 +139,11 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ if dvar.components: var_in_call_list = False # end if - #else: - # # If we get here, the variable is explicitly in the Group call_list, - # # not a DDT component. No need to modify the dimensions in the Scheme - # # call list, as these were handled in the Suite Cap call_list. - # host_var = True + else: + # If we get here, the variable is explicitly in the Group call_list, + # not a DDT component. No need to modify the dimensions in the Scheme + # call list, as these were handled in the Suite Cap call_list. + host_var = True # end if break # end if @@ -218,53 +164,34 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ # local pointers of _ptr if var.get_prop_value('optional'): lname = dummy+'_ptr' - # Finally, handle the dimensions unless it's an allocatable var - elif not var.get_prop_value('allocatable'): - # DH* host_var still needed? - if dimensions and not host_var: + # end if + # Finally, handle the dimensions. + else: + if dimensions:# and not host_var: dimstr = '(' for cnt,dim in enumerate(dimensions): if is_horizontal_dimension(dim): if self.routine.run_phase(): - # DH* Using this produces out of range exceptions. Begs a - # larger question of we should - internally - *always* use - # horizontal_loop_begin:horizontal_loop_end, regardless - # of how it is defined in the metadata (e.g. convert - # ccpp_constant_one:horizontal_loop_extent - # to - # horizontal_loop_begin:horizontal_loop_end - # for the run phase when parsing the metadata ? - #if var_in_call_list and \ - # self.find_variable(standard_name="horizontal_loop_extent"): - # ldim = "ccpp_constant_one" - # udim = "horizontal_loop_extent" - #else: - # ldim = "horizontal_loop_begin" - # udim = "horizontal_loop_end" + if var_in_call_list and \ + self.find_variable(standard_name="horizontal_loop_extent"): + ldim = "ccpp_constant_one" + udim = "horizontal_loop_extent" + else: + ldim = "horizontal_loop_begin" + udim = "horizontal_loop_end" # endif - ldim = "horizontal_loop_begin" - udim = "horizontal_loop_end" - # *DH else: ldim = "ccpp_constant_one" udim = "horizontal_dimension" # endif # Get dimension for lower bound - for cldict in cldicts: - #lvar = cldict.find_variable(standard_name=ldim, any_scope=True) - lvar = cldict.find_variable(standard_name=ldim, any_scope=False) - if lvar: - break + lvar = cldict.find_variable(standard_name=ldim, any_scope=True) if not lvar: raise Exception(f"No variable with standard name '{ldim}' in cldict") # end if ldim_lname = lvar.get_prop_value('local_name') # Get dimension for upper bound - for cldict in cldicts: - #uvar = cldict.find_variable(standard_name=udim, any_scope=True) - uvar = cldict.find_variable(standard_name=udim, any_scope=False) - if uvar: - break + uvar = cldict.find_variable(standard_name=udim, any_scope=True) if not uvar: raise Exception(f"No variable with standard name '{udim}' in cldict") # end if @@ -299,22 +226,22 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ # end if # end for # end if - #if is_func_call: - # if cldicts is not None: - # use_dicts = cldicts - # else: - # use_dicts = [self] - # # end if - # run_phase = self.routine.run_phase() - # # We only need dimensions for suite variables in run phase - # need_dims = SuiteObject.is_suite_variable(dvar) and run_phase - # vdims = var.call_dimstring(var_dicts=use_dicts, - # explicit_dims=need_dims, - # loop_subst=run_phase) - # if _BLANK_DIMS_RE.match(vdims) is None: - # lname = lname + vdims - # # end if - ## end if + if is_func_call: + if cldicts is not None: + use_dicts = cldicts + else: + use_dicts = [self] + # end if + run_phase = self.routine.run_phase() + # We only need dimensions for suite variables in run phase + need_dims = SuiteObject.is_suite_variable(dvar) and run_phase + vdims = var.call_dimstring(var_dicts=use_dicts, + explicit_dims=need_dims, + loop_subst=run_phase) + if _BLANK_DIMS_RE.match(vdims) is None: + lname = lname + vdims + # end if + # end if if is_func_call: arg_str += "{}{}={}".format(arg_sep, dummy, lname) else: @@ -1380,7 +1307,7 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, dvar = self.__group.find_variable(standard_name=standard_name, any_scope=False) if not dvar: # This variable is handled by the group - # and is declared as a module variable + # and is declared as a module variable for var_dict in self.__group.suite_dicts(): dvar = var_dict.find_variable(standard_name=standard_name, any_scope=False) if dvar: @@ -1393,38 +1320,28 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, if not dvar: raise Exception(f"No variable with standard name '{standard_name}' in cldicts") # end if - # Handle the dimensions... dimensions = dvar.get_dimensions() - # Need to use local_name in Group's call list (self.__group.call_list), - # not the local_name in var. - # DH* a lot of repeated logic here ... consolidate! - if (dict_var): - (conditional, _) = dvar.conditional(cldicts) - if (has_transform): - lname = var.get_prop_value('local_name')+'_local' - else: - ddims = dvar.get_dimensions() - for i in range(len(ddims)): - dim = ddims[i] - if is_horizontal_dimension(dim): - if self.run_phase(): - if var_in_call_list and \ - self.find_variable(standard_name="horizontal_loop_extent"): - ldim = "ccpp_constant_one" - udim = "horizontal_loop_extent" - else: - ldim = "horizontal_loop_begin" - udim = "horizontal_loop_end" - # endif - else: + dimstr = '' + if dimensions: + dimstr = dimstr + '(' + for cnt,dim in enumerate(dimensions): + if is_horizontal_dimension(dim): + if self.run_phase(): + if var_in_call_list and \ + self.find_variable(standard_name="horizontal_loop_extent"): ldim = "ccpp_constant_one" - udim = "horizontal_dimension" + udim = "horizontal_loop_extent" + else: + ldim = "horizontal_loop_begin" + udim = "horizontal_loop_end" # endif else: - ldim, udim = dim.split(':') - # end if + ldim = "ccpp_constant_one" + udim = "horizontal_dimension" + # endif # Get dimension for lower bound + #lvar = self.__group.call_list.find_variable(standard_name=ldim, any_scope=True) for var_dict in cldicts: lvar = var_dict.find_variable(standard_name=ldim, any_scope=False) if lvar is not None: @@ -1446,32 +1363,23 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, raise Exception(f"No variable with standard name '{udim}' in cldicts") # end if udim_lname = uvar.get_prop_value('local_name') - ddims[i] = ldim_lname + ':' + udim_lname - # end for - lname, ldims = split_dims_from_name(dvar.call_string(search_dict)) - # This is where it gets tricky. We have a dimension specifier - # as part of the local name, and we have a dimstr. The latter - # contains the correct horizontal and vertical extents, the former - # does not - they can be ranges (containing ":") or indices - # (that is, the variable is a slice of another variable). - # We need to walk the ldims list left to right and for each - # dimension we need to check if it is a range or not. If it is - # a range, we insert the first element from ddims, then we remove - # this range from ddims and move on to the next. - if ldims: - # Consistency check: - if len(ldims) < len(ddims): - raise Exception("Logic error in associate_optional_var: len({ldims}) < len({ddims})") - for i in range(len(ldims)): - if ':' in ldims[i]: - ldims[i] = ddims.pop(0) - # At the end ddims must be empty - if ddims: - raise Exception("Logic error in associate_optional_var: after filling ldims='{ldims}' from ddims, ddims is not empty: '{ddims}'") - dimstr = '(' + ','.join(ldims) + ')' + dimstr = dimstr + ldim_lname + ':' + udim_lname else: - dimstr = '(' + ','.join(ddims) + ')' - lname += dimstr + dimstr = dimstr + ':' + # end if + if cnt < len(dimensions)-1: dimstr = dimstr+',' + # end for + dimstr = dimstr+')' + # end if + + # Need to use local_name in Group's call list (self.__group.call_list), not + # the local_name in var. + if (dict_var): + (conditional, vars_needed) = dvar.conditional(cldicts) + if (has_transform): + lname = var.get_prop_value('local_name')+'_local' + else: + lname = dvar.call_string(search_dict)+dimstr # end if lname_ptr = var.get_prop_value('local_name') + '_ptr' # Scheme has optional varaible, host has varaible defined as Conditional (Active). @@ -1484,7 +1392,6 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, outfile.write(f"{lname_ptr} => {lname}", indent) # end if # end if - # *DH # end def def nullify_optional_var(self, dict_var, var, has_transform, cldicts, indent, outfile): @@ -1638,7 +1545,6 @@ def add_var_transform(self, var, compat_obj): local_trans_var.get_prop_value('local_name'), lindices, rindices, compat_obj]) # end if - def write_var_transform(self, var, var_name, dummy, rindices, lindices, compat_obj, outfile, indent, forward, cldicts): """Write variable transformation needed to call this Scheme in . @@ -1649,6 +1555,7 @@ def write_var_transform(self, var, var_name, dummy, rindices, lindices, compat_o are the LHS indices of for forward transforms (after Scheme). are the RHS indices of for forward transforms (after Scheme). """ + # # Write reverse (pre-Scheme) transform. # @@ -1668,7 +1575,7 @@ def write_var_transform(self, var, var_name, dummy, rindices, lindices, compat_o lvar_indices=rindices, rvar_indices=lindices) # end if - + (conditional, vars_needed) = var.conditional(cldicts) if conditional != '.true.': outfile.write(f"if {conditional} then", indent) diff --git a/test/capgen_test/test_capgen_host_integration.F90 b/test/capgen_test/test_capgen_host_integration.F90 index b8fdb3fc..f9cb1410 100644 --- a/test/capgen_test/test_capgen_host_integration.F90 +++ b/test/capgen_test/test_capgen_host_integration.F90 @@ -5,62 +5,64 @@ program test character(len=cs), target :: test_parts1(2) = (/ 'physics1 ', & 'physics2 ' /) character(len=cs), target :: test_parts2(1) = (/ 'data_prep ' /) - character(len=cm), target :: test_invars1(10) = (/ & - 'array_variable_for_testing ', & - 'coefficients_for_interpolation ', & - 'physics_state_derived_type ', & - 'potential_temperature ', & - 'potential_temperature_at_interface ', & - 'temperature_at_diagnostic_levels ', & - 'index_of_water_vapor_specific_humidity ', & - 'do_cloud_fraction_adjustment ', & - 'potential_temperature_increment ', & - 'time_step_for_physics '/) - - character(len=cm), target :: test_outvars1(9) = (/ & - 'array_variable_for_testing ', & - 'coefficients_for_interpolation ', & - 'physics_state_derived_type ', & - 'potential_temperature ', & - 'potential_temperature_at_interface ', & - 'temperature_at_diagnostic_levels ', & - 'index_of_water_vapor_specific_humidity ', & - 'ccpp_error_code ', & - 'ccpp_error_message '/) - - character(len=cm), target :: test_reqvars1(12) = (/ & - 'array_variable_for_testing ', & - 'coefficients_for_interpolation ', & - 'physics_state_derived_type ', & - 'potential_temperature ', & - 'potential_temperature_at_interface ', & - 'temperature_at_diagnostic_levels ', & - 'index_of_water_vapor_specific_humidity ', & - 'do_cloud_fraction_adjustment ', & - 'potential_temperature_increment ', & - 'time_step_for_physics ', & - 'ccpp_error_code ', & - 'ccpp_error_message '/) + character(len=cm), target :: test_invars1(12) = (/ & + 'potential_temperature ', & + 'potential_temperature_at_interface ', & + 'coefficients_for_interpolation ', & + 'surface_air_pressure ', & + 'water_vapor_specific_humidity ', & + 'potential_temperature_increment ', & + 'soil_levels ', & + 'temperature_at_diagnostic_levels ', & + 'time_step_for_physics ', & + 'array_variable_for_testing ', & + 'cloud_fraction ', & + 'do_cloud_fraction_adjustment '/) + character(len=cm), target :: test_outvars1(10) = (/ & + 'potential_temperature ', & + 'potential_temperature_at_interface ', & + 'coefficients_for_interpolation ', & + 'surface_air_pressure ', & + 'water_vapor_specific_humidity ', & + 'soil_levels ', & + 'temperature_at_diagnostic_levels ', & + 'ccpp_error_code ', & + 'ccpp_error_message ', & + 'array_variable_for_testing ' /) + character(len=cm), target :: test_reqvars1(14) = (/ & + 'potential_temperature ', & + 'potential_temperature_at_interface ', & + 'coefficients_for_interpolation ', & + 'surface_air_pressure ', & + 'water_vapor_specific_humidity ', & + 'potential_temperature_increment ', & + 'time_step_for_physics ', & + 'soil_levels ', & + 'temperature_at_diagnostic_levels ', & + 'ccpp_error_code ', & + 'ccpp_error_message ', & + 'array_variable_for_testing ', & + 'cloud_fraction ', & + 'do_cloud_fraction_adjustment '/) character(len=cm), target :: test_invars2(3) = (/ & - 'model_times ', & - 'number_of_model_times ', & - 'physics_state_derived_type ' /) + 'model_times ', & + 'number_of_model_times ', & + 'surface_air_pressure ' /) character(len=cm), target :: test_outvars2(5) = (/ & - 'number_of_model_times ', & - 'physics_state_derived_type ', & - 'ccpp_error_code ', & - 'ccpp_error_message ', & - 'model_times ' /) + 'ccpp_error_code ', & + 'ccpp_error_message ', & + 'model_times ', & + 'surface_air_pressure ', & + 'number_of_model_times ' /) character(len=cm), target :: test_reqvars2(5) = (/ & - 'number_of_model_times ', & - 'physics_state_derived_type ', & - 'model_times ', & - 'ccpp_error_code ', & - 'ccpp_error_message ' /) - + 'model_times ', & + 'number_of_model_times ', & + 'surface_air_pressure ', & + 'ccpp_error_code ', & + 'ccpp_error_message ' /) type(suite_info) :: test_suites(2) logical :: run_okay diff --git a/test/capgen_test/test_host.F90 b/test/capgen_test/test_host.F90 index 3a95d879..6e39c787 100644 --- a/test/capgen_test/test_host.F90 +++ b/test/capgen_test/test_host.F90 @@ -9,7 +9,7 @@ module test_prog ! Public data and interfaces integer, public, parameter :: cs = 16 - integer, public, parameter :: cm = 64 + integer, public, parameter :: cm = 36 !> \section arg_table_suite_info Argument Table !! \htmlinclude arg_table_suite_info.html diff --git a/test/utils/test_utils.F90 b/test/utils/test_utils.F90 index 8179ce1d..088c347d 100644 --- a/test/utils/test_utils.F90 +++ b/test/utils/test_utils.F90 @@ -34,7 +34,6 @@ logical function check_list(test_list, chk_list, list_desc, suite_name) end if write(errmsg(len_trim(errmsg)+1:), '(a,i0)') ', should be ', num_items write(6, *) trim(errmsg) - write(6,*) test_list errmsg = '' check_list = .false. end if From 385e0047c43099257638f11063e72b36261a6fc7 Mon Sep 17 00:00:00 2001 From: dustinswales Date: Mon, 27 Apr 2026 13:12:58 -0600 Subject: [PATCH 42/59] Reapply "Merge pull request #10 from climbfuji/bug/unalloc_var_to_Group_TMP" This reverts commit 41dedc8ab4d8a0b760272e8c719c8e84fab50aed. --- scripts/metavar.py | 9 + scripts/suite_objects.py | 233 ++++++++++++------ .../test_capgen_host_integration.F90 | 102 ++++---- test/capgen_test/test_host.F90 | 2 +- test/utils/test_utils.F90 | 1 + 5 files changed, 224 insertions(+), 123 deletions(-) diff --git a/scripts/metavar.py b/scripts/metavar.py index e4c2f3ad..331fe0be 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -699,12 +699,20 @@ def call_string(self, var_dicts, loop_vars=None): if is not None, look there first for array bounds, even if usage requires a loop substitution. """ + # DH* + if loop_vars: + raise Exception(f"DH DEBUG: WE DO USE loop_vars in call_string! '{loop_vars}'") + # *DH if not isinstance(var_dicts, list): var_dicts = [var_dicts] # end if if loop_vars is None: call_str = self.get_prop_value('local_name') # Look for dims in case this is an array selection variable + # DH* + # Will this work with something like ddt1(some_index)%nested_ddt(other_index)%myvar(dimstring)? + # Is local_name guaranteed to be just the member of the innermost DDT? + # Better to use split_dims_from_name ... dind = call_str.find('(') if dind > 0: dimstr = call_str[dind+1:].rstrip()[:-1] @@ -713,6 +721,7 @@ def call_string(self, var_dicts, loop_vars=None): else: dims = None # end if + # *DH else: call_str, dims = self.handle_array_ref() # end if diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index dfbc8dd0..7ef04e68 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -53,6 +53,60 @@ 'scheme_files':'', 'suites':''}) +# DH* MOVE THIS ELSEWHERE +def split_dims_from_name(expr: str): + """Split the full dimension specifier of the innermost variable + of a potentially nested derived data type from the variable name: + + foo%bar(idx1)%baz(idx2, idx3(idx4), idx5a(idx6):idx5b(idx6)) + --> + foo%bar(idx1)%baz AND (idx2, idx3(idx4), idx5a(idx6):idx5b(idx6)) + + Walk the string from right to left, and find the outermost ( that + starts the final argument list at depth 0.""" + name = expr + depth = 0 + dimstring = None + for i in range(len(expr)-1, -1, -1): + c = expr[i] + if c == ')': + depth += 1 + elif c == '(': + depth -= 1 + if depth == 0: + # Found the outermost opening parenthesis + name = expr[:i] + dimstring = expr[i:] + break + if not dimstring: + return name, None + + # Now turn the dimstring into a list of dimensions, + # taking into account that a dimension might have + # parentheses: (idx2, idx3(idx4), idx5a(idx6):idx5b(idx6)) + dimstring = dimstring.lstrip('(').rstrip(')') + dims = [] + current = [] + depth = 0 + for c in dimstring: + if c == '(': + depth += 1 + current.append(c) + elif c == ')': + depth -= 1 + current.append(c) + elif c == ',' and depth == 0: + dims.append(''.join(current).strip()) + current = [] + else: + current.append(c) + # Add last piece + if current: + dims.append(''.join(current).strip()) + + return name, dims +# *DH + ############################################################################### def new_suite_object(item, context, parent, run_env, loop_count=0): ############################################################################### @@ -131,7 +185,7 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ if cldicts is not None: for cldict in cldicts: dvar = cldict.find_variable(standard_name=stdname, - any_scope=True) + any_scope=False) host_var = False if dvar is not None: var_in_call_list = True @@ -139,11 +193,11 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ if dvar.components: var_in_call_list = False # end if - else: - # If we get here, the variable is explicitly in the Group call_list, - # not a DDT component. No need to modify the dimensions in the Scheme - # call list, as these were handled in the Suite Cap call_list. - host_var = True + #else: + # # If we get here, the variable is explicitly in the Group call_list, + # # not a DDT component. No need to modify the dimensions in the Scheme + # # call list, as these were handled in the Suite Cap call_list. + # host_var = True # end if break # end if @@ -164,34 +218,53 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ # local pointers of _ptr if var.get_prop_value('optional'): lname = dummy+'_ptr' - # end if - # Finally, handle the dimensions. - else: - if dimensions:# and not host_var: + # Finally, handle the dimensions unless it's an allocatable var + elif not var.get_prop_value('allocatable'): + # DH* host_var still needed? + if dimensions and not host_var: dimstr = '(' for cnt,dim in enumerate(dimensions): if is_horizontal_dimension(dim): if self.routine.run_phase(): - if var_in_call_list and \ - self.find_variable(standard_name="horizontal_loop_extent"): - ldim = "ccpp_constant_one" - udim = "horizontal_loop_extent" - else: - ldim = "horizontal_loop_begin" - udim = "horizontal_loop_end" + # DH* Using this produces out of range exceptions. Begs a + # larger question of we should - internally - *always* use + # horizontal_loop_begin:horizontal_loop_end, regardless + # of how it is defined in the metadata (e.g. convert + # ccpp_constant_one:horizontal_loop_extent + # to + # horizontal_loop_begin:horizontal_loop_end + # for the run phase when parsing the metadata ? + #if var_in_call_list and \ + # self.find_variable(standard_name="horizontal_loop_extent"): + # ldim = "ccpp_constant_one" + # udim = "horizontal_loop_extent" + #else: + # ldim = "horizontal_loop_begin" + # udim = "horizontal_loop_end" # endif + ldim = "horizontal_loop_begin" + udim = "horizontal_loop_end" + # *DH else: ldim = "ccpp_constant_one" udim = "horizontal_dimension" # endif # Get dimension for lower bound - lvar = cldict.find_variable(standard_name=ldim, any_scope=True) + for cldict in cldicts: + #lvar = cldict.find_variable(standard_name=ldim, any_scope=True) + lvar = cldict.find_variable(standard_name=ldim, any_scope=False) + if lvar: + break if not lvar: raise Exception(f"No variable with standard name '{ldim}' in cldict") # end if ldim_lname = lvar.get_prop_value('local_name') # Get dimension for upper bound - uvar = cldict.find_variable(standard_name=udim, any_scope=True) + for cldict in cldicts: + #uvar = cldict.find_variable(standard_name=udim, any_scope=True) + uvar = cldict.find_variable(standard_name=udim, any_scope=False) + if uvar: + break if not uvar: raise Exception(f"No variable with standard name '{udim}' in cldict") # end if @@ -226,22 +299,22 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ # end if # end for # end if - if is_func_call: - if cldicts is not None: - use_dicts = cldicts - else: - use_dicts = [self] - # end if - run_phase = self.routine.run_phase() - # We only need dimensions for suite variables in run phase - need_dims = SuiteObject.is_suite_variable(dvar) and run_phase - vdims = var.call_dimstring(var_dicts=use_dicts, - explicit_dims=need_dims, - loop_subst=run_phase) - if _BLANK_DIMS_RE.match(vdims) is None: - lname = lname + vdims - # end if - # end if + #if is_func_call: + # if cldicts is not None: + # use_dicts = cldicts + # else: + # use_dicts = [self] + # # end if + # run_phase = self.routine.run_phase() + # # We only need dimensions for suite variables in run phase + # need_dims = SuiteObject.is_suite_variable(dvar) and run_phase + # vdims = var.call_dimstring(var_dicts=use_dicts, + # explicit_dims=need_dims, + # loop_subst=run_phase) + # if _BLANK_DIMS_RE.match(vdims) is None: + # lname = lname + vdims + # # end if + ## end if if is_func_call: arg_str += "{}{}={}".format(arg_sep, dummy, lname) else: @@ -1307,7 +1380,7 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, dvar = self.__group.find_variable(standard_name=standard_name, any_scope=False) if not dvar: # This variable is handled by the group - # and is declared as a module variable + # and is declared as a module variable for var_dict in self.__group.suite_dicts(): dvar = var_dict.find_variable(standard_name=standard_name, any_scope=False) if dvar: @@ -1320,28 +1393,38 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, if not dvar: raise Exception(f"No variable with standard name '{standard_name}' in cldicts") # end if + # Handle the dimensions... dimensions = dvar.get_dimensions() - dimstr = '' - if dimensions: - dimstr = dimstr + '(' - for cnt,dim in enumerate(dimensions): - if is_horizontal_dimension(dim): - if self.run_phase(): - if var_in_call_list and \ - self.find_variable(standard_name="horizontal_loop_extent"): - ldim = "ccpp_constant_one" - udim = "horizontal_loop_extent" + # Need to use local_name in Group's call list (self.__group.call_list), + # not the local_name in var. + # DH* a lot of repeated logic here ... consolidate! + if (dict_var): + (conditional, _) = dvar.conditional(cldicts) + if (has_transform): + lname = var.get_prop_value('local_name')+'_local' + else: + ddims = dvar.get_dimensions() + for i in range(len(ddims)): + dim = ddims[i] + if is_horizontal_dimension(dim): + if self.run_phase(): + if var_in_call_list and \ + self.find_variable(standard_name="horizontal_loop_extent"): + ldim = "ccpp_constant_one" + udim = "horizontal_loop_extent" + else: + ldim = "horizontal_loop_begin" + udim = "horizontal_loop_end" + # endif else: - ldim = "horizontal_loop_begin" - udim = "horizontal_loop_end" + ldim = "ccpp_constant_one" + udim = "horizontal_dimension" # endif else: - ldim = "ccpp_constant_one" - udim = "horizontal_dimension" - # endif + ldim, udim = dim.split(':') + # end if # Get dimension for lower bound - #lvar = self.__group.call_list.find_variable(standard_name=ldim, any_scope=True) for var_dict in cldicts: lvar = var_dict.find_variable(standard_name=ldim, any_scope=False) if lvar is not None: @@ -1363,23 +1446,32 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, raise Exception(f"No variable with standard name '{udim}' in cldicts") # end if udim_lname = uvar.get_prop_value('local_name') - dimstr = dimstr + ldim_lname + ':' + udim_lname + ddims[i] = ldim_lname + ':' + udim_lname + # end for + lname, ldims = split_dims_from_name(dvar.call_string(search_dict)) + # This is where it gets tricky. We have a dimension specifier + # as part of the local name, and we have a dimstr. The latter + # contains the correct horizontal and vertical extents, the former + # does not - they can be ranges (containing ":") or indices + # (that is, the variable is a slice of another variable). + # We need to walk the ldims list left to right and for each + # dimension we need to check if it is a range or not. If it is + # a range, we insert the first element from ddims, then we remove + # this range from ddims and move on to the next. + if ldims: + # Consistency check: + if len(ldims) < len(ddims): + raise Exception("Logic error in associate_optional_var: len({ldims}) < len({ddims})") + for i in range(len(ldims)): + if ':' in ldims[i]: + ldims[i] = ddims.pop(0) + # At the end ddims must be empty + if ddims: + raise Exception("Logic error in associate_optional_var: after filling ldims='{ldims}' from ddims, ddims is not empty: '{ddims}'") + dimstr = '(' + ','.join(ldims) + ')' else: - dimstr = dimstr + ':' - # end if - if cnt < len(dimensions)-1: dimstr = dimstr+',' - # end for - dimstr = dimstr+')' - # end if - - # Need to use local_name in Group's call list (self.__group.call_list), not - # the local_name in var. - if (dict_var): - (conditional, vars_needed) = dvar.conditional(cldicts) - if (has_transform): - lname = var.get_prop_value('local_name')+'_local' - else: - lname = dvar.call_string(search_dict)+dimstr + dimstr = '(' + ','.join(ddims) + ')' + lname += dimstr # end if lname_ptr = var.get_prop_value('local_name') + '_ptr' # Scheme has optional varaible, host has varaible defined as Conditional (Active). @@ -1392,6 +1484,7 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, outfile.write(f"{lname_ptr} => {lname}", indent) # end if # end if + # *DH # end def def nullify_optional_var(self, dict_var, var, has_transform, cldicts, indent, outfile): @@ -1545,6 +1638,7 @@ def add_var_transform(self, var, compat_obj): local_trans_var.get_prop_value('local_name'), lindices, rindices, compat_obj]) # end if + def write_var_transform(self, var, var_name, dummy, rindices, lindices, compat_obj, outfile, indent, forward, cldicts): """Write variable transformation needed to call this Scheme in . @@ -1555,7 +1649,6 @@ def write_var_transform(self, var, var_name, dummy, rindices, lindices, compat_o are the LHS indices of for forward transforms (after Scheme). are the RHS indices of for forward transforms (after Scheme). """ - # # Write reverse (pre-Scheme) transform. # @@ -1575,7 +1668,7 @@ def write_var_transform(self, var, var_name, dummy, rindices, lindices, compat_o lvar_indices=rindices, rvar_indices=lindices) # end if - + (conditional, vars_needed) = var.conditional(cldicts) if conditional != '.true.': outfile.write(f"if {conditional} then", indent) diff --git a/test/capgen_test/test_capgen_host_integration.F90 b/test/capgen_test/test_capgen_host_integration.F90 index f9cb1410..b8fdb3fc 100644 --- a/test/capgen_test/test_capgen_host_integration.F90 +++ b/test/capgen_test/test_capgen_host_integration.F90 @@ -5,64 +5,62 @@ program test character(len=cs), target :: test_parts1(2) = (/ 'physics1 ', & 'physics2 ' /) character(len=cs), target :: test_parts2(1) = (/ 'data_prep ' /) - character(len=cm), target :: test_invars1(12) = (/ & - 'potential_temperature ', & - 'potential_temperature_at_interface ', & - 'coefficients_for_interpolation ', & - 'surface_air_pressure ', & - 'water_vapor_specific_humidity ', & - 'potential_temperature_increment ', & - 'soil_levels ', & - 'temperature_at_diagnostic_levels ', & - 'time_step_for_physics ', & - 'array_variable_for_testing ', & - 'cloud_fraction ', & - 'do_cloud_fraction_adjustment '/) - character(len=cm), target :: test_outvars1(10) = (/ & - 'potential_temperature ', & - 'potential_temperature_at_interface ', & - 'coefficients_for_interpolation ', & - 'surface_air_pressure ', & - 'water_vapor_specific_humidity ', & - 'soil_levels ', & - 'temperature_at_diagnostic_levels ', & - 'ccpp_error_code ', & - 'ccpp_error_message ', & - 'array_variable_for_testing ' /) - character(len=cm), target :: test_reqvars1(14) = (/ & - 'potential_temperature ', & - 'potential_temperature_at_interface ', & - 'coefficients_for_interpolation ', & - 'surface_air_pressure ', & - 'water_vapor_specific_humidity ', & - 'potential_temperature_increment ', & - 'time_step_for_physics ', & - 'soil_levels ', & - 'temperature_at_diagnostic_levels ', & - 'ccpp_error_code ', & - 'ccpp_error_message ', & - 'array_variable_for_testing ', & - 'cloud_fraction ', & - 'do_cloud_fraction_adjustment '/) + character(len=cm), target :: test_invars1(10) = (/ & + 'array_variable_for_testing ', & + 'coefficients_for_interpolation ', & + 'physics_state_derived_type ', & + 'potential_temperature ', & + 'potential_temperature_at_interface ', & + 'temperature_at_diagnostic_levels ', & + 'index_of_water_vapor_specific_humidity ', & + 'do_cloud_fraction_adjustment ', & + 'potential_temperature_increment ', & + 'time_step_for_physics '/) + + character(len=cm), target :: test_outvars1(9) = (/ & + 'array_variable_for_testing ', & + 'coefficients_for_interpolation ', & + 'physics_state_derived_type ', & + 'potential_temperature ', & + 'potential_temperature_at_interface ', & + 'temperature_at_diagnostic_levels ', & + 'index_of_water_vapor_specific_humidity ', & + 'ccpp_error_code ', & + 'ccpp_error_message '/) + + character(len=cm), target :: test_reqvars1(12) = (/ & + 'array_variable_for_testing ', & + 'coefficients_for_interpolation ', & + 'physics_state_derived_type ', & + 'potential_temperature ', & + 'potential_temperature_at_interface ', & + 'temperature_at_diagnostic_levels ', & + 'index_of_water_vapor_specific_humidity ', & + 'do_cloud_fraction_adjustment ', & + 'potential_temperature_increment ', & + 'time_step_for_physics ', & + 'ccpp_error_code ', & + 'ccpp_error_message '/) character(len=cm), target :: test_invars2(3) = (/ & - 'model_times ', & - 'number_of_model_times ', & - 'surface_air_pressure ' /) + 'model_times ', & + 'number_of_model_times ', & + 'physics_state_derived_type ' /) character(len=cm), target :: test_outvars2(5) = (/ & - 'ccpp_error_code ', & - 'ccpp_error_message ', & - 'model_times ', & - 'surface_air_pressure ', & - 'number_of_model_times ' /) + 'number_of_model_times ', & + 'physics_state_derived_type ', & + 'ccpp_error_code ', & + 'ccpp_error_message ', & + 'model_times ' /) character(len=cm), target :: test_reqvars2(5) = (/ & - 'model_times ', & - 'number_of_model_times ', & - 'surface_air_pressure ', & - 'ccpp_error_code ', & - 'ccpp_error_message ' /) + 'number_of_model_times ', & + 'physics_state_derived_type ', & + 'model_times ', & + 'ccpp_error_code ', & + 'ccpp_error_message ' /) + type(suite_info) :: test_suites(2) logical :: run_okay diff --git a/test/capgen_test/test_host.F90 b/test/capgen_test/test_host.F90 index 6e39c787..3a95d879 100644 --- a/test/capgen_test/test_host.F90 +++ b/test/capgen_test/test_host.F90 @@ -9,7 +9,7 @@ module test_prog ! Public data and interfaces integer, public, parameter :: cs = 16 - integer, public, parameter :: cm = 36 + integer, public, parameter :: cm = 64 !> \section arg_table_suite_info Argument Table !! \htmlinclude arg_table_suite_info.html diff --git a/test/utils/test_utils.F90 b/test/utils/test_utils.F90 index 088c347d..8179ce1d 100644 --- a/test/utils/test_utils.F90 +++ b/test/utils/test_utils.F90 @@ -34,6 +34,7 @@ logical function check_list(test_list, chk_list, list_desc, suite_name) end if write(errmsg(len_trim(errmsg)+1:), '(a,i0)') ', should be ', num_items write(6, *) trim(errmsg) + write(6,*) test_list errmsg = '' check_list = .false. end if From b6c255942f268ac0367e296e28e3dd162bdfdc19 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Mon, 27 Apr 2026 13:18:32 -0600 Subject: [PATCH 43/59] Repair var_compatibility_test and nested_suite_test --- scripts/suite_objects.py | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 7ef04e68..d58fa182 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -183,26 +183,32 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ dummy = var.get_prop_value('local_name') # Now, find the local variable name if cldicts is not None: + # DH* I believe this can be folded into find_variable + # to ALWAYS search with any_scope=False first, and if + # not found look with any_scope=True if the argument + # any_scope=True is passed to find_variable for cldict in cldicts: dvar = cldict.find_variable(standard_name=stdname, any_scope=False) - host_var = False - if dvar is not None: - var_in_call_list = True - if dvar.is_ddt(): - if dvar.components: - var_in_call_list = False - # end if - #else: - # # If we get here, the variable is explicitly in the Group call_list, - # # not a DDT component. No need to modify the dimensions in the Scheme - # # call list, as these were handled in the Suite Cap call_list. - # host_var = True - # end if + if dvar: break - # end if # end for - if dvar is None: + if not dvar: + for cldict in cldicts: + dvar = cldict.find_variable(standard_name=stdname, + any_scope=True) + if dvar: + break + # end for + # end if + # *DH + if dvar is not None: + var_in_call_list = True + if dvar.is_ddt(): + if dvar.components: + var_in_call_list = False + # end if + else: if subname is not None: errmsg = "{}: ".format(subname) else: @@ -212,6 +218,7 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ clnames = [x.name for x in cldicts] raise CCPPError(errmsg.format(stdname, clnames)) # end if + dimensions = dvar.get_dimensions() lname = dvar.call_string(cldicts) # Optional variables in the caps are associated with @@ -220,8 +227,7 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ lname = dummy+'_ptr' # Finally, handle the dimensions unless it's an allocatable var elif not var.get_prop_value('allocatable'): - # DH* host_var still needed? - if dimensions and not host_var: + if dimensions: dimstr = '(' for cnt,dim in enumerate(dimensions): if is_horizontal_dimension(dim): From 1bfa9aab92a309c1d9414fc54dc4ec1cfd9d13ce Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Mon, 27 Apr 2026 13:56:05 -0600 Subject: [PATCH 44/59] Update test/advection_test/test_advection_host_integration.F90 --- .../test_advection_host_integration.F90 | 67 ++++++++----------- 1 file changed, 29 insertions(+), 38 deletions(-) diff --git a/test/advection_test/test_advection_host_integration.F90 b/test/advection_test/test_advection_host_integration.F90 index 728137fa..c9822d68 100644 --- a/test/advection_test/test_advection_host_integration.F90 +++ b/test/advection_test/test_advection_host_integration.F90 @@ -4,60 +4,51 @@ program test implicit none character(len=cs), target :: test_parts1(1) - character(len=cm), target :: test_invars1(12) - character(len=cm), target :: test_outvars1(13) - character(len=cm), target :: test_reqvars1(18) + character(len=cm), target :: test_invars1(8) + character(len=cm), target :: test_outvars1(11) + character(len=cm), target :: test_reqvars1(14) type(suite_info) :: test_suites(1) logical :: run_okay test_parts1 = (/ 'physics '/) - test_invars1 = (/ & - 'banana_array_dim ', & + + test_invars1 = (/ & + 'ccpp_model_constituents_object ', & 'cloud_ice_dry_mixing_ratio ', & 'cloud_liquid_dry_mixing_ratio ', & + 'physics_state_derived_type ', & 'tendency_of_cloud_liquid_dry_mixing_ratio', & - 'surface_air_pressure ', & - 'temperature ', & + 'banana_array_dim ', & 'time_step_for_physics ', & - 'water_temperature_at_freezing ', & - 'ccpp_constituent_tendencies ', & - 'ccpp_constituents ', & - 'number_of_ccpp_constituents ', & - 'water_vapor_specific_humidity ' /) - test_outvars1 = (/ & - 'ccpp_error_message ', & - 'ccpp_error_code ', & - 'temperature ', & - 'water_vapor_specific_humidity ', & + 'water_temperature_at_freezing ' /) + test_outvars1 = (/ & + 'ccpp_model_constituents_object ', & + 'cloud_ice_dry_mixing_ratio ', & 'cloud_liquid_dry_mixing_ratio ', & - 'ccpp_constituent_tendencies ', & - 'ccpp_constituents ', & - 'dynamic_constituents_for_cld_liq ', & - 'dynamic_constituents_for_cld_ice ', & + 'physics_state_derived_type ', & 'tendency_of_cloud_liquid_dry_mixing_ratio', & + 'ccpp_error_code ', & + 'ccpp_error_message ', & + 'dynamic_constituents_for_cld_ice ', & + 'dynamic_constituents_for_cld_liq ', & 'test_banana_constituent_index ', & - 'test_banana_constituent_indices ', & - 'cloud_ice_dry_mixing_ratio ' /) - test_reqvars1 = (/ & - 'banana_array_dim ', & - 'surface_air_pressure ', & - 'temperature ', & - 'time_step_for_physics ', & + 'test_banana_constituent_indices ' /) + test_reqvars1 = (/ & + 'ccpp_model_constituents_object ', & + 'cloud_ice_dry_mixing_ratio ', & 'cloud_liquid_dry_mixing_ratio ', & + 'physics_state_derived_type ', & 'tendency_of_cloud_liquid_dry_mixing_ratio', & - 'cloud_ice_dry_mixing_ratio ', & - 'dynamic_constituents_for_cld_liq ', & - 'dynamic_constituents_for_cld_ice ', & + 'banana_array_dim ', & + 'time_step_for_physics ', & 'water_temperature_at_freezing ', & - 'ccpp_constituent_tendencies ', & - 'ccpp_constituents ', & - 'number_of_ccpp_constituents ', & - 'test_banana_constituent_index ', & - 'test_banana_constituent_indices ', & - 'water_vapor_specific_humidity ', & + 'ccpp_error_code ', & 'ccpp_error_message ', & - 'ccpp_error_code ' /) + 'dynamic_constituents_for_cld_ice ', & + 'dynamic_constituents_for_cld_liq ', & + 'test_banana_constituent_index ', & + 'test_banana_constituent_indices ' /) ! Setup expected test suite info test_suites(1)%suite_name = 'cld_suite' From 00b8dd0f34aecf9717e0eb767e3eeb2352ee12c6 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Mon, 27 Apr 2026 13:58:23 -0600 Subject: [PATCH 45/59] Fix advection test --- scripts/suite_objects.py | 44 ++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index d58fa182..31f44b1c 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -191,6 +191,7 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ dvar = cldict.find_variable(standard_name=stdname, any_scope=False) if dvar: + search_dict = cldict break # end for if not dvar: @@ -198,6 +199,7 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ dvar = cldict.find_variable(standard_name=stdname, any_scope=True) if dvar: + search_dict = cldict break # end for # end if @@ -227,10 +229,11 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ lname = dummy+'_ptr' # Finally, handle the dimensions unless it's an allocatable var elif not var.get_prop_value('allocatable'): + # DH* Consolidate this with the logic below for optional arguments ... if dimensions: - dimstr = '(' - for cnt,dim in enumerate(dimensions): - if is_horizontal_dimension(dim): + ddims = [] + for i in range(len(dimensions)): + if is_horizontal_dimension(dimensions[i]): if self.routine.run_phase(): # DH* Using this produces out of range exceptions. Begs a # larger question of we should - internally - *always* use @@ -275,14 +278,39 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ raise Exception(f"No variable with standard name '{udim}' in cldict") # end if udim_lname = uvar.get_prop_value('local_name') - dimstr = dimstr + ldim_lname + ':' + udim_lname + ddims.append(ldim_lname + ':' + udim_lname) else: - dimstr = dimstr + ':' + # DH* TODO - explicit lbound and ubound as for optional args below + ddims.append(':') + # *DH # endif - if cnt < len(dimensions)-1: dimstr = dimstr+',' # end for - dimstr = dimstr+')' - lname = lname + dimstr + + lname, ldims = split_dims_from_name(dvar.call_string(search_dict)) + # This is where it gets tricky. We have a dimension specifier + # as part of the local name, and we have a dimstr. The latter + # contains the correct horizontal and vertical extents, the former + # does not - they can be ranges (containing ":") or indices + # (that is, the variable is a slice of another variable). + # We need to walk the ldims list left to right and for each + # dimension we need to check if it is a range or not. If it is + # a range, we insert the first element from ddims, then we remove + # this range from ddims and move on to the next. + if ldims: + # Consistency check: + if len(ldims) < len(ddims): + raise Exception("Logic error in associate_optional_var: len({ldims}) < len({ddims})") + for i in range(len(ldims)): + if ':' in ldims[i]: + ldims[i] = ddims.pop(0) + # At the end ddims must be empty + if ddims: + raise Exception("Logic error in associate_optional_var: after filling ldims='{ldims}' from ddims, ddims is not empty: '{ddims}'") + dimstr = '(' + ','.join(ldims) + ')' + else: + dimstr = '(' + ','.join(ddims) + ')' + lname += dimstr + # end if # end if else: From 2a9d2df0107f94384a8970d968baa4009db2dcd5 Mon Sep 17 00:00:00 2001 From: dustinswales Date: Mon, 27 Apr 2026 14:46:32 -0600 Subject: [PATCH 46/59] DDT test passing --- scripts/suite_objects.py | 4 +- .../test_ddt_host_integration.F90 | 88 ++++++++++--------- test/ddthost_test/test_host.F90 | 2 +- 3 files changed, 49 insertions(+), 45 deletions(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 31f44b1c..2985d076 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -267,7 +267,7 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ if not lvar: raise Exception(f"No variable with standard name '{ldim}' in cldict") # end if - ldim_lname = lvar.get_prop_value('local_name') + ldim_lname = lvar.call_string('local_name') # Get dimension for upper bound for cldict in cldicts: #uvar = cldict.find_variable(standard_name=udim, any_scope=True) @@ -277,7 +277,7 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ if not uvar: raise Exception(f"No variable with standard name '{udim}' in cldict") # end if - udim_lname = uvar.get_prop_value('local_name') + udim_lname = uvar.call_string('local_name') ddims.append(ldim_lname + ':' + udim_lname) else: # DH* TODO - explicit lbound and ubound as for optional args below diff --git a/test/ddthost_test/test_ddt_host_integration.F90 b/test/ddthost_test/test_ddt_host_integration.F90 index 23a0e53c..9845631e 100644 --- a/test/ddthost_test/test_ddt_host_integration.F90 +++ b/test/ddthost_test/test_ddt_host_integration.F90 @@ -6,53 +6,57 @@ program test character(len=cs), target :: test_parts1(2) = (/ 'physics1 ', & 'physics2 ' /) character(len=cs), target :: test_parts2(1) = (/ 'data_prep ' /) - character(len=cm), target :: test_invars1(7) = (/ & - 'potential_temperature ', & - 'potential_temperature_at_interface ', & - 'coefficients_for_interpolation ', & - 'surface_air_pressure ', & - 'water_vapor_specific_humidity ', & - 'potential_temperature_increment ', & - 'time_step_for_physics ' /) - character(len=cm), target :: test_outvars1(7) = (/ & - 'potential_temperature ', & - 'potential_temperature_at_interface ', & - 'coefficients_for_interpolation ', & - 'surface_air_pressure ', & - 'water_vapor_specific_humidity ', & - 'ccpp_error_code ', & - 'ccpp_error_message ' /) - character(len=cm), target :: test_reqvars1(9) = (/ & - 'potential_temperature ', & - 'potential_temperature_at_interface ', & - 'coefficients_for_interpolation ', & - 'surface_air_pressure ', & - 'water_vapor_specific_humidity ', & - 'potential_temperature_increment ', & - 'time_step_for_physics ', & - 'ccpp_error_code ', & - 'ccpp_error_message ' /) + character(len=cm), target :: test_invars1(8) = (/ & + 'potential_temperature ', & + 'potential_temperature_at_interface ', & + 'coefficients_for_interpolation ', & + 'index_of_water_vapor_specific_humidity', & + 'host_standard_ccpp_type ', & + 'potential_temperature_increment ', & + 'physics_state_derived_type ', & + 'time_step_for_physics ' /) + character(len=cm), target :: test_outvars1(8) = (/ & + 'potential_temperature ', & + 'potential_temperature_at_interface ', & + 'coefficients_for_interpolation ', & + 'index_of_water_vapor_specific_humidity', & + 'host_standard_ccpp_type ', & + 'physics_state_derived_type ', & + 'ccpp_error_code ', & + 'ccpp_error_message ' /) + character(len=cm), target :: test_reqvars1(10) = (/ & + 'potential_temperature ', & + 'potential_temperature_at_interface ', & + 'coefficients_for_interpolation ', & + 'index_of_water_vapor_specific_humidity', & + 'host_standard_ccpp_type ', & + 'potential_temperature_increment ', & + 'physics_state_derived_type ', & + 'time_step_for_physics ', & + 'ccpp_error_code ', & + 'ccpp_error_message ' /) character(len=cm), target :: test_invars2(4) = (/ & - 'model_times ', & - 'number_of_model_times ', & - 'surface_air_pressure ', & - 'host_standard_ccpp_type ' /) + 'model_times ', & + 'number_of_model_times ', & + 'physics_state_derived_type ', & + 'host_standard_ccpp_type ' /) - character(len=cm), target :: test_outvars2(5) = (/ & - 'ccpp_error_code ', & - 'ccpp_error_message ', & - 'model_times ', & - 'surface_air_pressure ', & - 'number_of_model_times ' /) + character(len=cm), target :: test_outvars2(6) = (/ & + 'ccpp_error_code ', & + 'ccpp_error_message ', & + 'model_times ', & + 'physics_state_derived_type ', & + 'host_standard_ccpp_type ', & + 'number_of_model_times ' /) character(len=cm), target :: test_reqvars2(6) = (/ & - 'model_times ', & - 'number_of_model_times ', & - 'surface_air_pressure ', & - 'ccpp_error_code ', & - 'ccpp_error_message ', & - 'host_standard_ccpp_type ' /) + 'model_times ', & + 'number_of_model_times ', & + 'ccpp_error_code ', & + 'ccpp_error_message ', & + 'physics_state_derived_type ', & + 'host_standard_ccpp_type ' /) type(suite_info) :: test_suites(2) logical :: run_okay diff --git a/test/ddthost_test/test_host.F90 b/test/ddthost_test/test_host.F90 index c8213e20..c7db69c2 100644 --- a/test/ddthost_test/test_host.F90 +++ b/test/ddthost_test/test_host.F90 @@ -9,7 +9,7 @@ module test_prog ! Public data and interfaces integer, public, parameter :: cs = 16 - integer, public, parameter :: cm = 36 + integer, public, parameter :: cm = 38 !> \section arg_table_suite_info Argument Table !! \htmlinclude arg_table_suite_info.html From 80881798e1b35e6eb718bdbf69b1a48693bf45be Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Mon, 27 Apr 2026 15:51:19 -0600 Subject: [PATCH 47/59] Codee format test/advection_test/test_advection_host_integration.F90 --- .../test_advection_host_integration.F90 | 117 +++++++++--------- 1 file changed, 60 insertions(+), 57 deletions(-) diff --git a/test/advection_test/test_advection_host_integration.F90 b/test/advection_test/test_advection_host_integration.F90 index c9822d68..b080718d 100644 --- a/test/advection_test/test_advection_host_integration.F90 +++ b/test/advection_test/test_advection_host_integration.F90 @@ -1,68 +1,71 @@ program test - use test_prog, only: test_host, suite_info, cm, cs + use test_prog, only: test_host, & + suite_info, & + cm, & + cs - implicit none + implicit none - character(len=cs), target :: test_parts1(1) - character(len=cm), target :: test_invars1(8) - character(len=cm), target :: test_outvars1(11) - character(len=cm), target :: test_reqvars1(14) + character(len=cs), target :: test_parts1(1) + character(len=cm), target :: test_invars1(8) + character(len=cm), target :: test_outvars1(11) + character(len=cm), target :: test_reqvars1(14) - type(suite_info) :: test_suites(1) - logical :: run_okay + type(suite_info) :: test_suites(1) + logical :: run_okay - test_parts1 = (/ 'physics '/) + test_parts1 = (/ 'physics '/) - test_invars1 = (/ & - 'ccpp_model_constituents_object ', & - 'cloud_ice_dry_mixing_ratio ', & - 'cloud_liquid_dry_mixing_ratio ', & - 'physics_state_derived_type ', & - 'tendency_of_cloud_liquid_dry_mixing_ratio', & - 'banana_array_dim ', & - 'time_step_for_physics ', & - 'water_temperature_at_freezing ' /) - test_outvars1 = (/ & - 'ccpp_model_constituents_object ', & - 'cloud_ice_dry_mixing_ratio ', & - 'cloud_liquid_dry_mixing_ratio ', & - 'physics_state_derived_type ', & - 'tendency_of_cloud_liquid_dry_mixing_ratio', & - 'ccpp_error_code ', & - 'ccpp_error_message ', & - 'dynamic_constituents_for_cld_ice ', & - 'dynamic_constituents_for_cld_liq ', & - 'test_banana_constituent_index ', & - 'test_banana_constituent_indices ' /) - test_reqvars1 = (/ & - 'ccpp_model_constituents_object ', & - 'cloud_ice_dry_mixing_ratio ', & - 'cloud_liquid_dry_mixing_ratio ', & - 'physics_state_derived_type ', & - 'tendency_of_cloud_liquid_dry_mixing_ratio', & - 'banana_array_dim ', & - 'time_step_for_physics ', & - 'water_temperature_at_freezing ', & - 'ccpp_error_code ', & - 'ccpp_error_message ', & - 'dynamic_constituents_for_cld_ice ', & - 'dynamic_constituents_for_cld_liq ', & - 'test_banana_constituent_index ', & - 'test_banana_constituent_indices ' /) + test_invars1 = (/ & + 'ccpp_model_constituents_object ', & + 'cloud_ice_dry_mixing_ratio ', & + 'cloud_liquid_dry_mixing_ratio ', & + 'physics_state_derived_type ', & + 'tendency_of_cloud_liquid_dry_mixing_ratio', & + 'banana_array_dim ', & + 'time_step_for_physics ', & + 'water_temperature_at_freezing ' /) + test_outvars1 = (/ & + 'ccpp_model_constituents_object ', & + 'cloud_ice_dry_mixing_ratio ', & + 'cloud_liquid_dry_mixing_ratio ', & + 'physics_state_derived_type ', & + 'tendency_of_cloud_liquid_dry_mixing_ratio', & + 'ccpp_error_code ', & + 'ccpp_error_message ', & + 'dynamic_constituents_for_cld_ice ', & + 'dynamic_constituents_for_cld_liq ', & + 'test_banana_constituent_index ', & + 'test_banana_constituent_indices ' /) + test_reqvars1 = (/ & + 'ccpp_model_constituents_object ', & + 'cloud_ice_dry_mixing_ratio ', & + 'cloud_liquid_dry_mixing_ratio ', & + 'physics_state_derived_type ', & + 'tendency_of_cloud_liquid_dry_mixing_ratio', & + 'banana_array_dim ', & + 'time_step_for_physics ', & + 'water_temperature_at_freezing ', & + 'ccpp_error_code ', & + 'ccpp_error_message ', & + 'dynamic_constituents_for_cld_ice ', & + 'dynamic_constituents_for_cld_liq ', & + 'test_banana_constituent_index ', & + 'test_banana_constituent_indices ' /) - ! Setup expected test suite info - test_suites(1)%suite_name = 'cld_suite' - test_suites(1)%suite_parts => test_parts1 - test_suites(1)%suite_input_vars => test_invars1 - test_suites(1)%suite_output_vars => test_outvars1 - test_suites(1)%suite_required_vars => test_reqvars1 + ! Setup expected test suite info + test_suites(1)%suite_name = 'cld_suite' + test_suites(1)%suite_parts => test_parts1 + test_suites(1)%suite_input_vars => test_invars1 + test_suites(1)%suite_output_vars => test_outvars1 + test_suites(1)%suite_required_vars => test_reqvars1 - call test_host(run_okay, test_suites) + call test_host(run_okay, test_suites) - if (run_okay) then - STOP 0 - else - STOP -1 - end if + if (run_okay) then + stop 0 + else + stop -1 + end if end program test From d74bac0222e844bf8ba2b8fbadec2cd1628db937 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Mon, 27 Apr 2026 15:58:54 -0600 Subject: [PATCH 48/59] Simplify tests in .github/workflows/capgen_unit_tests.yaml --- .github/workflows/capgen_unit_tests.yaml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index 4b8598f0..d5d36ee1 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -29,11 +29,11 @@ jobs: python3 \ git \ libxml2-utils - python -m pip install --upgrade pip - pip install pytest + #python -m pip install --upgrade pip + #pip install pytest which xmllint xmllint --version - which pytest + #which pytest - name: Build the framework run: | @@ -46,15 +46,15 @@ jobs: cd build ctest --rerun-failed --output-on-failure . --verbose - - name: Run python tests - run: | - BUILD_DIR=./build \ - PYTHONPATH=test/:scripts/ \ - pytest \ - test/capgen_test/capgen_test_reports.py \ - test/advection_test/advection_test_reports.py \ - test/ddthost_test/ddthost_test_reports.py \ - test/var_compatibility_test/var_compatibility_test_reports.py + #- name: Run python tests + # run: | + # BUILD_DIR=./build \ + # PYTHONPATH=test/:scripts/ \ + # pytest \ + # test/capgen_test/capgen_test_reports.py \ + # test/advection_test/advection_test_reports.py \ + # test/ddthost_test/ddthost_test_reports.py \ + # test/var_compatibility_test/var_compatibility_test_reports.py - name: Run Fortran to metadata test run: cd test && ./test_fortran_to_metadata.sh From ef389c75de2fa60609bd272edb1fffc0ceb68042 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Mon, 27 Apr 2026 16:50:55 -0600 Subject: [PATCH 49/59] Add capgen Python tests back in --- .github/workflows/capgen_unit_tests.yaml | 24 +++++++++---------- test/advection_test/advection_test_reports.py | 23 +++++++----------- test/capgen_test/capgen_test_reports.py | 19 +++++++-------- test/ddthost_test/ddthost_test_reports.py | 21 +++++++++------- 4 files changed, 43 insertions(+), 44 deletions(-) diff --git a/.github/workflows/capgen_unit_tests.yaml b/.github/workflows/capgen_unit_tests.yaml index d5d36ee1..4b8598f0 100644 --- a/.github/workflows/capgen_unit_tests.yaml +++ b/.github/workflows/capgen_unit_tests.yaml @@ -29,11 +29,11 @@ jobs: python3 \ git \ libxml2-utils - #python -m pip install --upgrade pip - #pip install pytest + python -m pip install --upgrade pip + pip install pytest which xmllint xmllint --version - #which pytest + which pytest - name: Build the framework run: | @@ -46,15 +46,15 @@ jobs: cd build ctest --rerun-failed --output-on-failure . --verbose - #- name: Run python tests - # run: | - # BUILD_DIR=./build \ - # PYTHONPATH=test/:scripts/ \ - # pytest \ - # test/capgen_test/capgen_test_reports.py \ - # test/advection_test/advection_test_reports.py \ - # test/ddthost_test/ddthost_test_reports.py \ - # test/var_compatibility_test/var_compatibility_test_reports.py + - name: Run python tests + run: | + BUILD_DIR=./build \ + PYTHONPATH=test/:scripts/ \ + pytest \ + test/capgen_test/capgen_test_reports.py \ + test/advection_test/advection_test_reports.py \ + test/ddthost_test/ddthost_test_reports.py \ + test/var_compatibility_test/var_compatibility_test_reports.py - name: Run Fortran to metadata test run: cd test && ./test_fortran_to_metadata.sh diff --git a/test/advection_test/advection_test_reports.py b/test/advection_test/advection_test_reports.py index 4fbe8e68..de1d764b 100644 --- a/test/advection_test/advection_test_reports.py +++ b/test/advection_test/advection_test_reports.py @@ -39,15 +39,13 @@ _SUITE_LIST = ["cld_suite"] _REQUIRED_VARS_CLD = ["ccpp_error_code", "ccpp_error_message", "horizontal_loop_begin", "horizontal_loop_end", - "surface_air_pressure", "temperature", + "physics_state_derived_type", + "index_of_water_vapor_specific_humidity", "tendency_of_cloud_liquid_dry_mixing_ratio", "time_step_for_physics", "water_temperature_at_freezing", - "water_vapor_specific_humidity", "cloud_ice_dry_mixing_ratio", "cloud_liquid_dry_mixing_ratio", - "ccpp_constituents", - "ccpp_constituent_tendencies", - "number_of_ccpp_constituents", + "ccpp_model_constituents_object", "dynamic_constituents_for_cld_ice", "dynamic_constituents_for_cld_liq", "test_banana_constituent_indices", "test_banana_name", @@ -57,27 +55,24 @@ # Added by --debug option "horizontal_dimension", "vertical_layer_dimension"] -_INPUT_VARS_CLD = ["surface_air_pressure", "temperature", - "horizontal_loop_begin", "horizontal_loop_end", +_INPUT_VARS_CLD = ["horizontal_loop_begin", "horizontal_loop_end", "time_step_for_physics", "water_temperature_at_freezing", - "water_vapor_specific_humidity", "cloud_ice_dry_mixing_ratio", "cloud_liquid_dry_mixing_ratio", "tendency_of_cloud_liquid_dry_mixing_ratio", - "ccpp_constituents", - "ccpp_constituent_tendencies", - "number_of_ccpp_constituents", + "ccpp_model_constituents_object", + "physics_state_derived_type", + "index_of_water_vapor_specific_humidity", "banana_array_dim", "test_banana_name_array", "test_banana_name", # Added by --debug option "horizontal_dimension", "vertical_layer_dimension"] _OUTPUT_VARS_CLD = ["ccpp_error_code", "ccpp_error_message", - "water_vapor_specific_humidity", "temperature", "tendency_of_cloud_liquid_dry_mixing_ratio", "cloud_ice_dry_mixing_ratio", - "ccpp_constituents", - "ccpp_constituent_tendencies", + "ccpp_model_constituents_object", + "physics_state_derived_type", "cloud_liquid_dry_mixing_ratio", "dynamic_constituents_for_cld_ice", "dynamic_constituents_for_cld_liq", diff --git a/test/capgen_test/capgen_test_reports.py b/test/capgen_test/capgen_test_reports.py index 3c683aab..3b72a8c3 100644 --- a/test/capgen_test/capgen_test_reports.py +++ b/test/capgen_test/capgen_test_reports.py @@ -47,9 +47,9 @@ _SUITE_LIST = ["ddt_suite", "temp_suite"] _INPUT_VARS_DDT = ["model_times", "number_of_model_times", "horizontal_loop_begin", "horizontal_loop_end", - "surface_air_pressure", "horizontal_dimension"] + "physics_state_derived_type", "horizontal_dimension"] _OUTPUT_VARS_DDT = ["ccpp_error_code", "ccpp_error_message", "model_times", - "surface_air_pressure", "number_of_model_times"] + "physics_state_derived_type", "number_of_model_times"] _REQUIRED_VARS_DDT = _INPUT_VARS_DDT + _OUTPUT_VARS_DDT _PROT_VARS_TEMP = ["horizontal_loop_begin", "horizontal_loop_end", "horizontal_dimension", "vertical_layer_dimension", @@ -65,26 +65,25 @@ "potential_temperature_at_interface", "coefficients_for_interpolation", "potential_temperature_increment", - "surface_air_pressure", "time_step_for_physics", - "water_vapor_specific_humidity", - "soil_levels", + "time_step_for_physics", + "physics_state_derived_type", + "do_cloud_fraction_adjustment", "temperature_at_diagnostic_levels", "array_variable_for_testing"] _INPUT_VARS_TEMP = ["potential_temperature", "potential_temperature_at_interface", "coefficients_for_interpolation", "potential_temperature_increment", - "surface_air_pressure", "time_step_for_physics", - "water_vapor_specific_humidity", - "soil_levels", + "time_step_for_physics", + "do_cloud_fraction_adjustment", + "physics_state_derived_type", "temperature_at_diagnostic_levels", "array_variable_for_testing"] _OUTPUT_VARS_TEMP = ["ccpp_error_code", "ccpp_error_message", "potential_temperature", "potential_temperature_at_interface", "coefficients_for_interpolation", - "surface_air_pressure", "water_vapor_specific_humidity", - "soil_levels", + "physics_state_derived_type", "temperature_at_diagnostic_levels", "array_variable_for_testing"] diff --git a/test/ddthost_test/ddthost_test_reports.py b/test/ddthost_test/ddthost_test_reports.py index 612cbbbf..eafec55a 100644 --- a/test/ddthost_test/ddthost_test_reports.py +++ b/test/ddthost_test/ddthost_test_reports.py @@ -45,11 +45,13 @@ "temp_calc_adjust", "temp_set"] _SUITE_LIST = ["ddt_suite", "temp_suite"] _INPUT_VARS_DDT = ["model_times", "number_of_model_times", - "horizontal_loop_begin", "horizontal_loop_end", - "surface_air_pressure", "horizontal_dimension", + "physics_state_derived_type", + "horizontal_dimension", "host_standard_ccpp_type"] _OUTPUT_VARS_DDT = ["ccpp_error_code", "ccpp_error_message", "model_times", - "number_of_model_times", "surface_air_pressure"] + "number_of_model_times", + "host_standard_ccpp_type", + "physics_state_derived_type"] _REQUIRED_VARS_DDT = _INPUT_VARS_DDT + _OUTPUT_VARS_DDT _PROT_VARS_TEMP = ["horizontal_loop_begin", "horizontal_loop_end", "horizontal_dimension", "vertical_layer_dimension", @@ -62,19 +64,22 @@ "potential_temperature_at_interface", "coefficients_for_interpolation", "potential_temperature_increment", - "surface_air_pressure", "time_step_for_physics", - "water_vapor_specific_humidity"] + "physics_state_derived_type", + "time_step_for_physics", + "host_standard_ccpp_type"] _INPUT_VARS_TEMP = ["potential_temperature", "potential_temperature_at_interface", "coefficients_for_interpolation", "potential_temperature_increment", - "surface_air_pressure", "time_step_for_physics", - "water_vapor_specific_humidity"] + "physics_state_derived_type", + "time_step_for_physics", + "host_standard_ccpp_type"] _OUTPUT_VARS_TEMP = ["ccpp_error_code", "ccpp_error_message", "potential_temperature", "potential_temperature_at_interface", "coefficients_for_interpolation", - "surface_air_pressure", "water_vapor_specific_humidity"] + "physics_state_derived_type", + "host_standard_ccpp_type"] class TestDdtHostDataTables(unittest.TestCase, BaseTests.TestHostDataTables): database = _DATABASE From 805aaa90c459c9b16c5c093b7b5d92d69ceb33fd Mon Sep 17 00:00:00 2001 From: peverwhee Date: Mon, 27 Apr 2026 16:52:47 -0600 Subject: [PATCH 50/59] add more testing to advection test --- scripts/host_model.py | 8 +++++++- scripts/metavar.py | 4 ++-- test/advection_test/cld_ice.F90 | 4 +++- test/advection_test/cld_ice.meta | 7 +++++++ test/advection_test/cld_liq.F90 | 4 +++- test/advection_test/cld_liq.meta | 7 +++++++ .../test_advection_host_integration.F90 | 9 ++++++--- test/advection_test/test_host.F90 | 11 ++++++---- test/advection_test/test_host_mod.F90 | 20 +++++++++++++++++++ test/advection_test/test_host_mod.meta | 18 +++++++++++++++++ 10 files changed, 80 insertions(+), 12 deletions(-) diff --git a/scripts/host_model.py b/scripts/host_model.py index 6d758b52..587d1956 100644 --- a/scripts/host_model.py +++ b/scripts/host_model.py @@ -192,10 +192,16 @@ def variable_locations(self): # Now, find all the used module variables cap_modname = self.ccpp_cap_name() for name in lnames: + # Grab base local name; no dimensions + if '(' in name: + newname = name.split('(')[0] + else: + newname = name + # End if module = self.host_variable_module(name) used = self.__used_variables and (name in self.__used_variables) if module and used and (module != cap_modname): - varset.add((module, name)) + varset.add((module, newname)) # No else, either no module or a zero-length module name # End if # End for diff --git a/scripts/metavar.py b/scripts/metavar.py index 331fe0be..c82b16db 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -891,8 +891,8 @@ def parent(self): @parent.setter def parent(self, parent_var): """Set this variable's parent if not already set""" - if self.__parent_var is not None: - emsg = 'Attempting to set parent for {} but parent already set' + if self.__parent_var is not None and self.__parent_var != parent_var: + emsg = 'Attempting to set parent for {} but different parent already set' lname = self.get_prop_value('local_name') raise ParseInternalError(emsg.format(lname)) # end if diff --git a/test/advection_test/cld_ice.F90 b/test/advection_test/cld_ice.F90 index 3ace2f91..32f9e92e 100644 --- a/test/advection_test/cld_ice.F90 +++ b/test/advection_test/cld_ice.F90 @@ -48,13 +48,14 @@ end subroutine cld_ice_register !> \section arg_table_cld_ice_run Argument Table !! \htmlinclude arg_table_cld_ice_run.html !! - subroutine cld_ice_run(ncol, timestep, temp, qv, ps, cld_ice_array, & + subroutine cld_ice_run(ncol, timestep, temp, qv, qv_not_state, ps, cld_ice_array, & errmsg, errflg) integer, intent(in) :: ncol real(kind=kind_phys), intent(in) :: timestep real(kind=kind_phys), intent(inout) :: temp(:, :) real(kind=kind_phys), intent(inout) :: qv(:, :) + real(kind=kind_phys), intent(inout) :: qv_not_state(:, :) real(kind=kind_phys), intent(in) :: ps(:) real(kind=kind_phys), intent(inout) :: cld_ice_array(:, :) character(len=512), intent(out) :: errmsg @@ -75,6 +76,7 @@ subroutine cld_ice_run(ncol, timestep, temp, qv, ps, cld_ice_array, & frz = max(qv(icol, ilev) - 0.5_kind_phys, 0.0_kind_phys) cld_ice_array(icol, ilev) = cld_ice_array(icol, ilev) + frz qv(icol, ilev) = qv(icol, ilev) - frz + qv_not_state(icol, ilev) = qv_not_state(icol, ilev) - frz if (frz > 0.0_kind_phys) then temp(icol, ilev) = temp(icol, ilev) + 1.0_kind_phys end if diff --git a/test/advection_test/cld_ice.meta b/test/advection_test/cld_ice.meta index e57d0b08..63b0d749 100644 --- a/test/advection_test/cld_ice.meta +++ b/test/advection_test/cld_ice.meta @@ -58,6 +58,13 @@ type = real kind = kind_phys intent = inout +[ qv_not_state ] + standard_name = water_vapor_specific_humidity_not_state + units = kg kg-1 + dimensions = (horizontal_loop_extent, vertical_layer_dimension) + type = real + kind = kind_phys + intent = inout [ ps ] standard_name = surface_air_pressure state_variable = true diff --git a/test/advection_test/cld_liq.F90 b/test/advection_test/cld_liq.F90 index cb02cf11..e12a667b 100644 --- a/test/advection_test/cld_liq.F90 +++ b/test/advection_test/cld_liq.F90 @@ -41,7 +41,7 @@ end subroutine cld_liq_register !> \section arg_table_cld_liq_run Argument Table !! \htmlinclude arg_table_cld_liq_run.html !! - subroutine cld_liq_run(ncol, timestep, tcld, temp, qv, ps, & + subroutine cld_liq_run(ncol, timestep, tcld, temp, qv, qv_not_state, ps, & cld_liq_tend, errmsg, errflg) integer, intent(in) :: ncol @@ -49,6 +49,7 @@ subroutine cld_liq_run(ncol, timestep, tcld, temp, qv, ps, & real(kind=kind_phys), intent(in) :: tcld real(kind=kind_phys), intent(inout) :: temp(:, :) real(kind=kind_phys), intent(inout) :: qv(:, :) + real(kind=kind_phys), intent(inout) :: qv_not_state(:, :) real(kind=kind_phys), intent(in) :: ps(:) real(kind=kind_phys), intent(inout) :: cld_liq_tend(:, :) character(len=512), intent(out) :: errmsg @@ -70,6 +71,7 @@ subroutine cld_liq_run(ncol, timestep, tcld, temp, qv, ps, & cond = min(qv(icol, ilev), 0.1_kind_phys) cld_liq_tend(icol, ilev) = cond qv(icol, ilev) = qv(icol, ilev) - cond + qv_not_state(icol, ilev) = qv_not_state(icol, ilev) - cond if (cond > 0.0_kind_phys) then temp(icol, ilev) = temp(icol, ilev) + (cond * 5.0_kind_phys) end if diff --git a/test/advection_test/cld_liq.meta b/test/advection_test/cld_liq.meta index b3ef3a0d..319b1463 100644 --- a/test/advection_test/cld_liq.meta +++ b/test/advection_test/cld_liq.meta @@ -63,6 +63,13 @@ type = real kind = kind_phys intent = inout +[ qv_not_state ] + standard_name = water_vapor_specific_humidity_not_state + units = kg kg-1 + dimensions = (horizontal_loop_extent, vertical_layer_dimension) + type = real + kind = kind_phys + intent = inout [ ps ] standard_name = surface_air_pressure state_variable = true diff --git a/test/advection_test/test_advection_host_integration.F90 b/test/advection_test/test_advection_host_integration.F90 index b080718d..f392bd88 100644 --- a/test/advection_test/test_advection_host_integration.F90 +++ b/test/advection_test/test_advection_host_integration.F90 @@ -7,9 +7,9 @@ program test implicit none character(len=cs), target :: test_parts1(1) - character(len=cm), target :: test_invars1(8) - character(len=cm), target :: test_outvars1(11) - character(len=cm), target :: test_reqvars1(14) + character(len=cm), target :: test_invars1(9) + character(len=cm), target :: test_outvars1(12) + character(len=cm), target :: test_reqvars1(15) type(suite_info) :: test_suites(1) logical :: run_okay @@ -24,9 +24,11 @@ program test 'tendency_of_cloud_liquid_dry_mixing_ratio', & 'banana_array_dim ', & 'time_step_for_physics ', & + 'water_vapor_specific_humidity_not_state ', & 'water_temperature_at_freezing ' /) test_outvars1 = (/ & 'ccpp_model_constituents_object ', & + 'water_vapor_specific_humidity_not_state ', & 'cloud_ice_dry_mixing_ratio ', & 'cloud_liquid_dry_mixing_ratio ', & 'physics_state_derived_type ', & @@ -43,6 +45,7 @@ program test 'cloud_liquid_dry_mixing_ratio ', & 'physics_state_derived_type ', & 'tendency_of_cloud_liquid_dry_mixing_ratio', & + 'water_vapor_specific_humidity_not_state ', & 'banana_array_dim ', & 'time_step_for_physics ', & 'water_temperature_at_freezing ', & diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index cc8bbf89..df9812fa 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -78,7 +78,8 @@ logical function check_suite(test_suite) end if ! Check the input variables call ccpp_physics_suite_variables(test_suite%suite_name, test_list, & - errmsg, errflg, input_vars=.true., output_vars=.false.) + errmsg, errflg, input_vars=.true., output_vars=.false., & + struct_elements=.false.) if (errflg == 0) then check = check_list(test_list, test_suite%suite_input_vars, & 'input variable names', suite_name=test_suite%suite_name) @@ -92,7 +93,8 @@ logical function check_suite(test_suite) end if ! Check the output variables call ccpp_physics_suite_variables(test_suite%suite_name, test_list, & - errmsg, errflg, input_vars=.false., output_vars=.true.) + errmsg, errflg, input_vars=.false., output_vars=.true., & + struct_elements=.false.) if (errflg == 0) then check = check_list(test_list, test_suite%suite_output_vars, & 'output variable names', suite_name=test_suite%suite_name) @@ -106,7 +108,7 @@ logical function check_suite(test_suite) end if ! Check all required variables call ccpp_physics_suite_variables(test_suite%suite_name, test_list, & - errmsg, errflg) + errmsg, errflg, struct_elements=.false.) if (errflg == 0) then check = check_list(test_list, test_suite%suite_required_vars, & 'required variable names', suite_name=test_suite%suite_name) @@ -122,7 +124,7 @@ end function check_suite subroutine advect_constituents() use test_host_mod, only: phys_state, & - ncnst + ncnst, q_not_state use test_host_mod, only: twist_array ! Local variables @@ -130,6 +132,7 @@ subroutine advect_constituents() do q_ind = 1, ncnst ! Skip checks, they were done in constituents_in call twist_array(phys_state%q(:, :, q_ind)) + call twist_array(q_not_state(:, :, q_ind)) end do end subroutine advect_constituents diff --git a/test/advection_test/test_host_mod.F90 b/test/advection_test/test_host_mod.F90 index 5099b9c1..ca417d45 100644 --- a/test/advection_test/test_host_mod.F90 +++ b/test/advection_test/test_host_mod.F90 @@ -18,11 +18,13 @@ module test_host_mod integer, parameter :: pverp = pver + 1 integer, protected :: ncnst = -1 integer, protected :: index_qv = -1 + integer, protected :: index_qv_not_state = -1 real(kind=kind_phys) :: dt real(kind=kind_phys), parameter :: tfreeze = 273.15_kind_phys type(physics_state) :: phys_state integer :: num_model_times = -1 integer, allocatable :: model_times(:) + real(kind=kind_phys), allocatable :: q_not_state(:,:,:) public :: init_data public :: compare_data @@ -58,9 +60,12 @@ subroutine init_data(constituent_array, index_qv_use, index_liq, index_ice, inde ncnst = size(constituent_array, 3) call allocate_physics_state(ncols, pver, constituent_array, phys_state) index_qv = index_qv_use + index_qv_not_state = index_qv_use ind_liq = index_liq ind_ice = index_ice allocate(check_vals(ncols, pver, ncnst)) + allocate(q_not_state(ncols, pver, ncnst)) + q_not_state = constituent_array check_vals(:, :, :) = 0.0_kind_phys check_vals(:, :, index_dyn) = 1.0_kind_phys do lev = 1, pver @@ -69,8 +74,10 @@ subroutine init_data(constituent_array, index_qv_use, index_liq, index_ice, inde do col = 1, ncols if (mod(col, 2) == 1) then phys_state%q(col, lev, index_qv) = qmax + q_not_state(col, lev, index_qv_not_state) = qmax else phys_state%q(col, lev, index_qv) = 0.0_kind_phys + q_not_state(col, lev, index_qv_not_state) = 0.0_kind_phys end if end do end do @@ -167,6 +174,19 @@ logical function compare_data(ncnst) phys_state%q(col, lev, cind), check compare_data = .false. end if + if (cind == index_qv_not_state) then + if (abs((q_not_state(col, lev, cind) - check) / denom) > & + tolerance) then + if (need_header) then + write(6, '(2(2x,a),3x,a,10x,a,14x,a)') & + 'COL', 'LEV', 'C#', 'Q NOT STATE', 'EXPECTED' + need_header = .false. + end if + write(6, '(3i5,2(3x,es15.7))') col, lev, cind, & + q_not_state(col, lev, cind), check + compare_data = .false. + end if + end if end do end do end do diff --git a/test/advection_test/test_host_mod.meta b/test/advection_test/test_host_mod.meta index 9f04a6fc..57bb5baa 100644 --- a/test/advection_test/test_host_mod.meta +++ b/test/advection_test/test_host_mod.meta @@ -34,6 +34,12 @@ type = integer protected = True dimensions = () +[ index_qv_not_state ] + standard_name = index_of_water_vapor_specific_humidity_not_state + units = index + type = integer + protected = True + dimensions = () [ dt ] standard_name = time_step_for_physics long_name = time step @@ -62,3 +68,15 @@ dimensions = (number_of_model_times) type = integer allocatable = True +[ q_not_state ] + standard_name = constituent_mixing_ratio_not_state + type = real + kind = kind_phys + units = kg kg-1 moist or dry air depending on type + dimensions = (horizontal_dimension, vertical_layer_dimension, number_of_tracers) +[ q_not_state(:,:,index_of_water_vapor_specific_humidity_not_state) ] + standard_name = water_vapor_specific_humidity_not_state + type = real + kind = kind_phys + units = kg kg-1 + dimensions = (horizontal_dimension, vertical_layer_dimension) From 9c15ff34e567632f6dde0a8132b34d6ec0030e43 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Tue, 28 Apr 2026 09:48:53 -0600 Subject: [PATCH 51/59] Save progress --- scripts/metavar.py | 25 +++- scripts/suite_objects.py | 161 +++++++++++++--------- test/ddthost_test/environ_conditions.meta | 3 + test/ddthost_test/make_ddt.F90 | 4 +- test/ddthost_test/make_ddt.meta | 5 - 5 files changed, 117 insertions(+), 81 deletions(-) diff --git a/scripts/metavar.py b/scripts/metavar.py index 331fe0be..685636f0 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -360,6 +360,7 @@ def __init__(self, prop_dict, source, run_env, is_ddt=False, components=None, co # Make sure all the variable values are valid try: for prop_name, prop_val in self.var_properties(): + #print(f"DHDEBUG: prop_name, prop_val: '{prop_name}' / '{prop_val}'") prop = Var.get_prop(prop_name) _ = prop.valid_value(prop_val, prop_dict=self._prop_dict, error=True) @@ -561,6 +562,8 @@ def handle_array_ref(self): ('foo', ['ccpp_constant_one:dim1', 'ccpp_constant_one:dim2', 'bar']) >>> Var({'local_name' : 'foo(bar,:)', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '(ccpp_constant_one:dim1)', 'type' : 'real',}, ParseSource('vname', 'HOST', ParseContext()), _MVAR_DUMMY_RUN_ENV).handle_array_ref() ('foo', ['bar', 'ccpp_constant_one:dim1']) + >>> Var({'local_name' : 'foo(:)', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '(ccpp_constant_one:dim1)', 'type' : 'real',}, ParseSource('vname', 'HOST', ParseContext()), _MVAR_DUMMY_RUN_ENV).handle_array_ref() + ('foo', ['ccpp_constant_one:dim1']) >>> Var({'local_name' : 'foo(bar)', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '(ccpp_constant_one:dim1)', 'type' : 'real',}, ParseSource('vname', 'HOST', ParseContext()), _MVAR_DUMMY_RUN_ENV).handle_array_ref() #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: Call dims mismatch for foo(bar), not enough colons @@ -610,8 +613,8 @@ def handle_array_ref(self): # end if return lname, dimlist - def call_dimstring(self, var_dicts=None, - explicit_dims=False, loop_subst=False): + def call_dimstring(self, var_dicts=None, explicit_dims=False, + loop_subst=False, prepend_varname=False): """Return the dimensions string for a variable call. If is present, find and substitute a local_name for each standard_name in this variable's dimensions. @@ -621,7 +624,7 @@ def call_dimstring(self, var_dicts=None, missing dimension. """ emsg = '' - _, dims = self.handle_array_ref() + varname, dims = self.handle_array_ref() if var_dicts is not None: dimlist = [] sepstr = '' @@ -644,6 +647,13 @@ def call_dimstring(self, var_dicts=None, if (not dvar) and add_dims: dnames = [] for stdname in dstdnames: + # Leave integers (parameters) alone + try: + if isinstance(int(stdname), int): + dnames.append(stdname) + continue + except ValueError: + pass for vdict in var_dicts: dvar = vdict.find_variable(standard_name=stdname, any_scope=False) @@ -690,7 +700,10 @@ def call_dimstring(self, var_dicts=None, lname = self.get_prop_value('local_name') raise CCPPError(emsg.format(vlnam=lname, ctx=ctx)) # end if - return dimstr + if prepend_varname: + return varname + dimstr + else: + return dimstr def call_string(self, var_dicts, loop_vars=None): """Construct the actual argument string for this Var by translating @@ -700,8 +713,8 @@ def call_string(self, var_dicts, loop_vars=None): even if usage requires a loop substitution. """ # DH* - if loop_vars: - raise Exception(f"DH DEBUG: WE DO USE loop_vars in call_string! '{loop_vars}'") + #if loop_vars: + # raise Exception(f"DH DEBUG: WE DO USE loop_vars in call_string! '{loop_vars}'") # *DH if not isinstance(var_dicts, list): var_dicts = [var_dicts] diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 2985d076..b135328b 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -309,8 +309,24 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ dimstr = '(' + ','.join(ldims) + ')' else: dimstr = '(' + ','.join(ddims) + ')' + if True: #ldims: + print(f"DH DEBUG: dvar.call_string(search_dict) is '{dvar.call_string(search_dict)}'") + print(f"DH DEBUG: lname, ldims: '{lname}' / '{ldims}'") + print(f"DH DEBUG: dimstr: '{dimstr}'") + tlname, tldims = dvar.handle_array_ref() + print(f"DH DEBUG: tlname, tldims: '{tlname}' / '{tldims}'") + print(f"DH DEBUG: dvar.call_dimstring(): '{dvar.call_dimstring()}'") + print(f"DH DEBUG: dvar.call_dimstring(explicit_dims=True): '{dvar.call_dimstring(explicit_dims=True)}'") + #print(f"DH DEBUG: dvar.call_dimstring(var_dicts=[self], explicit_dims=True): '{dvar.call_dimstring(var_dicts=[self], explicit_dims=True)}'") + print(f"DH DEBUG: dvar.call_dimstring(var_dicts=cldicts, explicit_dims=True): '{dvar.call_dimstring(var_dicts=cldicts, explicit_dims=True)}'") + print(f"DH DEBUG: dvar.call_dimstring(var_dicts=cldicts, explicit_dims=True, loop_subst=True): '{dvar.call_dimstring(var_dicts=cldicts, explicit_dims=True, loop_subst=True)}'") + print(" ") + #raise Exception("XXX") + # DH* + lname, ldims = split_dims_from_name(dvar.call_string(search_dict)) + dimstr = dvar.call_dimstring(var_dicts=cldicts, explicit_dims=True, loop_subst=self.routine.run_phase()) + # *DH lname += dimstr - # end if # end if else: @@ -1438,74 +1454,85 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, if (has_transform): lname = var.get_prop_value('local_name')+'_local' else: - ddims = dvar.get_dimensions() - for i in range(len(ddims)): - dim = ddims[i] - if is_horizontal_dimension(dim): - if self.run_phase(): - if var_in_call_list and \ - self.find_variable(standard_name="horizontal_loop_extent"): - ldim = "ccpp_constant_one" - udim = "horizontal_loop_extent" - else: - ldim = "horizontal_loop_begin" - udim = "horizontal_loop_end" - # endif - else: - ldim = "ccpp_constant_one" - udim = "horizontal_dimension" - # endif - else: - ldim, udim = dim.split(':') - # end if - # Get dimension for lower bound - for var_dict in cldicts: - lvar = var_dict.find_variable(standard_name=ldim, any_scope=False) - if lvar is not None: - break - # end if - # end for - if not lvar: - raise Exception(f"No variable with standard name '{ldim}' in cldicts") - # end if - ldim_lname = lvar.get_prop_value('local_name') - # Get dimension for upper bound - for var_dict in cldicts: - uvar = var_dict.find_variable(standard_name=udim, any_scope=False) - if uvar is not None: - break - # end if - # end for - if not uvar: - raise Exception(f"No variable with standard name '{udim}' in cldicts") - # end if - udim_lname = uvar.get_prop_value('local_name') - ddims[i] = ldim_lname + ':' + udim_lname - # end for + #ddims = dvar.get_dimensions() + #for i in range(len(ddims)): + # dim = ddims[i] + # if is_horizontal_dimension(dim): + # if self.run_phase(): + # if var_in_call_list and \ + # self.find_variable(standard_name="horizontal_loop_extent"): + # ldim = "ccpp_constant_one" + # udim = "horizontal_loop_extent" + # else: + # ldim = "horizontal_loop_begin" + # udim = "horizontal_loop_end" + # # endif + # else: + # ldim = "ccpp_constant_one" + # udim = "horizontal_dimension" + # # endif + # else: + # ldim, udim = dim.split(':') + # # end if + # # Get dimension for lower bound + # for var_dict in cldicts: + # lvar = var_dict.find_variable(standard_name=ldim, any_scope=False) + # if lvar is not None: + # break + # # end if + # # end for + # if not lvar: + # raise Exception(f"No variable with standard name '{ldim}' in cldicts") + # # end if + # ldim_lname = lvar.get_prop_value('local_name') + # # Get dimension for upper bound + # for var_dict in cldicts: + # uvar = var_dict.find_variable(standard_name=udim, any_scope=False) + # if uvar is not None: + # break + # # end if + # # end for + # if not uvar: + # raise Exception(f"No variable with standard name '{udim}' in cldicts") + # # end if + # udim_lname = uvar.get_prop_value('local_name') + # ddims[i] = ldim_lname + ':' + udim_lname + ## end for + #lname, ldims = split_dims_from_name(dvar.call_string(search_dict)) + ## This is where it gets tricky. We have a dimension specifier + ## as part of the local name, and we have a dimstr. The latter + ## contains the correct horizontal and vertical extents, the former + ## does not - they can be ranges (containing ":") or indices + ## (that is, the variable is a slice of another variable). + ## We need to walk the ldims list left to right and for each + ## dimension we need to check if it is a range or not. If it is + ## a range, we insert the first element from ddims, then we remove + ## this range from ddims and move on to the next. + #if ldims: + # # Consistency check: + # if len(ldims) < len(ddims): + # raise Exception("Logic error in associate_optional_var: len({ldims}) < len({ddims})") + # for i in range(len(ldims)): + # if ':' in ldims[i]: + # ldims[i] = ddims.pop(0) + # # At the end ddims must be empty + # if ddims: + # raise Exception("Logic error in associate_optional_var: after filling ldims='{ldims}' from ddims, ddims is not empty: '{ddims}'") + # dimstr = '(' + ','.join(ldims) + ')' + #else: + # dimstr = '(' + ','.join(ddims) + ')' + lname = dvar.get_prop_value("local_name") + print(f"lname 1: {lname}") lname, ldims = split_dims_from_name(dvar.call_string(search_dict)) - # This is where it gets tricky. We have a dimension specifier - # as part of the local name, and we have a dimstr. The latter - # contains the correct horizontal and vertical extents, the former - # does not - they can be ranges (containing ":") or indices - # (that is, the variable is a slice of another variable). - # We need to walk the ldims list left to right and for each - # dimension we need to check if it is a range or not. If it is - # a range, we insert the first element from ddims, then we remove - # this range from ddims and move on to the next. - if ldims: - # Consistency check: - if len(ldims) < len(ddims): - raise Exception("Logic error in associate_optional_var: len({ldims}) < len({ddims})") - for i in range(len(ldims)): - if ':' in ldims[i]: - ldims[i] = ddims.pop(0) - # At the end ddims must be empty - if ddims: - raise Exception("Logic error in associate_optional_var: after filling ldims='{ldims}' from ddims, ddims is not empty: '{ddims}'") - dimstr = '(' + ','.join(ldims) + ')' - else: - dimstr = '(' + ','.join(ddims) + ')' + dimstr = dvar.call_dimstring(var_dicts=cldicts, explicit_dims=True, loop_subst=self.run_phase()) lname += dimstr + print(f"lname 2: {lname}") + lnametest = dvar.call_dimstring(var_dicts=cldicts, explicit_dims=True, loop_subst=self.run_phase(), prepend_varname=True) + print(f"lname 3: {lnametest}") + lnametest = dvar.call_string(search_dict, loop_vars=search_dict) + print(f"lname 4: {lnametest}") + #raise Exception("XXX") + # end if lname_ptr = var.get_prop_value('local_name') + '_ptr' # Scheme has optional varaible, host has varaible defined as Conditional (Active). diff --git a/test/ddthost_test/environ_conditions.meta b/test/ddthost_test/environ_conditions.meta index 894e0e92..8f35244d 100644 --- a/test/ddthost_test/environ_conditions.meta +++ b/test/ddthost_test/environ_conditions.meta @@ -1,6 +1,7 @@ [ccpp-table-properties] name = environ_conditions type = scheme + [ccpp-arg-table] name = environ_conditions_run type = scheme @@ -27,6 +28,7 @@ dimensions = () type = integer intent = out + [ccpp-arg-table] name = environ_conditions_init type = scheme @@ -78,6 +80,7 @@ dimensions = () type = integer intent = out + [ccpp-arg-table] name = environ_conditions_finalize type = scheme diff --git a/test/ddthost_test/make_ddt.F90 b/test/ddthost_test/make_ddt.F90 index a0de4177..d1c26657 100644 --- a/test/ddthost_test/make_ddt.F90 +++ b/test/ddthost_test/make_ddt.F90 @@ -68,12 +68,10 @@ end subroutine make_ddt_run !> \section arg_table_make_ddt_init Argument Table !! \htmlinclude arg_table_make_ddt_init.html !! - subroutine make_ddt_init(nbox, ccpp_info, vmr, errmsg, errflg) - use host_ccpp_ddt, only: ccpp_info_t + subroutine make_ddt_init(nbox, vmr, errmsg, errflg) ! Dummy arguments integer, intent(in) :: nbox - type(ccpp_info_t), intent(in) :: ccpp_info type(vmr_type), intent(out) :: vmr character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg diff --git a/test/ddthost_test/make_ddt.meta b/test/ddthost_test/make_ddt.meta index 4998e917..a252df09 100644 --- a/test/ddthost_test/make_ddt.meta +++ b/test/ddthost_test/make_ddt.meta @@ -76,11 +76,6 @@ units = count dimensions = () intent = in -[ ccpp_info ] - standard_name = host_standard_ccpp_type - type = ccpp_info_t - dimensions = () - intent = in [ vmr ] standard_name = volume_mixing_ratio_ddt dimensions = () From a730b349583e9f3c544491642c80cc52bbbfcf53 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Tue, 28 Apr 2026 11:19:50 -0600 Subject: [PATCH 52/59] scripts/ddt_library.py: add call_dimstring to VarDDT --- scripts/ddt_library.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/scripts/ddt_library.py b/scripts/ddt_library.py index bdd3e8be..47d6c4d9 100644 --- a/scripts/ddt_library.py +++ b/scripts/ddt_library.py @@ -101,6 +101,27 @@ def clone(self, subst_dict=None, remove_intent=False, # end if return clone_var + def call_dimstring(self, var_dicts=None, explicit_dims=False, + loop_subst=False, prepend_varname=False): + """Return the dimensions string for a variable call. + If is present, find and substitute a local_name for + each standard_name in this variable's dimensions. + If is not present, return a colon for each dimension. + If is True, include the variable's dimensions. + If is True, apply a loop substitution, if found for any + missing dimension. + If is True, prepend the VarDDT's local name sequence. + """ + if self.field is not None: + dimstr = self.field.call_dimstring( + var_dicts=var_dicts, explicit_dims=explicit_dims, + loop_subst=loop_subst, prepend_varname=prepend_varname) + # end if + # DH Copied from below: XXgoldyXX: Need to add dimensions to this + if prepend_varname: + dimstr = super().get_prop_value('local_name') + '%' + dimstr + return dimstr + def call_string(self, var_dict, loop_vars=None): """Return a legal call string of this VarDDT's local name sequence. """ From 79cc3b75443f7b531f71df63bd8f8198b50147c7 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Tue, 28 Apr 2026 11:20:22 -0600 Subject: [PATCH 53/59] scripts/metavar.py: add option to prepend varname to call_dimstring --- scripts/metavar.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/metavar.py b/scripts/metavar.py index f230e4bf..e9fe186a 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -622,6 +622,7 @@ def call_dimstring(self, var_dicts=None, explicit_dims=False, If is True, include the variable's dimensions. If is True, apply a loop substitution, if found for any missing dimension. + If is True, prepend the local variable name. """ emsg = '' varname, dims = self.handle_array_ref() From 84291d7b4de66a77100bb7f629f65a1a0b9094d8 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Tue, 28 Apr 2026 11:21:00 -0600 Subject: [PATCH 54/59] scripts/suite_objects.py: use varddt/var call_dimstring with prepend_varname=True and remove unnecessary logic --- scripts/suite_objects.py | 249 +-------------------------------------- 1 file changed, 2 insertions(+), 247 deletions(-) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index b135328b..7c5781ce 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -53,60 +53,6 @@ 'scheme_files':'', 'suites':''}) -# DH* MOVE THIS ELSEWHERE -def split_dims_from_name(expr: str): - """Split the full dimension specifier of the innermost variable - of a potentially nested derived data type from the variable name: - - foo%bar(idx1)%baz(idx2, idx3(idx4), idx5a(idx6):idx5b(idx6)) - --> - foo%bar(idx1)%baz AND (idx2, idx3(idx4), idx5a(idx6):idx5b(idx6)) - - Walk the string from right to left, and find the outermost ( that - starts the final argument list at depth 0.""" - name = expr - depth = 0 - dimstring = None - for i in range(len(expr)-1, -1, -1): - c = expr[i] - if c == ')': - depth += 1 - elif c == '(': - depth -= 1 - if depth == 0: - # Found the outermost opening parenthesis - name = expr[:i] - dimstring = expr[i:] - break - if not dimstring: - return name, None - - # Now turn the dimstring into a list of dimensions, - # taking into account that a dimension might have - # parentheses: (idx2, idx3(idx4), idx5a(idx6):idx5b(idx6)) - dimstring = dimstring.lstrip('(').rstrip(')') - dims = [] - current = [] - depth = 0 - for c in dimstring: - if c == '(': - depth += 1 - current.append(c) - elif c == ')': - depth -= 1 - current.append(c) - elif c == ',' and depth == 0: - dims.append(''.join(current).strip()) - current = [] - else: - current.append(c) - # Add last piece - if current: - dims.append(''.join(current).strip()) - - return name, dims -# *DH - ############################################################################### def new_suite_object(item, context, parent, run_env, loop_count=0): ############################################################################### @@ -229,104 +175,8 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ lname = dummy+'_ptr' # Finally, handle the dimensions unless it's an allocatable var elif not var.get_prop_value('allocatable'): - # DH* Consolidate this with the logic below for optional arguments ... if dimensions: - ddims = [] - for i in range(len(dimensions)): - if is_horizontal_dimension(dimensions[i]): - if self.routine.run_phase(): - # DH* Using this produces out of range exceptions. Begs a - # larger question of we should - internally - *always* use - # horizontal_loop_begin:horizontal_loop_end, regardless - # of how it is defined in the metadata (e.g. convert - # ccpp_constant_one:horizontal_loop_extent - # to - # horizontal_loop_begin:horizontal_loop_end - # for the run phase when parsing the metadata ? - #if var_in_call_list and \ - # self.find_variable(standard_name="horizontal_loop_extent"): - # ldim = "ccpp_constant_one" - # udim = "horizontal_loop_extent" - #else: - # ldim = "horizontal_loop_begin" - # udim = "horizontal_loop_end" - # endif - ldim = "horizontal_loop_begin" - udim = "horizontal_loop_end" - # *DH - else: - ldim = "ccpp_constant_one" - udim = "horizontal_dimension" - # endif - # Get dimension for lower bound - for cldict in cldicts: - #lvar = cldict.find_variable(standard_name=ldim, any_scope=True) - lvar = cldict.find_variable(standard_name=ldim, any_scope=False) - if lvar: - break - if not lvar: - raise Exception(f"No variable with standard name '{ldim}' in cldict") - # end if - ldim_lname = lvar.call_string('local_name') - # Get dimension for upper bound - for cldict in cldicts: - #uvar = cldict.find_variable(standard_name=udim, any_scope=True) - uvar = cldict.find_variable(standard_name=udim, any_scope=False) - if uvar: - break - if not uvar: - raise Exception(f"No variable with standard name '{udim}' in cldict") - # end if - udim_lname = uvar.call_string('local_name') - ddims.append(ldim_lname + ':' + udim_lname) - else: - # DH* TODO - explicit lbound and ubound as for optional args below - ddims.append(':') - # *DH - # endif - # end for - - lname, ldims = split_dims_from_name(dvar.call_string(search_dict)) - # This is where it gets tricky. We have a dimension specifier - # as part of the local name, and we have a dimstr. The latter - # contains the correct horizontal and vertical extents, the former - # does not - they can be ranges (containing ":") or indices - # (that is, the variable is a slice of another variable). - # We need to walk the ldims list left to right and for each - # dimension we need to check if it is a range or not. If it is - # a range, we insert the first element from ddims, then we remove - # this range from ddims and move on to the next. - if ldims: - # Consistency check: - if len(ldims) < len(ddims): - raise Exception("Logic error in associate_optional_var: len({ldims}) < len({ddims})") - for i in range(len(ldims)): - if ':' in ldims[i]: - ldims[i] = ddims.pop(0) - # At the end ddims must be empty - if ddims: - raise Exception("Logic error in associate_optional_var: after filling ldims='{ldims}' from ddims, ddims is not empty: '{ddims}'") - dimstr = '(' + ','.join(ldims) + ')' - else: - dimstr = '(' + ','.join(ddims) + ')' - if True: #ldims: - print(f"DH DEBUG: dvar.call_string(search_dict) is '{dvar.call_string(search_dict)}'") - print(f"DH DEBUG: lname, ldims: '{lname}' / '{ldims}'") - print(f"DH DEBUG: dimstr: '{dimstr}'") - tlname, tldims = dvar.handle_array_ref() - print(f"DH DEBUG: tlname, tldims: '{tlname}' / '{tldims}'") - print(f"DH DEBUG: dvar.call_dimstring(): '{dvar.call_dimstring()}'") - print(f"DH DEBUG: dvar.call_dimstring(explicit_dims=True): '{dvar.call_dimstring(explicit_dims=True)}'") - #print(f"DH DEBUG: dvar.call_dimstring(var_dicts=[self], explicit_dims=True): '{dvar.call_dimstring(var_dicts=[self], explicit_dims=True)}'") - print(f"DH DEBUG: dvar.call_dimstring(var_dicts=cldicts, explicit_dims=True): '{dvar.call_dimstring(var_dicts=cldicts, explicit_dims=True)}'") - print(f"DH DEBUG: dvar.call_dimstring(var_dicts=cldicts, explicit_dims=True, loop_subst=True): '{dvar.call_dimstring(var_dicts=cldicts, explicit_dims=True, loop_subst=True)}'") - print(" ") - #raise Exception("XXX") - # DH* - lname, ldims = split_dims_from_name(dvar.call_string(search_dict)) - dimstr = dvar.call_dimstring(var_dicts=cldicts, explicit_dims=True, loop_subst=self.routine.run_phase()) - # *DH - lname += dimstr + lname= dvar.call_dimstring(var_dicts=cldicts, explicit_dims=True, loop_subst=self.routine.run_phase(), prepend_varname=True) # end if # end if else: @@ -349,22 +199,6 @@ def call_string(self, cldicts=None, is_func_call=False, subname=None, sub_lname_ # end if # end for # end if - #if is_func_call: - # if cldicts is not None: - # use_dicts = cldicts - # else: - # use_dicts = [self] - # # end if - # run_phase = self.routine.run_phase() - # # We only need dimensions for suite variables in run phase - # need_dims = SuiteObject.is_suite_variable(dvar) and run_phase - # vdims = var.call_dimstring(var_dicts=use_dicts, - # explicit_dims=need_dims, - # loop_subst=run_phase) - # if _BLANK_DIMS_RE.match(vdims) is None: - # lname = lname + vdims - # # end if - ## end if if is_func_call: arg_str += "{}{}={}".format(arg_sep, dummy, lname) else: @@ -1448,90 +1282,12 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, dimensions = dvar.get_dimensions() # Need to use local_name in Group's call list (self.__group.call_list), # not the local_name in var. - # DH* a lot of repeated logic here ... consolidate! if (dict_var): (conditional, _) = dvar.conditional(cldicts) if (has_transform): lname = var.get_prop_value('local_name')+'_local' else: - #ddims = dvar.get_dimensions() - #for i in range(len(ddims)): - # dim = ddims[i] - # if is_horizontal_dimension(dim): - # if self.run_phase(): - # if var_in_call_list and \ - # self.find_variable(standard_name="horizontal_loop_extent"): - # ldim = "ccpp_constant_one" - # udim = "horizontal_loop_extent" - # else: - # ldim = "horizontal_loop_begin" - # udim = "horizontal_loop_end" - # # endif - # else: - # ldim = "ccpp_constant_one" - # udim = "horizontal_dimension" - # # endif - # else: - # ldim, udim = dim.split(':') - # # end if - # # Get dimension for lower bound - # for var_dict in cldicts: - # lvar = var_dict.find_variable(standard_name=ldim, any_scope=False) - # if lvar is not None: - # break - # # end if - # # end for - # if not lvar: - # raise Exception(f"No variable with standard name '{ldim}' in cldicts") - # # end if - # ldim_lname = lvar.get_prop_value('local_name') - # # Get dimension for upper bound - # for var_dict in cldicts: - # uvar = var_dict.find_variable(standard_name=udim, any_scope=False) - # if uvar is not None: - # break - # # end if - # # end for - # if not uvar: - # raise Exception(f"No variable with standard name '{udim}' in cldicts") - # # end if - # udim_lname = uvar.get_prop_value('local_name') - # ddims[i] = ldim_lname + ':' + udim_lname - ## end for - #lname, ldims = split_dims_from_name(dvar.call_string(search_dict)) - ## This is where it gets tricky. We have a dimension specifier - ## as part of the local name, and we have a dimstr. The latter - ## contains the correct horizontal and vertical extents, the former - ## does not - they can be ranges (containing ":") or indices - ## (that is, the variable is a slice of another variable). - ## We need to walk the ldims list left to right and for each - ## dimension we need to check if it is a range or not. If it is - ## a range, we insert the first element from ddims, then we remove - ## this range from ddims and move on to the next. - #if ldims: - # # Consistency check: - # if len(ldims) < len(ddims): - # raise Exception("Logic error in associate_optional_var: len({ldims}) < len({ddims})") - # for i in range(len(ldims)): - # if ':' in ldims[i]: - # ldims[i] = ddims.pop(0) - # # At the end ddims must be empty - # if ddims: - # raise Exception("Logic error in associate_optional_var: after filling ldims='{ldims}' from ddims, ddims is not empty: '{ddims}'") - # dimstr = '(' + ','.join(ldims) + ')' - #else: - # dimstr = '(' + ','.join(ddims) + ')' - lname = dvar.get_prop_value("local_name") - print(f"lname 1: {lname}") - lname, ldims = split_dims_from_name(dvar.call_string(search_dict)) - dimstr = dvar.call_dimstring(var_dicts=cldicts, explicit_dims=True, loop_subst=self.run_phase()) - lname += dimstr - print(f"lname 2: {lname}") - lnametest = dvar.call_dimstring(var_dicts=cldicts, explicit_dims=True, loop_subst=self.run_phase(), prepend_varname=True) - print(f"lname 3: {lnametest}") - lnametest = dvar.call_string(search_dict, loop_vars=search_dict) - print(f"lname 4: {lnametest}") - #raise Exception("XXX") + lname = dvar.call_dimstring(var_dicts=cldicts, explicit_dims=True, loop_subst=self.run_phase(), prepend_varname=True) # end if lname_ptr = var.get_prop_value('local_name') + '_ptr' @@ -1545,7 +1301,6 @@ def associate_optional_var(self, dict_var, var, has_transform, cldicts, indent, outfile.write(f"{lname_ptr} => {lname}", indent) # end if # end if - # *DH # end def def nullify_optional_var(self, dict_var, var, has_transform, cldicts, indent, outfile): From 6dea9d2e249376021e8388c52db19c5fb6d632a6 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Tue, 28 Apr 2026 11:25:22 -0600 Subject: [PATCH 55/59] Clean up comment in scripts/ddt_library.py --- scripts/ddt_library.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/scripts/ddt_library.py b/scripts/ddt_library.py index 47d6c4d9..42b97115 100644 --- a/scripts/ddt_library.py +++ b/scripts/ddt_library.py @@ -103,13 +103,7 @@ def clone(self, subst_dict=None, remove_intent=False, def call_dimstring(self, var_dicts=None, explicit_dims=False, loop_subst=False, prepend_varname=False): - """Return the dimensions string for a variable call. - If is present, find and substitute a local_name for - each standard_name in this variable's dimensions. - If is not present, return a colon for each dimension. - If is True, include the variable's dimensions. - If is True, apply a loop substitution, if found for any - missing dimension. + """Return the dimensions string for a variable call for self.field. If is True, prepend the VarDDT's local name sequence. """ if self.field is not None: From 2c931cad63b6528202c81b0991e923ee0a76d600 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Tue, 28 Apr 2026 11:25:58 -0600 Subject: [PATCH 56/59] Cleanup --- scripts/metavar.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/scripts/metavar.py b/scripts/metavar.py index e9fe186a..247647ce 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -360,7 +360,6 @@ def __init__(self, prop_dict, source, run_env, is_ddt=False, components=None, co # Make sure all the variable values are valid try: for prop_name, prop_val in self.var_properties(): - #print(f"DHDEBUG: prop_name, prop_val: '{prop_name}' / '{prop_val}'") prop = Var.get_prop(prop_name) _ = prop.valid_value(prop_val, prop_dict=self._prop_dict, error=True) @@ -562,8 +561,6 @@ def handle_array_ref(self): ('foo', ['ccpp_constant_one:dim1', 'ccpp_constant_one:dim2', 'bar']) >>> Var({'local_name' : 'foo(bar,:)', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '(ccpp_constant_one:dim1)', 'type' : 'real',}, ParseSource('vname', 'HOST', ParseContext()), _MVAR_DUMMY_RUN_ENV).handle_array_ref() ('foo', ['bar', 'ccpp_constant_one:dim1']) - >>> Var({'local_name' : 'foo(:)', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '(ccpp_constant_one:dim1)', 'type' : 'real',}, ParseSource('vname', 'HOST', ParseContext()), _MVAR_DUMMY_RUN_ENV).handle_array_ref() - ('foo', ['ccpp_constant_one:dim1']) >>> Var({'local_name' : 'foo(bar)', 'standard_name' : 'hi_mom', 'units' : 'm s-1', 'dimensions' : '(ccpp_constant_one:dim1)', 'type' : 'real',}, ParseSource('vname', 'HOST', ParseContext()), _MVAR_DUMMY_RUN_ENV).handle_array_ref() #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): CCPPError: Call dims mismatch for foo(bar), not enough colons @@ -714,8 +711,8 @@ def call_string(self, var_dicts, loop_vars=None): even if usage requires a loop substitution. """ # DH* - #if loop_vars: - # raise Exception(f"DH DEBUG: WE DO USE loop_vars in call_string! '{loop_vars}'") + if loop_vars: + raise Exception(f"DH DEBUG: WE DO USE loop_vars in call_string! '{loop_vars}'") # *DH if not isinstance(var_dicts, list): var_dicts = [var_dicts] From c9161fd95fb5962255265d1dd631456d247b9ca3 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Tue, 28 Apr 2026 15:12:51 -0600 Subject: [PATCH 57/59] Fix test/advection_test/advection_test_reports.py --- test/advection_test/advection_test_reports.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/advection_test/advection_test_reports.py b/test/advection_test/advection_test_reports.py index de1d764b..9673b171 100644 --- a/test/advection_test/advection_test_reports.py +++ b/test/advection_test/advection_test_reports.py @@ -52,6 +52,7 @@ "banana_array_dim", "test_banana_name_array", "test_banana_constituent_index", + "water_vapor_specific_humidity_not_state", # Added by --debug option "horizontal_dimension", "vertical_layer_dimension"] @@ -65,6 +66,7 @@ "index_of_water_vapor_specific_humidity", "banana_array_dim", "test_banana_name_array", "test_banana_name", + "water_vapor_specific_humidity_not_state", # Added by --debug option "horizontal_dimension", "vertical_layer_dimension"] @@ -78,7 +80,8 @@ "dynamic_constituents_for_cld_liq", "dynamic_constituents_for_cld_liq", "test_banana_constituent_indices", - "test_banana_constituent_index"] + "test_banana_constituent_index", + "water_vapor_specific_humidity_not_state"] class TestAdvectionHostDataTables(unittest.TestCase, BaseTests.TestHostDataTables): From cb4510092646abc30d39b22acb12ee2d05490c15 Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Wed, 29 Apr 2026 15:01:04 -0600 Subject: [PATCH 58/59] Update file_utils.py: fix evaluation of `fmove` --- scripts/file_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/file_utils.py b/scripts/file_utils.py index 1cad8338..1342f72b 100644 --- a/scripts/file_utils.py +++ b/scripts/file_utils.py @@ -286,7 +286,7 @@ def move_modified_files(src_dir, dest_dir, overwrite=False, remove_src=False): if overwrite: fmove = True else: - fmove = filecmp.cmp(src_path, dest_path, shallow=False) + fmove = not filecmp.cmp(src_path, dest_path, shallow=False) # end if else: fmove = True From da9d842ccc302271458e5082356ffc95ba19abbe Mon Sep 17 00:00:00 2001 From: Dom Heinzeller Date: Fri, 1 May 2026 13:07:59 -0600 Subject: [PATCH 59/59] Bug fix in scripts/fortran_tools/parse_fortran.py for parsing character variable declarations --- scripts/fortran_tools/parse_fortran.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/fortran_tools/parse_fortran.py b/scripts/fortran_tools/parse_fortran.py index ab072afd..00e22fce 100644 --- a/scripts/fortran_tools/parse_fortran.py +++ b/scripts/fortran_tools/parse_fortran.py @@ -317,6 +317,10 @@ class FtypeCharacter(Ftype): 'Character(len=512)' >>> FtypeCharacter('character(*)', None).__str__() 'character(len=*)' + >>> FtypeCharacter('character(nf_file_length)', None).__str__() + 'character(len=nf_file_length)' + >>> FtypeCharacter('character(len=nf_file_length)', None).__str__() + 'character(len=nf_file_length)' >>> FtypeCharacter('character*7', None).__str__() #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ParseSyntaxError: Invalid character declaration, 'character*7', at :1 @@ -335,9 +339,9 @@ class FtypeCharacter(Ftype): "character(len=15, kind=kind('b'))" """ - char_re = re.compile(r"(?i)(character)\s*(\([A-Za-z0-9,=*:\s\'\"()]+\))?") + char_re = re.compile(r"(?i)(character)\s*(\([A-Za-z0-9_,=*:\s\'\"()]+\))?") chartrail_re = re.compile(r"\s*[,:]|\s+[A-Z]") - oldchar_re = re.compile(r"(?i)(character)\s*(\*)\s*([0-9]+\s*)") + oldchar_re = re.compile(r"(?i)(character)\s*(\*)\s*([A-Za-z0-9_]+\s*)") oldchartrail_re = re.compile(r"\s*[,]|\s+[A-Z]") len_token_re = re.compile(r"(?i)([:]|[*]|[0-9]+|[A-Z][A-Z0-9_]*)$")