Skip to content

Commit ce381cf

Browse files
authored
Add constituent tendency capability (#584)
Adds constituent tendency array and enables the use of individual constituent tendency variables. In order to facilitate time-splitting, this PR adds/enables the following: - ccpp_constituent_tendencies: standard name for constituent tendencies array - standard names of the form "tendency_of_CONSTITUENT" now handled by the framework - must also include "constituent = True" in metadata for the tendency Testing: unit tests: Added doctests to new check_tendency_variables function in host_cap.F90 system tests: Modified advection_test to use tendency variable and tendency array
1 parent bcc3aba commit ce381cf

17 files changed

+311
-58
lines changed

scripts/constituents.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,17 +276,28 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars):
276276
for evar in err_vars:
277277
evar.write_def(outfile, indent+1, self, dummy=True)
278278
# end for
279+
# Figure out how many unique (non-tendency) constituent variables we have
280+
const_num = 0
281+
for std_name, _ in self.items():
282+
if not std_name.startswith('tendency_of_'):
283+
const_num += 1
284+
# end if
285+
# end for
279286
if self:
280287
outfile.write("! Local variables", indent+1)
281288
outfile.write("integer :: index", indent+1)
282-
stmt = f"allocate({self.constituent_prop_array_name()}({len(self)}))"
289+
stmt = f"allocate({self.constituent_prop_array_name()}({const_num}))"
283290
outfile.write(stmt, indent+1)
284291
outfile.write("index = 0", indent+1)
285292
# end if
286293
for evar in err_vars:
287294
self.__init_err_var(evar, outfile, indent+1)
288295
# end for
289296
for std_name, var in self.items():
297+
if std_name.startswith('tendency_of_'):
298+
# Skip tendency variables
299+
continue
300+
# end if
290301
outfile.write("index = index + 1", indent+1)
291302
long_name = var.get_prop_value('long_name')
292303
units = var.get_prop_value('units')
@@ -336,7 +347,7 @@ def write_constituent_routines(self, outfile, indent, suite_name, err_vars):
336347
outfile.write(stmt, indent+1)
337348
outfile.write(f"call {self.constituent_prop_init_consts()}({local_call})", indent+2)
338349
outfile.write("end if", indent+1)
339-
outfile.write(f"{fname} = {len(self)}", indent+1)
350+
outfile.write(f"{fname} = {const_num}", indent+1)
340351
outfile.write(f"end function {fname}", indent)
341352
outfile.write(f"\n! {border}\n", 1)
342353
# Return the name of a constituent given an index

scripts/host_cap.py

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from metavar import CCPP_LOOP_VAR_STDNAMES
2121
from fortran_tools import FortranWriter
2222
from parse_tools import CCPPError
23-
from parse_tools import ParseObject, ParseSource, ParseContext
23+
from parse_tools import ParseObject, ParseSource, ParseContext, ParseSyntaxError
2424

