Subsections


7 Conventions

Code and documentation developed for the ESMF will follow conventions for both style and content. The goal is to create software with a consistent look and feel so that it is easier to read and maintain.

Because much ESMF documentation is generated directly from source code, code and documentation conventions are closely intertwined.


7.1 Docs: Code and Documentation Templates and Associated Scripts

ESMF maintains a set of templates for developing Fortran and C++ codes and their documentation. The documentation templates include requirements, design, and reference documents. These templates are bundled with the source distribution, in the following directory:

$(ESMF_DIR)/scripts/doc_templates

7.1.1 Documentation Generation Script

The do_newdoc script in the document templates package creates a new document directory and populates it with the appropriate files. The READMEs in the package describe the procedure for running the script.

7.1.2 Code Generation Scripts

A set of scripts is available that creates a set of new directories for either a C++ or Fortran class and populates them with the appropriate code and document files. The READMEs in the doc_templates package describe the procedures for doing this.

The following scripts are included in the doc_templates package:

do_newcomp
This script creates a new component. It makes a new component directory tree if one does not already exist. In addition, it adds the required template files in the component directory tree with the proper root file names.
do_newclass
This script creates a new class template in the current directory. It may be used for both C++ and Fortran 90 classes. For C++, it adds an include file (ESMC_class.h) in the component include directory, if one does not exist.


7.2 Docs: Documentation Guidelines and Conventions

7.2.1 Accessibility

Software documentation for the last public release is at:

  http://www.earthsystemmodeling.org -> Users -> Documentation

Software documentation for all releases is at:

  http://www.earthsystemmodeling.org -> Download -> View All Releases

Documents are available to developers for editing through the ESMF repository on the SourceForge site.

7.2.2 File format

Documents will be available in both web-browsable (e.g. html) and printer-friendly formats. They will be written in LATEX and based on a set of templates. LATEX was chosen because:

7.2.3 Typeface and Diagram Conventions

The following conventions for fonts and capitalization are used in this and other ESMF documents.

Style Meaning Example
italics documents ESMF Reference Manual
courier code fragments ESMF_TRUE
courier() ESMF method name ESMF_FieldGet()
boldface first definitions An address space is ...
boldface web links and tabs Developers tab on the website
Capitals ESMF class name DataMap

ESMF class names frequently coincide with words commonly used within the Earth system domain (field, grid, component, array, etc.) The convention we adopt in this manual is that if a word is used in the context of an ESMF class name it is capitalized, and if the word is used in a more general context it remains in lower case. We would write, for example, that an ESMF Field class represents a physical field.

