diff --git a/scripts/constituents.py b/scripts/constituents.py index b1a08362..d4b08ab3 100644 --- a/scripts/constituents.py +++ b/scripts/constituents.py @@ -276,10 +276,17 @@ 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 unique (non-tendency) constituent variables we have + const_num = 0 + for std_name, _ in self.items(): + if not std_name.startswith('tendency_of_'): + const_num += 1 + # end if + # 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 +294,10 @@ 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 std_name.startswith('tendency_of_'): + # Skip tendency variables + 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 +347,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 3412e2cd..31a8e182 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" @@ -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]", @@ -300,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 {}" @@ -308,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, '{}'" @@ -329,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 '{}'" @@ -340,8 +349,13 @@ 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) + # Create an index variable for + if is_tend_var: + const_std_name = std_name.split("tendency_of_")[1] + else: + const_std_name = std_name + # end if + 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}") @@ -352,10 +366,44 @@ 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 + # end if # end for # end for + # Check that all tendency variables are valid + 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) diff --git a/scripts/metavar.py b/scripts/metavar.py index e84cd2b7..ed1f0155 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} @@ -372,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 . """ @@ -395,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/suite_objects.py b/scripts/suite_objects.py index 3fdbe8e6..d472acf4 100755 --- a/scripts/suite_objects.py +++ b/scripts/suite_objects.py @@ -1454,6 +1454,7 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er 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) diff --git a/scripts/var_props.py b/scripts/var_props.py index 53f39f5c..c41bd3cc 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,13 @@ 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, 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 incompat_reason.append("standard names") @@ -944,7 +952,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/src/ccpp_constituent_prop_mod.F90 b/src/ccpp_constituent_prop_mod.F90 index c4086099..de4498c3 100644 --- a/src/ccpp_constituent_prop_mod.F90 +++ b/src/ccpp_constituent_prop_mod.F90 @@ -151,6 +151,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 @@ -1468,12 +1469,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() @@ -1524,6 +1534,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 d6d9a4fc..77f446e6 100644 --- a/src/ccpp_constituent_prop_mod.meta +++ b/src/ccpp_constituent_prop_mod.meta @@ -45,6 +45,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_constituent_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/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 ec19cb17..835b59c8 100644 --- a/test/advection_test/cld_liq.F90 +++ b/test/advection_test/cld_liq.F90 @@ -43,8 +43,8 @@ end subroutine cld_liq_register !> \section arg_table_cld_liq_run Argument Table !! \htmlinclude arg_table_cld_liq_run.html !! - subroutine cld_liq_run(ncol, timestep, tcld, temp, qv, ps, cld_liq_array, & - errmsg, errflg) + subroutine cld_liq_run(ncol, timestep, tcld, temp, qv, ps, & + cld_liq_tend, errmsg, errflg) integer, intent(in) :: ncol real(kind_phys), intent(in) :: timestep @@ -52,7 +52,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(:,:) character(len=512), intent(out) :: errmsg integer, intent(out) :: errflg !---------------------------------------------------------------- @@ -70,7 +70,7 @@ 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 temp(icol, ilev) = temp(icol, ilev) + (cond * 5.0_kind_phys) diff --git a/test/advection_test/cld_liq.meta b/test/advection_test/cld_liq.meta index da04ccf5..69ad3f4f 100644 --- a/test/advection_test/cld_liq.meta +++ b/test/advection_test/cld_liq.meta @@ -71,13 +71,13 @@ units = hPa dimensions = (horizontal_loop_extent) intent = in -[ cld_liq_array ] - standard_name = cloud_liquid_dry_mixing_ratio - advected = .true. - units = kg kg-1 +[ 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 143c7d7f..5f9f8a8b 100755 --- a/test/advection_test/run_test +++ b/test/advection_test/run_test @@ -125,10 +125,11 @@ 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="" 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},dynamic_constituents_for_cld_ice" @@ -136,27 +137,34 @@ required_vars="${required_vars},dynamic_constituents_for_cld_liq" 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" 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},dynamic_constituents_for_cld_ice" output_vars="${output_vars},dynamic_constituents_for_cld_liq" 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 5146097b..3f442d8f 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 @@ -1034,44 +1034,54 @@ program test implicit none character(len=cs), target :: test_parts1(1) - character(len=cm), target :: test_invars1(7) - character(len=cm), target :: test_outvars1(8) - character(len=cm), target :: test_reqvars1(11) + character(len=cm), target :: test_invars1(11) + character(len=cm), target :: test_outvars1(11) + character(len=cm), target :: test_reqvars1(15) 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 ', & + 'ccpp_constituent_tendencies ', & + 'ccpp_constituents ', & + 'number_of_ccpp_constituents ', & + 'water_vapor_specific_humidity ' /) test_outvars1 = (/ & - 'ccpp_error_message ', & - 'ccpp_error_code ', & - 'temperature ', & - 'water_vapor_specific_humidity ', & - 'cloud_liquid_dry_mixing_ratio ', & - 'dynamic_constituents_for_cld_liq ', & - 'dynamic_constituents_for_cld_ice ', & - 'cloud_ice_dry_mixing_ratio ' /) + 'ccpp_error_message ', & + 'ccpp_error_code ', & + 'temperature ', & + 'water_vapor_specific_humidity ', & + 'cloud_liquid_dry_mixing_ratio ', & + 'ccpp_constituent_tendencies ', & + 'ccpp_constituents ', & + 'dynamic_constituents_for_cld_liq ', & + 'dynamic_constituents_for_cld_ice ', & + '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 ', & - 'dynamic_constituents_for_cld_liq ', & - 'dynamic_constituents_for_cld_ice ', & - '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 ', & + 'dynamic_constituents_for_cld_liq ', & + 'dynamic_constituents_for_cld_ice ', & + 'water_temperature_at_freezing ', & + 'ccpp_constituent_tendencies ', & + 'ccpp_constituents ', & + 'number_of_ccpp_constituents ', & + 'water_vapor_specific_humidity ', & + 'ccpp_error_message ', & + 'ccpp_error_code ' /) ! Setup expected test suite info test_suites(1)%suite_name = 'cld_suite' diff --git a/test/advection_test/test_reports.py b/test/advection_test/test_reports.py index 2061b796..2a51a411 100644 --- a/test/advection_test/test_reports.py +++ b/test/advection_test/test_reports.py @@ -64,16 +64,20 @@ 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", "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", "cloud_liquid_dry_mixing_ratio", + "ccpp_constituents", + "ccpp_constituent_tendencies", + "number_of_ccpp_constituents", "dynamic_constituents_for_cld_ice", "dynamic_constituents_for_cld_liq", # Added by --debug option @@ -85,12 +89,19 @@ 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", + "ccpp_constituents", + "ccpp_constituent_tendencies", + "number_of_ccpp_constituents", # Added by --debug option "horizontal_dimension", "vertical_layer_dimension"] _OUTPUT_VARS_CLD = ["ccpp_error_code", "ccpp_error_message", "water_vapor_specific_humidity", "temperature", + "tendency_of_cloud_liquid_dry_mixing_ratio", "cloud_ice_dry_mixing_ratio", + "ccpp_constituents", + "ccpp_constituent_tendencies", "cloud_liquid_dry_mixing_ratio", "dynamic_constituents_for_cld_ice", "dynamic_constituents_for_cld_liq"] 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()