Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add optional attribute to Capgen #529

Merged
merged 24 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
964b776
Add optional keyword. Work in progress.
dustinswales Jan 30, 2024
bcdb37e
Merge remote-tracking branch 'me/test_active_attribute' into add_opti…
dustinswales Jan 30, 2024
224fad3
Work in progress.
dustinswales Jan 30, 2024
aa652cd
Add optional attribute to varaible declarations in caps
dustinswales Jan 30, 2024
462d08b
Merge branch 'test_active_attribute' of https://github.com/dustinswal…
dustinswales Jan 31, 2024
9248678
Work in progress
dustinswales Feb 1, 2024
7a0c4c9
added active logic to pointer association
dustinswales Feb 2, 2024
a1a701b
Merge branch 'feature/capgen' of https://github.com/NCAR/ccpp-framewo…
dustinswales Feb 5, 2024
423dc71
Working now
dustinswales Feb 8, 2024
aa59672
Added null pointers to caps when scheme has optional argument, but no…
dustinswales Feb 8, 2024
7b20265
Address reviewers comments. Add whitespace to autogenerated code for …
dustinswales Feb 12, 2024
d7bad29
Revert some changes
dustinswales Feb 12, 2024
9337a2e
Add optional attribute to capgen test
dustinswales Feb 12, 2024
3e4f88f
Fix doctests.
dustinswales Feb 12, 2024
80da2a5
Fix doctest again
dustinswales Feb 12, 2024
c34496f
Fix doctest again again
dustinswales Feb 12, 2024
9125aae
Fix doctest again again again
dustinswales Feb 12, 2024
34aa7f5
Update scripts/metavar.py
dustinswales Feb 22, 2024
a9ae401
Merge branch 'feature/capgen' of https://github.com/NCAR/ccpp-framewo…
dustinswales Feb 22, 2024
adad650
Address reviewers comments
dustinswales Mar 4, 2024
00a905f
Update scripts/suite_objects.py
dustinswales Mar 6, 2024
b4991be
Update scripts/suite_objects.py
dustinswales Mar 6, 2024
b0ad2df
Update scripts/suite_objects.py
dustinswales Mar 6, 2024
809fc91
Merge branch 'feature/capgen' of https://github.com/NCAR/ccpp-framewo…
dustinswales Mar 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions scripts/ccpp_capgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,21 @@ def compare_fheader_to_mheader(meta_header, fort_header, logger):
# end for
list_match = mlen == flen
# end if
# Check for consistency between optional variables in metadata and
# optional variables in fortran. Error if optional attribute is
# missing from fortran declaration.
for mind, mvar in enumerate(mlist):
mopt = mvar.get_prop_value('optional')
mname = mvar.get_prop_value('local_name')
fvar, find = find_var_in_list(mname, flist)
if find and mopt:
fopt = fvar.get_prop_value('optional')
if (not fopt):
errmsg = 'Missing optional attribute in fortran declaration for variable {}, in file {}'
errors_found = add_error(errors_found, errmsg.format(mname,title))
# end if
# end if
# end for
peverwhee marked this conversation as resolved.
Show resolved Hide resolved
if not list_match:
if fht in _EXTRA_VARIABLE_TABLE_TYPES:
if flen > mlen:
Expand Down
2 changes: 1 addition & 1 deletion scripts/ccpp_datafile.py
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ def _new_var_entry(parent, var, full_entry=True):
"diagnostic_name", "diagnostic_name_fixed",
"kind", "persistence", "polymorphic", "protected",
"state_variable", "type", "units", "molar_mass",
"advected", "top_at_one"])
"advected", "top_at_one", "optional"])
prop_list.extend(Var.constituent_property_names())
# end if
ventry = ET.SubElement(parent, "var")
Expand Down
24 changes: 21 additions & 3 deletions scripts/metavar.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ class Var:
default_in=False),
VariableProperty('top_at_one', bool, optional_in=True,
default_in=False),
VariableProperty('optional', bool, optional_in=True,
default_in=False),
VariableProperty('target', bool, optional_in=True,
default_in=False)]

Expand Down Expand Up @@ -1025,8 +1027,8 @@ def conditional(self, vdicts):
vars_needed.append(dvar)
return (conditional, vars_needed)