2525
###############################################################################
2626
_HEADER = "cap for {host_model} calls to CCPP API"
@@ -284,6 +284,7 @@ def add_constituent_vars(cap, host_model, suite_list, run_env):
284284
vert_layer_dim = "vertical_layer_dimension"
285285
vert_interface_dim = "vertical_interface_dimension"
286286
array_layer = "vars_layer"
287+
tend_layer = "vars_layer_tend"
287288
# Table preamble (leave off ccpp-table-properties header)
288289
ddt_mdata = [
289290
#"[ccpp-table-properties]",
@@ -300,6 +301,9 @@ def add_constituent_vars(cap, host_model, suite_list, run_env):
300301
" type = real", " kind = kind_phys"]
301302
# Add entries for each constituent (once per standard name)
302303
const_stdnames = set()
304+
tend_stdnames = set()
305+
const_vars = set()
306+
tend_vars = set()
303307
for suite in suite_list:
304308
if run_env.verbose:
305309
lmsg = "Adding constituents from {} to {}"
@@ -308,12 +312,13 @@ def add_constituent_vars(cap, host_model, suite_list, run_env):
308312
scdict = suite.constituent_dictionary()
309313
for cvar in scdict.variable_list():
310314
std_name = cvar.get_prop_value('standard_name')
311-
if std_name not in const_stdnames:
315+
if std_name not in const_stdnames and std_name not in tend_stdnames:
312316
# Add a metadata entry for this constituent
313317
# Check dimensions and figure vertical dimension
314318
# Currently, we only support variables with first dimension,
315319
# horizontal_dimension, and second (optional) dimension,
316320
# vertical_layer_dimension or vertical_interface_dimension
321+
is_tend_var = 'tendency_of' in std_name
317322
dims = cvar.get_dimensions()
318323
if (len(dims) < 1) or (len(dims) > 2):
319324
emsg = "Unsupported constituent dimensions, '{}'"
@@ -329,7 +334,11 @@ def add_constituent_vars(cap, host_model, suite_list, run_env):
329334
if len(dims) > 1:
330335
vdim = dims[1].split(':')[-1]
331336
if vdim == vert_layer_dim:
332-
cvar_array_name = array_layer
337+
if is_tend_var:
338+
cvar_array_name = tend_layer
339+
else:
340+
cvar_array_name = array_layer
341+
# end if
333342
else:
334343
emsg = "Unsupported vertical constituent dimension, "
335344
emsg += "'{}', must be '{}' or '{}'"
@@ -340,8 +349,13 @@ def add_constituent_vars(cap, host_model, suite_list, run_env):
340349
emsg = f"Unsupported 2-D variable, '{std_name}'"
341350
raise CCPPError(emsg)
342351
# end if
343-
# First, create an index variable for <cvar>
344-
ind_std_name = "index_of_{}".format(std_name)
352+
# Create an index variable for <cvar>
353+
if is_tend_var:
354+
const_std_name = std_name.split("tendency_of_")[1]
355+
else:
356+
const_std_name = std_name
357+
# end if
358+
ind_std_name = f"index_of_{const_std_name}"
345359
loc_name = f"{cvar_array_name}(:,:,{ind_std_name})"
346360
ddt_mdata.append(f"[ {loc_name} ]")
347361
ddt_mdata.append(f" standard_name = {std_name}")
@@ -352,10 +366,44 @@ def add_constituent_vars(cap, host_model, suite_list, run_env):
352366
vtype = cvar.get_prop_value('type')
353367
vkind = cvar.get_prop_value('kind')
354368
ddt_mdata.append(f" type = {vtype} | kind = {vkind}")
355-
const_stdnames.add(std_name)
369+
if is_tend_var:
370+
tend_vars.add(cvar)
371+
tend_stdnames.add(std_name)
372+
else:
373+
const_vars.add(cvar)
374+
const_stdnames.add(std_name)
375+
# end if
376+
356377
# end if
357378
# end for
358379
# end for
380+
# Check that all tendency variables are valid
381+
for tendency_variable in tend_vars:
382+
tend_stdname = tendency_variable.get_prop_value('standard_name')
383+
tend_const_name = tend_stdname.split('tendency_of_')[1]
384+
found = False
385+
# Find the corresponding constituent variable
386+
for const_variable in const_vars:
387+
const_stdname = const_variable.get_prop_value('standard_name')
388+
if const_stdname == tend_const_name:
389+
found = True
390+
compat = tendency_variable.compatible(const_variable, run_env, is_tend=True)
391+
if not compat:
392+
errstr = f"Tendency variable, '{tend_stdname}'"
393+
errstr += f", incompatible with associated state variable '{tend_const_name}'"
394+
errstr += f". Reason: '{compat.incompat_reason}'"
395+
raise ParseSyntaxError(errstr, token=tend_stdname,
396+
context=tendency_variable.context)
397+
# end if
398+
# end if
399+
# end for
400+
if not found:
401+
# error because we couldn't find the associated constituent
402+
errstr = f"No associated state variable for tendency variable, '{tend_stdname}'"
403+
raise ParseSyntaxError(errstr, token=tend_stdname,
404+
context=tendency_variable.context)
405+
# end if
406+
# end for
359407
# Parse this table using a fake filename
360408
parse_obj = ParseObject(f"{host_model.name}_constituent_mod.meta",
361409
ddt_mdata)

scripts/metavar.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,9 @@ class Var:
229229
optional_in=True, default_in=False),
230230
VariableProperty('molar_mass', float,
231231
optional_in=True, default_in=0.0,
232-
check_fn_in=check_molar_mass)]
232+
check_fn_in=check_molar_mass),
233+
VariableProperty('constituent', bool,
234+
optional_in=True, default_in=False)]
233235

234236
__constituent_prop_dict = {x.name : x for x in __constituent_props}
235237

@@ -372,7 +374,7 @@ def __init__(self, prop_dict, source, run_env, context=None,
372374
context=self.context) from cperr
373375
# end try
374376

375-
def compatible(self, other, run_env):
377+
def compatible(self, other, run_env, is_tend=False):
376378
"""Return a VarCompatObj object which describes the equivalence,
377379
compatibility, or incompatibility between <self> and <other>.
378380
"""
@@ -395,7 +397,7 @@ def compatible(self, other, run_env):
395397
compat = VarCompatObj(sstd_name, stype, skind, sunits, sdims, sloc_name, stopp,
396398
ostd_name, otype, okind, ounits, odims, oloc_name, otopp,
397399
run_env,
398-
v1_context=self.context, v2_context=other.context)
400+
v1_context=self.context, v2_context=other.context, is_tend=is_tend)
399401
if (not compat) and (run_env.logger is not None):
400402
incompat_str = compat.incompat_reason
401403
if incompat_str is not None:

scripts/suite_objects.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1454,6 +1454,7 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er
14541454
intent = svar.get_prop_value('intent')
14551455
if intent == 'out' and allocatable:
14561456
return
1457+
# end if
14571458

14581459
# Get the condition on which the variable is active
14591460
(conditional, _) = var.conditional(cldicts)

scripts/var_props.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -857,7 +857,7 @@ class VarCompatObj:
857857
def __init__(self, var1_stdname, var1_type, var1_kind, var1_units,
858858
var1_dims, var1_lname, var1_top, var2_stdname, var2_type, var2_kind,
859859
var2_units, var2_dims, var2_lname, var2_top, run_env, v1_context=None,
860-
v2_context=None):
860+
v2_context=None, is_tend=False):
861861
"""Initialize this object with information on the equivalence and/or
862862
conformability of two variables.
863863
variable 1 is described by <var1_stdname>, <var1_type>, <var1_kind>,
@@ -866,6 +866,8 @@ def __init__(self, var1_stdname, var1_type, var1_kind, var1_units,
866866
<var2_units>, <var2_dims>, <var2_lname>, <var2_top>, and <v2_context>.
867867
<run_env> is the CCPPFrameworkEnv object used here to verify kind
868868
equivalence or to produce kind transformations.
869+
<is_tend> is a flag where, if true, we are validating a tendency variable (var1)
870+
against it's equivalent state variable (var2)
869871
"""
870872
self.__equiv = True # No transformation required
871873
self.__compat = True # Callable with transformation
@@ -881,7 +883,13 @@ def __init__(self, var1_stdname, var1_type, var1_kind, var1_units,
881883
self.has_vert_transforms = False
882884
incompat_reason = list()
883885
# First, check for fatal incompatibilities
884-
if var1_stdname != var2_stdname:
886+
# If it's a tendency variable, the standard name should be of the
887+
# form "tendency_of_var2_stdname"
888+
if is_tend and not var1_stdname.startswith('tendency_of'):
889+
self.__equiv = False
890+
self.__compat = False
891+
incompat_reason.append('not a tendency variable')
892+
if not is_tend and var1_stdname != var2_stdname:
885893
self.__equiv = False
886894
self.__compat = False
887895
incompat_reason.append("standard names")
@@ -944,7 +952,20 @@ def __init__(self, var1_stdname, var1_type, var1_kind, var1_units,
944952
var2_units = 'none'
945953
# end if
946954
# Check units argument
947-
if var1_units != var2_units:
955+
if is_tend:
956+
# A tendency variable's units should be "<var2_units> s-1"
957+
tendency_split_units = var1_units.split('s-1')[0].strip()
958+
if tendency_split_units != var2_units:
959+
# We don't currently support unit conversions for tendency variables
960+
emsg = f"\nMismatch tendency variable units '{var1_units}'"
961+
emsg += f" for variable '{var1_stdname}'."
962+
emsg += " No variable transforms supported for tendencies."
963+
emsg += f" Tendency units should be '{var2_units} s-1' to match state variable."
964+
self.__equiv = False
965+
self.__compat = False
966+
incompat_reason.append(emsg)
967+
# end if
968+
elif var1_units != var2_units:
948969
self.__equiv = False
949970
# Try to find a set of unit conversions
950971
self.__unit_transforms = self._get_unit_convstrs(var1_units,

src/ccpp_constituent_prop_mod.F90

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ module ccpp_constituent_prop_mod
151151
! These fields are public to allow for efficient (i.e., no copying)
152152
! usage even though it breaks object independence
153153
real(kind_phys), allocatable :: vars_layer(:,:,:)
154+
real(kind_phys), allocatable :: vars_layer_tend(:,:,:)
154155
real(kind_phys), allocatable :: vars_minvalue(:)
155156
! An array containing all the constituent metadata
156157
! Each element contains a pointer to a constituent from the hash table
@@ -1493,12 +1494,21 @@ subroutine ccp_model_const_data_lock(this, ncols, num_layers, errcode, errmsg)
14931494
call handle_allocate_error(astat, 'vars_layer', &
14941495
subname, errcode=errcode, errmsg=errmsg)
14951496
errcode_local = astat
1497+
if (astat == 0) then
1498+
allocate(this%vars_layer_tend(ncols, num_layers, this%hash_table%num_values()), &
1499+
stat=astat)
1500+
call handle_allocate_error(astat, 'vars_layer_tend', &
1501+
subname, errcode=errcode, errmsg=errmsg)
1502+
errcode_local = astat
1503+
end if
14961504
if (astat == 0) then
14971505
allocate(this%vars_minvalue(this%hash_table%num_values()), stat=astat)
14981506
call handle_allocate_error(astat, 'vars_minvalue', &
14991507
subname, errcode=errcode, errmsg=errmsg)
15001508
errcode_local = astat
15011509
end if
1510+
! Initialize tendencies to 0
1511+
this%vars_layer_tend(:,:,:) = 0._kind_phys
15021512
if (errcode_local == 0) then
15031513
this%num_layers = num_layers
15041514
do index = 1, this%hash_table%num_values()
@@ -1549,6 +1559,9 @@ subroutine ccp_model_const_reset(this, clear_hash_table)
15491559
if (allocated(this%vars_minvalue)) then
15501560
deallocate(this%vars_minvalue)
15511561
end if
1562+
if (allocated(this%vars_layer_tend)) then
1563+
deallocate(this%vars_layer_tend)
1564+
end if
15521565
if (allocated(this%const_metadata)) then
15531566
if (clear_table) then
15541567
do index = 1, size(this%const_metadata, 1)

src/ccpp_constituent_prop_mod.meta

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@
4545
state_variable = true
4646
dimensions = (horizontal_dimension, vertical_layer_dimension, number_of_ccpp_constituents)
4747
type = real | kind = kind_phys
48+
[ vars_layer_tend ]
49+
standard_name = ccpp_constituent_tendencies
50+
long_name = Array of constituent tendencies managed by CCPP Framework
51+
units = none
52+
dimensions = (horizontal_dimension, vertical_layer_dimension, number_of_ccpp_constituents)
53+
type = real | kind = kind_phys
4854
[ const_metadata ]
4955
standard_name = ccpp_constituent_properties
5056
units = None
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
module apply_constituent_tendencies
2+
3+
use ccpp_kinds, only: kind_phys
4+
5+
implicit none
6+
private
7+
8+
public :: apply_constituent_tendencies_run
9+
10+
CONTAINS
11+
12+
!> \section arg_table_apply_constituent_tendencies_run Argument Table
13+
!!! \htmlinclude apply_constituent_tendencies_run.html
14+
subroutine apply_constituent_tendencies_run(const_tend, const, errcode, errmsg)
15+
! Dummy arguments
16+
real(kind_phys), intent(inout) :: const_tend(:,:,:) ! constituent tendency array
17+
real(kind_phys), intent(inout) :: const(:,:,:) ! constituent state array
18+
integer, intent(out) :: errcode
19+
character(len=512), intent(out) :: errmsg
20+
21+
! Local variables
22+
integer :: klev, jcnst, icol
23+
24+
errcode = 0
25+
errmsg = ''
26+
27+
do icol = 1, size(const_tend, 1)
28+
do klev = 1, size(const_tend, 2)
29+
do jcnst = 1, size(const_tend, 3)
30+
const(icol, klev, jcnst) = const(icol, klev, jcnst) + const_tend(icol, klev, jcnst)
31+
end do
32+
end do
33+
end do
34+
35+
const_tend = 0._kind_phys
36+
37+
end subroutine apply_constituent_tendencies_run
38+
39+
end module apply_constituent_tendencies
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#####################################################################
2+
[ccpp-table-properties]
3+
name = apply_constituent_tendencies
4+
type = scheme
5+
[ccpp-arg-table]
6+
name = apply_constituent_tendencies_run
7+
type = scheme
8+
[ const_tend ]
9+
standard_name = ccpp_constituent_tendencies
10+
long_name = ccpp constituent tendencies
11+
units = none
12+
type = real | kind = kind_phys
13+
dimensions = (horizontal_loop_extent, vertical_layer_dimension, number_of_ccpp_constituents)
14+
intent = inout
15+
[ const ]
16+
standard_name = ccpp_constituents
17+
long_name = ccpp constituents
18+
units = none
19+
type = real | kind = kind_phys
20+
dimensions = (horizontal_loop_extent, vertical_layer_dimension, number_of_ccpp_constituents)
21+
intent = inout
22+
[ errcode ]
23+
standard_name = ccpp_error_code
24+
long_name = Error flag for error handling in CCPP
25+
units = 1
26+
type = integer
27+
dimensions = ()
28+
intent = out
29+
[ errmsg ]
30+
standard_name = ccpp_error_message
31+
long_name = Error message for error handling in CCPP
32+
units = none
33+
type = character | kind = len=512
34+
dimensions = ()
35+
intent = out
36+
#########################################################

0 commit comments

Comments
 (0)