Diagrams are drawn using the Unified Modeling Language (UML). UML is a visual tool that can illustrate the structure of classes, define relationships between classes, and describe sequences of actions. A reader interested in more detail can refer to a text such as The Unified Modeling Language Reference Manual. [#!uml!#]

7.2.4 Style Rules for LATEX

General style recommendations for LATEX documentation include the following:

  1. Limiting the source file text width to 80 characters;
  2. The use of proper indentation in sections and environments;
  3. The liberal use of blank lines to delimit blocks;
  4. Leaving as much of the typesetting as possible to LATEX itself (user-specified lengths, pagebreaks, etc. are discouraged);
  5. The use of the html package for convenient production of HTML and print formats;
  6. The use of the fontenc packages to permit use of LATEX special tokens in source;
  7. The use of a Type-I font (e.g the times or palatcm packages) to provide scaling PDF documents.

7.3 Docs: Performance Report Conventions

Measurements and analysis of ESMF performance are documented in performance reports. These reports are used to communicate information about framework performance, and are used to define and assess the success of optimization efforts. They are posted on the ESMF website under the Performance link. Reports typically contain the following sections: Objective, Description of the Benchmark, Results, Optimizations and Conclusions.

Performance reports should contain the following specific information:

  1. The tag or version of the framework used (not a moving tag, like HEAD or LAST_STABLE)
  2. Where to get the source code
  3. Machine location and type
  4. OS and compiler version
  5. Processor counts
  6. Complete description of grids and distributions or where to get that info
  7. How many iterations were performed for each data point
  8. How the timings were obtained from the iterations

7.4 Docs: Reference Manual Conventions

7.4.1 Description, Use and Examples, and Other Introductory Sections

  1. These sections shall contain thorough coverage of class behavior and usage. Any mention of private classes or implementation strategies shall be reserved for the sections called Design and Implementation Notes. Restrictions shall be listed in the Restrictions and Future Work sections.

  2. If a method name is referred to in the text it shall be in this fashion ESMF_<Class><MethodName>(). If a code fragment or flag is included in the text, we shall use the form ESMF_<FLAG_NAME> or surround the fragment with the LATEX verbatim command. The goal is to get all code fragments in an identifiable font.

  3. Class names shall be capitalized in these sections, and in the normal text font, e.g. Field. The reason is that Field is not an ESMF derived type or code fragment and writing the full type, e.g. ESMF_Field, everywhere makes long explanations less readable.

  4. Verbs like halo and regrid shall not be capitalized since they are not public classes. The exception is when such a verb is also a private class, and it is written about in that context. For example, when writing about Regrid as a private class in a Design and Implementation Notes section it is appropriate to capitalize it.

  5. Full class names shall be used everywhere (e.g., Layout will not be used as a shorthand for DELayout).

7.4.2 Examples Sections

A standard template for examples has not yet been created.

7.4.3 Flags and Options Sections

  1. All flags and options (hereafter referred to as options) shall be broken out into separate sections called <Class> Options. Options shall each have their own derived type, e.g. ESMF_Sync_Flag, and valid values will be all capitals with underscore separators, e.g. ESMF_SYNC_NONBLOCKING.

  2. When possible, call options <Class>Type or <Something>Flag and name the argument in the argument list the same thing. For example, the option ESMF_Sync_Flag has associated arguments called syncflag.

  3. The purpose of options shall be fully described along with all valid values in the options sections of the document. See the standard format in Calendar Options or Grid Options.

  4. In the sections describing the usage and values for options, only use the full class name, e.g. ESMF_Field, method names such as ESMF_FieldWrite, or particular argument names, such as syncflag. Do not use the more informal form of the short class name in a regular font, e.g. Field. The rationale is that here we are carefully specifying interface behavior and are referring only to particular ESMF types, values, and methods. It is a pain in the neck to be that rigorous in the longer text sections (like Description), but it is helpful to be precise for the API specification.

7.4.4 Class API Sections

  1. Public class methods shall be capitalized by name within a file. Generally private methods are listed at the end of the file. Especially for classes with many methods, this makes locating a particular method somewhat easier.

  2. All public methods shall be fully documented in the Reference Manual. Private methods without interfaces shall not appear at all. Private methods that are overloaded with public interface shall follow a standard format that clearly indicates that the methods are overloaded; see ESMF_FieldCreate() for an example.

  3. The Reference Manual shall include for each class a thorough specification of behavior, including any default values or behaviors. Description of options can reference <Class> Options sections. All descriptions should contain complete sentences with periods, e.g. "This method sets invalid values for an ESMF_FieldBundle object."

  4. The brief one-line description that follows the method name shall begin with a capital and have no period at the end.

  5. All methods shall conform to method naming conventions.

  6. Functions shall have their data type and return variable name declared on a separate line after the function statement, and prior to the argument list declarations. The data type shall not appear in the function statement. The optional result clause should also not be used.

  7. All arguments shall conform to variable naming conventions for capitalization and standard use of terms such as count, dim, etc. The conventions shall be used even if names become fairly long. All class names will be used in full, e.g. DELayout will not be used instead of DELayout. Cryptic abbreviations like btype shall not be used - use bundleType instead.

  8. As with descriptions of flags and options: in the sections describing methods, only use the full class name, e.g. ESMF_Field, method names such as ESMF_FieldWrite, or particular argument names, such as syncflag. Do not use the more informal short form in regular font, e.g. Field.

7.5 Code: Method Conventions

7.5.1 Standard Method Names

ESMF defines a set of standard methods and interface rules that hold across the entire API. These are:

7.5.2 Use of *Set and *Get

In general, Set and Get calls with optional arguments for each possible attribute to be processed shall be used instead of individual Set and Get calls. The goals are a smaller, clearer interface and easier extensibility. An exception is when the Set or Get does not translate to a simple name/value pair, for instance when one element of an array of internal attributes must be retrieved.

7.5.3 Use of Is* and Has*

Methods names that indicate the presence of some attribute or quality shall use the Has* form. Methods that indicate a condition shall use the Is* form; for example, ESMF_DELayoutIsLocal. In Fortran, these should return logical data type results. The goals are consistency and clarity.

7.5.4 Functions vs. Subroutines

Most Fortran calls in the ESMF are subroutines, with any returned values passed through the interface. For the sake of convenience, some ESMF calls are written as functions.

7.5.5 Source and Destination Ordering

In ESMF we shall follow a source first, then destination convention when ordering arguments.

7.6 Code: Argument Conventions

The naming conventions listed here apply to arguments that appear in ESMF public interfaces. It is nice if internal arguments also adhere to these conventions, but not enforced as it is as the interface.

7.6.1 Standard Variable Names

The following table lists a set of standard terms that we use in ESMF.

Cell grid cell
Coord coordinate
Count count
Dim dimension, used for grids
Dst destination
List indicates that the quantity is an array
Local indicates that the quantity is associated with a PET or DE
Per per item
Rank data array dimension
Send send
Src source
Recv receive

These are used in combination to create argument and variable names. For example, localCellCountPerDimList is an array of counts, per dimension, of the number of local grid cells.

7.6.2 Use of Is* and Has*

Variable names that indicate the presence of some attribute or quality shall use the Has* form; e.g., a variable such as hasGrid. Variables that indicate a condition shall use the Is* form. In Fortran, these should return logical data type results. The goals are consistency and clarity.

7.6.3 Variable Capitalization

For method arguments that are multi-word, the first word is lower case and subsequent words begin with upper case. ESMF class names (including typed flags) are an exception. When multi-word class names appear in argument lists, all letters after the first are lower case. The first letter is lower case if the class is the first word in the argument and upper case otherwise. For example, in an argument list the DELayout class name may appear as delayout or srcDelayout.

7.6.4 Variables Associated with Options

Variables associated with flags or option shall have the same name as the option (as long as it is not too awkward); for example, an option ESMF_Sync_Flag has an associated argument syncflag.

7.6.5 Variables Having Logical Data Type

Variables associated with flags or other boolean values shall use the Fortran logical data type in the public ESMF API. The ESMF_Logical derived type shall only be used in Fortran code internally, for example when passing logical values between Fortran and C++ code.

7.6.6 Arguments which are Arrays

Arguments which are arrays shall use the Fortran assumed-shape passing style. This allows the procedure to use intrinsic functions such as size, lbound, and ubound within the method to determine array dimension sizes. Also, ESMF convention is to place the array specification following the variable name, rather than by using the dimension attribute or a separate dimension statement. For example:
    type(ESMF_Field), intent(in) :: fieldList(:)

7.6.7 Arguments which are Pointers

Arguments which are pointers may point to either scalar or array targets. Pointers are typically used when an object must be allocated within the method and returned to the caller. A second use is when the original array bounds must be maintained. No intent attributes are currently used with pointers because they are not part of Fortran-90 or Fortran-95.

7.7 Code: File Rules

7.7.1 Version Identification

The first line of every file in ESMF must be its CVS version identification. For example, each LATEX file must begin:
% $Id$ 

In addition, for source code, the CVS identifier must have the ability to be written to a variable for output to a configuration log. Therefore each F90 module must contain a declaration:

character(len=80), private :: version = '$Id$'

7.7.2 License and Copyright Information

Source and documentation files must contain the ESMF license and copyright header:

! Earth System Modeling Framework
! Copyright (c) 2002-2023, University Corporation for Atmospheric Research,
! Massachusetts Institute of Technology, Geophysical Fluid Dynamics
! Laboratory, University of Michigan, National Centers for Environmental
! Prediction, Los Alamos National Laboratory, Argonne National Laboratory,
! NASA Goddard Space Flight Center.
! Licensed under the University of Illinois-NCSA License.

7.7.3 TODO: Reminder

To identify code sections that need additional work or other pending activity that must be done before a release, the code will be marked with a TODO identifier.
example:
! TODO: add support for other data types (CMD 4/01).
This will allow developers to 'grep' source files before software releases.

7.7.4 FILENAME Macros

Source code files should define the /tt ESMF_FILENAME or /tt ESMC_FILENAME macros to ensure that the filename is correctly displayed from the ESMF logging. This can be done using the following code:

#define ESMC_FILENAME "./src/Infrastructure/Attribute/src/ESMCI_Attribute.C"

with the appropriate filename. Note that the path is relative to the ESMF root directory.

7.8 Code: Style Rules for Source Code

General style recommendations for source code include the following:

  1. Limiting the text width to 80 characters;
  2. Each class implementation will be in a single file;
  3. The use of proper indentation of loops and blocks;
  4. The liberal use of blank lines to delimit code blocks;
  5. The use of comment lines of dashes or dots to delimit procedures;
  6. The use of useful descriptive names for physically meaningful variables; short conventional names for iterators (e.g (i,j,k) for spatial grid indices);
  7. The use of short comments on the same line to identify variables (i.e using ! or //); longer comments in well-delineated blocks to describe what a portion of code is doing;
  8. Compact code units: long procedures should be split up if possible. 200 lines is a rule-of-thumb procedure length limit.
  9. Use of global variables or public class variables should be kept to a minimum to increase encapsulation and ensure modular code.

7.9 Code: Error Handling Conventions

7.9.1 Objectives

The objectives of these conventions are:

  1. to produce appropriate error messages when users try to call a method or branch in the ESMF software that is incomplete;
  2. to produce appropriate error messages for methods that have been implemented and reviewed, and to identify these methods clearly in the ESMF source code;
  3. to create defaults that enable developers to generate appropriate error messages throughout the development process, with a minimum of effort;
  4. to flexibly move between a mode where incomplete code is treated as an error (the user perspective) to a mode where it is not (the developer perspective, when using stubs for code construction).

7.9.2 Approach

ESMF methods are classified into two categories: pre-review, and post-review. As of the version 3.0.1 release, *all* methods are pre-review.

Reviews are expected to begin once conventions for framework behavior are sufficiently well-defined. To pass review, methods must have their basic functionality fully implemented. If any unimplemented options are present, they must be documented and must generate appropriate errors in order for the method to pass review.

A pre-review method has its return code (rc) initialized to the value ESMF_RC_NOT_IMPL (not implemented). Possible sources of specific errors within the method, such as subroutine calls, generate local return codes (localrc).

The localrc values are evaluated using the function ESMF_LogMsgFoundError(), which takes both the localrc and the rc as input arguments. If the localrc does not indicate an error, rc is output from ESMF_LogMsgFoundError() with the value it had going into the call, and the value of the function is .false.. If localrc does indicate an error, rc is set to the localrc value before being output from ESMF_LogMsgFoundError(), and the value of the function is .true..

The convention is to place ESMF_LogMsgFoundError() into a conditional statement that causes a return from the method, with the correctly set rc, when an error is found in localrc, e.g.:

if (ESMF_LogFoundError(localrc, <other args ...>, rc)) return
Use of the ESMF_LogMsgFoundError() function is further described in the ESMF Reference Manual.

Note that if no error is found, rc retains its ESMF_RC_NOT_IMPL status throughout the method. The developer must explicitly set the rc to ESMF_SUCCESS just before returning from any branch that is believed to be successful.

The default rc value thus becomes ESMF_RC_NOT_IMPL for any code that is not explicitly marked as reaching success by the developer. Stub methods and incomplete branches can be handled very naturally this way - the developer simply does not set rc=ESMF_SUCCESS before returning. There are two differences in the treatment of pre-review and post-review methods:

  1. Post-review methods have their rc initialized to the ESMF_RC_NOT_SET value. The rationale is that methods that are supposed to be implemented should not default to an error code that says that they are unimplemented. The difference in initialization also indicates a particular method's review status to someone browsing the source code.

  2. Any unimplemented or incomplete branch must have its rc value explicitly set by the developer to ESMF_RC_NOT_IMPL before returning. This is to ensure that the behavior of the method is communicated as accurately as possible to the user via its return codes.

These conventions achieve the first three objectives above, namely producing appropriate error messages throughout a method's life cycle, with minimal developer effort.

7.9.3 Error Masking

Under normal use - what a user will encounter by default - ESMF_RC_NOT_IMPL is treated as an error. However, through a special LogErr setting a developer who wishes to use stub methods and prototyping during code construction can equate ESMF_RC_NOT_IMPL with ESMF_SUCCESS. This achieves the fourth objective above, allowing for different user and developer modes of handling incomplete code. This is done by setting the errorMask argument in the ESMF_LogSet() call to ESMF_RC_NOT_IMPL, e.g.:

call ESMF_LogSet(errorMask = /ESMF_RC_NOT_IMPL/)

7.9.4 Example (pre-review method)

The following is an example of a pre-review method, sub(), that calls two subroutines internally, subsub1() and subsub2(). The subroutine sub() takes as input an integer argument that it can branch on and outputs a return code. Here branch==1 is fully implemented, branch==2 is incomplete, and other values of branch are not yet addressed. Several possible error scenarios are described following the code listing.

subroutine sub(branch, rc)
integer :: branch
integer, optional :: rc
integer :: localrc   ! local return code

if (present(rc)) rc=ESMF_RC_NOT_IMPL
! ...code...
if (branch==1) then
call subsub1(localrc)
if (ESMF_LogFoundError(localrc, &
  ESMF_ERR_PASSTHRU, &
  ESMF_CONTEXT, rc=rc)) then
  !   ... any necessary garbage collection ...
  return ! Return point 1
endif !   ...fully implemented code...
if (present(rc)) rc=ESMF_SUCCESS
elseif (branch==2) then call subsub2(localrc)
if (ESMF_LogFoundError(localrc, &
  ESMF_ERR_PASSTHRU, &
  ESMF_CONTEXT, rc=rc)) then
  !   ... any necessary garbage collection ...
  return ! Return point 2
endif
!   ...incomplete code...
end if

end subroutine

Note: This example is quite artificial, since simple branching like this would be better handled with a switch case statement that had a well-defined default. However, the control sequence shown better illustrates the sorts of errors that can occur in a real code with complex conditionals.

Possible scenarios:

  1. branch==1 and there is no error in subsub1(). In this case, rc retains the value of ESMF_RC_NOT_IMPL up to the point at which it is set to ESMF_SUCCESS, after which sub() ends.

  2. branch==1 and there is an error in subsub1(). The localrc returned by subsub1() will have the value of a specific error code, say ESMF_RC_DIV_ZERO. This will get passed into the evaluation ESMF_LogMsgFoundErr(), which will set rc to the localrc error value. Since the value of the evaluation expression is true, the method will return at Return point 1. It is important to perform any necessary garbage collection before returning to avoid memory leaks.

  3. branch==2 and there is no error in subsub2(). Here the value of rc is still ESMF_RC_NOT_IMPL when it is input into the ESMF_LogMsgFoundErr() call following subsub2(). Since the code in this branch is not complete and rc is never set to ESMF_SUCCESS, the rc value is still ESMF_RC_NOT_IMPL at the end of sub(). Note that to allow the default to work properly, rc should not be set to ESMF_SUCCESS right before the end of the method!

  4. branch==2 and there is an error in subsub2(). Much like scenario 2, the localrc returned by subsub2() will have the value of a specific error code. The localrc will get passed into ESMF_LogMsgFoundErr(), which will set rc to the localrc error value, and then cause a return at Return point 2.

  5. any value where branch $/=1$ and branch $/= 2$ (e.g. branch==0). Here there is a “hole” in the code, since the behavior of branch==0 is not yet addressed. However, with this approach the rc value remains ESMF_RC_NOT_IMPL when sub() ends, producing an appropriate error message for the user.

7.9.5 Example (post-review method)

This example is modeled on the pre-review example, but illustrates error handling for a post-review method. Here branch==1 is fully implemented, branch==2 is known to be incomplete, and other values of branch are cases that the developer and reviewers have missed. This is not what one would wish to see in reviewed code - ideally all cases should be implemented - but we cannot count on this. This example shows how deficiencies can most effectively be handled.

The return code rc is now initialized to ESMF_RC_NOT_SET. The developer has recognized that the branch==2 code is incomplete and rc is set explicitly to ESMF_RC_NOT_IMPL before returning.

subroutine sub(branch, rc)
integer :: branch
integer, optional :: rc
integer :: localrc   ! local return code

if (present(rc)) rc=ESMF_RC_NOT_SET
! ...code...
if (branch==1) then
call subsub1(localrc)
if (ESMF_LogFoundError(localrc, &
  ESMF_ERR_PASSTHRU, &
  ESMF_CONTEXT, rc=rc)) then
  !   ... any necessary garbage collection ...      return ! Return point 1
endif
!   ...fully implemented code...
if (present(rc)) rc=ESMF_SUCCESS
elseif (branch==2) then call subsub2(localrc)
if (ESMF_LogFoundError(localrc, &
  ESMF_ERR_PASSTHRU, &
  ESMF_CONTEXT, rc=rc)) then
  !   ... any necessary garbage collection ...
  return ! Return point 2
endif
!   ...incomplete code...
if (present(rc)) rc=ESMF_RC_NOT_IMPL       end if
end subroutine
{

Possible scenarios:

branch==1. Here the behavior is essentially the same as in scenarios 1 and 2. When there is an error in subsub1(), the returned rc from sub() has the error code subsub1() generated. When there is not an error in subsub1(), the returned rc from sub() is ESMF_SUCCESS.

branch==2 and there is an error in subsub2(). The behavior here is analogous to scenario 3. The returned rc from sub() has the error code subsub2() generated.

branch==2 and there is no error in subsub2(). The value of rc remains ESMF_RC_NOT_SET up to the point at which it is explicitly set to ESMF_RC_NOT_IMPL, and sub() ends. Method documentation is expected to explain the gap in functionality.

any value where branch $/=1$ and branch $/= 2$ (e.g. branch==0). Here rc is not explicitly set to a value before the branch returns, and so sub() returns with the default rc value of ESMF_RC_NOT_SET. The user can tell from this error code that the developer did not consider this either unimplemented code or successful code, and that there is an unexpected “hole” in the method's functionality.

7.9.6 Memory Allocation Checking

In Fortran code, the allocate and deallocate statements should always have their success or failure checked by using stat= and checking the value it returns. Since this value is defined by the Fortran standard, and is not an ESMF return code, the alternative checking calls are ESMF_LogFoundAllocError() and ESMF_LogFoundDeAllocError(). To further distinguish the difference between a Fortran status return and a ESMF return code, the local error variable should be named “memstat” rather than “localrc”.

  real, allocatable :: big_array(:,:)
  integer :: memstat
  ! ...
  allocate (big_array(m,n), stat=memstat)
  if (ESMF_LogFoundAllocError(memstat, &
    ESMF_ERR_PASSTHRU, &
    ESMF_CONTEXT, rc=rc)) then
    ! ... any necessary clean up
    return
  end if
  ! ... use big_array for a while
  deallocate (big_array, stat=memstat)
  if (ESMF_LogFoundDeAllocError(memstat, &
    ESMF_ERR_PASSTHRU, &
    ESMF_CONTEXT, rc=rc)) then
    ! ... any necessary clean up
    return
  end if

In C++ code, where the new operator is used, allocation errors can be caught by wrapping the allocation code inside a try block to catch any exceptions. Multiple allocations may be wrapped inside a single block in order to reduce the amount of checking code.

#include "ESMCI.h"
//...
void alloc_func (int m, int n, int *rc) {
#undef  ESMC_METHOD
#define ESMC_METHOD alloc_func
  double *array1, *array2;
  try {
    array1 = new double[m];
    array2 = new double[n];
  }
  catch (std::bad_alloc) {
    // ... any necessary clean up
    ESMC_LogDefault.MsgAllocError("allocating array1 and array2",
        ESMC_CONTEXT, rc);
    return;
  }
  // ... use array1 and array2 for a while
  delete array2;
  delete array1;
}

In code which uses malloc() and free(), the return value from malloc() may be tested against NULL to determine if an error occurred. No checking is possible with free(). In new code, the C++ new operator and associated checking should be used.

7.10 Initialization Standardization Instructions

7.10.1 Overview

Currently not all Fortran compilers support the automatic initialization of class components. However, in the ESMF system this initialization is used for two tasks. The first is the initialization of shallow class components which need to be set before they're used (e.g. flags). The second is the detection of invalid (e.g. not yet created) deep class variables. In order to handle these tasks in the absence of compiler initialization a software based solution has been developed.

The software solution is based on the improbability of an uninitialized variable containing a specific value. For example, given a 64 bit integer variable x, the probability of x containing the value 10 are approximately 1 in $10^{19}$. This of course assumes that the value in an uninitialized variable is a uniformly distributed random variable, which is not necessarily true. However, the small probability of a given value occurring is not an unreasonable assumption given that the value is not pathological (e.g. 0 or binary 111111..). The probability can also be made as small as is necessary by using larger precision types.

Given the improbability of a specific value occurring in an uninitialized variable, the task of initializing a shallow class is implemented as follows. First, a non-pathological value, ESMF_INIT_DEFINED, is chosen. Next, an integer component, isInit, is added to the shallow class. Finally, for every routine taking the shallow class as an input variable, the component isInit is checked against ESMF_INIT_DEFINED. If they don't match, then the variable containing isInit is initialized, including setting isInit to ESMF_INIT_DEFINED. If they do match, then with extremely high probability the variable has already been initialized and nothing need be done. This algorithm helps to ensure that the shallow class is initialized before being used in the system.

The task of detecting an invalid deep class is implemented as follows. As before, we chose a value ESMF_INIT_CREATED, and add the isInit component to the class definition. Next, inside the constructors for the class isInit is set to ESMF_INIT_CREATED, and inside the destructors for the class isInit is set to something other than ESMF_INIT_CREATED. Finally, for every routine taking the deep class as an input variable, the component isInit is checked against ESMF_INIT_CREATED. If they match, then nothing happens because with extremely high probability the variable has been though the constructor and thus is valid. If they don't match, then the variable is invalid and an error occurs. This allows invalid deep class variables to be detected before being used in the system.

In order to make the process of adding these non-compiler based initialization tasks easier, a set of macros has been defined. The following is a description of the procedures to use them.

7.10.2 Instructions

These changes should be added to all new F90 code in both the source (/src) and interface (/interface) subdirectories of the classes.

When adding code as a part of this task remember to use the standard ESMF coding conventions. In particular, when adding new routines remember to use the standard ESMF protex style (BOP/EOP) subroutine description, like those that appear in every other ESMF subroutine. For Validate use the BOP/EOP tags for ESMF_TYPEGetInit and ESMF_TYPEInit use the internal BOPI/EOPI tags.

Some F90 source files are autogenerated. In this case, modify the macro file which is used to create the autogenerated file. The macro file is usually named something like ESMF_TypeMacos.h. When modifying a macro file remember to end each line with: @ To recognize when an F90 file is autogenerated they usually have the line: <Created by maco - do not edit directly> close to the beginning. There will also usually be a .cpp file with the same name as the .F90 file.

For the purposes of initialization standardization the classes in the system have been divided into three types: shallow, deep, and parameter. Shallow classes have little if any allocation and can often be used right after declaration. An example of a shallow class is ESMF_ArraySpec. Deep classes on the other hand have a lot of allocated data and need to be constructed before being used. An example of a deep class is ESMF_State. Parameter classes are those which are used to add type checking to a set of parameter values. This type of class is typically just a single integer wrapped in a Fortran type. An example of a parameter class is ESMF_GridType. Both shallow and deep classes are effected by the standardization, but parameter classes are left unchanged. See section [*] for a list of ESMF classes and their classification.

The instructions for the standardization have been broken up by what to do for each module, what to do for each type of class, and what to do for routines.

7.10.3 Module

When adding a new F90 module do the following:

  1. If the following line is not present in the use statement list, then add it (except in ESMF_LogErr.F90 and ESMF_UtilTypes.F90):
        use ESMF_InitMacrosMod
    

  2. If the following line is not present in the include statement list, then add it (except in ESMF_LogErr.F90):
        #include "ESMF.h"
    

7.10.4 Shallow Class

When adding a new shallow class definition perform the following steps:

  1. If the line:
        use ESMF_UtilTypesMod
    
    isn't in the list of used modules, then add it.

  2. Add the line ESMF_INIT_DECLARE to the type definition.
  3. Be sure the #ifndef ESMF_NO_INITIALIZERS construct is used around any initialization of type components. The declarations inside the #else to #endif half of the construct should be identical to the #ifndef half except without initialization.
  4. Create an initialization subroutine for the class. Given that the name of the class is ESMF_STYPE, the name of the subroutine should be ESMF_STYPEInit(). This subroutine should take a single parameter of class ESMF_STYPE and set all the variables that are initialized in the #ifndef ESMF_NO_INITIALIZERS branch of 3. Note, for pointers that are set using ptr->Null() in the type initialization, use nullify(t%ptr) in the ESMF_STYPEInit subroutine instead.
  5. Add the line ESMF_INIT_SET_DEFINED(S1) to the body of the new subroutine ESMF_STYPEInit() described in 4. S1 is the variable being initialized in ESMF_STYPEInit().
  6. Create an access subroutine for the new init component. The name of the subroutine should be ESMF_STYPEGetInit. It should have the following form:

        function ESMF_STYPEGetInit(s)
           type(ESMF_STYPE), intent(in), optional :: s
           ESMF_INIT_TYPE :: ESMF_STYPEGetInit
    
          if (present(s)) then
              ESMF_STYPEGetInit=ESMF_INIT_GET(s)
          else
              ESMF_STYPEGetInit=ESMF_INIT_DEFINED
          endif
    
        end function ESMF_STYPEGetInit
    

  7. Create an ESMF_STYPEValidate subroutine with the following form:

        ! Comments describing validate (see ESMF_StateValidate for an example)
        subroutine ESMF_STYPEValidate(s,rc)
           type(ESMF_STYPE), intent(inout) :: s
           integer, intent(out),optional :: rc
    
           ! check initialization status
           ESMF_INIT_CHECK_SET_SHALLOW(ESMF_STYPEGetInit,ESMF_STYPEInit,s)
           
           ! return success
           if (present(rc)) then
              rc=ESMF_SUCCESS
           endif
    
        end subroutine ESMF_STYPEValidate
    

  8. If the shallow class in question is public, then add the lines:
           public ESMF_STYPEInit
           public ESMF_STYPEGetInit
           public ESMF_STYPEValidate
    
    to the PUBLIC MEMBER FUNCTION section.

  9. Add the new class to the list in section [*].

Here is an example illustrating the whole procedure:

Starting with this shallow class definition:

   module ESMF_ExampleMod

       type ESMF_Shallow
           private
            ... other components ...
#ifndef ESMF_NO_INITIALIZERS
            integer :: num=0
            integer, pointer :: list(:) => Null()
#else
            integer :: num
            integer, pointer :: list(:)
#endif
       end type

! !PUBLIC TYPES:
      public ESMF_Shallow 
     
! !PUBLIC MEMBER FUNCTIONS:

       contains

       ... other routines ...

   end module ESMF_ExampleMod

The standardization procedure yields this:
(modified lines marked with *)

   module ESMF_ExampleMod
*       use ESMF_UtilTypesMod 
*       use ESMF_InitMacrosMod 

       type ESMF_Shallow
            private
            ... other components ...
#ifndef ESMF_NO_INITIALIZERS
            integer :: num=0
            integer, pointer :: list(:) => Null()
#else
            integer :: num
            integer, pointer :: list(:)
#endif
*            ESMF_INIT_DECLARE
       end type

! !PUBLIC TYPES:
      public ESMF_Shallow 

! !PUBLIC MEMBER FUNCTIONS:
*      public ESMF_ShallowInit
*      public ESMF_ShallowValidate
*      public ESMF_ShallowGetInit

       contains

*    function ESMF_ShallowGetInit(s)
*       type(ESMF_Shallow), intent(in), optional :: s
*       ESMF_INIT_TYPE :: ESMF_ShallowGetInit
*
*      if (present(s)) then
*          ESMF_ShallowGetInit=ESMF_INIT_GET(s)
*      else
*          ESMF_ShallowGetInit=ESMF_INIT_DEFINED
*      endif
*
*    end function ESMF_ShallowGetInit
*
*    subroutine ESMF_ShallowInit(s)
*       type(ESMF_Shallow) :: s
*    
*        s%num=0 
*        nullify(s%list)
*
*        ESMF_INIT_SET_DEFINED(s) 
*    end subroutine ESMF_ShallowInit
*
*    subroutine ESMF_ShallowValidate(s,rc)
*       type(ESMF_Shallow), intent(inout) :: s
*       integer, intent(out),optional :: rc
*
*       ! check initialization status
*       ESMF_INIT_CHECK_SET_SHALLOW(ESMF_ShallowGetInit,ESMF_ShallowInit,s)
*       
*       ! return success
*       if (present(rc)) then
*          rc=ESMF_SUCCESS
*       endif
*
*    end subroutine ESMF_ShallowValidate

       ... other routines ...

   end module ESMF_Shallow

7.10.5 Deep Class

When adding a new deep class definition perform the following steps:

  1. If the line:
          use ESMF_UtilTypesMod
    
    isn't in the list of used modules, then add it.
  2. Add the line ESMF_INIT_DECLARE to the type definition.
  3. Add the line ESMF_INIT_SET_CREATED(D1) to the TypeCreate() function. D1 is the deep class variable being created.
  4. Add the line ESMF_INIT_SET_DELETED(D1) to the TypeDestroy() subroutine. D1 is the deep class variable being destroyed.
  5. Create an access subroutine for the new init component. The name of the subroutine should be ESMF_DTYPEGetInit. It should have the following form:
        function ESMF_DTYPEGetInit(d)
           type(ESMF_DTYPE), intent(in),optional :: d
           ESMF_INIT_TYPE :: ESMF_DTYPEGetInit
    
          if (present(d)) then
              ESMF_DTYPEGetInit=ESMF_INIT_GET(d)
          else
              ESMF_DTYPEGetInit=ESMF_INIT_CREATED
          endif
    
        end function ESMF_DTYPEGetInit
    

  6. Given that the deep class is ESMF_DTYPE create a validate subroutine with the following form:
        ! Comments describing validate (see ESMF_StateValidate for an example)
        subroutine ESMF_DTYPEValidate(d1,rc)
           type(ESMF_DTYPE), intent(in) :: d1
           integer, intent(out),optional :: rc
    
           ! Check Init Status
           ESMF_INIT_CHECK_DEEP(ESMF_DTYPEGetInit,d1,rc)
    
           ! Add other checks here
    
           ! If all checks passed return success
           if (present(rc)) then
               rc=ESMF_SUCCESS
           endif
    
        end subroutine ESMF_DTYPEValidate
    

  7. If the deep class in question is public, then add the lines:
           public ESMF_DTYPEGetInit
           public ESMF_DTYPEValidate
    
    to the PUBLIC MEMBER FUNCTION section.

  8. Some deep classes are private and occasionally it will become necessary to access their internal components to set the initialization flag or get the this pointer in code which doesn't have access. The standard way to do this in ESMF is to write a public subroutine in the class module where the subroutine can have access to the classes' internal components.

    The following are the standard names for the subroutines which set a class as created, set a class as deleted, and set the this pointer, and get the this pointer:

    ESMF_DTYPESetInitCreated
    ESMF_DTYPESetInitDeleted
    ESMF_DTYPESetThis
    ESMF_DTYPEGetThis
    

  9. Add the new class to the list in section [*].

Here is an example illustrating the whole procedure:

Starting with this deep type definition:

   module ESMF_ExampleMod

       type ESMF_Deep
            private
            ... other components ...
       end type

! !PUBLIC TYPES:
      public ESMF_Deep

! !PUBLIC MEMBER FUNCTIONS:

       contains

        function ESMF_DeepCreate()
              type(ESMF_Deep) :: ESMF_DeepCreate

              ... other create code ...

        end function ESMF_DeepCreate

        subroutine ESMF_DeepDestroy(d)
              type(ESMF_Deep) :: d

              ... other create code ...

        end subroutine ESMF_DeepDestroy

        ... other routines ...

   end module ESMF_ExampleMod

The standardization procedure yields this:
(modified lines marked with *)

   module ESMF_ExampleMod
*       use ESMF_UtilTypesMod 
*       use ESMF_InitMacrosMod       

       type ESMF_Deep
           private
           ... other components ...
*           ESMF_INIT_DECLARE
       end type

! !PUBLIC TYPES:
      public ESMF_Deep

! !PUBLIC MEMBER FUNCTIONS:
*      public ESMF_DeepValidate     
*      public ESMF_DeepGetInit     

       contains

*    function ESMF_DeepGetInit(d)
*       type(ESMF_Deep), intent(in),optional :: d
*       ESMF_INIT_TYPE :: ESMF_DeepGetInit
*
*       if (present(d)) then
*          ESMF_DeepGetInit=ESMF_INIT_GET(d)
*      else
*          ESMF_DeepGetInit=ESMF_INIT_CREATED
*      endif
*
*    end function ESMF_DeepGetInit
*
*       subroutine ESMF_DeepValidate(d,rc)
*          type(ESMF_Deep), intent(in) :: d
*          integer, intent(out),optional :: rc
*
*          ! Check Init Status
*          ESMF_INIT_CHECK_DEEP(ESMF_DeepGetInit,d,rc)
*
*         ! Add other checks here
*
*         ! If all checks passed return success
*         if (present(rc)) then
*            rc=ESMF_SUCCESS
*         endif
*
*        end subroutine ESMF_DeepValidate

        function ESMF_DeepCreate()
              type(ESMF_Deep) :: ESMF_DeepCreate

              ... other create code ...

*              ESMF_INIT_SET_CREATED(ESMF_DeepCreate)

        end function ESMF_DeepCreate

        subroutine ESMF_DeepDestroy(d)
              type(ESMF_Deep) :: d

              ... other create code ...

*              ESMF_INIT_SET_DELETED(d)

        end subroutine ESMF_DeepDestroy

        ... other routines ...

   end module ESMF_ExampleMod

7.10.6 Parameter Class

When adding a new parameter class definition, don't add any initialization standardization code. However, do add the new class to the class list in section [*].

7.10.7 Subroutine

When adding a new subroutine or function (both referred to as 'routine' henceforth) perform the following steps:

  1. At the beginning of the routine, for each shallow class parameter S1 of type ESMF_STYPE. Add the line ESMF_INIT_CHECK_SHALLOW(ESMF_STYPEGetInit, ESMF_STYPEInit, S1). In addition, if the intent of S1 is intent(in), switch it to intent(inout) to allow it to be modified.

  2. At the beginning of each routine, for each deep class *input* parameter D1 of type ESMF_DTYPE, add the line ESMF_INIT_CHECK_DEEP(ESMF_DTYPEGetInit, D1, rc). Here rc is the return code variable for the routine.

The initialization macros in 1. and 2. should be added before any code which uses the types being checked.

When adding the check macros to code there are a couple of issues to keep in mind for compatibility with all compilers. First, don't break up the macro across lines (e.g. using &). Second, some compilers have a maximum line length. Occasionally, the Deep class check macro will expand to larger than this length, if you find that this is occurring with a particular line use the ESMF_INIT_CHECK_DEEP_SHORT macro instead. It takes exactly the same parameter list as the normal deep class check macro, but expands to a much shorter line.

Here is an example illustrating this procedure:

Starting with this routine:

   subroutine ESMF_EXAMPLE(s1,d1,s2,d2,d3,rc)
         type(ESMF_Shallow1), intent(in) :: s1
         type(ESMF_Shallow2), intent(out) :: s2
         type(ESMF_Deep1),intent(in) :: d1
         type(ESMF_Deep2),intent(inout) :: d2
         type(ESMF_Deep3),intent(out) :: d3
         integer :: rc
         .... local variable declarations ...

         ! initialize return code
         rc=ESMF_FAILURE

          ...... rest of subroutine code....

  end subroutine ESMF_Example

The standardization yields this:
(modified lines marked with *)

   subroutine ESMF_EXAMPLE(s1,d1,s2,d2,d3,rc)
*         type(ESMF_Shallow1), intent(inout) :: s1
         type(ESMF_Shallow2) :: s2
         type(ESMF_Deep1) :: d1
         type(ESMF_Deep2),intent(inout) :: d2
         type(ESMF_Deep3),intent(out) :: d3
         integer :: rc
         .... other local variable declarations ...

         ! initialize return code
         rc=ESMF_FAILURE

*         ! check variables
*         ESMF_INIT_CHECK_DEEP(ESMF_Deep1GetInit,d1,rc)
*         ESMF_INIT_CHECK_DEEP(ESMF_Deep2GetInit,d2,rc)
*
*         ESMF_INIT_CHECK_SET_SHALLOW(ESMF_Shallow1GetInit,ESMF_Shallow1Init,s1)
*         ESMF_INIT_CHECK_SET_SHALLOW(ESMF_Shallow2GetInit,ESMF_Shallow2Init,s2)

          ...... rest of subroutine code....

  end subroutine ESMF_Example


7.10.8 ESMF Class Types

CAUTION: The following lists are very much outdated!!!


Class Type Fortran ESMF
ESMF_Alarm Deep public public
ESMF_AlarmList_Flag Parameter public public
ESMF_Array Deep    
ESMF_ArrayBundle Deep    
ESMF_ArraySpec Shallow    
ESMF_Attribute Shallow    
ESMF_Base Deep    
ESMF_BaseTime Shallow    
ESMF_CWrap Deep    
ESMF_Calendar Deep    
ESMF_CalKind_Flag Parameter    
ESMF_Clock Deep    
ESMF_CommHandle Deep    
ESMF_CommTable Deep    
ESMF_CompClass Deep    
ESMF_CompType_Flag Parameter    
ESMF_Config Deep    
ESMF_ConfigAttrUsed Shallow    
ESMF_Context_Flag Parameter    
ESMF_CoordOrder Parameter    
ESMF_CplComp Deep    
ESMF_DELayout Deep    
ESMF_DataHolder Shallow    
ESMF_DataValue Ignore    
ESMF_Decomp_Flag Parameter    
ESMF_Direction_Flag Parameter    
ESMF_DistGrid Deep    
ESMF_Field Deep    
ESMF_FieldBundle Deep    
ESMF_FieldBundleDataMap Shallow    
ESMF_FieldBundleType Deep    
ESMF_FieldDataMap Shallow    
ESMF_FieldType Deep    
ESMF_Fraction Shallow    
ESMF_Grid Deep    
ESMF_GridComp Deep    
ESMF_GridStatus_Flag Parameter    
ESMF_Index_Flag Parameter    
ESMF_InterfaceInt Ignore    


Class Type Fortran ESMF
ESMF_LOGENTRY Shallow    
ESMF_LocalArray Deep    
ESMF_Log Shallow    
ESMF_LogKind_Flag Parameter    
ESMF_Logical Ignore    
ESMF_Mask Shallow    
ESMF_LogMsg_Flag Parameter    
ESMF_NeededFlag Parameter    
ESMF_ObjectID Shallow    
ESMF_Pin_Flag Parameter    
ESMF_PhysGrid Deep    
ESMF_Pointer Ignore    
ESMF_ReadyFlag Parameter    
ESMF_Reduce_Flag Parameter    
ESMF_Region_Flag Parameter    
ESMF_RegridMethod_Flag Parameter    
ESMF_RelLoc Parameter    
ESMF_ReqForRestartFlag Parameter    
ESMF_Route Deep    
ESMF_RouteHandle Deep    
ESMF_StaggerLoc Shallow    
ESMF_State Deep    
ESMF_StateClass Deep    
ESMF_StateItem Shallow    
ESMF_StateItem_Flag Parameter    
ESMF_StateIntent_Flag Parameter    
ESMF_Status Parameter    
ESMF_Sync_Flag Parameter    
ESMF_End_Flag Parameter    
ESMF_Time Shallow    
ESMF_TimeInterval Shallow    
ESMF_VM Deep    
ESMF_VMId Deep    
ESMF_VMPlan Deep    
ESMF_ValidFlag Parameter    

7.11 Code: Data Type Consistency Guidelines

The following tools and guidelines are in place in order to maintain the ESMF framework's portability and robustness, in light of the fact that neither the Fortran nor C++ standards require a fixed size for any given data type and/or kind. Please note that these guidelines pertain to the internal ESMF code, not to the user code that interfaces to it.

7.11.1 Use ESMF names for data types in C and ESMF data kinds in Fortran

7.11.2 Fortran

Any occurrence of Fortran data kind parameters within ESMF code must be specified with ESMF parameters only. e.g.

       ESMF_KIND_I4
       ESMF_KIND_I8
       ESMF_KIND_R4
       ESMF_KIND_R8
These are internally defined such that the size of the data they describe is the number at the end of the parameter. e.g. in

       integer(ESMF_KIND_I8) :: someInteger
someInteger is an 8 byte integer in any platform.

Kind of literal constants:

Only the ESMF kind parameters listed above are allowed when specifying the kind of literal Fortran constants. e.g.

     real(ESMF_KIND_R8) :: a,b
     a=b*0.1_ESMF_KIND_R8

In the example above 0.1_ESMF_KIND_R8 is a literal constant of kind ESMF_KIND_R8 and value 0.1.

This means that any other syntax, such as:

           a=b*0.1d0
           a=b*0.1_8
is NOT to be used.

ESMF_KIND_I, ESMF_KIND_R: Note that the ESMF_TypeKind_Flag parameters: ESMF_KIND_I and ESMF_KIND_R are defined for use in user code only. They are handles offered to the user for ESMF internal default kinds and not to be used internally in the ESMF framework.

Real Data:

  1. All real data in Fortran should be declared with an explicit ESMF defined kind parameter in the form:
                  real(ESMF_KIND_R<n>) :: RealVariable
    

    where $<n>$ is the size in bytes of the memory occupied by each real datum. Allowed values of n are those that correspond to supported type/kind data, e.g.

                  real (ESMF_KIND_R8) :: a,b
    
    declares a and b to be 8 byte reals

  2. Routines should be overloaded for all supported kinds(sizes) of real user data (e.g. in Array class routines).

  3. Real parameter arguments, such as the arguments to grid or regrid routines need not be cause for overloading. They should be declared with the kind required for appropriate accuracy and uniform behavior across platforms. Documentation should identify when specific data types and kinds are required in calling such routines.

Integer Data:

The standards for integer data differ between integers that store user data and integers that do not.

Routines with user integer data arguments (e.g. integer arrays), must be overloaded for all integer data kinds supported. These will be declared as

                         integer(ESMF_KIND_I<n>)
where $<n>$ denotes data size in byte units.

Integers that do not represent user data must generally be declared without a specific kind (see the section below on Default Type Representation). Possible exceptions to this rule follow on items (3) and (4) below.

Care should be exercised to insure that integer arguments to intrinsic Fortran functions are of the correct kind (usually default). Likewise, argument parameters to VM routines should be of default kind.

In the case where a specific kind is required for integer ESMF routine arguments, such as occurs in TimeMgr routines, the requirement must be clearly documented (as noted above, ESMF kind parameters are to be used to specify the kind).

Default Type Representation:

Default Integer Data Internal to the ESMF:

Internally in the ESMF framework, all integer data intended to be of default kind must be declared without specifying their kind, e.g.

               subroutine ESMF_SomeRoutine(foo)
               integer :: foo
This is for clarity purposes, as well as to minimize the overall size of the framework.

ESMF_KIND_I must not be used to declare data internally within ESMF.

To provide some background, the handle ESMF_KIND_I is provided to aid users who want to insure that their integer user code matches the kind of ESMF internal default integer data. It is particularly useful when user code is compiled with integer autopromotion. User code that calls ESMF routines with internal default integer arguments could declare those integers to be of kind=ESMF_KIND_I, e.g.

                 program myUserCode
                 integer(ESMF_KIND_I) :: my_foo
                 call ESMF_SomeRoutine(my_foo)

Logical Data:

The standards for logical data differ between logicals which are used as dummy arguments in user-callable entry points, and those which need to be passed to ESMF C++ routines.

Routines with user logical data arguments use default kind arguments:

                 logical :: isActive

Local logicals within Fortran code also generally use the default Fortran logical data type.

ESMF defines a ESMF_Logical derived type for use when passing logical values between Fortran and C++ code. The constants ESMF_TRUE and ESMF_FALSE are available, and must be used to ensure compatibility with the C++ code.

For convenience, defined assignment operators are available which convert between Fortran logical and the ESMF_Logical derived type. Finally, there are .eq. and .ne. operators defined to compare ESMF_Logical values.

               subroutine ESMF_SomeRoutine(myFlag)
                 use ESMF
                 implicit none
		 
                 logical, intent(inout) :: myFlag
	       
                 type(ESMF_Logical) :: myFlag_local

                 myFlag_local = myFlag                  ! Defined assignment
                 if (myFlag_local .eq. ESMF_TRUE) then  ! Use of .eq. operator
                   call c_ESMC_SomeCRoutine (myFlag_local)  ! Pass to C++
                 else
                   call c_ESMC_OtherCRtoutine (myFlag_local)
                 end if
                 myFlag = myFlag_local                  ! Defined assignment
               end subroutine

7.11.3 C and C++

ESMF C datatypes are also declared with the property that their size remains constant across platforms. This convention is set in order to make ESMF more robust in terms of portability, as well as for the correct matching of Fortran-C routine interfaces. They have a direct size correspondence to their Fortran counterparts. Their names are:

   
          ESMC_I4 (=) integer type of size 4 bytes
          ESMC_I8 (=) integer type of size 8 bytes
          ESMC_R4 (=) floating point type of size 4 bytes
          ESMC_R8 (=) floating point type of size 8 bytes

e.g.

          ESMC_R4 someFloat;
here someFloat is a 4 byte floating point in any platform.

Real Data:

  1. All real data in C should be declared with an explicit ESMF type in the form:
                  ESMC_R<n>  RealVariable;
    
    where $<n>$ is the size in bytes of the memory occupied by each real datum. (Allowed values of n are those that correspond to supported type/kind data). e.g.
                  ESMC_R8  a,b;
    
    which declares a and b to be 8 byte reals.

  2. Methods containing user data - such as arrays - as arguments must handle all supported real user data types and sizes.

Integer Data:

  1. All integers that do not represent user data must be declared using the C datatype int.

  2. Methods containing user data - such as arrays - as arguments must handle all supported integer user data types and sizes.

Boolean Data:

  1. Boolean values used internally within the C++ routines may use the bool data type.

  2. Methods which are Fortran-callable, and pass boolean values through the dummy argument list, must use the ESMC_Logical enumerated type. The constants ESMF_TRUE and ESMF_FALSE are available for comparison purposes. These constants must be used to ensure compatibility with the Fortran code.

               void fortran_caller(bool &myFlag) {	       
                 ESMF_Logical myFlag_local;

                 myFlag_local = myFlag;
                 if (myFlag_local == ESMF_TRUE)
                   ESMF_SomeFortranRoutine (&myFlag_local);  // Pass to Fortran
                 else
                   ESMF_OtherFortranRtoutine (&myFlag_local);

                 myFlag = myFlag_local;
               }

7.11.4 ESMF_TypeKind_Flag “labels”: When knowing the data type/kind is necessary

An enumerated parameter in C++ and a corresponding sequence parameter in Fortran are provided in order to identify the type-and-kind of data being processed in various ESMF routines. There is a one to one correspondence between the Fortran sequence and the C++ enumerated type. They are mostly used as a tool to customize code for a specific data type and kind, and to insure the correct handling of user data across Fortran-C interfaces.

In Fortran this sequenced type's name is ESMF_TypeKind_Flag.

The ESMF_TypeKind_Flag parameter names declared are:

               ESMF_TYPEKIND_I4   (4 byte integer)
               ESMF_TYPEKIND_I8   (8 byte integer)
               ESMF_TYPEKIND_R4   (4 byte floating point)
               ESMF_TYPEKIND_R8   (8 byte floating point)

The following are supported for user interface use only (they are not to be used internally in the ESMF framework):

               ESMF_TYPEKIND_I    (labels integer_not_autopromoted data)
               ESMF_TYPEKIND_R    (labels real_not_autopromoted data)

Fortran ESMF_TypeKind_Flag parameters mostly appear in calls to C routines that create or manipulate arrays, fields, or bundles. e.g.

         ....
         ESMF_TypeKind_Flag :: tk
         ...
          !-set tk
         ...
         call FTN_X(c_ESMC_VMAllFullReduce( ..., tk, ...  )
         ...
         ...
Likewise in C the enumerated type's name is ESMC_TypeKind_Flag.

The ESMC_TypeKind_Flag parameter names declared are:

               ESMC_TYPEKIND_I4   (4 byte integer)
               ESMC_TYPEKIND_I8   (8 byte integer)
               ESMC_TYPEKIND_R4   (4 byte floating point)
               ESMC_TYPEKIND_R8   (8 byte floating point)

In C, ESMC_TypeKind_Flag parameters are used to tailor computation to the type and kind of user data arguments being processed. e.g

  void FTN_X(c_esmc_vmallfullreduce)(.., ESMC_TypeKind_Flag *dtk, ..,int *rc){
    .....      
    switch (*dtk){
    case ESMC_TYPEKIND_I4:
      ....
      break;
    case ESMC_TYPEKIND_R4:
      ....
      break;
    case ESMC_TYPEKIND_R8:
      ...         
      break;
    default:
      ....
    }
    ...

7.11.5 Guidelines for Fortran-C Interfaces

There must be a one to one correspondence of Fortran and C arguments as illustrated in the table below. So for example, if a Fortran routine calls a C method that expects an argument of type ESMC_I4, the argument in the Fortran call should be declared to be an integer of kind=ESMF_KIND_I4.

     integer(ESMF_KIND_I4)  <-->  ESMC_I4  [4 bytes]
     integer(ESMF_KIND_I8)  <-->  ESMC_I8  [8 bytes]
     real(ESMF_KIND_R4)     <-->  ESMC_R4  [4 bytes]
     real(ESMF_KIND_R8)     <-->  ESMC_R8  [8 bytes]
     integer                <-->  int
     type(ESMF_Logical)     <-->  ESMC_Logical
     etc.
Also, ESMF_TypeKind_Flag and ESMC_TypeKind_Flag values are set to have matching values. That is:
             ESMF_TYPEKIND_I<n> = ESMC_TYPEKIND_I<n>
             ESMF_TYPEKIND_R<n> = ESMC_TYPEKIND_R<n>

Character strings:

When passing character strings to subprograms, most Fortran compilers pass 'hidden' string length arguments by value after all of the user-supplied arguments. Each hidden argument corresponds to each character string that is passed. Because of varying compiler support of 32-bit vs 64-bit string lengths, the ESMCI_FortranStrLenArg macro is used in C++ code to specify the data type of the hidden arguments.

For input arguments is usually convenient to copy the Fortran string into a C++ string. The ESMC_F90lentrim procedure provides a common way to obtain the length of the Fortran string - while ignoring trailing blanks. It is analogous to the Fortran len_trim intrinsic function.

  void FTN_X(c_esmc_somecode) (
      const char *name1,   // in - some name in a character string
      const char *name2,   // in - some other name
      int *rc,             // out - return code
      ESMCI_FortranStrLenArg name1_l,   // in - hidden length of name1
      ESMCI_FortranStrLenArg name2_l) { // in - hidden length of name2

      string name1_local = string (name1, ESMC_F90lentrim (name1, name1_l));
      string name2_local = string (name2, ESMC_F90lentrim (name2, name2_l));
      ...
  }

Likewise, when C++ code needs to pass a character string to Fortran code, passing the string itself is performed by it address. The hidden length argument uses the standard string length method:

      FTN_X(f_esmf_somef90code) (
          name1_local.c_str(),
          name2_local.c_str(),
          &localrc,
          (ESMCI_FortranStrLenArg) name1_local.length(),
          (ESMCI_FortranStrLenArg) name2_local.length())

Note that when an array of characters is passed, the hidden string size is that of an individual array element. The size of each dimension of the array should be explicitly passed through separate arguments. Thus, in the following declaration, the hidden argument would have a value of 32. The dimensions, 20 and 30, would need to be separately passed:

    character(32) :: char_array(20,30)
    :
    call c_esmc_something (  &
        char_array, size (char_array,1), size (char_array, 2), &
        localrc)

7.12 Code: Optional Argument Conventions for the C/C++ API

7.12.1 Overview

Optional arguments in the public ESMC interface are implemented using the variable-length argument list functionality provided by <stdarg.h>. The <stdarg.h> header contains a set of macros which allows portable functions that accept variable-length argument lists (also known as variadic functions) to be written. Variadic functions are declared with an ellipsis in place of the last parameter and must have at least one named (fixed) parameter. A typical example of such a function is printf which is declared as

        int printf(char *fmt, ...).

The following type and macros are provided by <stdarg.h> for processing variable-length argument lists.

Arguments corresponding to the variable-length argument list specified by ",..." in a function prototype always undergo the following argument conversions: bool or char or short are converted to int, and float is converted to double.

In order to correctly retrieve arguments from the variable argument list it is necessary to know the type of each argument. In order to correctly terminate parsing the variable argument list it is also necessary to know the number of arguments in the list. Since the number of arguments and their types are not known at compile time the best one can do is establish a convention such that the caller will provide type and length information about the variable argument list. The convention, however, is not a guarantee that the caller does not mistakenly specify the types or number of arguments. The scanf and printf functions do this by having the last fixed argument be a character (format) string in which the caller specifies the types (for example, "%f%d%s" for each argument in the variable argument list. Although, the format string specifies the number of expected arguments there is no actual "termination-flag" inserted at the end of the variable argument list. If the caller incorrectly specifies the in the format string more arguments than are actually provided then the function will likely access invalid sections of memory while parsing the variable argument list. Unless this error is trapped by the system the function will unwittingly process erroneous data.

7.12.2 Approach

The following conventions are established for the public ESMC optional argument API. The "class" associated with this API is called ESMC_Arg and its header files are located in Infrastructure/Util/include. The public optional argument API for a class is provided by the public header for that class together with ESMC_Arg.h. Macros for internal processing of optional arguments are provided by the ESMCI_Arg.h header. Each ESMC class has a set of named optional arguments associated with its methods. Associated with each optional argument of a class is a unique (within the class) optional argument identifier (id) of type ESMCI_ArgID. The optional argument list provided by the caller must consist of a sequence of argument identifier and argument (id, arg) pairs. The optional argument list must be terminated by the global identifier ESMCI_ArgLastID. The ESMC_ArgLast macro is provided by ESMC_Arg.h for the user to indicate the end of an optional argument list.

The global identifier ESMCI_ArgBaseID is the starting identifier for local (class) optional argument identifiers. ESMCI_ArgBaseID establishes a set of global optional argument identifiers, such that, the global set does not intersect with any class identifier set. The optional argument identifier list for a class will be declared in the public header for that class. The naming convention for optional argument identifiers is

                ESMCI_<ClassName>Arg<ArgName>ID
where ClassName is the name of the class and ArgName is the name of the optional argument with the first letter capitalized. The optional argument identifier list is declared as enumeration constants with the first identifier set equal to ESMCI_ArgBaseID. Here is an example for a class called "Xclass".
   // Optional argument identifier list for the ESMC_Xclass API.
   enum {
     ESMCI_XclassArgAoptID = ESMCI_ArgBaseID,  // ESMC_I1
     ESMCI_XclassArgBoptID,                    // ESMC_I4
     ESMCI_XclassArgCoptID,                    // ESMC_R4
     ESMCI_XclassArgDoptID,                    // ESMC_R8
     ESMCI_XclassArgEoptID,                    // ESMC_Fred
   };
It is helpful for the class developer to list the data type of each optional argument (as a comment) with its associated id.

The ESMCI_Arg(ID,AR) internal macro (declared in ESMC_Arg.h) is provided for casting an optional argument and its associated id into the required sequence for passing to functions.

   #define ESMCI_Arg(ID,ARG)  ((ESMCI_ArgID)ID),(ARG)
Each class will use this internal macro in its public header to declare user macros for the class specific optional arguments. The naming convention for class specific optional argument expansion macros is
                ESMC_<ClassName>Arg<ArgName>(ARG)
where ClassName is the name of the class and ArgName is the name of the optional argument with the first letter capitalized. The argument of the macro is the actual caller provided optional argument. Here is an example of how the macros are used for the class "Xclass".
   // Argument expansion macros for the ESMC_Xclass API.
   #define ESMC_XclassArgAopt(ARG)  ESMCI_Arg(ESMCI_XclassArgAoptID,ARG)
   #define ESMC_XclassArgBopt(ARG)  ESMCI_Arg(ESMCI_XclassArgBoptID,ARG)
   #define ESMC_XclassArgCopt(ARG)  ESMCI_Arg(ESMCI_XclassArgCoptID,ARG)
   #define ESMC_XclassArgDopt(ARG)  ESMCI_Arg(ESMCI_XclassArgDoptID,ARG)
   #define ESMC_XclassArgEopt(ARG)  ESMCI_Arg(ESMCI_XclassArgEoptID,ARG)
Here is an example call, with properly specified optional arguments, to a class function. The function has three named (fixed) arguments.
   rc = ESMC_XclassFunc(fixedArg1,fixedArg2,fixedArg3,
                        ESMC_XclassArgAopt(aOptArg),
                        ESMC_XclassArgCopt(cOptArg),
                        ESMC_XclassArgDopt(dOptArg),
                        ESMC_ArgLast)

7.12.3 Internal Macros for Processing the Optional Argument List

Internal macros for processing the optional argument list are declared in ESMCI_Arg.h. These macros provide a consistent internal interface to the macros defined in stdarg.h. What follows is a description of each macro available to a class developer.

The following type specific ESMCI_ArgGet macros are provided for returning optional argument values. These macros correctly account for the forced type conversions described above. In each macro, AP is the optional argument list pointer.

Internal macros for returning standard C (non-pointer) types.

   #define ESMCI_ArgGetChar(AP)           (char)va_arg(AP,int)
   #define ESMCI_ArgGetShort(AP)         (short)va_arg(AP,int)
   #define ESMCI_ArgGetInt(AP)             (int)va_arg(AP,int)
   #define ESMCI_ArgGetLong(AP)           (long)va_arg(AP,long)
   #define ESMCI_ArgGetLongLong(AP)  (long long)va_arg(AP,long long)
   #define ESMCI_ArgGetFloat(AP)         (float)va_arg(AP,double)
   #define ESMCI_ArgGetDouble(AP)       (double)va_arg(AP,double)
Internal macros for returning defined ESMC types.
   #define ESMCI_ArgGetI1(AP)          (ESMC_I1)va_arg(AP,int)
   #define ESMCI_ArgGetI2(AP)          (ESMC_I2)va_arg(AP,int)
   #define ESMCI_ArgGetI4(AP)          (ESMC_I4)va_arg(AP,int)
   #define ESMCI_ArgGetI8(AP)          (ESMC_I8)va_arg(AP,ESMC_I8)
   #define ESMCI_ArgGetR8(AP)          (ESMC_R8)va_arg(AP,double)
   #define ESMCI_ArgGetR4(AP)          (ESMC_R4)va_arg(AP,double)
Special internal macro for returning strings.
   #define ESMCI_ArgGetString(AP)               va_arg(AP,char*)
General internal macro for returning all non-converted types.
   #define ESMCI_ArgGet(AP,TYPE)                va_arg(AP,TYPE)
TYPE is the expected type of returned argument.

7.12.4 Parsing the Optional Argument List

Each class function with optional arguments will declare an optional argument list pointer and optional argument list id.

  ESMCI_ArgList argPtr;       // optional argument list pointer
  ESMCI_ArgID argID;          // optional argument list id

Parsing the optional argument list consists of three distinct phases: initializing the argument list pointer, retrieving each argument in succession based on its id, and finalizing the argument list pointer. The block for retrieving arguments must first retrieve an argument id. If that id is not ESMCI_ArgLastID and is in the set of valid ids for that function, then the argument is retrieved using a type-appropriate ESMCI_ArgGet macro. The following example code should be considered as "template" code for parsing an optional argument list.

  // parse the optional argument list:
  ESMCI_ArgStart(argPtr,ifix);
  while ( (argID = ESMCI_ArgGetID(argPtr)) != ESMCI_ArgLastID ) {
    switch ( argID ) {
      case ESMCI_XclassArgAoptID:
        aOpt = ESMCI_ArgGetI1(argPtr);
        break;
      case ESMCI_XclassArgBoptID:
        bOpt = ESMCI_ArgGetI4(argPtr);
        break;
      case ESMCI_XclassArgCoptID:
        cOpt = ESMCI_ArgGetR4(argPtr);
        break;
      case ESMCI_XclassArgDoptID:
        dOpt = ESMCI_ArgGetR8(argPtr);
        break;
      case ESMCI_XclassArgEoptID:
        eOpt = ESMCI_ArgGet(argPtr,ESMC_Fred);
        break;
      default:
        ESMC_LogDefault.MsgFoundError(ESMC_RC_OPTARG_BAD, "", &rc);
        return rc;
    } // end switch (argID)
  } // end while (argID)
  ESMCI_ArgEnd(argPtr);

Prior to actually parsing the optional argument list a function must check the list for proper specification. This is done by using a code block similar to the parsing block shown above. The difference is that in this case the ESMCI_ArgGet macros are only used to increment the argument list pointer.

  // check the optional argument list:
  ESMCI_ArgStart(argPtr,lastFixed);
  while ( (argID = ESMCI_ArgGetID(argPtr)) != ESMCI_ArgLastID ) {
    switch ( argID ) {
      case ESMCI_XclassArgAoptID: ESMCI_ArgGetI1(argPtr); break;
      case ESMCI_XclassArgBoptID: ESMCI_ArgGetI4(argPtr); break;
      case ESMCI_XclassArgCoptID: ESMCI_ArgGetR4(argPtr); break;
      case ESMCI_XclassArgDoptID: ESMCI_ArgGetR8(argPtr); break;
      case ESMCI_XclassArgEoptID: ESMCI_ArgGet(argPtr,ESMC_Fred); break;
      default:
        ESMC_LogDefault.MsgFoundError(ESMC_RC_OPTARG_BAD, "", &rc);
        return rc;
    } // end switch (argID)
  } // end while (argID)
  ESMCI_ArgEnd(argPtr);

The following example shows how an optional argument list check block can be structured to handle an optional argument whose type is call dependent. In this case the type of the dValue optional argument depends on the value of the ESMC_TypeKind_Flag argument tk (which is the last fixed argument).

  // check the optional argument list:
  ESMCI_ArgStart(argPtr,tk);
  while ( (argID = ESMCI_ArgGetID(argPtr)) != ESMCI_ArgLastID ) {
    switch ( argID ) {
      case ESMCI_ConfigArgCountID: ESMCI_ArgGetInt(argPtr); break;
      case ESMCI_ConfigArgLabelID: ESMCI_ArgGetString(argPtr); break;
      case ESMCI_ConfigArgDvalueID:
        switch ( tk ) {
          case ESMC_TYPEKIND_I4: ESMCI_ArgGetI4(argPtr); break;
          case ESMC_TYPEKIND_I8: ESMCI_ArgGetI8(argPtr); break;
          case ESMC_TYPEKIND_R4: ESMCI_ArgGetR4(argPtr); break;
          case ESMC_TYPEKIND_R8: ESMCI_ArgGetR8(argPtr); break;
          case ESMC_TYPEKIND_LOGICAL: ESMCI_ArgGetInt(argPtr); break;
          case ESMC_TYPEKIND_CHARACTER: ESMCI_ArgGetString(argPtr); break;
        } // end switch (tk)
        break;
      default:
        ESMC_LogDefault.MsgFoundError(ESMC_RC_OPTARG_BAD, "", &rc);
        return rc;
    } // end switch (argID)
  } // end while (argID)
  ESMCI_ArgEnd(argPtr);

7.13 Code: Makefile Conventions

The makefiles use GNU standard target names when possible. The default rule is to remake the ESMF library. Targets exist to build the unit and system tests, the examples, the demos, and to run them together or individually. Targets also exist to build the documentation and create pdf and html versions. For more details on targets, refer to the README file in the top level ESMF directory, and also the ESMF User's Guide.

7.13.1 Code Building Rules

During software development it is advantageous to recompile a portion of ESMF and not build the entire framework when testing localized software modifications. The makefiles are configured to compile only files in the current directory and rebuild the shared libraries only when necessary. Therefore when "gmake" is entered in any directory, only files in that directory are compiled. However, the entire ESMF framework may be built from any directory by entering "gmake lib".

The unit tests, the system tests, the examples, and the demos all share a common set of targets. The following target list illustrates the options for the examples. The names unit_tests, system_tests, demos, and all_tests can be substituted wherever examples occurs.

examples
build and run all examples
examples_uni
build and run all examples single process
build_examples
build the examples
run_examples
run the examples and report the success/fail status
run_examples_uni
run the examples single process
check_examples
report the success/fail status without reexecution
clean_examples
remove the example executables

7.13.2 Document Building Rules

The makefile rules for building documents are now located in:
 build/common.mk
The rules exist as pattern rules and are controlled by the variables set in a doc directory's makefile. The pattern rules will build .tex from source code and .pdf, .dvi and html files from .ctex files.

Variables that can be set in the individual doc/makefiles are called "makefile variables". These are:

DVIFILES
PDFFILES
HTMLFILES
TEXTFILES_TO_MAKE

Document file dependencies are set with:

REFDOC_DEP_FILES

The makefiles targets work from both local directory and from ESMF_DIR:

gmake alldoc
gmake pdf
gmake dvi
gmake html

7.13.3 Include Files

At the highest directory level, the "include" directory will contain public include files intended to be used in user-written code. They will be broken up into separate files, with a single "ESMF.h" include file which includes all the others.

At the 'src' level, parallel to Infrastructure, Superstructure, and doc, is another include directory. This is for private include files which are ESMF-wide. These might include system-wide architecture dependent #defines constants, etc.

Below the Superstructure & Infrastructure level are the individual component levels (TimeMgr, Field, Component, etc). Under each of these directories is an include directory for the component specific include files.

7.14 Preprocessor Usage

7.14.1 Using the Preprocessor For Generic Fortran Code

Fortran allows the creation of generic subprogram names via the INTERFACE facility. This lets the caller use a simplified single name for a call. Then, based on the callers actual argument list, the compiler will choose an appropriate specific routine to perform the task.

When several specific variants of a generic call exist, and only differ in minor ways, for example by ESMF object type, it can be desirable to have the variants generated from a single definition. This allows improvements in the reliability and maintainability of the code. Feature code can be added in a single place, and all of the specific routines will automatically incorporate the new code.

By convention, generic ESMF Fortran code uses the GNU C preprocessor macro facility to generate specific routines. These routines generally differ only in the type, kind, and rank of the dummy arguments. The internal code is otherwise identical.

When writing a cpp macro, the definition must be 'one line' long. Since coding an entire subprogram requires many lines to implement, a backslash character is used at the end of each line to concatenate multiple lines from the input file into a single, very long, preprocessor line. After macro expansion, each preprocessor line needs to be converted into a sequence of Fortran statements. So again by convention, at-sign characters, @, are used as the second-to-last character in each line. After cpp has been run, the long lines from the result are split at the @ characters by converting them to newline characters. The tr command is used for this translation.

ESMF Fortran files which need to be preprocessed in this manner use the file name suffix .cppF90. This triggers the makefile system to use the following sequence when processing the file:

A simple example of generic code written in this style is:

  module ESMF_Example_mod
    use ESMF
    implicit none

! The following header needs to be preprocessed by the compiler,
! not the first cpp pass.  So use a caret as the first character,
! rather than a pound-sign.

^include "my_header.inc"

! Define a generic name with three specific routines

    interface ESMF_generic_name
      module procedure ESMF_ArraySpecifc
      module procedure ESMF_FieldSpecfic
      module procedure ESMF_MeshSpecific
    end interface

  contains

! Define a macro for expansion.  Each line is terminated with a
! @\ sequence.  Also note the use of ## for concatenation
! and # for stringization.

#define MySpecificMacro(mclass, mthis)  \
  subroutine mclass##Specific (mthis, rc) @\
    type(mclass), intent(inout) :: mthis @\
    integer, optional :: rc @\
 @\
    print *, |class = |, #mclass @\
 @\
    if (present (rc)) rc = ESMF_SUCCESSFUL @\
 @\
  end subroutine mclass##Specfic @\

! Expand macro for a few classes

MySpecificMacro(ESMF_Array, array)
MySpefificMacro(ESMF_Field, field)
MySpecificMacro(ESMF_Mesh, mesh)

end module ESMF_Example_mod

Character string concatenation via the Fortran concatenation operator // can be problematic due to the first pass preprocessing treating it as a C++ inline comment. An alternative is to use the ESMF_StringConcat function.

7.14.2 System Dependent Strategy Using Preprocessor

Since the code must compile across different platforms, a strategy must be adopted to handle the system differences. Examples of system differences are: the subroutines (bcopy vs memcopy) or include filenames (strings.h vs str.h, etc).

Rather than putting architecture names in all the source files, there will be an ESMF-wide file which contains sections like this:

 #ifdef sgi 
 #define HAS_VFORK 0 
 #define BCOPY_FASTER 1 
 #define FOPEN_3RD_ARG 1 
 #endif

 #ifdef sun 
 #define HAS_VFORK 1 
 #define BCOPY_FASTER 0 
 #define FOPEN_3RD_ARG 1 
 #endif
This allows system-dependent code to be bracketed with meaningful names:

 

 #if HAS_VFORK
    vfork();
 #else
    fork(); 
    exec();
 #endif
and not an endless string of architecture names.

7.15 ESMF Data Type Autopromotion Support Policy and Guide

ESMF supports autopromotion of user code with respect to integer and real data types. This is data that a user would put in an array or field. By autopromotion we mean compiling user code with $-r8$, $-i8$ or similar options on those platforms where these options are available. If a code is compiled with autopromotion, user data will be processed correctly by ESMF. That is because user data arguments in ESMF calls already are overloaded for all TKR(type-kind-rank). It is important to note that ESMF itself must not be compiled with autopromotion flags.

The ESMF API distinguishes four different flavors of integer and real arguments:

The ESMF API handles integer user data by overloading for TKR. The ESMF API handles real user data by overloading for TKR. The ESMF API handles integer parameters by using the system's default integer type. The ESMF API handles real parameters by explicitly specifying a kind.

As a result of the ESMF API conventions the autopromotion of integer and real user data is automatically handled within ESMF.

Integer parameters that are arguments to ESMF calls must be protected against integer autopromotion by explicitly specifying their kind as ESMF_KIND_I in the user code.

The safest way to handle real parameters is for the user to specify their explicit kind as expected by the API. However, the user may choose to omit the kind specifier for reasons of convenience. This however will make their code vulnerable with respect to autopromotion and make the user code less portable, breaking it where the system default real kind does not match the ESMF API.

The requirements for safe interfacing of autopromoted user code with ESMF are as follows:

  1. All integer parameter arguments (as opposed to user data) to ESMF routines must be of kind ESMF_KIND_I. Following is an example of an ESMF call that includes both a user data argument, and integer parameter arguments
        ...
    
     integer, dimension(:), pointer :: intptr        !User data arg.
                                                     !->No need for 
                                                     !kind specifier.
    
     integer(ESMF_KIND_I), dimension(3) :: counts    ! Integer 
                                                     ! parameter 
    
     integer(ESMF_KIND_I), dimension(3) :: lbounds   ! Integer 
                                                     ! parameter
    
     integer(ESMF_KIND_I), dimension(3) :: ubounds   ! Integer 
                                                     ! parameter
    
     integer(ESMF_KIND_I) :: rc                      ! Integer 
                                                     ! parameter
      ...
     call  ESMF_LocalArrayCreate(array, counts, intptr, &
             lboounds, ubounds, rc)
    

  2. As explained in section I.3 above, all real parameters must be of the kind expected by the routine being called. e.g. In a call to ESMF_ClockGet(), the runTimeStepCount argument must be of kind ESMF_KIND_R8.
                 ...
                type(ESMF_Clock)  :: clock
                real(ESMF_KIND_R8) :: runTimeStepCount
                integer(ESMF_KIND_I) :: rc
                 ...
                call ESMF_ClockGet(clock, runTimeStepCount, rc)
    
  3. When user data is autopromoted, the ESMF_TypeKind_Flag parameter argument corresponding to the autopromoted data must be adjusted appropriately. The ESMF_TypeKind_Flag parameter for an array or field that has been autopromoted can be obtained during execution by using the function ESMF_TypeKindFind(). ESMF_TypeKindFind() is an overloaded function that returns the ESMF_TypeKind_Flag parameter corresponding to the runtime type and kind of an input scalar.

    e.g. In the following code excerpt ESMF_TypeKindFind() is used to determine at runtime the correct ESMF_TypeKind_Flag parameter to use in the ESMF_ArraySpecSet() routine in preparation for creation of an ESMF Array that will store integer data that may or may not be autopromoted.

              ...
              integer :: iScalar
              ...
              type(ESMF_TypeKind_Flag) myTypeKind
              myTypeKind= ESMF_TypeKindFind(iScalar)
              call ESMF_ArraySpecSet(arrayspec, rank, myTypeKind, rc)
    

7.15.1 How We Arrived at This Autopromotion Support Policy

We considered the possibility of expanding autopromotion support to ESMF routine integer parameter arguments, in addition to those storing user data. Two options were considered as explained below. The reasons why we decided against such support, after discussion with the ESMF community, are as follows:

  1. All user interfaces would need to be overloaded -this in addition to the overloading already present on some routines for user data.
  2. Most likely, cpp macros for all user interfaces would be needed, which does not improve readability.
  3. The additional necessary overloading would double (or more, depending on the option) the size of our post-processor code base.
  4. Typecasting support would incur performance overhead, regardless of whether autopromotion is used or not.
  5. There are routines, such as ESMF_LocalArrayCreate(), where all integer parameter arguments are optional. Overloading those routines violates the Fortran 95 standard and will not compile.

Rejected Options:

We considered and rejected two alternate options for autopromotion support. Here we illustrate with an example their impact on both the user code and the ESMF code. Note that this routine is overloaded for data arrays for all TKR supported and thus supports both real and integer autopromotion of user data.

1st option: support autopromotion of integers (options sizes 4 or 8 bytes) as long as all integer parameter arguments are of the same kind.

2nd option: support autopromotion of integers (options sizes 4 or 8 bytes). Mixed integer argument kinds.

For our example we use a call to ESMF_LocalArrayCreate() in each of the 2 option scenarios. In reading these example scenarios please keep in mind that ESMF code will not be compiled with autopromotion.

That is because only user code autopromotion is being considered. Thus the following statement could have different meaning if it is found within ESMF than in user code:

                         integer :: foo

 In ESMF-------------------------------- ===>  foo is of default 
                                               integer kind/size.

 In User code compiled with autopromotion ==>  foo is an integer
                                               of kind/size 
                                               determined by
                                               autopromotion flag.

With 1st Option:

  USER CODE:
  ---- -----
      ...
     integer, dimension(:), pointer :: intptr
     integer             , dimension(3) :: counts
     integer             , dimension(3) :: lbounds
     integer             , dimension(3) :: ubounds
     integer              :: rc
      ...
      ...
     call  ESMF_LocalArrayCreate(array, counts, intptr, &
           lboounds, ubounds, rc)

Changes in ESMF:

The routine would need to be further overloaded in order to insure that all integer parameter call arguments (counts, lbounds, ubounds, and rc), are typecast to default integer inside the method (note that ESMF kind can be of size 4 or 8). It would be something like this:

  
   subroutine ESMF_LocalArrayCreate(array, counts, intptr,.... &
                       lbounds, ubounds, rc)
   ......
    integer(<esmfKind>) , intent(in), optional :: counts, &
                          lbounds, ubounds
    integer(<esmfKind>), intent(out), optional :: rc 
    integer :: counts_noAP, lbounds_noAP, ubounds_noAP, rc_noAP
  
    if (PRESENT(counts)) counts_noAP=counts
    if (PRESENT(lbounds)) lbounds_noAP=lbounds
    if (PRESENT(ubounds)) ubounds_noAP=ubounds
   ...
   ....
    if (PRESENT(rc)) rc=rc_nonAP
   return

With $<esmfKind>$ = ESMF_KIND_I4 in one copy and ESMF_KIND_I8 in the other. The number of copies of the routine will increase from 42 to 84.

*(Note that in this particular routine, overloading is problematic because all the integer parameters are optional)

With 2nd Option:

  USER CODE:
  ---- -----
      ...
     integer, dimension(:), pointer :: intptr
     integer(ESMF_KIND_I4) , dimension(3) :: counts
     integer(ESMF_KIND_I8), dimension(3) :: lbounds
     integer             , dimension(3) :: ubounds
     integer              :: rc
      ...
      ...
     call  ESMF_LocalArrayCreate(array, counts, intptr, &
                      lboounds, ubounds, rc)
Changes in ESMF:

The routine would need to be further overloaded to support call arguments counts, lbounds, ubounds, and rc each being of either 4-byte or 8-byte size. In order to provide for all possible combinations of data sizes for these 4 integer parameters, the number of overloads would increase from 42 to $[42*(2**4)]=672$, which explains why support of this option is not practical.

7.16 Scripts: Script Coding Standard

7.16.1 Content Rules

7.17 Lang: Interlanguage Coding Conventions

ESMF is written in a combination of C/C++ and Fortran. Techniques used in ESMF for interfacing C/C++ and Fortran codes are described in the ESMF Implementation Report[#!bib:ESMFimplrep!#], which is available via the Users tab on the ESMF website. These techniques, which address issues of memory allocation, passing objects across language boundaries, handling optional arguments, and so on, are general and have been applied to multiple projects.

We distinguish between these techniques and the conventions used by the ESMF project when interfacing C/C++ and Fortran. These conventions, which represent specific implementation choices, require additional input and explanation, and this section in the Guide is currently incomplete. The list below outlines the topics that we intend to address:

  1. Logicals across language interfaces
  2. Optional arguments across language interfaces
  3. Layering Fortran on top of C/C++
  4. Layering C/C++ on top of Fortran

7.17.1 Optional Arguments Across Language Interfaces

It is often necessary for C++ code to call Fortran code where optional arguments are used. By convention, most Fortran compilers use a C NULL for the argument address to indicate that the optional argument is missing.

The following Fortran subroutine has optional arguments. Note that Fortran 77 style adjustable size array dimensioning is used for array b:

    subroutine f_ESMF_SomeProcedure (a, b, b_size, rc)
      implicit none
      real,    intent(in),  optional :: a
      integer, intent(in)            :: b_size
      real,    intent(out), optional :: b(b_size)
      integer, intent(out), optional :: rc

      if (present (a)) then
        ... = a
      end if
      ...
      if (present (b)) then
        b(i) = ...
      end if
      ...
      if (present (rc)) then
        rc = localrc
      end if
    end subroutine f_ESMF_SomeProcedure

When calling the above from C++, NULL can be used to indicate missing arguments:

    extern "C" {
        FTN_X(f_esmf_someprocedure)(float &a, float &b, int &b_size, int &rc);
    }
    ...
    // array b not present, so use NULL.
    int b_size = 0;
    FTN_X(f_esmf_someprocedure)(&a, NULL, &b_size, &rc);
    ...
    // array b is present.  Pass size and address.
    float *b = new float[20];
    int b_size = 20;
    FTN_X(f_esmf_someprocedure)(&a, b, &b_size, &rc);


7.18 Lang: Fortran Coding Standard

This standard is derived from the GFDL Flexible Modeling System Developers' Manual and the coding standard for the NCAR Community Atmospheric Model(CAM) developed by Jim Rosinski. Other documents containing coding conventions include the "Report on Column Physics Standards" (http://nsipp.gsfc.nasa.gov/infra/) and "European Standards For Writing and Documenting Exchangeable Fortran 90 Code" (http://nsipp.gsfc.nasa.gov/infra/eurorules.html).

The conventions assume the use of embedded documentation extractor ProTeX.

7.18.1 Content Rules

F95 Standard
ESMF will adhere to the Fortran 95 language standard [#!ref:f95!#], to the extent that it is implemented.

  1. All elements of the ANSI f95 standard are permitted, with a few listed exceptions whose use is discouraged or prohibited. These are enumerated below.
  2. Language extensions are severely restricted. They may be used in limited fashion, provided a pressing reason exists (e.g major performance enhancement using a particular proprietary software system), and an alternate formulation is provided for compiling environments that do not permit the extension.
  3. The standard may change in the future, e.g to Fortran 2003, or any other, after review.

Preprocessing
The use of preprocessing directives is intended for language extensions, and in some circumstances, it is used to generate module procedures under a generic interface for variables of different type, kind and rank (thus circumventing f90's strict typing), while maintaining a single copy of the source.

The use of preprocessor directives in ESMF is permitted under the following conditions:

  1. Where language extensions are used, cpp #ifdef statements must be used to shield lines from compilers that may not recognize them.
  2. Use is restricted to the built-in preprocessor of the f90 compiler (based on cpp), and cannot be based on external preprocessors such as m4. This condition may be relaxed on platforms where the builtin preprocessor proves to be inadequate.
  3. Use is restricted to short code sections (a useful rule of thumb is that an #ifdef and the matching #endif should both be visible on a single 80x24 editor window).
  4. Tokens must be uppercase.
  5. Owing to restrictions in certain compilers, preprocessor variable names may not exceed 31 characters.

Source files
Each source code file defines a single program or f90 module. The filename must be the same as the module name with the following extensions:
filename.f - fixed format, no preprocessing.
filename.F - free format, no preprocessing.


filename.f90 - fixed format, with preprocessing.
filename.F90 - free format, with preprocessing.
Module name
The names of Fortran procedure interfaces will be preceded by ESMF_. Class names will normally be the first item in a procedure name, followed by the specific method, e.g., ESMF_TimeMgrGetCurrDate. Compilers produce object code for each source file, usually with a .o extension. During linking, it is required that each object file have a unique name; extremely generic names must be avoided. This convention is used to prevent name collisions.

Scope
Each module in ESMF must have private scope by default. Each public interface therefore needs to be explicitly published.

Typing
The use of implicit typing is forbidden. Every module must contain the line:

implicit none

in the module header, and every variable explicitly declared.

There are a few restrictions on the length of a character variable:

  1. Character variables that are arguments to routines should be declared with (len=*). It has been observed that compilers are inconsistent in their “padding” practices, and the standard is silent on the subject.
  2. It is recommended that other character variables be declared with length a multiple of 4, or preferably 8. This is a requirement for variables that are components of derived types, since it has been observed that without these restriction, there are occasional word alignment fault errors generated.

Arguments
The intent of arguments to subroutines and functions must be explictly specified.

Intrinsics
The f90 language provides a number of intrinsic functions for performing common operations. The use of the standard intrinsics is generally encouraged. Notes:

  1. The generic form of the intrinsic (e.g max()) must be used rather than the specific one (e.g dmax0()). This permits flexibility to later changes of type.
  2. Many of the intrinsic array operations have been found to be poorly optimized for performance (e.g reshape(), matmul()) since they have to be perfectly general. These must be used with care in code regions that are critical for performance.
  3. Several older standard intrinsic names have been declared obsolescent, and the current names are preferred (e.g modulo() instead of mod(), real() instead of float()).

Constants and magic numbers
Shared constants must never be hardcoded: instead mnemonically useful names are required. This applies to physical constants such as the universal gas constant, gravity, and so on, but also for flags used to select code options. In particular, this coding construct:

subroutine advection(flag)
integer, intent(in) :: flag
...
if( flag.EQ.1 )then
    call upwind_advection( ... )
else if( flag.EQ.2 )then
    call smolar_advection( ... )
...
endif
end subroutine advection
...
call advection(1)

is discouraged. This should instead be written as:

integer, parameter :: UPWIND=1, SMOLAR=2
...
subroutine advection(flag)
integer, intent(in) :: flag
...
if( flag.EQ.UPWIND )then
    call upwind_advection( ... )
else if( flag.EQ.SMOLAR )then
    call smolar_advection( ... )
...
endif
end subroutine advection
...
call advection(UPWIND)

Procedural interfaces
Procedural interfaces are the public interfaces to subroutines and functions provided by a module.

  1. Procedures that perform the same function on different datatypes (e.g of differing type, kind or rank) should have a single generic interface. When the generic public interface exists, all the module procedures that constitute it must be private.
  2. Optional arguments, if any, should follow the required arguments, so that the procedure may be called without explicit argument keywords. Optional arguments in all new public interfaces must have a keywordEnforcer argument separating the required arguments from the optional arguments. This requires the caller to specify keywords for the arguments, allowing for upward compatibility as new optional arguments are added over time.
  3. Argument lists should be as short as possible. If necessary, related elements of an argument list should be encapsulated in a public derived type.

Deprecated elements of the standard
Deprecated language elements include:

  1. implicit typing. Use implicit none in all modules and external procedures.
  2. common blocks. Use module global variables instead;
  3. assumed size arrays: i.e declarations of the form a(*) or a(1) with the intention of over-indexing. This can inhibit effective bounds-checking at compile- and runtime.
  4. STOP statements: this can generate single-processor exits in some parallel environments;
  5. array syntax. Though compact and concise, many compilers have trouble generating efficient code from source written in this notation.

mkmf
The mkmf tool may be used to generate Makefiles with correct dependencies for F90 and hybrid-language codes. This places minor restrictions on module and use statements: these declarations must be on a single line, and the use of continuation lines, e.g:

module &
  module_name
use &
  module_name

is forbidden.

Use statement
  1. The use statement must appear on the same line as the module name, i.e, do not use:

    use &
      module_name
    

    This is to be consistent with the dependency analysis performed by mkmf outlined above.

  2. The use, only: clause is required so that all imported elements are explicitly declared.
  3. Variables imported by a use statement must not be modified by the importing module.
  4. Modules cannot publish variables and interfaces imported from another module. Thus, each public element of a module is only available through that module.

7.18.2 Style Rules

Style is somewhat personal, and it would be needlessly restrictive to attempt to impose style requirements. These are recommendations which we believe will lead to pleasant encounters with clear, legible and understandable code. The only style requirement we place is that of consistency: a single code unit is required to be rigorous in using the author's preferred set of stylistic attributes. It is not onerous to follow a style: modern editors have many language-aware features designed to produce a consistent, customizable style.

Style recommendations include the following:

  1. The use of free format;
  2. The use of do...end do constructs (as opposed to numbered loops as in Fortran-IV);
  3. The use of proper indentation of loops and blocks;
  4. The liberal use of blank lines to delimit code blocks;
  5. The use of comment lines of dashes or dots to delimit procedures;
  6. The use of useful descriptive names for physically meaningful variables; short conventional names for iterators (e.g (i,j,k) for spatial grid indices);
  7. The use of uppercase for constants (parameters), lowercase for variables;
  8. The use of verbose syntax on end statements (e.g subroutine sub...end subroutine sub rather than subroutine sub...end);
  9. The use of short comments on the same line to identify variables; longer comments in well-delineated blocks to describe what a portion of code is doing;
  10. Compact code units: long procedures should be split up if possible. 200 lines is a rule-of-thumb procedure length limit.

7.19 Lang: C/C++ Coding Standard


7.20 Repo: Source Code Naming and Tagging Conventions

We provide two types of releases, public and internal, with similar tagging conventions.

7.20.1 Public Releases

Public releases are each given a branch created from the main CVS repository ESMF trunk. The tagging convention for public releases is ESMF_*_*_*r[p#], e.g., ESMF_0_2_1r, where the first digit represents a major release, the second digit an incremental release, the third digit a routine update, and an official release r. Subsequent patches to the release are identified with the letter p followed by the patch number, e.g., ESMF_1_0_0rp2. Patches are tagged on the release branch.

7.20.2 Internal Releases

ESMF software internal releases are identified as tags on the CVS main trunk. The tagging convention for internal releases is ESMF_*_*_*, e.g., ESMF_1_0_1, where the first digit represents a major release, the second digit an incremental release, and the third digit a routine update.

7.21 Data Management Conventions

The ESMF team will adopt the Climate and Forecast (CF) Metadata conventions. These are available at:
http://www.unidata.ucar.edu/software/netcdf/conventions.html

esmf_support@ucar.edu