def write_def(self, outfile, indent, wdict, allocatable=False,
dummy=False, add_intent=None, extra_space=0, public=False):
def write_def(self, outfile, indent, wdict, allocatable=False, target=None,
dustinswales marked this conversation as resolved.
Show resolved Hide resolved
pointer=None, dummy=False, add_intent=None, extra_space=0, public=False):
"""Write the definition line for the variable to <outfile>.
If <dummy> is True, include the variable's intent.
If <dummy> is True but the variable has no intent, add the
Expand Down Expand Up @@ -1077,18 +1079,24 @@ def write_def(self, outfile, indent, wdict, allocatable=False,
raise CCPPError(errmsg.format(name))
# end if
# end if
optional = self.get_prop_value('optional')
if protected and dummy:
intent_str = 'intent(in) '
elif allocatable:
if dimstr or polymorphic:
intent_str = 'allocatable '
if target:
intent_str += ',target '
else:
intent_str = ' '*13
# end if
elif intent is not None:
alloval = self.get_prop_value('allocatable')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be True here given the elif allocatable above? If it can, then what if is also optional?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure of the purpose of alloval here?
If we are in this block of code, the variable is not an allocatable?

if (intent.lower()[-3:] == 'out') and alloval:
intent_str = f"allocatable, intent({intent})"
elif optional:
intent_str = f"intent({intent}),{' '*(5 - len(intent))}"
intent_str += 'optional '
else:
intent_str = f"intent({intent}){' '*(5 - len(intent))}"
# end if
Expand Down Expand Up @@ -1126,10 +1134,20 @@ def write_def(self, outfile, indent, wdict, allocatable=False,
cspc = comma + ' '*(extra_space + 19 - len(vtype))
# end if
# end if

outfile.write(dstr.format(type=vtype, kind=kind, intent=intent_str,
name=name, dims=dimstr, cspc=cspc,
sname=stdname), indent)
if pointer:
name = name+'_ptr'
if kind:
dstr = "{type}({kind}){cspc}pointer :: {name}{dims} => null()"
cspc = comma + ' '*(extra_space + 20 - len(vtype) - len(kind))
else:
dstr = "{type}{cspc}pointer :: {name}{dims} => null()"
cspc = comma + ' '*(extra_space + 22 - len(vtype))
# end if
outfile.write(dstr.format(type=vtype, kind=kind, intent=intent_str,
name=name, dims=dimstr, cspc=cspc), indent)

def is_ddt(self):
"""Return True iff <self> is a DDT type."""
Expand Down
84 changes: 81 additions & 3 deletions scripts/suite_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -871,10 +871,20 @@ def match_variable(self, var, run_env):
new_vdims = list()
new_dict_dims = dict_dims
match = True
# end if
# Create compatability object, containing any necessary forward/reverse
# transforms from <var> and <dict_var>
compat_obj = var.compatible(dict_var, run_env)

# If variable is defined as "inactive" by the host, ensure that
# this variable is declared as "optional" by the scheme. If
# not satisfied, return error.
host_var_active = dict_var.get_prop_value('active')
scheme_var_optional = var.get_prop_value('optional')
if (not scheme_var_optional and host_var_active.lower() != '.true.'):
errmsg = "Non optional scheme arguments for conditionally allocatable variables"
sname = dict_var.get_prop_value('standard_name')
errmsg += ", {}".format(sname)
raise CCPPError(errmsg)
peverwhee marked this conversation as resolved.
Show resolved Hide resolved
# end if
# Add the variable to the parent call tree
if dict_dims == new_dict_dims:
Expand Down Expand Up @@ -1083,6 +1093,7 @@ def __init__(self, scheme_xml, context, parent, run_env):
self.__var_debug_checks = list()
self.__forward_transforms = list()
self.__reverse_transforms = list()
self.__optional_vars = list()
super().__init__(name, context, parent, run_env, active_call_list=True)

def update_group_call_list_variable(self, var):
Expand Down Expand Up @@ -1216,6 +1227,14 @@ def analyze(self, phase, group, scheme_library, suite_vars, level):
compat_obj.has_unit_transforms or
compat_obj.has_kind_transforms):
self.add_var_transform(var, compat_obj, vert_dim)
# end if

# Is this a conditionally allocated variable?
# If so, declare local target and pointer varaibles. This is needed to
# pass inactive (not present) status through the caps.
if var.get_prop_value('optional'):
self.add_optional_var(var, dict_var)
# end if

# end for
if self.needs_vertical is not None:
Expand Down Expand Up @@ -1525,6 +1544,18 @@ def write_var_debug_check(self, var, internal_var, cldicts, outfile, errcode, er
outfile.write('',indent)
# end if

def add_optional_var(self, var, dict_var):
newvar_ptr = var.clone(var.get_prop_value('local_name')+'_local_ptr')
self.add_variable(newvar_ptr, self.run_env, exists_ok=True)
self.__optional_vars.append([dict_var, var.get_prop_value('local_name')+'_local',
newvar_ptr.get_prop_value('local_name')])

def write_optional_var(self, var, cldicts, lname, lname_ptr, indent, outfile):
(conditional, _) = var.conditional(cldicts)
outfile.write(f"if {conditional} then", indent)
outfile.write(f"{lname_ptr} => {lname}", indent+1)
outfile.write(f"end if", indent)

def add_var_transform(self, var, compat_obj, vert_dim):
"""Register any variable transformation needed by <var> for this Scheme.
For any transformation identified in <compat_obj>, create dummy variable
Expand Down Expand Up @@ -1598,6 +1629,7 @@ def write_var_transform(self, var, dummy, rindices, lindices, compat_obj,
rvar_lname=dummy,
lvar_indices=rindices,
rvar_indices=lindices)
# end if
outfile.write(stmt, indent)

