WRF Coding Conventions - Draft

[The primary target audience for this release of the document is for those scientists and researchers who would like to specifically include a physics package into the WRF model.  Therefore, the majority of effort will be spent describing the intricacies of the model-layer in WRF, with just a tacit discussion of the other two layers. Later efforts of this document will remedy this deficiency.]

WRF Coding Conventions

Table of Contents

1. Introduction
1.1 Purpose
1.2 Conventions and Standards Scope
1.3 Definition of Terms
1.4 Document Outline
2. Generic WRF Conventions
2.1 Language Selection
2.2 Acceptable cpp Uses
2.3 Free Form Source
2.4 Explicit Variable Declarations
2.5 Global Variable Declarations
2.6 Variable Intent
2.7 Parallel and Threaded Choices
3. Driver-Layer Subroutine Conventions
4. Mediation-Layer Subroutine Conventions
5. Model-Layer Subroutine Conventions
5.1 Overview
5.2 Variable and Subroutine Conventions
5.3 File, Module and Subroutine Naming Conventions
5.4 Miscellaneous Variable Assumptions
Appendix A. Draft Model-Layer Module Template

1. Introduction

The overall goal of the WRF Model project is to develop a next-generation mesoscale forecast model and assimilation system that will advance both the understanding and prediction of important mesoscale precipitation systems, and promote closer ties between the research and operational forecasting communities.  The model is being developed as a collaborative effort among NCAR, NCEP, FSL and CAPS, together with the participation of a number of university scientists.

                                                                   NCAR USWRP Proposal
                                                                   31 December, 1998

1.1. Purpose

As stated in the opening sentence of the WRF proposal, it  is anticipated that there will be a large and diverse group of individuals working on the WRF model.  The purpose of this document is twofold: Variations in style, while having the potential to impair readability, are more often a matter of personal preference. Therefore, regulations that impose arbitrary standards for capitalization, indenting, variable naming, are likely to overwhelm the volunteering scientist with minutae, and in any case, are difficult  to enforce. The goal of this document is to provide a common sense, minimal standard to which the WRF code should adhere.

There are two levels of compliance: "desired vs. required."  A minimum set of conventions, necessary to ensure that the resulting code lives up to the requirements for portability and efficiency, are required.  Developers who write code to the minimum required standard will be able to move the code into the WRF model without too much difficulty and with the assurance that the model will run as designed.  Code written to the more expansive desirable framework will be able to have the code included as part of the official release.

1.2 Conventions and Standards Scope

This document is only specifically directed towards the WRF predictive model itself, not the assimilation system, the initialization package, or post-processors.   With modification by members of those groups, this document could reflect the view of the entire WRF system.  However, it is possible that the initial versions of some ancillary packages may incorporate existing legacy code making it difficult to impose constraints that are too rigid. For example, pre-processors will be derived from pre-existing, dusty-deck Fortran codes.  Some of the working groups have even talked about using various higher-level scripting languages or GUI-based technologies as the basis of their products.  Clearly, neither of these scenarios is able to be fully addressed in a coding standards document which is admittedly focusing on a new Fortran 90 program.

This document is to provide guidance for developers as to what constitutes either a minimally acceptable form of source code in its developmental stages or the desirable form of a more mature package which is ready for inclusion in the WRF model.  If an initial development effort relies on a legacy Fortran 77 set of subroutines (such as is the case for the plethora of physics packages that are expected to be imported into WRF), then a certain latitude is provided for these codes.  However, only fully compliant codes will be adopted and imported into official WRF releases.

1.3. Definition of Terms

Following are definitions of terms used throughout this document.
 
         Term                                          Definition
