NCAR / ccpp-framework

Common Community Physics Package (CCPP)
http://www.dtcenter.org/community-code/common-community-physics-package-ccpp/
Other
26 stars 64 forks source link

Tools to debug auto-generated code #294

Closed climbfuji closed 4 years ago

climbfuji commented 4 years ago

We need to be able to debug auto-generated code correctly.

The Intel compiler (and possibly others) do not detect out-of-bounds errors if they are inside argument lists passed to a subroutine. We therefore need to add logic that enables the compiler to check for bounds (or do the check ourselves), check if an array is allocated etc.

This is further complicated by the presence of arrays that may or may not be allocated, depending on some condition. Example: In the UFS, 3-dim. diagnostic tracer arrays are only allocated if ldiag3d is true.

An idea here is to have a new metadata attribute that enables debug checks, which by default is true:

debug_check = .true.

but can contain a logical expression in standard names, for the example of a 3-dim. diagnostic tracer array:

debug_check = flag_diagnostics_3D

or, for the example of an array that is only allocated if an integer is set to a certain value:

debug_check = (flag_for_microphysics_scheme == flag_for_thompson_microphysics_scheme)

or a combination, for example:

debug_check = (flag_for_microphysics_scheme == flag_for_thompson_microphysics_scheme .and. flag_for_aerosol_physics)
climbfuji commented 4 years ago

This feature was implemented in a broader context for the gsd/develop branch in https://github.com/NOAA-GSD/ccpp-framework/pull/4. The new attribute is (temporarily) called active, since it is not only required for debug checks, but also for handling blocked data structures and performing unit conversions (skip both if the variable is not active). The syntax is the same as described above.

For example, the metadata in GFS_typedefs.meta has the following conditions for some of the tracer variables:

...
[qgrs(:,:,index_for_water_friendly_aerosols)]
   standard_name = water_friendly_aerosol_number_concentration
   long_name = number concentration of water-friendly aerosols
   units = kg-1
   dimensions = (horizontal_dimension,vertical_dimension)
   active = (index_for_water_friendly_aerosols > 0)
   type = real
   kind = kind_phys
...
[nwfa2d]
   standard_name = tendency_of_water_friendly_aerosols_at_surface
   long_name = instantaneous water-friendly sfc aerosol source
   units = kg-1 s-1
   dimensions = (horizontal_dimension)
   active = (flag_for_microphysics_scheme == flag_for_thompson_microphysics_scheme .and. flag_for_aerosol_physics)
   type = real
   kind = kind_phys
...

The parser understands all possible Fortran syntax used in logical expressions:

FORTRAN_CONDITIONAL_REGEX_WORDS = [' ', '(', ')', '==', '/=', '<=', '>=', '<', '>', '.eqv.', '.neqv.',
                                    '.true.', '.false.', '.lt.', '.le.', '.eq.', '.ge.', '.gt.', '.ne.',
                                    '.not.', '.and.', '.or.', '.xor.']

FORTRAN_CONDITIONAL_REGEX = re.compile(r"[\w']+|" + "|".join([word.replace('(','\(').replace(')', '\)') for word in FORTRAN_CONDITIONAL_REGEX_WORDS]))

This is used as follows:

# If the variable is only active/used under certain conditions, add necessary variables
 # also record the conditional for later use in unit conversions / blocked data conversions.
 if var.active == 'T':
     conditional = '.true.'
 elif var.active == 'F':
     conditional = '.false.'
 else:
     # Convert conditional expression in standard_name format to local names known to the host model
     conditional = ''
     # Find all words in the conditional, for each of them look for a matching
     # standard name in the list of known variables
     items = FORTRAN_CONDITIONAL_REGEX.findall(var.active.lower())
     for item in items:
         if item in FORTRAN_CONDITIONAL_REGEX_WORDS:
             conditional += item
         else:
             # Detect integers, following Python's "easier to ask forgiveness than permission" mentality
             try:
                 int(item)
                 conditional += item
             except ValueError:
                 if not item in metadata_define.keys():
                     raise Exception("Variable {} used in conditional for {} not known to host model".format(
                                                                                    item, var_standard_name))
                 var2 = metadata_define[item][0]
                 conditional += var2.local_name
gold2718 commented 4 years ago

This feature request is superseded by #325.