def write(self, outfile, errcode, errmsg, indent):
Expand All @@ -1623,6 +1655,12 @@ def write(self, outfile, errcode, errmsg, indent):
outfile.write('! Compute reverse (pre-scheme) transforms', indent+1)
for (dummy, var, rindices, lindices, compat_obj) in self.__reverse_transforms:
tstmt = self.write_var_transform(var, dummy, rindices, lindices, compat_obj, outfile, indent+1, False)
outfile.write('',indent+1)
# Associate conditionally allocated variables.
outfile.write('! Associate conditional variables', indent+1)
for (var, lname, lname_ptr) in self.__optional_vars:
tstmt = self.write_optional_var(var, cldicts, lname, lname_ptr, indent+1, outfile)
# end if
# Write the scheme call.
stmt = 'call {}({})'
outfile.write('',indent+1)
Expand Down Expand Up @@ -2165,23 +2203,43 @@ def write(self, outfile, host_arglist, indent, const_mod,
# Collect information on local variables
subpart_vars = {}
allocatable_var_set = set()
subpart_ptrs = {}
pointer_var_set = set()
for item in [self]:# + self.parts:
for var in item.declarations():
lname = var.get_prop_value('local_name')
opt_var = var.get_prop_value('optional')
if lname in subpart_vars:
if subpart_vars[lname][0].compatible(var, self.run_env):
pass # We already are going to declare this variable
else:
errmsg = "Duplicate Group variable, {}"
raise ParseInternalError(errmsg.format(lname))
# end if
#elif not opt_var:
else:
subpart_vars[lname] = (var, item)
subpart_vars[lname] = (var, item, False, False)
dims = var.get_dimensions()
if (dims is not None) and dims:
allocatable_var_set.add(lname)
# end if
# end if
opt_var = var.get_prop_value('optional')
#if lname in subpart_ptrs:
# if subpart_ptrs[lname][0].compatible(var, self.run_env):
# pass
# else:
# errmsg = "Duplicate Group variable, {}"
# raise ParseInternalError(errmsg.format(lname))
# # end if
if opt_var:
subpart_vars[lname] = (var, item, True, True)
subpart_ptrs[lname+'_ptr'] = (var, item)
dims = var.get_dimensions()
if (dims is not None) and dims:
pointer_var_set.add(lname+'_ptr')
# endif
# end if
# end for
# end for
# First, write out the subroutine header
Expand Down Expand Up @@ -2221,13 +2279,18 @@ def write(self, outfile, host_arglist, indent, const_mod,
self.call_list.declare_variables(outfile, indent+1, dummy=True)
if subpart_vars:
outfile.write('\n! Local Variables', indent+1)
# end if
# Write out local variables
for key in subpart_vars:
var = subpart_vars[key][0]
spdict = subpart_vars[key][1]
target = subpart_vars[key][2]
pointer= subpart_vars[key][3]
var.write_def(outfile, indent+1, spdict,
allocatable=(key in allocatable_var_set))
allocatable=(key in allocatable_var_set),
target=target, pointer=pointer)
# end for

outfile.write('', 0)
# Get error variable names
if self.run_env.use_error_obj:
Expand All @@ -2248,20 +2311,25 @@ def write(self, outfile, host_arglist, indent, const_mod,
raise CCPPError(errmsg.format(self.name))
# end if
# Initialize error variables
outfile.write("! Initialize ccpp error handling", 2)
outfile.write("{} = 0".format(errcode), 2)
outfile.write("{} = ''".format(errmsg), 2)
outfile.write("",2)
# end if
# Output threaded region check (except for run phase)
if not self.run_phase():
outfile.write("! Output threaded region check ",indent+1)
dustinswales marked this conversation as resolved.
Show resolved Hide resolved
Group.__thread_check.write(outfile, indent,
{'phase' : self.phase(),
'errcode' : errcode,
'errmsg' : errmsg})
# Check state machine
outfile.write("! Check state machine",indent+1)
self._phase_check_stmts.write(outfile, indent,
{'errcode' : errcode, 'errmsg' : errmsg,
'funcname' : self.name})
# Allocate local arrays
outfile.write('\n! Allocate local arrays', indent+1)
alloc_stmt = "allocate({}({}))"
for lname in allocatable_var_set:
var = subpart_vars[lname][0]
Expand All @@ -2271,6 +2339,7 @@ def write(self, outfile, host_arglist, indent, const_mod,
# end for
# Allocate suite vars
if allocate:
outfile.write('\n! Allocate suite_vars', indent+1)
for svar in suite_vars.variable_list():
dims = svar.get_dimensions()
if dims:
Expand All @@ -2296,9 +2365,18 @@ def write(self, outfile, host_arglist, indent, const_mod,
item.write(outfile, errcode, errmsg, indent + 1)
# end for
# Deallocate local arrays
if allocatable_var_set:
outfile.write('\n! Deallocate local arrays', indent+1)
# end if
for lname in allocatable_var_set:
outfile.write('deallocate({})'.format(lname), indent+1)
# end for
# Nullify local pointers
if pointer_var_set:
outfile.write('\n! Nullify local pointers', indent+1)
# end if
for lname in pointer_var_set:
outfile.write('nullify({})'.format(lname), indent+1)
# Deallocate suite vars
if deallocate:
for svar in suite_vars.variable_list():
Expand Down
7 changes: 3 additions & 4 deletions test/var_compatibility_test/effr_calc.F90
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,15 @@ module effr_calc
!! \htmlinclude arg_table_effr_calc_run.html
!!
subroutine effr_calc_run(ncol, nlev, effrr_in, effrg_in, effrl_inout, &
effri_out, effrs_inout, has_graupel, errmsg, errflg)
effri_out, effrs_inout, errmsg, errflg)

integer, intent(in) :: ncol
integer, intent(in) :: nlev
real(kind_phys), intent(in) :: effrr_in(:,:)
real(kind_phys), intent(in) :: effrg_in(:,:)
real(kind_phys), intent(in),optional :: effrg_in(:,:)
real(kind_phys), intent(inout) :: effrl_inout(:,:)
real(kind_phys), intent(out) :: effri_out(:,:)
real(8),intent(inout) :: effrs_inout(:,:)
logical, intent(in) :: has_graupel
character(len=512), intent(out) :: errmsg
integer, intent(out) :: errflg
!----------------------------------------------------------------
Expand All @@ -40,7 +39,7 @@ subroutine effr_calc_run(ncol, nlev, effrr_in, effrg_in, effrl_inout, &
errflg = 0

effrr_local = effrr_in
if (has_graupel) effrg_local = effrg_in
if (present(effrg_in)) effrg_local = effrg_in
effrl_inout = min(max(effrl_inout,re_qc_min),re_qc_max)
effri_out = re_qi_avg
effrs_inout = effrs_inout + 10.0 ! in micrometer
Expand Down
8 changes: 1 addition & 7 deletions test/var_compatibility_test/effr_calc.meta
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
type = real
kind = kind_phys
intent = in
optional = .true.
dustinswales marked this conversation as resolved.
Show resolved Hide resolved
[effrl_inout]
standard_name = effective_radius_of_stratiform_cloud_liquid_water_particle
long_name = effective radius of cloud liquid water particle in micrometer
Expand All @@ -59,13 +60,6 @@
kind = 8
intent = inout
top_at_one = .true.
[has_graupel]
standard_name = flag_indicating_cloud_microphysics_has_graupel
long_name = flag indicating that the cloud microphysics produces graupel
units = flag
dimensions = ()
type = logical
intent = in
[ errmsg ]
standard_name = ccpp_error_message
long_name = Error message for error handling in CCPP
Expand Down
Loading