driver layer Topmost level of calling tree, responsible for top-level control of initialization, I/O, time-stepping, starting and stopping nests, domain decomposition
mediation layer Intermediate layer between the top-level driver layer and the low-level model layer, contains information that pertains to both the driver and model laters such as the flow of control to compute a time step on a single domain and driver specific mechanisms for tiling and communication
model layer The layer which comprises the subroutines which perform the actual model computations
domain The logical (undecomposed, global) extent of one three-dimensional model grid.
patch The portion of a domain stored within a single address space; for example, one node of a distributed memory computer or the memory of a workstation. A patch includes all points of the domain that are stored locally on a memory, including extra memory for boundary regions. Extra local memory for message-passing halos is not considered part of the patch.
tile The extent of a patch that is computed in a single invocation of a model layer subroutine. A tile is a subdivision of a patch, and a patch may include only one or multiple tiles. Every cell within a patch is a member of one and only one tile. Tiles are the unit of work for a shared-memory thread, but a domain may also be multi-tiled for other reasons such as cache blocking.
halo region Extra memory allocated around a patch for exchange of information with neighboring packages through message passing. The halo region is part of local memory but is not considered to be part of the patch and needs only exist if the domain is decomposed over multiple patches. The patch size plus the halo region constitute the minimum memory size for the local processor.
boundary region Extra rows or columns around the logical domain that are used for certain lateral boundary conditions. Boundary regions are considered to be part of a patch (if an edge of the patch is on a domain boundary) and so, are also part of some tile. Boundary regions exist whether or not a domain is decomposed over multiple address spaces.
logical dimension A dimension of a domain, expressed as a start and end global index.
run dimension A dimension of a tile, expressed as a start and end global index over which the computation is to run
memory dimension A dimension of a local array intended to contain a patch and its halo region, if any, expressed as a start and end global index. Memory may be larger than size of the patch and the halo but never smaller.
patch dimension A dimension of a patch, expressed as a start and end global index.
state variable class of data that persists over the life of the model forecast and is the size of the memory dimension (such as wind and temperature)
i1 variable first intermediate class of data that persists over the duration of a time step and must be preserved between calls to different sections of the model (such as tendency arrays); defined and allocated at the beginning of the solver routine with the same dimensions as the state variables; is communicated between processors
i2 variable second intermediate class of data is strictly local to one section of the model and is not preserved with successive sections between a single time step (such as local storage arrays inside physics routines); defined and allocated within the local subroutine with the tile dimensions; private to a thread and not communicated between processors

1.4.  Document Outline

Section 2 of this document deals with conventions that are common to all of the WRF functional layers (driver, mediation and model).  Sections 3, 4, and 5 handle these WRF functional layers separately, respectively: driver, mediation and model.

2. Generic WRF Conventions

2.1. Language Selection

The language chosen for the WRF model is loosely referred to as Fortran 90.  Since the vast majority of developers working on the WRF project have extensive experience with Fortran 77, the move to use the current standard of the Fortran language is logical.  In this document, reference to the term Fortran 90 implies the Fortran language that permits the use of features such as dynamic stack and heap allocation, use of modules, defined types and pointer attributes.  Whether this is in reality Fortran 90, Fortran 95, or a later release is not too important.

Exceptions to this requirement are the use of cpp (the C language pre-processor) and the underlying language chosen for the MPI interface.  The use of cpp is required for conditional compilation and for permitting machine dependencies in the code.  The cpp utility also permits a more powerful and consistent method to include files from a specific directory.  This document is not applicable to the packages which are written as interfaces between WRF and MPI.  The need for cpp nearly implies an assumption for the requirement for some flavor of the Unix operating system.

All efforts should be made by developers to be writing only in strict compliance to the latest Fortran 90 standard.  To fix a few loose ends in the original Fortran 90, the Fortran 95 standard was adopted by ISO.  Both Fortran 90 and Fortran 95 selected features that were to be possibly discontinued in later releases (obsolescent features), and Fortran 95 actually removed some from the language standard.  In anticipation of the gradual shift to the ever-new definition of (yet another) Fortran, use of the following obsolescent and deleted features is discouraged (the D signifies that this is a desired convention, but not required to actually work):

As there are features within the Fortran 90 standard that are to be avoided because they are defined as obsolescent, there are also components that would unnecessarily cause user confusion, greatly degrade source code readability and possibly hamper performance.  These are capabilities that were possibly added to the language to answer the HPF or C++ challenge.  Following are Fortran 90 features that are also to be avoided (D is desired for inclusion into the official release, R is required for the code to probably work): While it is tempting to become bogged down by extensively listing pet peeves about stylistics restrictions that (subjectively) should be enforced, author restraint has been exercised to produce this small subset:

