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

Changed ddt_type metadata keyword to just type. #383

Merged
merged 2 commits into from
Jul 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
312 changes: 265 additions & 47 deletions scripts/ccpp_suite.py

Large diffs are not rendered by default.

49 changes: 32 additions & 17 deletions scripts/ddt_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,39 @@
class VarDDT(Var):
"""A class to store a variable that is a component of a DDT (at any
DDT nesting level).
<new_field> is the DDT component.
<var_ref> is a Var or VarDDT whose root originates in a model
dictionary.
The structure of the VarDDT object is:
The super class Var object is a copy of the model root Var.
The self._var_ref object is a VarDDT containing the top-level
field that leads to this component.
Thus, <new_field> (a Var) ends up at the end of a VarDDT chain.
"""

def __init__(self, new_field, var_ref, logger=None, recur=False):
self._field = None
"""Initialize a new VarDDT object.
<new_field> is the DDT component.
<var_ref> is a Var or VarDDT whose root originates in a model
dictionary.
The structure of the VarDDT object is:
The super class Var object is a copy of the model root Var.
The <var_ref object is a VarDDT containing the top-level
field that leads to this component.
Thus, <new_field> (a Var) ends up at the end of a VarDDT chain.
"""
self.__field = None
# Grab the info from the root of <var_ref>
source = var_ref.source
super(VarDDT, self).__init__(var_ref, source, context=source.context,
logger=logger)
# Find the correct place for <new_field>
if isinstance(var_ref, Var):
self._field = new_field
# We are at a top level DDT var, set our field
self.__field = new_field
else:
self._field = VarDDT(new_field, var_ref.field,
# Recurse to find correct (tail) location for <new_field>
self.__field = VarDDT(new_field, var_ref.field,
logger=logger, recur=True)
# End if
if (not recur) and (logger is not None):
logger.debug('Adding DDT field, {}'.format(self))
# End if

def is_ddt(self):
'''Return True iff <self> is a DDT type.'''
"""Return True iff <self> is a DDT type."""
return True

def get_parent_prop(self, name):
Expand All @@ -65,6 +69,17 @@ def get_prop_value(self, name):
# End if
return pvalue

def intrinsic_elements(self, check_dict=None):
"""Return the Var intrinsic elements for the leaf Var object.
See Var.intrinsic_elem for details
"""
if self.field is None:
pvalue = super(VarDDT, self).intrinsic_elements(check_dict=check_dict)
else:
pvalue = self.field.intrinsic_elements(check_dict=check_dict)
# End if
return pvalue

def clone(self, subst_dict, source_name=None, source_type=None,
context=None):
"""Create a clone of this VarDDT object's leaf Var with properties
Expand Down Expand Up @@ -100,9 +115,9 @@ def call_string(self, var_dict, loop_vars=None):
return call_str

def write_def(self, outfile, indent, ddict, allocatable=False, dummy=False):
'''Write the definition line for this DDT.
"""Write the definition line for this DDT.
The type of this declaration is the type of the Var at the
end of the chain of references.'''
end of the chain of references."""
if self.field is None:
super(VarDDT, self).write_def(outfile, indent, ddict,
allocatable=allocatable, dummy=dummy)
Expand Down Expand Up @@ -135,7 +150,7 @@ def __var_rep(var, prefix=""):
return lstr

def __repr__(self):
'''Print representation for VarDDT objects'''
"""Print representation for VarDDT objects"""
# Note, recursion would be messy because of formatting issues
lstr = ""
sep = ""
Expand All @@ -153,7 +168,7 @@ def __repr__(self):
return "<VarDDT {}>".format(lstr)

def __str__(self):
'''Print string for VarDDT objects'''
"""Print string for VarDDT objects"""
return self.__repr__()

@property
Expand All @@ -164,7 +179,7 @@ def var(self):
@property
def field(self):
"Return this objects field object, or None"
return self._field
return self.__field

###############################################################################
class DDTLibrary(dict):
Expand Down
3 changes: 2 additions & 1 deletion scripts/fortran_tools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""Public API for the fortran_parser library
"""
import os
from __future__ import absolute_import
import sys
import os.path
sys.path.insert(0, os.path.dirname(__file__))

# pylint: disable=wrong-import-position
Expand Down
5 changes: 4 additions & 1 deletion scripts/metadata_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
from parse_tools import ParseInternalError, ParseSyntaxError, CCPPError
from parse_tools import FORTRAN_ID, FORTRAN_SCALAR_REF, FORTRAN_SCALAR_REF_RE
from parse_tools import check_fortran_ref, check_fortran_id
from parse_tools import check_fortran_intrinsic
from parse_tools import register_fortran_ddt_name, unique_standard_name

########################################################################
Expand Down Expand Up @@ -843,9 +844,11 @@ def parse_variable(self, curr_line, known_ddts):
for prop in properties:
pname = prop[0].strip().lower()
pval_str = prop[1].strip()
if pname == 'ddt_type':
if ((pname == 'type') and
(not check_fortran_intrinsic(pval_str, error=False))):
if pval_str in known_ddts:
pval = pval_str
pname = 'ddt_type'
else:
errmsg = "Unknown DDT type, {}".format(pval_str)
self.__pobj.add_syntax_err(errmsg)
Expand Down
105 changes: 90 additions & 15 deletions scripts/metavar.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,14 +567,19 @@ class Var(object):
# All constituent props are optional so no check

def __init__(self, prop_dict, source, context=None,
invalid_ok=False, logger=None):
invalid_ok=False, logger=None, clone_source=None):
"""Initialize a new Var object.
NB: invalid_ok=True is dangerous because it allows creation
of a Var object with invalid properties.
In order to prevent silent failures, invalid_ok requires a logger
in order to take effect.
if <prop_dict> is really a Var object, use that object's prop_dict."""
self._parent_var = None # for array references
NB: <invalid_ok>=True is dangerous because it allows creation
of a Var object with invalid properties.
In order to prevent silent failures, <invalid_ok> requires a logger
(passed through the <logger> input) in order to take effect.
If <prop_dict> is really a Var object, use that object's prop_dict.
If this Var object is a clone, record the original Var object
for reference
"""
self.__parent_var = None # for array references
self.__children = list() # This Var's array references
self.__clone_source = clone_source
if isinstance(prop_dict, Var):
prop_dict = prop_dict.copy_prop_dict()
# end if
Expand Down Expand Up @@ -920,7 +925,7 @@ def clone(self, subst_dict=None, remove_intent=False,
# end if
psource = ParseSource(source_name, source_type, context)

return Var(cprop_dict, psource)
return Var(cprop_dict, psource, clone_source=self)

def get_prop_value(self, name):
"""Return the value of key, <name> if <name> is in this variable's
Expand Down Expand Up @@ -1186,6 +1191,56 @@ def array_ref(self, local_name=None):
match = FORTRAN_SCALAR_REF_RE.match(local_name)
return match

def intrinsic_elements(self, check_dict=None):
"""Return a list of the standard names of this Var object's 'leaf'
intrinsic elements or this Var object's standard name if it is an
intrinsic 'leaf' variable.
If this Var object cannot be reduced to one or more intrinsic 'leaf'
variables (e.g., a DDT Var with no named elements), return None.
A 'leaf' intrinsic Var is a Var of intrinsic Fortran type which has
no children. If a Var has children, those children will be searched
to find leaves. If a Var is a DDT, its named elements are searched.
If <check_dict> is not None, it is checked for children if none are
found in this variable (via finding a variable in <check_dict> with
the same standard name).
Currently, an array of DDTs is not processed (return None) since
Fortran does not support a way to reference those elements.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I am not sure of the implication of this, hope we can clarify at the ccpp-framework meeting.

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 guess it depends on whether or not the NOAA models or the SCM will ever need this feature. We generate code to make sure that all input variables in the active physics suite have been initialized. To do this, we need to be able to look into arrays of fields or into DDTs to find fields that could be initialized (2D or 3D physical fields). Is this something you will likely need?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I am not sure. Maybe we should look at some specific examples of how the UFS works at the moment in our next developer meeting (or in a separate meeting) to see if this is already an issue or not.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Okay. You can prepare by answering the question:
At run time, does / will the UFS need to query the standard names used by a suite?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think so. We have never used the standard names at run time (after switching to the static build 2 years ago), only at build time.

"""
if self.is_ddt():
element_names = None
raise ValueError("shouldn't happen?")
# To Do, find and process named elements of DDT
else:
children = self.children()
if (not children) and check_dict:
stdname = self.get_prop_value("standard_name")
pvar = check_dict.find_variable(standard_name=stdname,
any_scope=True)
if pvar:
children = pvar.children()
# end if
# end if
if children:
element_names = list()
for child in children:
child_elements = child.intrinsic_elements()
if isinstance(child_elements, str):
child_elements = [child_elements]
# end if
if child_elements:
for elem in child_elements:
if elem:
element_names.append(elem)
# end if
# end for
# end if
# end for
else:
element_names = self.get_prop_value('standard_name')
# end if
# end if
return element_names

@classmethod
def constituent_property_names(cls):
"""Return a list of the names of constituent properties"""
Expand All @@ -1194,24 +1249,44 @@ def constituent_property_names(cls):
@property
def parent(self):
"""Return this variable's parent variable (or None)"""
return self._parent_var
return self.__parent_var

@parent.setter
def parent(self, parent_var):
"""Set this variable's parent if not already set"""
if self._parent_var is not None:
if self.__parent_var is not None:
emsg = 'Attempting to set parent for {} but parent already set'
lname = self.get_prop_value('local_name')
raise ParseInternalError(emsg.format(lname))
# end if
if isinstance(parent_var, Var):
self._parent_var = parent_var
self.__parent_var = parent_var
parent_var._add_child(self)
else:
emsg = 'Attempting to set parent for {}, bad parent type, {}'
lname = self.get_prop_value('local_name')
raise ParseInternalError(emsg.format(lname, type(parent_var)))
# end if

def _add_child(self, cvar):
"""Add <cvar> as a child of this Var object"""
if cvar not in self.__children:
self.__children.append(cvar)
# end if

def children(self):
"""Return an iterator over this object's children or None if the
object has no children."""
children = self.__children
if not children:
pvar = self
while (not children) and pvar.__clone_source:
pvar = pvar.__clone_source
children = pvar.children()
# end while
# end if
return iter(children) if children else None

@property
def context(self):
"""Return this variable's parsed context"""
Expand Down Expand Up @@ -1290,7 +1365,7 @@ def has_vertical_dimension(self, dims=None):
return Var.find_vertical_dimension(vdims)[0]

def write_def(self, outfile, indent, wdict, allocatable=False,
dummy=False, add_intent=None):
dummy=False, add_intent=None, extra_space=0):
"""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 @@ -1368,14 +1443,14 @@ def write_def(self, outfile, indent, wdict, allocatable=False,
# end if
if self.is_ddt():
dstr = "type({kind}){cspc}{intent} :: {name}{dims} ! {sname}"
cspc = comma + ' '*(13 - len(kind))
cspc = comma + ' '*(extra_space + 13 - len(kind))
else:
if kind:
dstr = "{type}({kind}){cspc}{intent} :: {name}{dims} ! {sname}"
cspc = comma + ' '*(17 - len(vtype) - len(kind))
cspc = comma + ' '*(extra_space + 17 - len(vtype) - len(kind))
else:
dstr = "{type}{cspc}{intent} :: {name}{dims} ! {sname}"
cspc = comma + ' '*(19 - len(vtype))
cspc = comma + ' '*(extra_space + 19 - len(vtype))
# end if
# end if
outfile.write(dstr.format(type=vtype, kind=kind, intent=intent_str,
Expand Down
10 changes: 5 additions & 5 deletions scripts/parse_tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
'FORTRAN_ID',
'FORTRAN_SCALAR_REF',
'FORTRAN_SCALAR_REF_RE',
'initLog',
'init_log',
'ParseContext',
'ParseInternalError',
'ParseSource',
Expand All @@ -65,10 +65,10 @@
'read_xml_file',
'registered_fortran_ddt_name',
'reset_standard_name_counter',
'setLogLevel',
'setLogToFile',
'setLogToNull',
'setLogToStdout',
'set_log_level',
'set_log_to_file',
'set_log_to_null',
'set_log_to_stdout',
'unique_standard_name',
'validate_xml_file'
]
9 changes: 5 additions & 4 deletions scripts/parse_tools/parse_checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,11 +471,12 @@ def check_fortran_intrinsic(typestr, error=False):
>>> check_fortran_intrinsic("complex(kind=r8)")

"""
match = typestr.strip().lower() in FORTRAN_INTRINSIC_TYPES
if (not match) and (typestr.lower()[0:6] == 'double'):
chk_type = typestr.strip().lower()
match = chk_type in FORTRAN_INTRINSIC_TYPES
if (not match) and (chk_type[0:6] == 'double'):
# Special case for double precision
match = FORTRAN_DP_RE.match(typestr.strip()) is not None
# end if
match = FORTRAN_DP_RE.match(chk_type) is not None
# End if
if not match:
if error:
raise CCPPError("'{}' is not a valid Fortran type".format(typestr))
Expand Down
6 changes: 5 additions & 1 deletion scripts/parse_tools/parse_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ def last_line_num(self):
"""Return the last line parsed"""
return self.__line_end

def valid_line(self):
"""Return True if the current line is valid"""
return (self.line_num >= 0) and (self.line_num < self.__num_lines)

@property
def file_name(self):
"""Return this object's filename"""
Expand All @@ -68,7 +72,7 @@ def error_message(self):
def curr_line(self):
"""Return the current line (if valid) and the current line number.
If the current line is invalid, return None"""
valid_line = (self.line_num >= 0) and (self.line_num < self.__num_lines)
valid_line = self.valid_line()
_curr_line = None
_my_curr_lineno = self.line_num
if valid_line:
Expand Down
4 changes: 2 additions & 2 deletions test/advection_test/test_host.F90
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ logical function check_suite(test_suite)
end if
! Check the input variables
call ccpp_physics_suite_variables(test_suite%suite_name, test_list, &
errmsg, errflg, input_vars_in=.true., output_vars_in=.false.)
errmsg, errflg, input_vars=.true., output_vars=.false.)
if (errflg == 0) then
check = check_list(test_list, test_suite%suite_input_vars, &
'input variable names', suite_name=test_suite%suite_name)
Expand All @@ -154,7 +154,7 @@ logical function check_suite(test_suite)
end if
! Check the output variables
call ccpp_physics_suite_variables(test_suite%suite_name, test_list, &
errmsg, errflg, input_vars_in=.false., output_vars_in=.true.)
errmsg, errflg, input_vars=.false., output_vars=.true.)
if (errflg == 0) then
check = check_list(test_list, test_suite%suite_output_vars, &
'output variable names', suite_name=test_suite%suite_name)
Expand Down
2 changes: 1 addition & 1 deletion test/advection_test/test_host_mod.meta
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
[ phys_state ]
standard_name = physics_state_derived_type
long_name = Physics State DDT
ddt_type = physics_state
type = physics_state
dimensions = ()
[ num_model_times ]
standard_name = number_of_model_times
Expand Down
Loading