From 1ee7a16483d0292282e135a830051e217a1fb4a2 Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 12 Aug 2024 14:01:13 -0600 Subject: [PATCH 01/12] tendencies working --- scripts/constituents.py | 23 ++++++++++-- scripts/host_cap.py | 43 +++++++++++++++------- scripts/suite_objects.py | 6 +++- src/ccpp_constituent_prop_mod.F90 | 13 +++++++ src/ccpp_constituent_prop_mod.meta | 6 ++++ test/advection_test/cld_liq.F90 | 4 ++- test/advection_test/cld_liq.meta | 6 ++++ test/advection_test/run_test | 3 ++ test/advection_test/test_host.F90 | 55 +++++++++++++++-------------- test/advection_test/test_reports.py | 3 ++ 10 files changed, 119 insertions(+), 43 deletions(-) diff --git a/scripts/constituents.py b/scripts/constituents.py index 84f19b20..9689635f 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -115,6 +115,14 @@ def find_variable(self, standard_name=None, source_var=None, var = source_var.clone({'dimensions' : newdims}, remove_intent=True, source_type=self.__constituent_type) self.add_variable(var, self.__run_env) + # Also add a nearly-identical tendency_of_{standard_name} variable + new_stdname = f"tendency_of_{standard_name}" + new_units = f"{source_var.get_prop_value('units')} s-1" + new_lname = f"{source_var.get_prop_value('local_name')}_tend" + var_tend = source_var.clone({'dimensions' : newdims, 'standard_name' : new_stdname, + 'units': new_units, 'local_name': new_lname}, + remove_intent=True, source_type=self.__constituent_type) + self.add_variable(var_tend, self.__run_env) return var @staticmethod @@ -276,10 +284,18 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): for evar in err_vars: evar.write_def(outfile, indent+1, self, dummy=True) # end for + # Figure out how many constituents variables we have + const_num = 0 + for std_name, var in self.items(): + if 'tendency_of' in std_name or 'index_of' in std_name: + continue + # end if + const_num += 1 + # end for if self: outfile.write("! Local variables", indent+1) outfile.write("integer :: index", indent+1) - stmt = f"allocate({self.constituent_prop_array_name()}({len(self)}))" + stmt = f"allocate({self.constituent_prop_array_name()}({const_num}))" outfile.write(stmt, indent+1) outfile.write("index = 0", indent+1) # end if @@ -287,6 +303,9 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): self.__init_err_var(evar, outfile, indent+1) # end for for std_name, var in self.items(): + if 'tendency_of' in std_name or 'index_of' in std_name: + continue + # end if outfile.write("index = index + 1", indent+1) long_name = var.get_prop_value('long_name') units = var.get_prop_value('units') @@ -336,7 +355,7 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): outfile.write(stmt, indent+1) outfile.write(f"call {self.constituent_prop_init_consts()}({local_call})", indent+2) outfile.write("end if", indent+1) - outfile.write(f"{fname} = {len(self)}", indent+1) + outfile.write(f"{fname} = {const_num}", indent+1) outfile.write(f"end function {fname}", indent) outfile.write(f"\n! {border}\n", 1) # Return the name of a constituent given an index diff --git a/scripts/host_cap.py b/scripts/host_cap.py index 91d91493..09a08ab3 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -284,6 +284,7 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): vert_layer_dim = "vertical_layer_dimension" vert_interface_dim = "vertical_interface_dimension" array_layer = "vars_layer" + tend_layer = "vars_layer_tend" # Table preamble (leave off ccpp-table-properties header) ddt_mdata = [ #"[ccpp-table-properties]", @@ -340,19 +341,35 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): emsg = f"Unsupported 2-D variable, '{std_name}'" raise CCPPError(emsg) # end if - # First, create an index variable for - ind_std_name = "index_of_{}".format(std_name) - loc_name = f"{cvar_array_name}(:,:,{ind_std_name})" - ddt_mdata.append(f"[ {loc_name} ]") - ddt_mdata.append(f" standard_name = {std_name}") - units = cvar.get_prop_value('units') - ddt_mdata.append(f" units = {units}") - dimstr = f"({', '.join(dims)})" - ddt_mdata.append(f" dimensions = {dimstr}") - vtype = cvar.get_prop_value('type') - vkind = cvar.get_prop_value('kind') - ddt_mdata.append(f" type = {vtype} | kind = {vkind}") - const_stdnames.add(std_name) + if "tendency_of" not in std_name: + # Create an index variable for + ind_std_name = "index_of_{}".format(std_name) + loc_name = f"{cvar_array_name}(:,:,{ind_std_name})" + ddt_mdata.append(f"[ {loc_name} ]") + ddt_mdata.append(f" standard_name = {std_name}") + units = cvar.get_prop_value('units') + ddt_mdata.append(f" units = {units}") + dimstr = f"({', '.join(dims)})" + ddt_mdata.append(f" dimensions = {dimstr}") + vtype = cvar.get_prop_value('type') + vkind = cvar.get_prop_value('kind') + ddt_mdata.append(f" type = {vtype} | kind = {vkind}") + const_stdnames.add(std_name) + else: + # Create an index variable for tendency of constituent + const_std_name = std_name.split('tendency_of_')[1] + ind_std_name = "index_of_{}".format(const_std_name) + loc_name = f"{tend_layer}(:,:,{ind_std_name})" + ddt_mdata.append(f"[ {loc_name} ]") + ddt_mdata.append(f" standard_name = {std_name}") + units = cvar.get_prop_value('units') + ddt_mdata.append(f" units = {units} s-1") + dimstr = f"({', '.join(dims)})" + ddt_mdata.append(f" dimensions = {dimstr}") + vtype = cvar.get_prop_value('type') + vkind = cvar.get_prop_value('kind') + ddt_mdata.append(f" type = {vtype} | kind = {vkind}") + # end if # end if # end for # end for diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 1056b8dd..4dcd1ba6 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1430,7 +1430,11 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er # 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 svar: + intent = svar.get_prop_value('intent') + else: + intent = 'in' + # end if if intent == 'out' and allocatable: return diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index 3370bff4..af1c01e1 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -148,6 +148,7 @@ module ccpp_constituent_prop_mod ! These fields are public to allow for efficient (i.e., no copying) ! usage even though it breaks object independence real(kind_phys), allocatable :: vars_layer(:,:,:) + real(kind_phys), allocatable :: vars_layer_tend(:,:,:) real(kind_phys), allocatable :: vars_minvalue(:) ! An array containing all the constituent metadata ! Each element contains a pointer to a constituent from the hash table @@ -1465,12 +1466,21 @@ subroutine ccp_model_const_data_lock(this, ncols, num_layers, errcode, errmsg) call handle_allocate_error(astat, 'vars_layer', & subname, errcode=errcode, errmsg=errmsg) errcode_local = astat + if (astat == 0) then + allocate(this%vars_layer_tend(ncols, num_layers, this%hash_table%num_values()), & + stat=astat) + call handle_allocate_error(astat, 'vars_layer_tend', & + subname, errcode=errcode, errmsg=errmsg) + errcode_local = astat + end if if (astat == 0) then allocate(this%vars_minvalue(this%hash_table%num_values()), stat=astat) call handle_allocate_error(astat, 'vars_minvalue', & subname, errcode=errcode, errmsg=errmsg) errcode_local = astat end if + ! Initialize tendencies to 0 + this%vars_layer_tend(:,:,:) = 0._kind_phys if (errcode_local == 0) then this%num_layers = num_layers do index = 1, this%hash_table%num_values() @@ -1521,6 +1531,9 @@ subroutine ccp_model_const_reset(this, clear_hash_table) if (allocated(this%vars_minvalue)) then deallocate(this%vars_minvalue) end if + if (allocated(this%vars_layer_tend)) then + deallocate(this%vars_layer_tend) + end if if (allocated(this%const_metadata)) then if (clear_table) then do index = 1, size(this%const_metadata, 1) diff --git a/src/ccpp_constituent_prop_mod.meta b/src/ccpp_constituent_prop_mod.meta index 99cf3145..f5848484 100644 --- a/src/ccpp_constituent_prop_mod.meta +++ b/src/ccpp_constituent_prop_mod.meta @@ -34,6 +34,12 @@ state_variable = true dimensions = (horizontal_dimension, vertical_layer_dimension, number_of_ccpp_constituents) type = real | kind = kind_phys +[ vars_layer_tend ] + standard_name = ccpp_constituents_tendencies + long_name = Array of constituent tendencies managed by CCPP Framework + units = none + dimensions = (horizontal_dimension, vertical_layer_dimension, number_of_ccpp_constituents) + type = real | kind = kind_phys [ const_metadata ] standard_name = ccpp_constituent_properties units = None diff --git a/test/advection_test/cld_liq.F90 b/test/advection_test/cld_liq.F90 index 63148c52..f9591b6c 100644 --- a/test/advection_test/cld_liq.F90 +++ b/test/advection_test/cld_liq.F90 @@ -18,7 +18,7 @@ MODULE cld_liq !! \htmlinclude arg_table_cld_liq_run.html !! subroutine cld_liq_run(ncol, timestep, tcld, temp, qv, ps, cld_liq_array, & - errmsg, errflg) + cld_liq_tend, errmsg, errflg) integer, intent(in) :: ncol real(kind_phys), intent(in) :: timestep @@ -27,6 +27,8 @@ subroutine cld_liq_run(ncol, timestep, tcld, temp, qv, ps, cld_liq_array, & real(kind_phys), intent(inout) :: qv(:,:) real(kind_phys), intent(in) :: ps(:) REAL(kind_phys), intent(inout) :: cld_liq_array(:,:) + REAL(kind_phys), intent(inout) :: cld_liq_tend(:,:) +! integer, intent(in) :: cld_liq_ind character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg !---------------------------------------------------------------- diff --git a/test/advection_test/cld_liq.meta b/test/advection_test/cld_liq.meta index 1b9373f9..650ba052 100644 --- a/test/advection_test/cld_liq.meta +++ b/test/advection_test/cld_liq.meta @@ -55,6 +55,12 @@ dimensions = (horizontal_loop_extent, vertical_layer_dimension) type = real | kind = kind_phys intent = inout +[ cld_liq_tend ] + standard_name = tendency_of_cloud_liquid_dry_mixing_ratio + units = kg kg-1 s-1 + dimensions = (horizontal_loop_extent, vertical_layer_dimension) + type = real | kind = kind_phys + intent = inout [ errmsg ] standard_name = ccpp_error_message long_name = Error message for error handling in CCPP diff --git a/test/advection_test/run_test b/test/advection_test/run_test index 1d9a6d44..67f338a0 100755 --- a/test/advection_test/run_test +++ b/test/advection_test/run_test @@ -137,6 +137,7 @@ required_vars="${required_vars},horizontal_loop_begin" required_vars="${required_vars},horizontal_loop_end" required_vars="${required_vars},surface_air_pressure" required_vars="${required_vars},temperature" +required_vars="${required_vars},tendency_of_cloud_liquid_dry_mixing_ratio" required_vars="${required_vars},time_step_for_physics" required_vars="${required_vars},vertical_layer_dimension" required_vars="${required_vars},water_temperature_at_freezing" @@ -146,6 +147,7 @@ input_vars="${input_vars},horizontal_dimension" input_vars="${input_vars},horizontal_loop_begin" input_vars="${input_vars},horizontal_loop_end" input_vars="${input_vars},surface_air_pressure,temperature" +input_vars="${input_vars},tendency_of_cloud_liquid_dry_mixing_ratio" input_vars="${input_vars},time_step_for_physics" input_vars="${input_vars},vertical_layer_dimension" input_vars="${input_vars},water_temperature_at_freezing" @@ -154,6 +156,7 @@ output_vars="ccpp_error_code,ccpp_error_message" output_vars="${output_vars},cloud_ice_dry_mixing_ratio" output_vars="${output_vars},cloud_liquid_dry_mixing_ratio" output_vars="${output_vars},temperature" +output_vars="${output_vars},tendency_of_cloud_liquid_dry_mixing_ratio" output_vars="${output_vars},water_vapor_specific_humidity" ## diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index fe1f9e0b..04b0dca2 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -10,7 +10,7 @@ module test_prog ! Public data and interfaces integer, public, parameter :: cs = 16 - integer, public, parameter :: cm = 36 + integer, public, parameter :: cm = 41 !> \section arg_table_suite_info Argument Table !! \htmlinclude arg_table_suite_info.html @@ -1008,39 +1008,42 @@ program test implicit none character(len=cs), target :: test_parts1(1) - character(len=cm), target :: test_invars1(7) - character(len=cm), target :: test_outvars1(6) - character(len=cm), target :: test_reqvars1(9) + character(len=cm), target :: test_invars1(8) + character(len=cm), target :: test_outvars1(7) + character(len=cm), target :: test_reqvars1(10) type(suite_info) :: test_suites(1) logical :: run_okay test_parts1 = (/ 'physics '/) test_invars1 = (/ & - 'cloud_ice_dry_mixing_ratio ', & - 'cloud_liquid_dry_mixing_ratio ', & - 'surface_air_pressure ', & - 'temperature ', & - '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', & + 'surface_air_pressure ', & + 'temperature ', & + 'time_step_for_physics ', & + 'water_temperature_at_freezing ', & + 'water_vapor_specific_humidity ' /) test_outvars1 = (/ & - 'ccpp_error_message ', & - 'ccpp_error_code ', & - 'temperature ', & - 'water_vapor_specific_humidity ', & - 'cloud_liquid_dry_mixing_ratio ', & - 'cloud_ice_dry_mixing_ratio ' /) + 'ccpp_error_message ', & + 'ccpp_error_code ', & + 'temperature ', & + 'water_vapor_specific_humidity ', & + 'cloud_liquid_dry_mixing_ratio ', & + 'tendency_of_cloud_liquid_dry_mixing_ratio', & + 'cloud_ice_dry_mixing_ratio ' /) test_reqvars1 = (/ & - 'surface_air_pressure ', & - 'temperature ', & - 'time_step_for_physics ', & - 'cloud_liquid_dry_mixing_ratio ', & - 'cloud_ice_dry_mixing_ratio ', & - 'water_temperature_at_freezing ', & - 'water_vapor_specific_humidity ', & - 'ccpp_error_message ', & - 'ccpp_error_code ' /) + 'surface_air_pressure ', & + 'temperature ', & + 'time_step_for_physics ', & + 'cloud_liquid_dry_mixing_ratio ', & + 'tendency_of_cloud_liquid_dry_mixing_ratio', & + 'cloud_ice_dry_mixing_ratio ', & + 'water_temperature_at_freezing ', & + 'water_vapor_specific_humidity ', & + 'ccpp_error_message ', & + 'ccpp_error_code ' /) ! Setup expected test suite info diff --git a/test/advection_test/test_reports.py b/test/advection_test/test_reports.py index 1a34462a..f30a947c 100644 --- a/test/advection_test/test_reports.py +++ b/test/advection_test/test_reports.py @@ -70,6 +70,7 @@ def usage(errmsg=None): _REQUIRED_VARS_CLD = ["ccpp_error_code", "ccpp_error_message", "horizontal_loop_begin", "horizontal_loop_end", "surface_air_pressure", "temperature", + "tendency_of_cloud_liquid_dry_mixing_ratio", "time_step_for_physics", "water_temperature_at_freezing", "water_vapor_specific_humidity", "cloud_ice_dry_mixing_ratio", @@ -83,11 +84,13 @@ def usage(errmsg=None): "water_vapor_specific_humidity", "cloud_ice_dry_mixing_ratio", "cloud_liquid_dry_mixing_ratio", + "tendency_of_cloud_liquid_dry_mixing_ratio", # 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", "cloud_liquid_dry_mixing_ratio"] From 922a4ddbf55ab0241fe4c9712e6dbb8d8894fb5a Mon Sep 17 00:00:00 2001 From: Courtney Peverley Date: Mon, 12 Aug 2024 14:06:54 -0600 Subject: [PATCH 02/12] tendencies working --- scripts/constituents.py | 23 ++++++++++-- scripts/host_cap.py | 43 +++++++++++++++------- scripts/suite_objects.py | 6 +++- src/ccpp_constituent_prop_mod.F90 | 13 +++++++ src/ccpp_constituent_prop_mod.meta | 6 ++++ test/advection_test/cld_liq.F90 | 4 ++- test/advection_test/cld_liq.meta | 6 ++++ test/advection_test/run_test | 3 ++ test/advection_test/test_host.F90 | 55 +++++++++++++++-------------- test/advection_test/test_reports.py | 3 ++ 10 files changed, 119 insertions(+), 43 deletions(-) diff --git a/scripts/constituents.py b/scripts/constituents.py index 84f19b20..9689635f 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -115,6 +115,14 @@ def find_variable(self, standard_name=None, source_var=None, var = source_var.clone({'dimensions' : newdims}, remove_intent=True, source_type=self.__constituent_type) self.add_variable(var, self.__run_env) + # Also add a nearly-identical tendency_of_{standard_name} variable + new_stdname = f"tendency_of_{standard_name}" + new_units = f"{source_var.get_prop_value('units')} s-1" + new_lname = f"{source_var.get_prop_value('local_name')}_tend" + var_tend = source_var.clone({'dimensions' : newdims, 'standard_name' : new_stdname, + 'units': new_units, 'local_name': new_lname}, + remove_intent=True, source_type=self.__constituent_type) + self.add_variable(var_tend, self.__run_env) return var @staticmethod @@ -276,10 +284,18 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): for evar in err_vars: evar.write_def(outfile, indent+1, self, dummy=True) # end for + # Figure out how many constituents variables we have + const_num = 0 + for std_name, var in self.items(): + if 'tendency_of' in std_name or 'index_of' in std_name: + continue + # end if + const_num += 1 + # end for if self: outfile.write("! Local variables", indent+1) outfile.write("integer :: index", indent+1) - stmt = f"allocate({self.constituent_prop_array_name()}({len(self)}))" + stmt = f"allocate({self.constituent_prop_array_name()}({const_num}))" outfile.write(stmt, indent+1) outfile.write("index = 0", indent+1) # end if @@ -287,6 +303,9 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): self.__init_err_var(evar, outfile, indent+1) # end for for std_name, var in self.items(): + if 'tendency_of' in std_name or 'index_of' in std_name: + continue + # end if outfile.write("index = index + 1", indent+1) long_name = var.get_prop_value('long_name') units = var.get_prop_value('units') @@ -336,7 +355,7 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): outfile.write(stmt, indent+1) outfile.write(f"call {self.constituent_prop_init_consts()}({local_call})", indent+2) outfile.write("end if", indent+1) - outfile.write(f"{fname} = {len(self)}", indent+1) + outfile.write(f"{fname} = {const_num}", indent+1) outfile.write(f"end function {fname}", indent) outfile.write(f"\n! {border}\n", 1) # Return the name of a constituent given an index diff --git a/scripts/host_cap.py b/scripts/host_cap.py index 91d91493..09a08ab3 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -284,6 +284,7 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): vert_layer_dim = "vertical_layer_dimension" vert_interface_dim = "vertical_interface_dimension" array_layer = "vars_layer" + tend_layer = "vars_layer_tend" # Table preamble (leave off ccpp-table-properties header) ddt_mdata = [ #"[ccpp-table-properties]", @@ -340,19 +341,35 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): emsg = f"Unsupported 2-D variable, '{std_name}'" raise CCPPError(emsg) # end if - # First, create an index variable for - ind_std_name = "index_of_{}".format(std_name) - loc_name = f"{cvar_array_name}(:,:,{ind_std_name})" - ddt_mdata.append(f"[ {loc_name} ]") - ddt_mdata.append(f" standard_name = {std_name}") - units = cvar.get_prop_value('units') - ddt_mdata.append(f" units = {units}") - dimstr = f"({', '.join(dims)})" - ddt_mdata.append(f" dimensions = {dimstr}") - vtype = cvar.get_prop_value('type') - vkind = cvar.get_prop_value('kind') - ddt_mdata.append(f" type = {vtype} | kind = {vkind}") - const_stdnames.add(std_name) + if "tendency_of" not in std_name: + # Create an index variable for + ind_std_name = "index_of_{}".format(std_name) + loc_name = f"{cvar_array_name}(:,:,{ind_std_name})" + ddt_mdata.append(f"[ {loc_name} ]") + ddt_mdata.append(f" standard_name = {std_name}") + units = cvar.get_prop_value('units') + ddt_mdata.append(f" units = {units}") + dimstr = f"({', '.join(dims)})" + ddt_mdata.append(f" dimensions = {dimstr}") + vtype = cvar.get_prop_value('type') + vkind = cvar.get_prop_value('kind') + ddt_mdata.append(f" type = {vtype} | kind = {vkind}") + const_stdnames.add(std_name) + else: + # Create an index variable for tendency of constituent + const_std_name = std_name.split('tendency_of_')[1] + ind_std_name = "index_of_{}".format(const_std_name) + loc_name = f"{tend_layer}(:,:,{ind_std_name})" + ddt_mdata.append(f"[ {loc_name} ]") + ddt_mdata.append(f" standard_name = {std_name}") + units = cvar.get_prop_value('units') + ddt_mdata.append(f" units = {units} s-1") + dimstr = f"({', '.join(dims)})" + ddt_mdata.append(f" dimensions = {dimstr}") + vtype = cvar.get_prop_value('type') + vkind = cvar.get_prop_value('kind') + ddt_mdata.append(f" type = {vtype} | kind = {vkind}") + # end if # end if # end for # end for diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 1056b8dd..4dcd1ba6 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1430,7 +1430,11 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er # 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 svar: + intent = svar.get_prop_value('intent') + else: + intent = 'in' + # end if if intent == 'out' and allocatable: return diff --git a/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index 3370bff4..af1c01e1 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -148,6 +148,7 @@ module ccpp_constituent_prop_mod ! These fields are public to allow for efficient (i.e., no copying) ! usage even though it breaks object independence real(kind_phys), allocatable :: vars_layer(:,:,:) + real(kind_phys), allocatable :: vars_layer_tend(:,:,:) real(kind_phys), allocatable :: vars_minvalue(:) ! An array containing all the constituent metadata ! Each element contains a pointer to a constituent from the hash table @@ -1465,12 +1466,21 @@ subroutine ccp_model_const_data_lock(this, ncols, num_layers, errcode, errmsg) call handle_allocate_error(astat, 'vars_layer', & subname, errcode=errcode, errmsg=errmsg) errcode_local = astat + if (astat == 0) then + allocate(this%vars_layer_tend(ncols, num_layers, this%hash_table%num_values()), & + stat=astat) + call handle_allocate_error(astat, 'vars_layer_tend', & + subname, errcode=errcode, errmsg=errmsg) + errcode_local = astat + end if if (astat == 0) then allocate(this%vars_minvalue(this%hash_table%num_values()), stat=astat) call handle_allocate_error(astat, 'vars_minvalue', & subname, errcode=errcode, errmsg=errmsg) errcode_local = astat end if + ! Initialize tendencies to 0 + this%vars_layer_tend(:,:,:) = 0._kind_phys if (errcode_local == 0) then this%num_layers = num_layers do index = 1, this%hash_table%num_values() @@ -1521,6 +1531,9 @@ subroutine ccp_model_const_reset(this, clear_hash_table) if (allocated(this%vars_minvalue)) then deallocate(this%vars_minvalue) end if + if (allocated(this%vars_layer_tend)) then + deallocate(this%vars_layer_tend) + end if if (allocated(this%const_metadata)) then if (clear_table) then do index = 1, size(this%const_metadata, 1) diff --git a/src/ccpp_constituent_prop_mod.meta b/src/ccpp_constituent_prop_mod.meta index 99cf3145..f5848484 100644 --- a/src/ccpp_constituent_prop_mod.meta +++ b/src/ccpp_constituent_prop_mod.meta @@ -34,6 +34,12 @@ state_variable = true dimensions = (horizontal_dimension, vertical_layer_dimension, number_of_ccpp_constituents) type = real | kind = kind_phys +[ vars_layer_tend ] + standard_name = ccpp_constituents_tendencies + long_name = Array of constituent tendencies managed by CCPP Framework + units = none + dimensions = (horizontal_dimension, vertical_layer_dimension, number_of_ccpp_constituents) + type = real | kind = kind_phys [ const_metadata ] standard_name = ccpp_constituent_properties units = None diff --git a/test/advection_test/cld_liq.F90 b/test/advection_test/cld_liq.F90 index 63148c52..f9591b6c 100644 --- a/test/advection_test/cld_liq.F90 +++ b/test/advection_test/cld_liq.F90 @@ -18,7 +18,7 @@ MODULE cld_liq !! \htmlinclude arg_table_cld_liq_run.html !! subroutine cld_liq_run(ncol, timestep, tcld, temp, qv, ps, cld_liq_array, & - errmsg, errflg) + cld_liq_tend, errmsg, errflg) integer, intent(in) :: ncol real(kind_phys), intent(in) :: timestep @@ -27,6 +27,8 @@ subroutine cld_liq_run(ncol, timestep, tcld, temp, qv, ps, cld_liq_array, & real(kind_phys), intent(inout) :: qv(:,:) real(kind_phys), intent(in) :: ps(:) REAL(kind_phys), intent(inout) :: cld_liq_array(:,:) + REAL(kind_phys), intent(inout) :: cld_liq_tend(:,:) +! integer, intent(in) :: cld_liq_ind character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg !---------------------------------------------------------------- diff --git a/test/advection_test/cld_liq.meta b/test/advection_test/cld_liq.meta index 1b9373f9..650ba052 100644 --- a/test/advection_test/cld_liq.meta +++ b/test/advection_test/cld_liq.meta @@ -55,6 +55,12 @@ dimensions = (horizontal_loop_extent, vertical_layer_dimension) type = real | kind = kind_phys intent = inout +[ cld_liq_tend ] + standard_name = tendency_of_cloud_liquid_dry_mixing_ratio + units = kg kg-1 s-1 + dimensions = (horizontal_loop_extent, vertical_layer_dimension) + type = real | kind = kind_phys + intent = inout [ errmsg ] standard_name = ccpp_error_message long_name = Error message for error handling in CCPP diff --git a/test/advection_test/run_test b/test/advection_test/run_test index 1d9a6d44..67f338a0 100755 --- a/test/advection_test/run_test +++ b/test/advection_test/run_test @@ -137,6 +137,7 @@ required_vars="${required_vars},horizontal_loop_begin" required_vars="${required_vars},horizontal_loop_end" required_vars="${required_vars},surface_air_pressure" required_vars="${required_vars},temperature" +required_vars="${required_vars},tendency_of_cloud_liquid_dry_mixing_ratio" required_vars="${required_vars},time_step_for_physics" required_vars="${required_vars},vertical_layer_dimension" required_vars="${required_vars},water_temperature_at_freezing" @@ -146,6 +147,7 @@ input_vars="${input_vars},horizontal_dimension" input_vars="${input_vars},horizontal_loop_begin" input_vars="${input_vars},horizontal_loop_end" input_vars="${input_vars},surface_air_pressure,temperature" +input_vars="${input_vars},tendency_of_cloud_liquid_dry_mixing_ratio" input_vars="${input_vars},time_step_for_physics" input_vars="${input_vars},vertical_layer_dimension" input_vars="${input_vars},water_temperature_at_freezing" @@ -154,6 +156,7 @@ output_vars="ccpp_error_code,ccpp_error_message" output_vars="${output_vars},cloud_ice_dry_mixing_ratio" output_vars="${output_vars},cloud_liquid_dry_mixing_ratio" output_vars="${output_vars},temperature" +output_vars="${output_vars},tendency_of_cloud_liquid_dry_mixing_ratio" output_vars="${output_vars},water_vapor_specific_humidity" ## diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index fe1f9e0b..04b0dca2 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -10,7 +10,7 @@ module test_prog ! Public data and interfaces integer, public, parameter :: cs = 16 - integer, public, parameter :: cm = 36 + integer, public, parameter :: cm = 41 !> \section arg_table_suite_info Argument Table !! \htmlinclude arg_table_suite_info.html @@ -1008,39 +1008,42 @@ program test implicit none character(len=cs), target :: test_parts1(1) - character(len=cm), target :: test_invars1(7) - character(len=cm), target :: test_outvars1(6) - character(len=cm), target :: test_reqvars1(9) + character(len=cm), target :: test_invars1(8) + character(len=cm), target :: test_outvars1(7) + character(len=cm), target :: test_reqvars1(10) type(suite_info) :: test_suites(1) logical :: run_okay test_parts1 = (/ 'physics '/) test_invars1 = (/ & - 'cloud_ice_dry_mixing_ratio ', & - 'cloud_liquid_dry_mixing_ratio ', & - 'surface_air_pressure ', & - 'temperature ', & - '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', & + 'surface_air_pressure ', & + 'temperature ', & + 'time_step_for_physics ', & + 'water_temperature_at_freezing ', & + 'water_vapor_specific_humidity ' /) test_outvars1 = (/ & - 'ccpp_error_message ', & - 'ccpp_error_code ', & - 'temperature ', & - 'water_vapor_specific_humidity ', & - 'cloud_liquid_dry_mixing_ratio ', & - 'cloud_ice_dry_mixing_ratio ' /) + 'ccpp_error_message ', & + 'ccpp_error_code ', & + 'temperature ', & + 'water_vapor_specific_humidity ', & + 'cloud_liquid_dry_mixing_ratio ', & + 'tendency_of_cloud_liquid_dry_mixing_ratio', & + 'cloud_ice_dry_mixing_ratio ' /) test_reqvars1 = (/ & - 'surface_air_pressure ', & - 'temperature ', & - 'time_step_for_physics ', & - 'cloud_liquid_dry_mixing_ratio ', & - 'cloud_ice_dry_mixing_ratio ', & - 'water_temperature_at_freezing ', & - 'water_vapor_specific_humidity ', & - 'ccpp_error_message ', & - 'ccpp_error_code ' /) + 'surface_air_pressure ', & + 'temperature ', & + 'time_step_for_physics ', & + 'cloud_liquid_dry_mixing_ratio ', & + 'tendency_of_cloud_liquid_dry_mixing_ratio', & + 'cloud_ice_dry_mixing_ratio ', & + 'water_temperature_at_freezing ', & + 'water_vapor_specific_humidity ', & + 'ccpp_error_message ', & + 'ccpp_error_code ' /) ! Setup expected test suite info diff --git a/test/advection_test/test_reports.py b/test/advection_test/test_reports.py index 1a34462a..f30a947c 100644 --- a/test/advection_test/test_reports.py +++ b/test/advection_test/test_reports.py @@ -70,6 +70,7 @@ def usage(errmsg=None): _REQUIRED_VARS_CLD = ["ccpp_error_code", "ccpp_error_message", "horizontal_loop_begin", "horizontal_loop_end", "surface_air_pressure", "temperature", + "tendency_of_cloud_liquid_dry_mixing_ratio", "time_step_for_physics", "water_temperature_at_freezing", "water_vapor_specific_humidity", "cloud_ice_dry_mixing_ratio", @@ -83,11 +84,13 @@ def usage(errmsg=None): "water_vapor_specific_humidity", "cloud_ice_dry_mixing_ratio", "cloud_liquid_dry_mixing_ratio", + "tendency_of_cloud_liquid_dry_mixing_ratio", # 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", "cloud_liquid_dry_mixing_ratio"] From 44179da9313c1faac793ff25ab87046a05be84bf Mon Sep 17 00:00:00 2001 From: peverwhee Date: Mon, 19 Aug 2024 15:36:50 -0600 Subject: [PATCH 03/12] better implementation; tendencies working --- scripts/constituents.py | 8 ---- scripts/host_cap.py | 66 ++++++++++++++++----------------- scripts/suite_objects.py | 55 +++++++++++++++++++++++++++ test/advection_test/cld_liq.F90 | 1 + 4 files changed, 88 insertions(+), 42 deletions(-) diff --git a/scripts/constituents.py b/scripts/constituents.py index 9689635f..7456735b 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -115,14 +115,6 @@ def find_variable(self, standard_name=None, source_var=None, var = source_var.clone({'dimensions' : newdims}, remove_intent=True, source_type=self.__constituent_type) self.add_variable(var, self.__run_env) - # Also add a nearly-identical tendency_of_{standard_name} variable - new_stdname = f"tendency_of_{standard_name}" - new_units = f"{source_var.get_prop_value('units')} s-1" - new_lname = f"{source_var.get_prop_value('local_name')}_tend" - var_tend = source_var.clone({'dimensions' : newdims, 'standard_name' : new_stdname, - 'units': new_units, 'local_name': new_lname}, - remove_intent=True, source_type=self.__constituent_type) - self.add_variable(var_tend, self.__run_env) return var @staticmethod diff --git a/scripts/host_cap.py b/scripts/host_cap.py index 09a08ab3..34f767d4 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -19,7 +19,7 @@ from metavar import Var, VarDictionary, CCPP_CONSTANT_VARS from metavar import CCPP_LOOP_VAR_STDNAMES from fortran_tools import FortranWriter -from parse_tools import CCPPError +from parse_tools import CCPPError, ParseInternalError from parse_tools import ParseObject, ParseSource, ParseContext ############################################################################### @@ -341,35 +341,32 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): emsg = f"Unsupported 2-D variable, '{std_name}'" raise CCPPError(emsg) # end if - if "tendency_of" not in std_name: - # Create an index variable for - ind_std_name = "index_of_{}".format(std_name) - loc_name = f"{cvar_array_name}(:,:,{ind_std_name})" - ddt_mdata.append(f"[ {loc_name} ]") - ddt_mdata.append(f" standard_name = {std_name}") - units = cvar.get_prop_value('units') - ddt_mdata.append(f" units = {units}") - dimstr = f"({', '.join(dims)})" - ddt_mdata.append(f" dimensions = {dimstr}") - vtype = cvar.get_prop_value('type') - vkind = cvar.get_prop_value('kind') - ddt_mdata.append(f" type = {vtype} | kind = {vkind}") - const_stdnames.add(std_name) - else: - # Create an index variable for tendency of constituent - const_std_name = std_name.split('tendency_of_')[1] - ind_std_name = "index_of_{}".format(const_std_name) - loc_name = f"{tend_layer}(:,:,{ind_std_name})" - ddt_mdata.append(f"[ {loc_name} ]") - ddt_mdata.append(f" standard_name = {std_name}") - units = cvar.get_prop_value('units') - ddt_mdata.append(f" units = {units} s-1") - dimstr = f"({', '.join(dims)})" - ddt_mdata.append(f" dimensions = {dimstr}") - vtype = cvar.get_prop_value('type') - vkind = cvar.get_prop_value('kind') - ddt_mdata.append(f" type = {vtype} | kind = {vkind}") - # end if + # Create an index variable for + ind_std_name = "index_of_{}".format(std_name) + loc_name = f"{cvar_array_name}(:,:,{ind_std_name})" + ddt_mdata.append(f"[ {loc_name} ]") + ddt_mdata.append(f" standard_name = {std_name}") + units = cvar.get_prop_value('units') + ddt_mdata.append(f" units = {units}") + dimstr = f"({', '.join(dims)})" + ddt_mdata.append(f" dimensions = {dimstr}") + vtype = cvar.get_prop_value('type') + vkind = cvar.get_prop_value('kind') + ddt_mdata.append(f" type = {vtype} | kind = {vkind}") + const_stdnames.add(std_name) + + # Create a tendency variable for the constituent + ind_std_name = "index_of_{}".format(std_name) + loc_name = f"{tend_layer}(:,:,{ind_std_name})" + ddt_mdata.append(f"[ {loc_name} ]") + ddt_mdata.append(f" standard_name = tendency_of_{std_name}") + units = cvar.get_prop_value('units') + ddt_mdata.append(f" units = {units} s-1") + dimstr = f"({', '.join(dims)})" + ddt_mdata.append(f" dimensions = {dimstr}") + vtype = cvar.get_prop_value('type') + vkind = cvar.get_prop_value('kind') + ddt_mdata.append(f" type = {vtype} | kind = {vkind}") # end if # end for # end for @@ -384,7 +381,7 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): del ddt_mdata # Now, create the "host constituent module" dictionary const_dict = VarDictionary(f"{host_model.name}_constituents", - run_env, parent_dict=host_model) + run_env, parent_dict=host_model) # Add the constituents object to const_dict and write its declaration const_var = host_model.find_variable(CONST_OBJ_STDNAME) if const_var: @@ -483,7 +480,7 @@ def suite_part_call_list(host_model, const_dict, suite_part, subst_loop_vars): # end for if hvar is None: errmsg = 'No host model variable for {} in {}' - raise CCPPError(errmsg.format(stdname, suite_part.name)) + raise ParseInternalError(errmsg.format(stdname, suite_part.name)) # End if if stdname not in CCPP_CONSTANT_VARS: lname = var_dict.var_call_string(hvar, loop_vars=loop_vars) @@ -574,9 +571,10 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): stdname = sp_var.get_prop_value('standard_name') hvar = const_dict.find_variable(standard_name=stdname, any_scope=True) + # end if if hvar is None: - errmsg = 'No host model variable for {} in {}' - raise CCPPError(errmsg.format(stdname, spart.name)) + errmsg = '2No host model variable for {} in {}' + raise ParseInternalError(errmsg.format(stdname, spart.name)) # End if # End for (loop over part variables) # End for (loop of suite parts) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 4dcd1ba6..9ad10758 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1137,6 +1137,7 @@ def is_local_variable(self, var): def analyze(self, phase, group, scheme_library, suite_vars, level): """Analyze the scheme's interface to prepare for writing""" + constituent_prefixes = ['tendency_of_', 'index_of_'] self.__group = group my_header = None if self.name in scheme_library: @@ -1165,11 +1166,16 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): # end if scheme_mods = set() scheme_mods.add((my_header.module, self.subroutine_name)) + secondary_const_vars = [] + primary_const_vars = [] 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') + if var.is_constituent: + primary_const_vars.append(var) + # end if args = self.match_variable(var, self.run_env) found, dict_var, vert_dim, new_dims, missing_vert, compat_obj = args if found: @@ -1214,6 +1220,9 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): self.__group.manage_variable(var) # We still need it in our call list (the group uses a clone) self.add_call_list_variable(var) + elif any(prefix in vstdname for prefix in constituent_prefixes): + # Ignore these, but save them for later checking + secondary_const_vars.append(var) else: errmsg = 'Input argument for {}, {}, not found.' if self.find_variable(source_var=var) is not None: @@ -1257,8 +1266,54 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): suite_vars, level) # end if # end if + # Check any secondary constituent variables (index, tendency) + errflg, errmsg = self.check_secondary_constituent_variables(secondary_const_vars, primary_const_vars, + constituent_prefixes) + if errflg == 1: + raise CCPPError(errmsg) + # end if + + # Add secondary constituent variables to group and call list + # peverwhee TODO - variable transforms? + for var in secondary_const_vars: + #self.__group.manage_variable(var) + self.add_call_list_variable(var) + self.update_group_call_list_variable(var) + # end for +# if self.call_list.name == 'cld_liq_call_list' and len(secondary_const_vars) > 0: +# raise ParseInternalError('hi') + return scheme_mods + def check_secondary_constituent_variables(self, secondary_list, primary_list, secondary_prefixes): + """Return an error if there are any secondary variables that reference + a nonexisting constituent""" + errflg = 0 + errmsg = '' + for secondary_const in secondary_list: + valid_var = False + for primary_const in primary_list: + sec_stdname = secondary_const.get_prop_value('standard_name') + prim_stdname = primary_const.get_prop_value('standard_name') + for prefix in secondary_prefixes: + if prefix in sec_stdname: + sec_stdname = sec_stdname.split(prefix, 1)[1] + exit + # end if + # end for + if prim_stdname == sec_stdname: + valid_var = True + exit + # end if + if not valid_var: + errmsg = f'Invalid index or tendency variable {sec_stdname}.' + errmsg += f'\n Host model must handle non-constituent index and tendency variables' + errflg = 1 + return errflg, errmsg + # end for + # end for + return errflg, errmsg + 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. diff --git a/test/advection_test/cld_liq.F90 b/test/advection_test/cld_liq.F90 index f9591b6c..3e4e241f 100644 --- a/test/advection_test/cld_liq.F90 +++ b/test/advection_test/cld_liq.F90 @@ -47,6 +47,7 @@ subroutine cld_liq_run(ncol, timestep, tcld, temp, qv, ps, cld_liq_array, & (temp(icol, ilev) <= tcld)) then cond = MIN(qv(icol, ilev), 0.1_kind_phys) cld_liq_array(icol, ilev) = cld_liq_array(icol, ilev) + cond + cld_liq_tend(icol, ilev) = cond qv(icol, ilev) = qv(icol, ilev) - cond if (cond > 0.0_kind_phys) then temp(icol, ilev) = temp(icol, ilev) + (cond * 5.0_kind_phys) From 0dbc88133e2ab64b53038d9a817c0774285f38d1 Mon Sep 17 00:00:00 2001 From: peverwhee Date: Tue, 20 Aug 2024 18:55:40 -0600 Subject: [PATCH 04/12] update const tend stdname --- src/ccpp_constituent_prop_mod.meta | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ccpp_constituent_prop_mod.meta b/src/ccpp_constituent_prop_mod.meta index f5848484..20834636 100644 --- a/src/ccpp_constituent_prop_mod.meta +++ b/src/ccpp_constituent_prop_mod.meta @@ -35,7 +35,7 @@ dimensions = (horizontal_dimension, vertical_layer_dimension, number_of_ccpp_constituents) type = real | kind = kind_phys [ vars_layer_tend ] - standard_name = ccpp_constituents_tendencies + standard_name = ccpp_constituent_tendencies long_name = Array of constituent tendencies managed by CCPP Framework units = none dimensions = (horizontal_dimension, vertical_layer_dimension, number_of_ccpp_constituents) From 1387cf273e245ca874ca84903f939d6561545652 Mon Sep 17 00:00:00 2001 From: peverwhee Date: Thu, 22 Aug 2024 15:56:58 -0600 Subject: [PATCH 05/12] better testing, add constituent metadata attribute --- scripts/constituents.py | 13 +- scripts/host_cap.py | 160 ++++++++++++++++-- scripts/metavar.py | 4 +- scripts/suite_objects.py | 54 ------ .../apply_constituent_tendencies.F90 | 39 +++++ .../apply_constituent_tendencies.meta | 36 ++++ test/advection_test/cld_liq.F90 | 5 +- test/advection_test/cld_liq.meta | 8 +- test/advection_test/cld_suite.xml | 2 + test/advection_test/cld_suite_files.txt | 1 + test/advection_test/run_test | 13 +- test/advection_test/test_host.F90 | 14 +- test/advection_test/test_reports.py | 10 +- 13 files changed, 257 insertions(+), 102 deletions(-) create mode 100644 test/advection_test/apply_constituent_tendencies.F90 create mode 100644 test/advection_test/apply_constituent_tendencies.meta diff --git a/scripts/constituents.py b/scripts/constituents.py index 9689635f..f147e249 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -115,14 +115,6 @@ def find_variable(self, standard_name=None, source_var=None, var = source_var.clone({'dimensions' : newdims}, remove_intent=True, source_type=self.__constituent_type) self.add_variable(var, self.__run_env) - # Also add a nearly-identical tendency_of_{standard_name} variable - new_stdname = f"tendency_of_{standard_name}" - new_units = f"{source_var.get_prop_value('units')} s-1" - new_lname = f"{source_var.get_prop_value('local_name')}_tend" - var_tend = source_var.clone({'dimensions' : newdims, 'standard_name' : new_stdname, - 'units': new_units, 'local_name': new_lname}, - remove_intent=True, source_type=self.__constituent_type) - self.add_variable(var_tend, self.__run_env) return var @staticmethod @@ -287,7 +279,7 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): # Figure out how many constituents variables we have const_num = 0 for std_name, var in self.items(): - if 'tendency_of' in std_name or 'index_of' in std_name: + if 'tendency_of' in std_name: continue # end if const_num += 1 @@ -303,7 +295,8 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): self.__init_err_var(evar, outfile, indent+1) # end for for std_name, var in self.items(): - if 'tendency_of' in std_name or 'index_of' in std_name: + if 'tendency_of' in std_name: + # Skip tendency variables continue # end if outfile.write("index = index + 1", indent+1) diff --git a/scripts/host_cap.py b/scripts/host_cap.py index 34f767d4..6cf9ef45 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -301,6 +301,9 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): " type = real", " kind = kind_phys"] # Add entries for each constituent (once per standard name) const_stdnames = set() + tend_stdnames = set() + const_vars = set() + tend_vars = set() for suite in suite_list: if run_env.verbose: lmsg = "Adding constituents from {} to {}" @@ -309,12 +312,13 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): scdict = suite.constituent_dictionary() for cvar in scdict.variable_list(): std_name = cvar.get_prop_value('standard_name') - if std_name not in const_stdnames: + if std_name not in const_stdnames and std_name not in tend_stdnames: # Add a metadata entry for this constituent # Check dimensions and figure vertical dimension # Currently, we only support variables with first dimension, # horizontal_dimension, and second (optional) dimension, # vertical_layer_dimension or vertical_interface_dimension + is_tend_var = 'tendency_of' in std_name dims = cvar.get_dimensions() if (len(dims) < 1) or (len(dims) > 2): emsg = "Unsupported constituent dimensions, '{}'" @@ -330,7 +334,11 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): if len(dims) > 1: vdim = dims[1].split(':')[-1] if vdim == vert_layer_dim: - cvar_array_name = array_layer + if is_tend_var: + cvar_array_name = tend_layer + else: + cvar_array_name = array_layer + # end if else: emsg = "Unsupported vertical constituent dimension, " emsg += "'{}', must be '{}' or '{}'" @@ -342,7 +350,12 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): raise CCPPError(emsg) # end if # Create an index variable for - ind_std_name = "index_of_{}".format(std_name) + if is_tend_var: + const_std_name = std_name.split("tendency_of_")[1] + else: + const_std_name = std_name + # end if + ind_std_name = "index_of_{}".format(const_std_name) loc_name = f"{cvar_array_name}(:,:,{ind_std_name})" ddt_mdata.append(f"[ {loc_name} ]") ddt_mdata.append(f" standard_name = {std_name}") @@ -353,23 +366,23 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): vtype = cvar.get_prop_value('type') vkind = cvar.get_prop_value('kind') ddt_mdata.append(f" type = {vtype} | kind = {vkind}") - const_stdnames.add(std_name) + if is_tend_var: + tend_vars.add(cvar) + tend_stdnames.add(std_name) + else: + const_vars.add(cvar) + const_stdnames.add(std_name) + # end if - # Create a tendency variable for the constituent - ind_std_name = "index_of_{}".format(std_name) - loc_name = f"{tend_layer}(:,:,{ind_std_name})" - ddt_mdata.append(f"[ {loc_name} ]") - ddt_mdata.append(f" standard_name = tendency_of_{std_name}") - units = cvar.get_prop_value('units') - ddt_mdata.append(f" units = {units} s-1") - dimstr = f"({', '.join(dims)})" - ddt_mdata.append(f" dimensions = {dimstr}") - vtype = cvar.get_prop_value('type') - vkind = cvar.get_prop_value('kind') - ddt_mdata.append(f" type = {vtype} | kind = {vkind}") # end if # end for # end for + # Check that all tendency variables are valid + errmsg, errflg = check_tendency_variables(tend_vars, const_vars) + if errflg != 0: + # There is at least one invalid tendency variable - this is a fatal error + raise CCPPError(errmsg) + # end if # Parse this table using a fake filename parse_obj = ParseObject(f"{host_model.name}_constituent_mod.meta", ddt_mdata) @@ -454,6 +467,117 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): return const_dict +############################################################################### +def check_tendency_variables(tend_vars, const_vars): + """Return an error flag and relevant error message if there are mismatches + between the tendency variable and the constituent variable (standard name, + units) + >>> tendency_vars = [Var({'local_name':'qv_tend', 'standard_name':'tendency_of_water_vapor', 'units':'kg kg-1 s-1', 'dimensions':'(horizontal_dimension, vertical_layer_dimension)', 'type':'real', 'kind':'kind_phys'}, _API_SOURCE, _API_DUMMY_RUN_ENV)] + >>> const_vars = [Var({'local_name':'qv', 'standard_name':'water_vapor', 'units': 'kg kg-1', 'dimensions': '(horizontal_dimension, vertical_layer_dimension)', 'type': 'real', 'kind':'kind_phys'}, _API_SOURCE, _API_DUMMY_RUN_ENV)] + >>> check_tendency_variables(tendency_vars, const_vars) + ('', 0) + >>> tendency_vars.append(Var({'local_name':'qc_tend', 'standard_name':'tendency_of_cloud_liquid_water_mixing_ratio', 'units':'kg kg-1 s-1', 'dimensions':'(horizontal_dimension, vertical_layer_dimension)', 'type':'real', 'kind':'kind_phys'}, _API_SOURCE, _API_DUMMY_RUN_ENV)) + >>> const_vars.append(Var({'local_name':'qc', 'standard_name':'cloud_liquid_water_mixing_ratio', 'units': 'kg kg-1', 'dimensions': '(horizontal_dimension, vertical_layer_dimension)', 'type': 'real', 'kind':'kind_phys'}, _API_SOURCE, _API_DUMMY_RUN_ENV)) + >>> check_tendency_variables(tendency_vars, const_vars) + ('', 0) + >>> tendency_vars = [Var({'local_name':'qv_tend', 'standard_name':'tendency_of_water_vapor', 'units':'mol m-3 s-1', 'dimensions':'(horizontal_dimension, vertical_layer_dimension)', 'type':'real', 'kind':'kind_phys'}, _API_SOURCE, _API_DUMMY_RUN_ENV)] + >>> check_tendency_variables(tendency_vars, const_vars) + ("\\nMismatch tendency variable units 'mol m-3 s-1' for variable 'tendency_of_water_vapor'. No variable transforms supported for constituent tendencies. Tendency units should be 'kg kg-1 s-1' to match constituent.", 1) + >>> tendency_vars.append(Var({'local_name':'qc_tend', 'standard_name':'tendency_of_cloud_liquid_water_mixing_ratio', 'units':'kg kg-1 s-1', 'dimensions':'(horizontal_dimension, vertical_interface_dimension)', 'type':'real', 'kind':'kind_phys'}, _API_SOURCE, _API_DUMMY_RUN_ENV)) + >>> check_tendency_variables(tendency_vars, const_vars) + ("\\nMismatch tendency variable units 'mol m-3 s-1' for variable 'tendency_of_water_vapor'. No variable transforms supported for constituent tendencies. Tendency units should be 'kg kg-1 s-1' to match constituent.\\nMismatch tendency variable dimension for dim 2: 'vertical_interface_dimension' for variable 'tendency_of_cloud_liquid_water_mixing_ratio'. Dimension should match constituent dimension of 'vertical_layer_dimension'", 1) + >>> tendency_vars = [Var({'local_name':'qv_tend', 'standard_name':'tendency_of_water_vapor', 'units':'kg kg-1 s-1', 'dimensions':'(horizontal_dimension, vertical_layer_dimension)', 'type':'character', 'kind':'len=32'}, _API_SOURCE, _API_DUMMY_RUN_ENV)] + >>> check_tendency_variables(tendency_vars, const_vars) + ("\\nMismatch tendency variable type 'character' for variable 'tendency_of_water_vapor'. Type should match constituent type of 'real'\\nMismatch tendency variable kind 'len=32' for variable 'tendency_of_water_vapor'. Kind should match constituent kind of 'kind_phys'", 1) + >>> tendency_vars = [Var({'local_name':'qv_tend', 'standard_name':'tendency_of_water_vapor_mixing_ratio', 'units':'kg kg-1 s-1', 'dimensions':'(horizontal_dimension, vertical_layer_dimension)', 'type':'real', 'kind':'kind_phys'}, _API_SOURCE, _API_DUMMY_RUN_ENV)] + >>> tendency_vars.append(Var({'local_name':'qc_tend', 'standard_name':'tendency_of_cloud_liquid', 'units':'kg kg-1 s-1', 'dimensions':'(horizontal_dimension, vertical_layer_dimension)', 'type':'real', 'kind':'kind_phys'}, _API_SOURCE, _API_DUMMY_RUN_ENV)) + >>> check_tendency_variables(tendency_vars, const_vars) + ("\\nInvalid tendency variable 'tendency_of_water_vapor_mixing_ratio'. All constituent tendency variable standard names must be of the form 'tendency_of_'\\nInvalid tendency variable 'tendency_of_cloud_liquid'. All constituent tendency variable standard names must be of the form 'tendency_of_'", 1) + """ + errflg = 0 + errmsg = '' + + for tend_var in tend_vars: + prop_error = False + valid_var = False + tend_stdname = tend_var.get_prop_value('standard_name') + tend_units = tend_var.get_prop_value('units') + tend_stdname_split = tend_stdname.split('tendency_of_')[1] + tend_dimensions = tend_var.get_dimensions() + tend_type = tend_var.get_prop_value('type') + tend_kind = tend_var.get_prop_value('kind') + for const_var in const_vars: + const_stdname = const_var.get_prop_value('standard_name') + const_units = const_var.get_prop_value('units') + const_dimensions = const_var.get_dimensions() + const_type = const_var.get_prop_value('type') + const_kind = const_var.get_prop_value('kind') + if tend_stdname_split == const_stdname: + # We found a match! Check units + if tend_units.split('s-1')[0].strip() != const_units: + errmsg += f"\nMismatch tendency variable units '{tend_units}'" + errmsg += f" for variable '{tend_stdname}'." + errmsg += f" No variable transforms supported for constituent tendencies." + errmsg += f" Tendency units should be '{const_units} s-1' to match constituent." + prop_error = True + # end if + # Check the dimensions + for index, dim in enumerate(tend_dimensions): + # dimension replacement for horizontal_loop_extent + if dim == 'horizontal_loop_extent': + tend_dim = 'horizontal_dimension' + else: + tend_dim = dim + # end if + if const_dimensions[index] == 'horizontal_loop_extent': + const_dim = 'horizontal_dimension' + else: + const_dim = const_dimensions[index] + # end if + if tend_dim != const_dim: + errmsg += f"\nMismatch tendency variable dimension for dim {index + 1}: '{tend_dim}'" + errmsg += f" for variable '{tend_stdname}'." + errmsg += f" Dimension should match constituent dimension of '{const_dim}'" + prop_error = True + # end if + # end for + # Check the type + if const_type != tend_type: + errmsg += f"\nMismatch tendency variable type '{tend_type}'" + errmsg += f" for variable '{tend_stdname}'." + errmsg += f" Type should match constituent type of '{const_type}'" + prop_error = True + # end if + # Check the kind + if const_kind != tend_kind: + errmsg += f"\nMismatch tendency variable kind '{tend_kind}'" + errmsg += f" for variable '{tend_stdname}'." + errmsg += f" Kind should match constituent kind of '{const_kind}'" + prop_error = True + # end if + if prop_error == 0: + valid_var = True + exit + # end if + # end if + # end for + if not valid_var and not prop_error: + # Tendency standard name doesn't match an existing constituent + errmsg += f"\nInvalid tendency variable '{tend_stdname}'." + errmsg += " All constituent tendency variable standard names must " + errmsg += "be of the form 'tendency_of_'" + errflg = 1 + elif prop_error: + errflg = 1 + # end if + # end for + if errflg != 0: + return errmsg, errflg + # end if + + return errmsg, errflg +############################################################################### + ############################################################################### def suite_part_call_list(host_model, const_dict, suite_part, subst_loop_vars): ############################################################################### @@ -480,7 +604,7 @@ def suite_part_call_list(host_model, const_dict, suite_part, subst_loop_vars): # end for if hvar is None: errmsg = 'No host model variable for {} in {}' - raise ParseInternalError(errmsg.format(stdname, suite_part.name)) + raise CCPPError(errmsg.format(stdname, suite_part.name)) # End if if stdname not in CCPP_CONSTANT_VARS: lname = var_dict.var_call_string(hvar, loop_vars=loop_vars) @@ -573,7 +697,7 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): any_scope=True) # end if if hvar is None: - errmsg = '2No host model variable for {} in {}' + errmsg = 'No host model variable for {} in {}' raise ParseInternalError(errmsg.format(stdname, spart.name)) # End if # End for (loop over part variables) diff --git a/scripts/metavar.py b/scripts/metavar.py index e84cd2b7..bf84bac2 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -229,7 +229,9 @@ class Var: optional_in=True, default_in=False), VariableProperty('molar_mass', float, optional_in=True, default_in=0.0, - check_fn_in=check_molar_mass)] + check_fn_in=check_molar_mass), + VariableProperty('constituent', bool, + optional_in=True, default_in=False)] __constituent_prop_dict = {x.name : x for x in __constituent_props} diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 9ad10758..0e47eba6 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1137,7 +1137,6 @@ def is_local_variable(self, var): def analyze(self, phase, group, scheme_library, suite_vars, level): """Analyze the scheme's interface to prepare for writing""" - constituent_prefixes = ['tendency_of_', 'index_of_'] self.__group = group my_header = None if self.name in scheme_library: @@ -1166,16 +1165,11 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): # end if scheme_mods = set() scheme_mods.add((my_header.module, self.subroutine_name)) - secondary_const_vars = [] - primary_const_vars = [] 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') - if var.is_constituent: - primary_const_vars.append(var) - # end if args = self.match_variable(var, self.run_env) found, dict_var, vert_dim, new_dims, missing_vert, compat_obj = args if found: @@ -1220,9 +1214,6 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): self.__group.manage_variable(var) # We still need it in our call list (the group uses a clone) self.add_call_list_variable(var) - elif any(prefix in vstdname for prefix in constituent_prefixes): - # Ignore these, but save them for later checking - secondary_const_vars.append(var) else: errmsg = 'Input argument for {}, {}, not found.' if self.find_variable(source_var=var) is not None: @@ -1266,54 +1257,9 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): suite_vars, level) # end if # end if - # Check any secondary constituent variables (index, tendency) - errflg, errmsg = self.check_secondary_constituent_variables(secondary_const_vars, primary_const_vars, - constituent_prefixes) - if errflg == 1: - raise CCPPError(errmsg) - # end if - - # Add secondary constituent variables to group and call list - # peverwhee TODO - variable transforms? - for var in secondary_const_vars: - #self.__group.manage_variable(var) - self.add_call_list_variable(var) - self.update_group_call_list_variable(var) - # end for -# if self.call_list.name == 'cld_liq_call_list' and len(secondary_const_vars) > 0: -# raise ParseInternalError('hi') return scheme_mods - def check_secondary_constituent_variables(self, secondary_list, primary_list, secondary_prefixes): - """Return an error if there are any secondary variables that reference - a nonexisting constituent""" - errflg = 0 - errmsg = '' - for secondary_const in secondary_list: - valid_var = False - for primary_const in primary_list: - sec_stdname = secondary_const.get_prop_value('standard_name') - prim_stdname = primary_const.get_prop_value('standard_name') - for prefix in secondary_prefixes: - if prefix in sec_stdname: - sec_stdname = sec_stdname.split(prefix, 1)[1] - exit - # end if - # end for - if prim_stdname == sec_stdname: - valid_var = True - exit - # end if - if not valid_var: - errmsg = f'Invalid index or tendency variable {sec_stdname}.' - errmsg += f'\n Host model must handle non-constituent index and tendency variables' - errflg = 1 - return errflg, errmsg - # end for - # end for - return errflg, errmsg - 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. diff --git a/test/advection_test/apply_constituent_tendencies.F90 b/test/advection_test/apply_constituent_tendencies.F90 new file mode 100644 index 00000000..150b1190 --- /dev/null +++ b/test/advection_test/apply_constituent_tendencies.F90 @@ -0,0 +1,39 @@ +module apply_constituent_tendencies + + use ccpp_kinds, only: kind_phys + + implicit none + private + + public :: apply_constituent_tendencies_run + +CONTAINS + + !> \section arg_table_apply_constituent_tendencies_run Argument Table + !!! \htmlinclude apply_constituent_tendencies_run.html + subroutine apply_constituent_tendencies_run(const_tend, const, errcode, errmsg) + ! Dummy arguments + real(kind_phys), intent(inout) :: const_tend(:,:,:) ! constituent tendency array + real(kind_phys), intent(inout) :: const(:,:,:) ! constituent state array + integer, intent(out) :: errcode + character(len=512), intent(out) :: errmsg + + ! Local variables + integer :: klev, jcnst, icol + + errcode = 0 + errmsg = '' + + do icol = 1, size(const_tend, 1) + do klev = 1, size(const_tend, 2) + do jcnst = 1, size(const_tend, 3) + const(icol, klev, jcnst) = const(icol, klev, jcnst) + const_tend(icol, klev, jcnst) + end do + end do + end do + + const_tend = 0._kind_phys + + end subroutine apply_constituent_tendencies_run + +end module apply_constituent_tendencies diff --git a/test/advection_test/apply_constituent_tendencies.meta b/test/advection_test/apply_constituent_tendencies.meta new file mode 100644 index 00000000..b7645a1b --- /dev/null +++ b/test/advection_test/apply_constituent_tendencies.meta @@ -0,0 +1,36 @@ +##################################################################### +[ccpp-table-properties] + name = apply_constituent_tendencies + type = scheme +[ccpp-arg-table] + name = apply_constituent_tendencies_run + type = scheme +[ const_tend ] + standard_name = ccpp_constituent_tendencies + long_name = ccpp constituent tendencies + units = none + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent, vertical_layer_dimension, number_of_ccpp_constituents) + intent = inout +[ const ] + standard_name = ccpp_constituents + long_name = ccpp constituents + units = none + type = real | kind = kind_phys + dimensions = (horizontal_loop_extent, vertical_layer_dimension, number_of_ccpp_constituents) + intent = inout +[ errcode ] + standard_name = ccpp_error_code + long_name = Error flag for error handling in CCPP + units = 1 + type = integer + dimensions = () + intent = out +[ errmsg ] + standard_name = ccpp_error_message + long_name = Error message for error handling in CCPP + units = none + type = character | kind = len=512 + dimensions = () + intent = out +######################################################### diff --git a/test/advection_test/cld_liq.F90 b/test/advection_test/cld_liq.F90 index 3e4e241f..e31c6c23 100644 --- a/test/advection_test/cld_liq.F90 +++ b/test/advection_test/cld_liq.F90 @@ -17,7 +17,7 @@ MODULE cld_liq !> \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, cld_liq_array, & + subroutine cld_liq_run(ncol, timestep, tcld, temp, qv, ps, & cld_liq_tend, errmsg, errflg) integer, intent(in) :: ncol @@ -26,9 +26,7 @@ subroutine cld_liq_run(ncol, timestep, tcld, temp, qv, ps, cld_liq_array, & real(kind_phys), intent(inout) :: temp(:,:) real(kind_phys), intent(inout) :: qv(:,:) real(kind_phys), intent(in) :: ps(:) - REAL(kind_phys), intent(inout) :: cld_liq_array(:,:) REAL(kind_phys), intent(inout) :: cld_liq_tend(:,:) -! integer, intent(in) :: cld_liq_ind character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg !---------------------------------------------------------------- @@ -46,7 +44,6 @@ subroutine cld_liq_run(ncol, timestep, tcld, temp, qv, ps, cld_liq_array, & if ( (qv(icol, ilev) > 0.0_kind_phys) .and. & (temp(icol, ilev) <= tcld)) then cond = MIN(qv(icol, ilev), 0.1_kind_phys) - cld_liq_array(icol, ilev) = cld_liq_array(icol, ilev) + cond cld_liq_tend(icol, ilev) = cond qv(icol, ilev) = qv(icol, ilev) - cond if (cond > 0.0_kind_phys) then diff --git a/test/advection_test/cld_liq.meta b/test/advection_test/cld_liq.meta index 650ba052..100e57d0 100644 --- a/test/advection_test/cld_liq.meta +++ b/test/advection_test/cld_liq.meta @@ -48,19 +48,13 @@ units = Pa dimensions = (horizontal_loop_extent) intent = in -[ cld_liq_array ] - standard_name = cloud_liquid_dry_mixing_ratio - advected = .true. - units = kg kg-1 - dimensions = (horizontal_loop_extent, vertical_layer_dimension) - type = real | kind = kind_phys - intent = inout [ cld_liq_tend ] standard_name = tendency_of_cloud_liquid_dry_mixing_ratio units = kg kg-1 s-1 dimensions = (horizontal_loop_extent, vertical_layer_dimension) type = real | kind = kind_phys intent = inout + constituent = True [ errmsg ] standard_name = ccpp_error_message long_name = Error message for error handling in CCPP diff --git a/test/advection_test/cld_suite.xml b/test/advection_test/cld_suite.xml index f3fe1531..361518d1 100644 --- a/test/advection_test/cld_suite.xml +++ b/test/advection_test/cld_suite.xml @@ -3,6 +3,8 @@ cld_liq + apply_constituent_tendencies cld_ice + apply_constituent_tendencies diff --git a/test/advection_test/cld_suite_files.txt b/test/advection_test/cld_suite_files.txt index a40306ed..301bb4ee 100644 --- a/test/advection_test/cld_suite_files.txt +++ b/test/advection_test/cld_suite_files.txt @@ -1,2 +1,3 @@ cld_liq.meta cld_ice.meta +apply_constituent_tendencies.meta diff --git a/test/advection_test/run_test b/test/advection_test/run_test index 67f338a0..64ebc626 100755 --- a/test/advection_test/run_test +++ b/test/advection_test/run_test @@ -125,16 +125,18 @@ utility_files="${utility_files},${fsrc}/ccpp_constituent_prop_mod.F90" utility_files="${utility_files},${hash_files}" ccpp_files="${utility_files},${host_files},${suite_files}" process_list="" -module_list="cld_ice,cld_liq" +module_list="apply_constituent_tendencies,cld_ice,cld_liq" dependencies="" dyn_const_routines="cld_ice_dynamic_constituents,cld_liq_dynamic_constituents" suite_list="cld_suite" -required_vars="ccpp_error_code,ccpp_error_message" +required_vars="ccpp_constituent_tendencies,ccpp_constituents" +required_vars="${required_vars},ccpp_error_code,ccpp_error_message" required_vars="${required_vars},cloud_ice_dry_mixing_ratio" required_vars="${required_vars},cloud_liquid_dry_mixing_ratio" required_vars="${required_vars},horizontal_dimension" required_vars="${required_vars},horizontal_loop_begin" required_vars="${required_vars},horizontal_loop_end" +required_vars="${required_vars},number_of_ccpp_constituents" required_vars="${required_vars},surface_air_pressure" required_vars="${required_vars},temperature" required_vars="${required_vars},tendency_of_cloud_liquid_dry_mixing_ratio" @@ -142,17 +144,20 @@ required_vars="${required_vars},time_step_for_physics" required_vars="${required_vars},vertical_layer_dimension" required_vars="${required_vars},water_temperature_at_freezing" required_vars="${required_vars},water_vapor_specific_humidity" -input_vars="cloud_ice_dry_mixing_ratio,cloud_liquid_dry_mixing_ratio" +input_vars="ccpp_constituent_tendencies,ccpp_constituents" +input_vars="${input_vars},cloud_ice_dry_mixing_ratio,cloud_liquid_dry_mixing_ratio" input_vars="${input_vars},horizontal_dimension" input_vars="${input_vars},horizontal_loop_begin" input_vars="${input_vars},horizontal_loop_end" +input_vars="${input_vars},number_of_ccpp_constituents" input_vars="${input_vars},surface_air_pressure,temperature" input_vars="${input_vars},tendency_of_cloud_liquid_dry_mixing_ratio" input_vars="${input_vars},time_step_for_physics" input_vars="${input_vars},vertical_layer_dimension" input_vars="${input_vars},water_temperature_at_freezing" input_vars="${input_vars},water_vapor_specific_humidity" -output_vars="ccpp_error_code,ccpp_error_message" +output_vars="ccpp_constituent_tendencies,ccpp_constituents" +output_vars="${output_vars},ccpp_error_code,ccpp_error_message" output_vars="${output_vars},cloud_ice_dry_mixing_ratio" output_vars="${output_vars},cloud_liquid_dry_mixing_ratio" output_vars="${output_vars},temperature" diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index 04b0dca2..3b4d60ed 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -1008,9 +1008,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(7) - character(len=cm), target :: test_reqvars1(10) + character(len=cm), target :: test_invars1(11) + character(len=cm), target :: test_outvars1(9) + character(len=cm), target :: test_reqvars1(13) type(suite_info) :: test_suites(1) logical :: run_okay @@ -1024,6 +1024,9 @@ program test 'temperature ', & '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 ', & @@ -1031,6 +1034,8 @@ program test 'temperature ', & 'water_vapor_specific_humidity ', & 'cloud_liquid_dry_mixing_ratio ', & + 'ccpp_constituent_tendencies ', & + 'ccpp_constituents ', & 'tendency_of_cloud_liquid_dry_mixing_ratio', & 'cloud_ice_dry_mixing_ratio ' /) test_reqvars1 = (/ & @@ -1041,6 +1046,9 @@ program test 'tendency_of_cloud_liquid_dry_mixing_ratio', & 'cloud_ice_dry_mixing_ratio ', & 'water_temperature_at_freezing ', & + 'ccpp_constituent_tendencies ', & + 'ccpp_constituents ', & + 'number_of_ccpp_constituents ', & 'water_vapor_specific_humidity ', & 'ccpp_error_message ', & 'ccpp_error_code ' /) diff --git a/test/advection_test/test_reports.py b/test/advection_test/test_reports.py index f30a947c..82bc9812 100644 --- a/test/advection_test/test_reports.py +++ b/test/advection_test/test_reports.py @@ -64,7 +64,7 @@ def usage(errmsg=None): os.path.join(_FRAMEWORK_DIR, "src", "ccpp_hash_table.F90")] _CCPP_FILES = _UTILITY_FILES + _HOST_FILES + _SUITE_FILES _PROCESS_LIST = list() -_MODULE_LIST = ["cld_ice", "cld_liq"] +_MODULE_LIST = ["cld_ice", "cld_liq", "apply_constituent_tendencies"] _SUITE_LIST = ["cld_suite"] _DYN_CONST_ROUTINES = ["cld_ice_dynamic_constituents", "cld_liq_dynamic_constituents"] _REQUIRED_VARS_CLD = ["ccpp_error_code", "ccpp_error_message", @@ -75,6 +75,9 @@ def usage(errmsg=None): "water_vapor_specific_humidity", "cloud_ice_dry_mixing_ratio", "cloud_liquid_dry_mixing_ratio", + "ccpp_constituents", + "ccpp_constituent_tendencies", + "number_of_ccpp_constituents", # Added by --debug option "horizontal_dimension", "vertical_layer_dimension"] @@ -85,6 +88,9 @@ def usage(errmsg=None): "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", # Added by --debug option "horizontal_dimension", "vertical_layer_dimension"] @@ -92,6 +98,8 @@ def usage(errmsg=None): "water_vapor_specific_humidity", "temperature", "tendency_of_cloud_liquid_dry_mixing_ratio", "cloud_ice_dry_mixing_ratio", + "ccpp_constituents", + "ccpp_constituent_tendencies", "cloud_liquid_dry_mixing_ratio"] def fields_string(field_type, field_list, sep): From dc3f3f3784dce225f857fc12cd0a99609ee1b01a Mon Sep 17 00:00:00 2001 From: peverwhee Date: Thu, 22 Aug 2024 16:21:38 -0600 Subject: [PATCH 06/12] code cleanup --- scripts/constituents.py | 7 +++---- scripts/host_cap.py | 8 ++++---- scripts/suite_objects.py | 8 ++------ 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/scripts/constituents.py b/scripts/constituents.py index f147e249..c60f31e0 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -276,13 +276,12 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): for evar in err_vars: evar.write_def(outfile, indent+1, self, dummy=True) # end for - # Figure out how many constituents variables we have + # Figure out how many unique (non-tendency) constituent variables we have const_num = 0 for std_name, var in self.items(): - if 'tendency_of' in std_name: - continue + if 'tendency_of' not in std_name: + const_num += 1 # end if - const_num += 1 # end for if self: outfile.write("! Local variables", indent+1) diff --git a/scripts/host_cap.py b/scripts/host_cap.py index 6cf9ef45..c61428b4 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -19,7 +19,7 @@ from metavar import Var, VarDictionary, CCPP_CONSTANT_VARS from metavar import CCPP_LOOP_VAR_STDNAMES from fortran_tools import FortranWriter -from parse_tools import CCPPError, ParseInternalError +from parse_tools import CCPPError from parse_tools import ParseObject, ParseSource, ParseContext ############################################################################### @@ -394,7 +394,7 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): del ddt_mdata # Now, create the "host constituent module" dictionary const_dict = VarDictionary(f"{host_model.name}_constituents", - run_env, parent_dict=host_model) + run_env, parent_dict=host_model) # Add the constituents object to const_dict and write its declaration const_var = host_model.find_variable(CONST_OBJ_STDNAME) if const_var: @@ -469,6 +469,7 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): ############################################################################### def check_tendency_variables(tend_vars, const_vars): +############################################################################### """Return an error flag and relevant error message if there are mismatches between the tendency variable and the constituent variable (standard name, units) @@ -576,7 +577,6 @@ def check_tendency_variables(tend_vars, const_vars): # end if return errmsg, errflg -############################################################################### ############################################################################### def suite_part_call_list(host_model, const_dict, suite_part, subst_loop_vars): @@ -698,7 +698,7 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): # end if if hvar is None: errmsg = 'No host model variable for {} in {}' - raise ParseInternalError(errmsg.format(stdname, spart.name)) + raise CCPPError(errmsg.format(stdname, spart.name)) # End if # End for (loop over part variables) # End for (loop of suite parts) diff --git a/scripts/suite_objects.py b/scripts/suite_objects.py index 0e47eba6..8383ee0d 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1257,7 +1257,6 @@ def analyze(self, phase, group, scheme_library, suite_vars, level): suite_vars, level) # end if # end if - return scheme_mods def add_var_debug_check(self, var): @@ -1431,13 +1430,10 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er # 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) - if svar: - intent = svar.get_prop_value('intent') - else: - intent = 'in' - # end if + 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) From fb088635baa122e8859e1da5856cddd582e1ae75 Mon Sep 17 00:00:00 2001 From: peverwhee Date: Thu, 22 Aug 2024 16:22:49 -0600 Subject: [PATCH 07/12] remove orphaned end if --- scripts/host_cap.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/host_cap.py b/scripts/host_cap.py index c61428b4..cf60e7b1 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -695,7 +695,6 @@ def write_host_cap(host_model, api, module_name, output_dir, run_env): stdname = sp_var.get_prop_value('standard_name') hvar = const_dict.find_variable(standard_name=stdname, any_scope=True) - # end if if hvar is None: errmsg = 'No host model variable for {} in {}' raise CCPPError(errmsg.format(stdname, spart.name)) From 1f7387d7fcef44ed062233125d6246e0b2776408 Mon Sep 17 00:00:00 2001 From: peverwhee Date: Fri, 13 Sep 2024 23:32:37 -0600 Subject: [PATCH 08/12] review comments; handle if "tendency_of" is not at the start of the stdname --- scripts/constituents.py | 6 +++--- scripts/host_cap.py | 12 ++++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/scripts/constituents.py b/scripts/constituents.py index c60f31e0..65f42255 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -278,8 +278,8 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): # end for # Figure out how many unique (non-tendency) constituent variables we have const_num = 0 - for std_name, var in self.items(): - if 'tendency_of' not in std_name: + for std_name, _ in self.items(): + if not std_name.startswith('tendency_of_'): const_num += 1 # end if # end for @@ -294,7 +294,7 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars): self.__init_err_var(evar, outfile, indent+1) # end for for std_name, var in self.items(): - if 'tendency_of' in std_name: + if std_name.startswith('tendency_of_'): # Skip tendency variables continue # end if diff --git a/scripts/host_cap.py b/scripts/host_cap.py index cf60e7b1..b59bc997 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -355,7 +355,7 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): else: const_std_name = std_name # end if - ind_std_name = "index_of_{}".format(const_std_name) + ind_std_name = f"index_of_{const_std_name}" loc_name = f"{cvar_array_name}(:,:,{ind_std_name})" ddt_mdata.append(f"[ {loc_name} ]") ddt_mdata.append(f" standard_name = {std_name}") @@ -492,8 +492,9 @@ def check_tendency_variables(tend_vars, const_vars): ("\\nMismatch tendency variable type 'character' for variable 'tendency_of_water_vapor'. Type should match constituent type of 'real'\\nMismatch tendency variable kind 'len=32' for variable 'tendency_of_water_vapor'. Kind should match constituent kind of 'kind_phys'", 1) >>> tendency_vars = [Var({'local_name':'qv_tend', 'standard_name':'tendency_of_water_vapor_mixing_ratio', 'units':'kg kg-1 s-1', 'dimensions':'(horizontal_dimension, vertical_layer_dimension)', 'type':'real', 'kind':'kind_phys'}, _API_SOURCE, _API_DUMMY_RUN_ENV)] >>> tendency_vars.append(Var({'local_name':'qc_tend', 'standard_name':'tendency_of_cloud_liquid', 'units':'kg kg-1 s-1', 'dimensions':'(horizontal_dimension, vertical_layer_dimension)', 'type':'real', 'kind':'kind_phys'}, _API_SOURCE, _API_DUMMY_RUN_ENV)) + >>> tendency_vars.append(Var({'local_name':'qc_tend', 'standard_name':'typotendency_of_cloud_liquid', 'units':'kg kg-1 s-1', 'dimensions':'(horizontal_dimension, vertical_layer_dimension)', 'type':'real', 'kind':'kind_phys'}, _API_SOURCE, _API_DUMMY_RUN_ENV)) >>> check_tendency_variables(tendency_vars, const_vars) - ("\\nInvalid tendency variable 'tendency_of_water_vapor_mixing_ratio'. All constituent tendency variable standard names must be of the form 'tendency_of_'\\nInvalid tendency variable 'tendency_of_cloud_liquid'. All constituent tendency variable standard names must be of the form 'tendency_of_'", 1) + ("\\nInvalid tendency variable 'tendency_of_water_vapor_mixing_ratio'. All constituent tendency variable standard names must be of the form 'tendency_of_'\\nInvalid tendency variable 'tendency_of_cloud_liquid'. All constituent tendency variable standard names must be of the form 'tendency_of_'\\nInvalid tendency variable 'typotendency_of_cloud_liquid'. All constituent tendency variable standard names must be of the form 'tendency_of_'", 1) """ errflg = 0 errmsg = '' @@ -503,6 +504,13 @@ def check_tendency_variables(tend_vars, const_vars): valid_var = False tend_stdname = tend_var.get_prop_value('standard_name') tend_units = tend_var.get_prop_value('units') + if not tend_stdname.startswith('tendency_of_'): + # Tendency standard name is invalid + errmsg += f"\nInvalid tendency variable '{tend_stdname}'." + errmsg += " All constituent tendency variable standard names must " + errmsg += "be of the form 'tendency_of_'" + errflg = 1 + continue tend_stdname_split = tend_stdname.split('tendency_of_')[1] tend_dimensions = tend_var.get_dimensions() tend_type = tend_var.get_prop_value('type') From d49fcfaccc3641169ebddc689a36182f70aa8114 Mon Sep 17 00:00:00 2001 From: peverwhee Date: Fri, 13 Sep 2024 23:48:20 -0600 Subject: [PATCH 09/12] cleanup in check_tendency_variables --- scripts/host_cap.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/host_cap.py b/scripts/host_cap.py index b59bc997..e47e4344 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -526,7 +526,7 @@ def check_tendency_variables(tend_vars, const_vars): if tend_units.split('s-1')[0].strip() != const_units: errmsg += f"\nMismatch tendency variable units '{tend_units}'" errmsg += f" for variable '{tend_stdname}'." - errmsg += f" No variable transforms supported for constituent tendencies." + errmsg += " No variable transforms supported for constituent tendencies." errmsg += f" Tendency units should be '{const_units} s-1' to match constituent." prop_error = True # end if @@ -580,9 +580,6 @@ def check_tendency_variables(tend_vars, const_vars): errflg = 1 # end if # end for - if errflg != 0: - return errmsg, errflg - # end if return errmsg, errflg From 17db5c0c240e2182b9e71213c688071ecc96290c Mon Sep 17 00:00:00 2001 From: peverwhee Date: Tue, 8 Oct 2024 16:52:30 -0600 Subject: [PATCH 10/12] amend compatibility functionality/testing to validate tendencies --- scripts/host_cap.py | 149 +++++-------------------- scripts/metavar.py | 4 +- scripts/var_props.py | 23 +++- test/unit_tests/test_var_transforms.py | 44 ++++++++ 4 files changed, 93 insertions(+), 127 deletions(-) diff --git a/scripts/host_cap.py b/scripts/host_cap.py index e47e4344..c979b9e9 100644 --- a/scripts/host_cap.py +++ b/scripts/host_cap.py @@ -20,7 +20,7 @@ from metavar import CCPP_LOOP_VAR_STDNAMES from fortran_tools import FortranWriter from parse_tools import CCPPError -from parse_tools import ParseObject, ParseSource, ParseContext +from parse_tools import ParseObject, ParseSource, ParseContext, ParseSyntaxError ############################################################################### _HEADER = "cap for {host_model} calls to CCPP API" @@ -378,11 +378,32 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): # end for # end for # Check that all tendency variables are valid - errmsg, errflg = check_tendency_variables(tend_vars, const_vars) - if errflg != 0: - # There is at least one invalid tendency variable - this is a fatal error - raise CCPPError(errmsg) - # end if + for tendency_variable in tend_vars: + tend_stdname = tendency_variable.get_prop_value('standard_name') + tend_const_name = tend_stdname.split('tendency_of_')[1] + found = False + # Find the corresponding constituent variable + for const_variable in const_vars: + const_stdname = const_variable.get_prop_value('standard_name') + if const_stdname == tend_const_name: + found = True + compat = tendency_variable.compatible(const_variable, run_env, is_tend=True) + if not compat: + errstr = f"Tendency variable, '{tend_stdname}'" + errstr += f", incompatible with associated state variable '{tend_const_name}'" + errstr += f". Reason: '{compat.incompat_reason}'" + raise ParseSyntaxError(errstr, token=tend_stdname, + context=tendency_variable.context) + # end if + # end if + # end for + if not found: + # error because we couldn't find the associated constituent + errstr = f"No associated state variable for tendency variable, '{tend_stdname}'" + raise ParseSyntaxError(errstr, token=tend_stdname, + context=tendency_variable.context) + # end if + # end for # Parse this table using a fake filename parse_obj = ParseObject(f"{host_model.name}_constituent_mod.meta", ddt_mdata) @@ -467,122 +488,6 @@ def add_constituent_vars(cap, host_model, suite_list, run_env): return const_dict -############################################################################### -def check_tendency_variables(tend_vars, const_vars): -############################################################################### - """Return an error flag and relevant error message if there are mismatches - between the tendency variable and the constituent variable (standard name, - units) - >>> tendency_vars = [Var({'local_name':'qv_tend', 'standard_name':'tendency_of_water_vapor', 'units':'kg kg-1 s-1', 'dimensions':'(horizontal_dimension, vertical_layer_dimension)', 'type':'real', 'kind':'kind_phys'}, _API_SOURCE, _API_DUMMY_RUN_ENV)] - >>> const_vars = [Var({'local_name':'qv', 'standard_name':'water_vapor', 'units': 'kg kg-1', 'dimensions': '(horizontal_dimension, vertical_layer_dimension)', 'type': 'real', 'kind':'kind_phys'}, _API_SOURCE, _API_DUMMY_RUN_ENV)] - >>> check_tendency_variables(tendency_vars, const_vars) - ('', 0) - >>> tendency_vars.append(Var({'local_name':'qc_tend', 'standard_name':'tendency_of_cloud_liquid_water_mixing_ratio', 'units':'kg kg-1 s-1', 'dimensions':'(horizontal_dimension, vertical_layer_dimension)', 'type':'real', 'kind':'kind_phys'}, _API_SOURCE, _API_DUMMY_RUN_ENV)) - >>> const_vars.append(Var({'local_name':'qc', 'standard_name':'cloud_liquid_water_mixing_ratio', 'units': 'kg kg-1', 'dimensions': '(horizontal_dimension, vertical_layer_dimension)', 'type': 'real', 'kind':'kind_phys'}, _API_SOURCE, _API_DUMMY_RUN_ENV)) - >>> check_tendency_variables(tendency_vars, const_vars) - ('', 0) - >>> tendency_vars = [Var({'local_name':'qv_tend', 'standard_name':'tendency_of_water_vapor', 'units':'mol m-3 s-1', 'dimensions':'(horizontal_dimension, vertical_layer_dimension)', 'type':'real', 'kind':'kind_phys'}, _API_SOURCE, _API_DUMMY_RUN_ENV)] - >>> check_tendency_variables(tendency_vars, const_vars) - ("\\nMismatch tendency variable units 'mol m-3 s-1' for variable 'tendency_of_water_vapor'. No variable transforms supported for constituent tendencies. Tendency units should be 'kg kg-1 s-1' to match constituent.", 1) - >>> tendency_vars.append(Var({'local_name':'qc_tend', 'standard_name':'tendency_of_cloud_liquid_water_mixing_ratio', 'units':'kg kg-1 s-1', 'dimensions':'(horizontal_dimension, vertical_interface_dimension)', 'type':'real', 'kind':'kind_phys'}, _API_SOURCE, _API_DUMMY_RUN_ENV)) - >>> check_tendency_variables(tendency_vars, const_vars) - ("\\nMismatch tendency variable units 'mol m-3 s-1' for variable 'tendency_of_water_vapor'. No variable transforms supported for constituent tendencies. Tendency units should be 'kg kg-1 s-1' to match constituent.\\nMismatch tendency variable dimension for dim 2: 'vertical_interface_dimension' for variable 'tendency_of_cloud_liquid_water_mixing_ratio'. Dimension should match constituent dimension of 'vertical_layer_dimension'", 1) - >>> tendency_vars = [Var({'local_name':'qv_tend', 'standard_name':'tendency_of_water_vapor', 'units':'kg kg-1 s-1', 'dimensions':'(horizontal_dimension, vertical_layer_dimension)', 'type':'character', 'kind':'len=32'}, _API_SOURCE, _API_DUMMY_RUN_ENV)] - >>> check_tendency_variables(tendency_vars, const_vars) - ("\\nMismatch tendency variable type 'character' for variable 'tendency_of_water_vapor'. Type should match constituent type of 'real'\\nMismatch tendency variable kind 'len=32' for variable 'tendency_of_water_vapor'. Kind should match constituent kind of 'kind_phys'", 1) - >>> tendency_vars = [Var({'local_name':'qv_tend', 'standard_name':'tendency_of_water_vapor_mixing_ratio', 'units':'kg kg-1 s-1', 'dimensions':'(horizontal_dimension, vertical_layer_dimension)', 'type':'real', 'kind':'kind_phys'}, _API_SOURCE, _API_DUMMY_RUN_ENV)] - >>> tendency_vars.append(Var({'local_name':'qc_tend', 'standard_name':'tendency_of_cloud_liquid', 'units':'kg kg-1 s-1', 'dimensions':'(horizontal_dimension, vertical_layer_dimension)', 'type':'real', 'kind':'kind_phys'}, _API_SOURCE, _API_DUMMY_RUN_ENV)) - >>> tendency_vars.append(Var({'local_name':'qc_tend', 'standard_name':'typotendency_of_cloud_liquid', 'units':'kg kg-1 s-1', 'dimensions':'(horizontal_dimension, vertical_layer_dimension)', 'type':'real', 'kind':'kind_phys'}, _API_SOURCE, _API_DUMMY_RUN_ENV)) - >>> check_tendency_variables(tendency_vars, const_vars) - ("\\nInvalid tendency variable 'tendency_of_water_vapor_mixing_ratio'. All constituent tendency variable standard names must be of the form 'tendency_of_'\\nInvalid tendency variable 'tendency_of_cloud_liquid'. All constituent tendency variable standard names must be of the form 'tendency_of_'\\nInvalid tendency variable 'typotendency_of_cloud_liquid'. All constituent tendency variable standard names must be of the form 'tendency_of_'", 1) - """ - errflg = 0 - errmsg = '' - - for tend_var in tend_vars: - prop_error = False - valid_var = False - tend_stdname = tend_var.get_prop_value('standard_name') - tend_units = tend_var.get_prop_value('units') - if not tend_stdname.startswith('tendency_of_'): - # Tendency standard name is invalid - errmsg += f"\nInvalid tendency variable '{tend_stdname}'." - errmsg += " All constituent tendency variable standard names must " - errmsg += "be of the form 'tendency_of_'" - errflg = 1 - continue - tend_stdname_split = tend_stdname.split('tendency_of_')[1] - tend_dimensions = tend_var.get_dimensions() - tend_type = tend_var.get_prop_value('type') - tend_kind = tend_var.get_prop_value('kind') - for const_var in const_vars: - const_stdname = const_var.get_prop_value('standard_name') - const_units = const_var.get_prop_value('units') - const_dimensions = const_var.get_dimensions() - const_type = const_var.get_prop_value('type') - const_kind = const_var.get_prop_value('kind') - if tend_stdname_split == const_stdname: - # We found a match! Check units - if tend_units.split('s-1')[0].strip() != const_units: - errmsg += f"\nMismatch tendency variable units '{tend_units}'" - errmsg += f" for variable '{tend_stdname}'." - errmsg += " No variable transforms supported for constituent tendencies." - errmsg += f" Tendency units should be '{const_units} s-1' to match constituent." - prop_error = True - # end if - # Check the dimensions - for index, dim in enumerate(tend_dimensions): - # dimension replacement for horizontal_loop_extent - if dim == 'horizontal_loop_extent': - tend_dim = 'horizontal_dimension' - else: - tend_dim = dim - # end if - if const_dimensions[index] == 'horizontal_loop_extent': - const_dim = 'horizontal_dimension' - else: - const_dim = const_dimensions[index] - # end if - if tend_dim != const_dim: - errmsg += f"\nMismatch tendency variable dimension for dim {index + 1}: '{tend_dim}'" - errmsg += f" for variable '{tend_stdname}'." - errmsg += f" Dimension should match constituent dimension of '{const_dim}'" - prop_error = True - # end if - # end for - # Check the type - if const_type != tend_type: - errmsg += f"\nMismatch tendency variable type '{tend_type}'" - errmsg += f" for variable '{tend_stdname}'." - errmsg += f" Type should match constituent type of '{const_type}'" - prop_error = True - # end if - # Check the kind - if const_kind != tend_kind: - errmsg += f"\nMismatch tendency variable kind '{tend_kind}'" - errmsg += f" for variable '{tend_stdname}'." - errmsg += f" Kind should match constituent kind of '{const_kind}'" - prop_error = True - # end if - if prop_error == 0: - valid_var = True - exit - # end if - # end if - # end for - if not valid_var and not prop_error: - # Tendency standard name doesn't match an existing constituent - errmsg += f"\nInvalid tendency variable '{tend_stdname}'." - errmsg += " All constituent tendency variable standard names must " - errmsg += "be of the form 'tendency_of_'" - errflg = 1 - elif prop_error: - errflg = 1 - # end if - # end for - - return errmsg, errflg - ############################################################################### def suite_part_call_list(host_model, const_dict, suite_part, subst_loop_vars): ############################################################################### diff --git a/scripts/metavar.py b/scripts/metavar.py index bf84bac2..ed1f0155 100755 --- a/scripts/metavar.py +++ b/scripts/metavar.py @@ -374,7 +374,7 @@ def __init__(self, prop_dict, source, run_env, context=None, context=self.context) from cperr # end try - def compatible(self, other, run_env): + def compatible(self, other, run_env, is_tend=False): """Return a VarCompatObj object which describes the equivalence, compatibility, or incompatibility between and . """ @@ -397,7 +397,7 @@ def compatible(self, other, run_env): compat = VarCompatObj(sstd_name, stype, skind, sunits, sdims, sloc_name, stopp, ostd_name, otype, okind, ounits, odims, oloc_name, otopp, run_env, - v1_context=self.context, v2_context=other.context) + v1_context=self.context, v2_context=other.context, is_tend=is_tend) if (not compat) and (run_env.logger is not None): incompat_str = compat.incompat_reason if incompat_str is not None: diff --git a/scripts/var_props.py b/scripts/var_props.py index 53f39f5c..0ffe0904 100755 --- a/scripts/var_props.py +++ b/scripts/var_props.py @@ -857,7 +857,7 @@ class VarCompatObj: def __init__(self, var1_stdname, var1_type, var1_kind, var1_units, var1_dims, var1_lname, var1_top, var2_stdname, var2_type, var2_kind, var2_units, var2_dims, var2_lname, var2_top, run_env, v1_context=None, - v2_context=None): + v2_context=None, is_tend=False): """Initialize this object with information on the equivalence and/or conformability of two variables. variable 1 is described by , , , @@ -866,6 +866,8 @@ def __init__(self, var1_stdname, var1_type, var1_kind, var1_units, , , , , and . is the CCPPFrameworkEnv object used here to verify kind equivalence or to produce kind transformations. + is a flag where, if true, we are validating a tendency variable (var1) + against it's equivalent state variable (var2) """ self.__equiv = True # No transformation required self.__compat = True # Callable with transformation @@ -881,7 +883,9 @@ def __init__(self, var1_stdname, var1_type, var1_kind, var1_units, self.has_vert_transforms = False incompat_reason = list() # First, check for fatal incompatibilities - if var1_stdname != var2_stdname: + # If it's a tendency variable, it's assumed the standard name is of the + # form "tendency_of_var2_stdname" + if not is_tend and var1_stdname != var2_stdname: self.__equiv = False self.__compat = False incompat_reason.append("standard names") @@ -944,7 +948,20 @@ def __init__(self, var1_stdname, var1_type, var1_kind, var1_units, var2_units = 'none' # end if # Check units argument - if var1_units != var2_units: + if is_tend: + # A tendency variable's units should be " s-1" + tendency_split_units = var1_units.split('s-1')[0].strip() + if tendency_split_units != var2_units: + # We don't currently support unit conversions for tendency variables + emsg = f"\nMismatch tendency variable units '{var1_units}'" + emsg += f" for variable '{var1_stdname}'." + emsg += " No variable transforms supported for tendencies." + emsg += f" Tendency units should be '{var2_units} s-1' to match state variable." + self.__equiv = False + self.__compat = False + incompat_reason.append(emsg) + # end if + elif var1_units != var2_units: self.__equiv = False # Try to find a set of unit conversions self.__unit_transforms = self._get_unit_convstrs(var1_units, diff --git a/test/unit_tests/test_var_transforms.py b/test/unit_tests/test_var_transforms.py index 94a6c4a0..9fa5fb31 100755 --- a/test/unit_tests/test_var_transforms.py +++ b/test/unit_tests/test_var_transforms.py @@ -407,6 +407,50 @@ def test_valid_dim_transforms(self): expected = f"{v5_lname}({lind_str}) = {v4_lname}({rind_str})" self.assertEqual(rev_stmt, expected) + def test_compatible_tendency_variable(self): + """Test that a given tendency variable is compatible with + its corresponding state variable""" + real_array1 = self._new_var('real_stdname1', 'C', + ['horizontal_dimension', + 'vertical_layer_dimension'], + 'real', vkind='kind_phys') + real_array2 = self._new_var('tendency_of_real_stdname1', 'C s-1', + ['horizontal_dimension', + 'vertical_layer_dimension'], + 'real', vkind='kind_phys') + compat = real_array2.compatible(real_array1, self.__run_env, is_tend=True) + self.assertIsInstance(compat, VarCompatObj, + msg=self.__inst_emsg.format(type(compat))) + self.assertTrue(compat) + self.assertTrue(compat.compat) + self.assertEqual(compat.incompat_reason, '') + self.assertFalse(compat.has_kind_transforms) + self.assertFalse(compat.has_dim_transforms) + self.assertFalse(compat.has_unit_transforms) + + def test_incompatible_tendency_variable(self): + """Test that the correct error is returned when a given tendency + variable has inconsistent units vs the state variable""" + real_array1 = self._new_var('real_stdname1', 'C', + ['horizontal_dimension', + 'vertical_layer_dimension'], + 'real', vkind='kind_phys') + real_array2 = self._new_var('tendency_of_real_stdname1', 'C kg s-1', + ['horizontal_dimension', + 'vertical_layer_dimension'], + 'real', vkind='kind_phys') + compat = real_array2.compatible(real_array1, self.__run_env, is_tend=True) + self.assertIsInstance(compat, VarCompatObj, + msg=self.__inst_emsg.format(type(compat))) + #Verify correct error message returned + emsg = "\nMismatch tendency variable units 'C kg s-1' for variable 'tendency_of_real_stdname1'. No variable transforms supported for tendencies. Tendency units should be 'C s-1' to match state variable." + self.assertEqual(compat.incompat_reason, emsg) + self.assertFalse(compat.has_kind_transforms) + self.assertFalse(compat.has_dim_transforms) + self.assertFalse(compat.has_unit_transforms) + #Verify correct error message returned + + if __name__ == "__main__": unittest.main() From 69186d16f4e1ccbf4a8ec482a26e18079d2250f1 Mon Sep 17 00:00:00 2001 From: peverwhee Date: Thu, 31 Oct 2024 09:28:28 -0600 Subject: [PATCH 11/12] add check for "tendency_of" in VarCompatObj --- scripts/var_props.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/var_props.py b/scripts/var_props.py index 0ffe0904..c41bd3cc 100755 --- a/scripts/var_props.py +++ b/scripts/var_props.py @@ -883,8 +883,12 @@ def __init__(self, var1_stdname, var1_type, var1_kind, var1_units, self.has_vert_transforms = False incompat_reason = list() # First, check for fatal incompatibilities - # If it's a tendency variable, it's assumed the standard name is of the + # If it's a tendency variable, the standard name should be of the # form "tendency_of_var2_stdname" + if is_tend and not var1_stdname.startswith('tendency_of'): + self.__equiv = False + self.__compat = False + incompat_reason.append('not a tendency variable') if not is_tend and var1_stdname != var2_stdname: self.__equiv = False self.__compat = False From e070589ca004b0a057ed0e58a45e3ade11366ea7 Mon Sep 17 00:00:00 2001 From: peverwhee Date: Thu, 31 Oct 2024 13:52:05 -0600 Subject: [PATCH 12/12] fix merge --- test/advection_test/test_host.F90 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/advection_test/test_host.F90 b/test/advection_test/test_host.F90 index f2916580..3f442d8f 100644 --- a/test/advection_test/test_host.F90 +++ b/test/advection_test/test_host.F90 @@ -1062,8 +1062,8 @@ program test 'cloud_liquid_dry_mixing_ratio ', & 'ccpp_constituent_tendencies ', & 'ccpp_constituents ', & - 'dynamic_constituents_for_cld_liq ', & - 'dynamic_constituents_for_cld_ice ', & + 'dynamic_constituents_for_cld_liq ', & + 'dynamic_constituents_for_cld_ice ', & 'tendency_of_cloud_liquid_dry_mixing_ratio', & 'cloud_ice_dry_mixing_ratio ' /) test_reqvars1 = (/ & @@ -1073,8 +1073,8 @@ program test 'cloud_liquid_dry_mixing_ratio ', & 'tendency_of_cloud_liquid_dry_mixing_ratio', & 'cloud_ice_dry_mixing_ratio ', & - 'dynamic_constituents_for_cld_liq ', & - 'dynamic_constituents_for_cld_ice ', & + 'dynamic_constituents_for_cld_liq ', & + 'dynamic_constituents_for_cld_ice ', & 'water_temperature_at_freezing ', & 'ccpp_constituent_tendencies ', & 'ccpp_constituents ', &