2.2. Acceptable cpp Uses

The C pre-processor is used extensively in WRF.
2.2.1 File Inclusion
The Fortran 90 standard provides for file inclusion with the INCLUDE statement.  Since there is no explicit way to reference files in other directories, without putting directory information in the INCLUDE line, it is preferable to use the #include capability found in cpp.  Files which are included in this fashion should be blocks of code that provide declarations, some sort of variable definition, subroutine comments, or interface blocks.

SUBROUTINE foo

   IMPLICIT NONE

   INTERFACE

#include <type_package1.int>

#include <type_package2.int>

#include <type_package3.int>

   END INTERFACE

when processed with cpp becomes:

SUBROUTINE foo

   IMPLICIT NONE

   INTERFACE

      SUBROUTINE type_package1 ( arg1 , arg2 )
         IMPLICIT NONE
         INTEGER :: arg1
         REAL :: arg2
      END SUBROUTINE type_package1

      SUBROUTINE type_package2 ( arg1 , arg2 )
         IMPLICIT NONE
         INTEGER :: arg1
         REAL :: arg2
      END SUBROUTINE type_package2

      SUBROUTINE type_package3 ( arg1 , arg2 )
         IMPLICIT NONE
         INTEGER :: arg1
         REAL :: arg2
      END SUBROUTINE type_package3

   END INTERFACE

2.2.2 Conditional Compilation
The WRF code is expected to have options, from which the user may choose, for many of the physical parameterizations.  If it is know a priori that certain packages will not be required, there is no need to compile or load those into the executable.  The cpp utility provides a compile-time ability to remove sections of code where calls to subroutines are made.

For example, let the following line be defined in the define.incl file:

#define type_package_init 1

In a subroutine which includes that file (define.incl), the cpp will remove the enclosing text if the macro is not defined.

SUBROUTINE foo

#include <define.incl>

#ifdef type_package_init
      CALL type_package_init
#endif

The ability to remove the subroutine calls is necessitated when the target routines will not be compiled and loaded into the executable.  Without the use of the #include, the user would see messages regarding unsatisfied externals.

2.3. Free Form Source

The fixed form source has been declared obsolescent in the Fortran 90 standard.  It is not necessary to write code that is wildly varying from standard Fortran 77 to comply with free form source.  The few important considerations are the following: C234567
      SU  BR
     * O U T
     * IN E foo !234567
      x = x + &
         (y+1)

      x = x +
     *    (y+1)

Free form source is desirable.  Since the rest of the code will be in free form, developers who use fixed format on their test codes will need to modify the Makefile structure to add the architecturally specific compiler flags to those particular routines to handle the mixed source forms.  -D

2.4. Explicit Variable Declarations

The IMPLICIT NONE facility was added to the Fortran 90 standard to move Fortran programmers away from the assumed implicit nature of the variables based upon the leading letter of the variable name.  With the use of modules, derived types, contained routines, it is imperative that each variable be explicitly defined in each subroutine where the declaration would be valid.  Otherwise it is possible to have a variable by the same name available to the subroutine that is being supplied from from a larger scope.  A simple example is given here.

program foo

   x=7

   call sub1

   print *,'x=',x

contains

   subroutine sub1

   character (len=10) :: x='abcdefghij'

   print *,'x inside sub1=',x

   end subroutine

end program foo

The output from the above program is given below.

 x inside sub1=abcdefghij
 x=   7.000000

By commenting out the character declaration for x in subroutine sub1, the output is as follows:

 x inside sub1=   7.000000
 x=   7.000000

While there are valid reasons to have variables provided by a scoping unit beyond the local routine boundaries, such as with routines inside a module sharing PRIVATE constants or arrays, this does not diminish the need to encourage users into a healthy appreciation of the time savings that is available through the use of explicit variable declarations and the use of the IMPLICIT NONE.

2.5. Global Variable Declarations

Through the use of modules, Fortran 90 provides a technique for global access to data in a more controlled format than was available from common blocks.  The desirable method to share information is therefore through the use of modules, and not common blocks.  Because all information that is declared at the beginning a module (before the contains statement) is available to other subroutines through USE association, care must be taken with these variables.
  • No common blocks, use modules.  Because of the architecture of the WRF data storage, common blocks that are not thread-safe will cause the code to fail when running with OpenMP. -D
  • Variables in the declaration area of a module that are not needed by outside routines (those subroutines not contained within the module) should be declared as PRIVATE.  Failure to do so will cause a possibly unreported conflict with variable values. -D
  • If a module defines a derived type variable, this module should be used for all subsequent variables of that type (do not make this a SEQUENCE type). -D
  • 2.6. Variable INTENT

    One of the useful debugging tools for Fortran 90 is the option to declare variables that are passed through a subroutine call as INTENT(IN), INTENT(OUT), or INTENT(INOUT).  If a developer tries to write to an INTENT(IN) variable or tries to access the value of an unassigned INTENT(OUT) variable, the compiler can catch these situations.  Additionally, the user is directly informed of the intended functioning of the subroutine when these are explicitly stated in the variable declaration section.

    Use of INTENT attributes on all variables passed through the argument lists of subroutines is desirable. -D

    2.7. Parallel and Threaded Choices

    The WRF model uses the OpenMP directives for what is called shared-memory parallelism, threaded code or for use on CCNUMA machines.  [What do we say about MPI and John's preprocessor?]

    3. Driver-Layer Subroutine Conventions

    The WRF driver-layer is to be generic enough to be replaced by another model's driver-layer.  This could imply a completely different data storage organization and parallel decomposition.  At this time, no requirements are placed on the model's driver-layer, as even the choice of language is not a requirement.

    4. Mediation-Layer Subroutine Conventions

    This section of the document is knowingly left empty to provide initial efforts for the model-layer specifically.

    5. Model-Layer Subroutine Definition Conventions

    5.1. Overview

    The purpose of the model-layer subroutine definition conventions are to ensure a well-defined interface to the other layers, primarily the mediation layer, within the WRF hierarchy that meets the requirements for performance, portability and single-source at the model layer. This interface specifies that subroutines be tile-callable, that is callable for an arbitrarily sized and shaped subdomain, without regard for memory size or logical domain size considerations. This provides the ability to control the number, size, and shape of tiles for multi-threading, cache-blocking, or vectorization without modification. It further insulates the code from cache and alignment issues.

    The WRF model supports a two-level parallel decomposition.  The model is able to run on distributed memory, shared memory, clusters of shared memory machines, as well as on single processor architectures.  If users adhere to the rules for passing variables between the solver routine and the physics package, then the parallel aspects of the WRF code will be maintained in the new parameterization.

    5.2. Variable and Subroutine Conventions

    The requirements of the model-layer interface are enforced by the following conventions:
    1. Logical, memory, run, and patch dimensions are provided unambiguously. At a minimum,
    the run dimensions must be provided through argument lists to allow multiple
    instances of the routine to be called concurrently within a patch. -R

    2. All distributed State and I1 data is passed through argument lists. -R

    3. All distributed local data is be defined to be tile-sized. -R

    4. Data may be only passed into and out of a subroutine as part of the data
    portion of a module only if all of the following are true: -D

    a) The subroutine is CONTAINed within the module
    b) The shared data is PRIVATE to the module
    c) The data is strictly local to a package; that it is not accessible from any subroutine outside the package
    d) The data is not distributed over either horizontal dimension
    e) The data is defined (written) at package initialization and then ingested (read) for the remainder of the model run
    5. A single model-layer wide module containing constants (such as gravity and circumference of the earth) is provided. It's name is module_constants and every routine that wants access to this should USE it. If a new package needs a constant not found in this file, the integrator will add the constant to module_constants and check for consistency/collisions with the rest of the model at that time. Otherwise we run the risk of having multiple and inconsistent constant definitions.  A rule of thumb is that users who have PARAMETER statements in their code probably need to add information to the module_constants file. -D
    6. Packages will provide separate initialization routines. If such initialization is unnecessary, provide a stub. That allows a generic init routine to be generated automatically at build time simply by including a call to package_init in every package that is linked in. Packages will not use first-call "latch" mechanisms for initialization. Communication between the initialization routine and the rest of the package will respect the rules for data interchange specified above.  The package_init routine is not threaded but it is able to be called in parallel.  This routine may provide diagnostic print information. -R [This *print* thing violates some rules.  Is that OK?]

    7. A storage and loop order is prescribed: (i,j,k). The interface with the WRF solver routine and the package initialization route must both use this ordering. -R

    8. Internal to the physics package, ordering may be with the same as prescribed to avoid the penalty of the additional cost of copying or data transposition.  If there is no significant performance issue with the internal choice of the loop ordering, the code may be implemented into the standard WRF release.

    9. Inline documentation for each physics package is minimally required to have all of the following items at the top of each routine: -D

    10. Each physics package is a separate module.  Each package has its own initialization routine.  Other than the initialization routine, each package has a single entry point which is to be used from outside the module. -R

    11. Subroutines in physics modules do not call subroutines in other physics modules.  All physics packages are called from a package driver routine.  For example, all radiation modules are called from the rad_driver routine (which is called by the solver subroutine). -R

    12. Physics packages are tile-callable from the package driver.  If the package has loops over decomposed dimensions (i and j), then the internal subroutines must also be tile-callable. -R

    13. Other than diagnostic print statements preceeding a call to the provided error handler    routine and print information provided in the package_initialization routine, no I/O is permitted in any of the model layer routines. -D

    14. STOP statements are not permitted in the model-layer routines.  Use the provided error handler routine instead. -D

    15. Derived types are not passed through the argument lists from the solver to the physics package driver routine or the physics package initialization routine. -R

    16. Each physics module should include the define.inc file as a #include option.  This will allow the code to be modified with conditional compilation.  The package initialization and main entry routines should have the executable portion of the code surrounded by appropriate #ifdef statements. -D

    17. No heap memory allocation is permitted in the physics routines (such as with the use of an ALLOCATE statement).  The user may allocate as many I2 work arrays as necessary (tile-sized and automatic), which are stack based and evaporate on exiting the routine. -R

    5.3. File, Module and Subroutine Naming Conventions

    The following are the types of physics packages in WRF:
     
                   Type of 
            Physics Package
      Mnemonic 
     Abbreviation
    Radiation          rad
    Cumulus          cum
    Explicitly Resolved Moisture          exp
    Planetary Boundary Layer          pbl
    Surface Processes          sfc
    Soil             soi

    The file which contains the subroutines and variables for the radiation package from Smith, would be called module_rad_smith.F (note that this is a module!).  If there is an include file (such as for local DATA statements), this file would be called rad_smith.inc.  When subroutines wish to access this module through USE association, the module name is module_rad_smith.  The initialization routine in the module is named rad_smith_init.  The entry routine into the module (the routine which the solver routine calls) is named rad_smith_driver.  The user needs to modify the define.inc file to include the line for the particular physics package (i.e. #define rad_smith). -R

    5.4. Miscellaneous Variable Assumptions

    6. Issues

    1. How to deal with lateral boundaries cleanly and removing such considerations
    from routines that shouldn't be considering them. For example, some of the
    physics modules in the current prototype are checking for lateral boundaries to
    avoid computing on the last row or column. This shouldn't be in the routine
    itself since physics is fundamentally column-oriented and doesn't care where it
    is in a domain. Rather, this should be addressed by the caller in determining
    the run-dimensions for the routine.

    2. Likewise, grid staggering issues crop up at various points in the low level
    routines when they don't need to be or shouldn't be addressed at that level.  What is necesary to permit users to know about vertical staggering?  [Could this be handled with naming conventions?]

    3. Over-dimensioning of local arrays to allow computation out onto temporary
    "halo" regions. Some of this is happening at points within the code now such as
    the dpzxy arrays in advance_uv. Not a problem with doing this but it needs to
    be conventionalized.

    4. If we chose an I-innermost storage order, we need to prescribe a recommended
    method for handling minor loops over vertical in physics packages where the
    number of iterations is a function of the state of the column -- for example in
    PBL or radiation code. This may require some kind of conditional mask over the
    inner-most I-loops within those loops. We do not want to allow the practice of
    over-iterating some columns (as is done in MM5's implementation of the
    Blackadar scheme, for example) because this introduces a non-local dependency
    and may produce different answers than if the number of iterations is computed
    locally for each column.


    5. Adjoint required for each physics package: who does this?

    6. External documentation for each physics package: who does this, what format?

    Appendix A: Draft Model-Layer Module Template

    Following is a model-layer template for a physics routine.  For the purposes of this example, we assume that the package is a radiation scheme from Smith.  Note that the continuation mark ("&") has been o0mitted from the following lines for purported source code.
     
     
    #include <define.inc>

    !  MODULE INFORMATION:
    !  Author: Something Smith
    !  Email: smith@big_wig.edu
    !  Reference papers: "Cool Radiation Stuff", MWR 123
     

    MODULE module_rad_smith

    PRIVATE

       ! Variable declarations: Package-local, non-distributed, write-once data

    CONTAINS

      SUBROUTINE rad_smith_entry ( arg3d1, arg3d2, ... ,
                                   arg2d1, arg2d2, ... ,
                                   arg1d1, ... ,
                                   ids, ide, jde, jde, kds, kde,
                                   ips, ipe, jps, jpe, kps, kpe,
                                   ims, ime, jms, jme, kms, kme,
                                   its, ite, jts, jte, kts, kte )

         USE module_constants

         IMPLICIT NONE

         ! Dimension arguments, since some compilers want these to come first.
     
         INTEGER, INTENT(IN) :: ids, ide, jde, jde, kds, kde, ! domain indices
                                ips, ipe, jps, jpe, kps, kpe, ! patch indices
                                ims, ime, jms, jme, kms, kme, ! memory indices
                                its, ite, jts, jte, kts, kte  ! tile indices

         !  3D, 2D, 1D, 0D argument declarations, declare INTENT IN, OUT, or INOUT
         !  as appropriate. 
         !  Note that memory dimensions are used in range expressions

         REAL, DIMENSION( ims:ime, jms:jme, kms:kme ), INTENT(IN) ::
                       arg3d1,   ! variable definition
                       arg3d2    ! variable definition
         REAL, DIMENSION( ims:ime, jms:jme, kms:kme ), INTENT(OUT) ::
                       arg3d3,   ! variable definition 
                       arg3d4    ! variable definition
         REAL, DIMENSION( ims:ime, jms:jme, kms:kme ), INTENT(INOUT) ::
                       arg3d5,   ! variable definition 
                       arg3d6    ! variable definition

         REAL, DIMENSION( ims:ime, jms:jme ), INTENT(IN)    :: 
                       arg2d1    ! variable definition
         REAL, DIMENSION( ims:ime, jms:jme ), INTENT(OUT)   :: 
                       arg2d2    ! variable definition
         REAL, DIMENSION( ims:ime, jms:jme ), INTENT(INOUT) ::
                       arg2d3    ! variable definition

         REAL, DIMENSION( kms:kme ), INTENT(IN) :: arg1d1    ! variable definition

         !  3D, 2D declarations of local data. Note that these use run dimensions
         !  because we want to avoid overflowing thread stacks. Each tile allocates
         !  only what it needs for the tile.

         REAL, DIMENSION( its:ite, jts:jte, kts:kte ) :: 
                       loc3d1,   ! variable definition 
                       loc3d2    ! variable definition
         REAL, DIMENSION( its:ite, jts:jte ) :: 
                       loc2d1,   ! variable definition 
                       loc2d2    ! variable definition
         REAL, DIMENSION( kms:kme ) :: 
                       loc1d1,   ! variable definition 
                       loc1d2    ! variable definition

    #ifdef rad_smith

         ! Executable part of main entry subroutine.

    #endif

      END SUBROUTINE rad_smith_entry
     


    ! Standard init subroutine

      SUBROUTINE rad_smith_init

         USE module_constants

         IMPLICIT NONE

      ! Any package specific beginning of model run initializations

      ! These may only modify data listed as PRIVATE at the top of the
      ! module and this data must not be distributed -- that is, must not
      ! have an i or j index.

    #ifdef rad_smith

         ! Executable part of initialization subroutine.

    #endif

      END SUBROUTINE rad_smith_init

    END MODULE