Parafem f90 Coding
Parafem f90 Coding
Contents
1.0 Introduction 2.0 Documentation 2.1 External Documentation 2.2 Internal Documentation 3.0 Coding Rules For Packages 4.0 Guidance for The Use Of Dynamic Memory 5.0 Coding Rules For Routines 5.1 Banned Fortran Features 5.2 General Style Rules 5.3 Use Of New Fortran Features 6.0 Enforcing These Standards References Appendix A: Modification History Appendix B: Standard Headers Appendix C: Examples
1.0 Introduction
The aim of this document is to provide a framework for the use of Fortran 90 in ParaFEM and related projects and thereby to facilitate the exchange of code between them. In order to achieve this goal we set standards for the documentation of code, both internal and external to the software itself, as well as setting standards for writing the code. The latter standards are designed to improve code readability and maintainability as well as to ensure, as far as possible, its portability and the efficient use of computer resources. Back to top
2.0 Documentation
Documentation may be split into two categories: external documentation, outside the code; and internal documentation, inside the code. These are described in sections 2.1 and 2.2 respectively. In order for the documentation to be useful it needs to be both up to date and readable at centres other than that at which the code was originally produced. Since these other centres may wish or need to modify the imported code we specify that all documentation, both internal and external, must be available in English.
16/03/2010
Page 2 of 11
A User Guide: this describes in detail all inputs into the package. This includes both subroutine arguments to the package and any switches or 'tuneable' variables within the package. Where appropriate default values; sensible value ranges; etc should be given. Any files or namelists read should be described in detail.
where the text in [] is to be replaced appropriately. Other comments: these are aimed at a programmer reading the code and are intended to simplify the task of understanding what is going on. These comments must be placed on the line either immediately before or after the code they are commenting. The recommended format for these comments is:
! [Comment]
where the text in [] is to be replaced appropriately. Meaningful names: code is much more readable if meaningful words are used to construct variable & subprogram names. It is a requirement that all internal documentation be written in English. Back to top
16/03/2010
Page 3 of 11
documented in and set by a script appropriate to the operating system in use (i.e. a posix script for the unix operating system). Internally the package may use Modules - for example the set up and running procedures may communicate in this way. Interface blocks must be provided for the set up and running procedures (possibly via module (s)). This allows the use of assumed shape arrays, optional arguments, etc as well as allowing the compiler to check that the actual and dummy arguments types match. If variables in these external argument lists are of derived type, a module must be supplied which contains the type definition statements required to make these derived types available to the routine calling the package. The package shall not terminate program execution. If any error occurs within the package it should gracefully exit, externally reporting the error via an integer variable in its argument list (+ve = fatal error). Packages should also write a diagnostic message describing the problem, using fortran I/O, to an 'error stream' unit number selectable via the package's set up routine. Note that if the package starts at the unix script, rather than Fortran, level making a graceful exit includes returning control to the package's script level by using a STOP statement in the Fortran part of the package. Precompilers: these are used, for example, to provide a means of selecting (or deselecting) parts of the code for compilation. Clearly to simplify portability of code we need to all use the same precompiler, and this needs to be available to every centre. The C precompiler is probably the best option since it will be found on all machines using the unix operating system. All unix scripts must be written using the posix shell. This is a standardized shell, available on all POSIX compliant unix systems, with many useful features. Each program unit should be stored in a separate file. Back to top
16/03/2010
Page 4 of 11
memory. 7. Always test the success of a dynamic memory allocation and deallocation. The ALLOCATE and DEALLOCATE statements have an optional argument to let you do this. Back to top
16/03/2010
Page 5 of 11
Implicitly changing the shape of an array when passing it into a subroutine. Although actually forbidden in the standard it was very common practice in FORTRAN 77 to pass 'n' dimensional arrays into a subroutine where they would, say, be treated as a 1 dimensional array. This practice, though banned in Fortran 90, is still possible with external routines for which no Interface block has been supplied. This only works because of assumptions made about how the data is stored: it is therefore unlikely to work on a massively parallel computer. Hence the practice is banned. Back to top
Write:
! Initialize Variables
16/03/2010
Page 6 of 11
Similarly, try to make equations recognizable and readable as equations. Readability is greatly enhanced by starting a continuation line with an operator placed in an appropriate column rather than ending the continued line with an operator. Do not use tab characters in your code: this will ensure that the code looks as intended when ported. Separate the information to be output from the formatting information on how to output it on I/O statements. That is don't put text inside the brackets of the I/O statement. Delete unused header components. Back to top
16/03/2010
Page 7 of 11
Option III: Automatic Interface Blocks Fortran 90 compilers can automatically provide explicit interface blocks between routines following a Contains statement. The interface blocks are also supplied to any routine Useing the module. Thus, it is possible to design a system where no Interface blocks are actually coded and yet explicit interface blocks are provided between all routines by the compiler. One way to do this would be to 'modularise' the code at the f90 module level, i.e. to place related code together in one module after the Contains statement. Routine a, in module A calling routine b in module B would then only have to Use module B to be automatically provided with an explicit interface to routine b. Obviously if routine b was in module a instead then no Use would be required. One consequence of this approach is that a module and all the routines contained within it make up a single compilation unit. This may be a disadvantage if modules are large or if each module in a package contains routines which Use many other modules within the package (in which case changing one routine in one module would necessitate the recompilation of virtually the entire package). On the other hand the number of compilation units is greatly reduced, simplifying the compilation and configuration control systems. Conclusion Options II) and III) both provide workable solutions to the problem of explicit interface blocks. Option III is probably preferable as the compiler does the work of providing the interface blocks, reducing programming overheads, and at the same time guaranteeing that the interface blocks used are correct. Which ever option is chosen will have significant impact on code management and configuration control as well as program design. Subroutine arguments should be ordered in a logical and consistent manner. The suggested scheme is to place those arguments with INTENT(IN) first, then INTENT(INOUT) followed by INTENT (OUT). It is convenient to order the arguments alphabetically for each INTENT, e.g.:
SUBROUTINE READALL_ELS_BINARY_FNAME(fname,iel_start,ndim,nels,nn, & npes,numpe,g_num_pp) rather than SUBROUTINE READALL_ELS_BINARY_FNAME(fname,g_num_pp,nn,ndim,nels, & iel_start,numpe,npes)
Array notation should be used whenever possible. This should help optimization and will reduce the number of lines of code required. To improve readability show the array's shape in brackets, e.g.:
1dArrayA(:) = 1dArrayB(:) + 1dArrayC(:)
When accessing subsections of arrays, for example in finite difference equations, do so by using the triplet notation on the full array, e.g.:
2dArray(:, 2:len2) = scalar &
Always name 'program units' and always use the End program; End subroutine; End interface; End module; etc constructs, again specifying the name of the 'program unit'. Use >, >=, ==, <, <=, /= instead of .gt., .ge., .eq., .lt., .le., .ne. in logical comparisons. The new syntax, being closer to standard mathematical notation, should be clearer. Don't put multiple statements on one line: this will reduce code readability. Variable declarations: it would improve understandability if we all adopt the same conventions for
16/03/2010
Page 8 of 11
declaring variables as Fortran 90 offers many different syntaxes to achieve the same result. Don't use the DIMENSION statement or attribute: declare the shape and size of arrays inside brackets after the variable name on the declaration statement. Always use the :: notation, even if their are no attributes. Declare the length of a character variable using the (len = ) syntax. We recommend against the use of recursive routines on efficiency grounds (they tend to be inefficient in their use of cpu and memory). It is recommended that new operators be defined rather than overload existing ones. This will more clearly document what is going on and should avoid degrading code readability or maintainability. To improve portability between 32 and 64 bit platforms, it is extremely useful to make use of kinds to obtain the required numerical precision and range. A module should be written to define parameters corresponding to each required kind, for example:
Integer, parameter :: single & ! single precision kind. = selected_real_kind(6,50) :: double & ! double precision kind. = selected_real_kind(12,150)
Integer, parameter
This module can then be Used in every routine allowing all variables declared with an appropriate kind e.g.
Real(single), pointer :: geopotential(:,:,:) ! Geopotential height
Back to top
References
Kalnay et al. (1989) "Rules for Interchange of Physical Parametrizations" Bull. A.M.S., 70 No. 6, p 620. Back to top
16/03/2010
Page 9 of 11
Back to top
Appendix C: Examples
SUBROUTINE READALL_ELS_BINARY_FNAME(fname,iel_start,ndim,nels,nn, & npes,numpe,g_num_pp) !/****f* devel_mpi/readall_els_binary_fname !* NAME !* SUBROUTINE: readall_els_binary_fname !* SYNOPSIS !* Usage: CALL readall_els_binary_fname(fname,iel_start,ndim,nels,nn, & !* npes,numpe,g_num_pp !* FUNCTION !* Read the node numbers for each element from a binary file and !* distribute the data to the appropriate processor. !* !* The strategy is to send all the data to all the processors. !* Afterwards each processor records only what it is interested in. !* !* 1. The node numbers for each element in the mesh are read from the !* file FNAME into a global array G_NUM(NOD,NELS) by the processor !* of rank NPES-1. G_NUM has the dimensions NOD (number of nodes per !* element) and NELS (total number of elements in the mesh). !* !* 2. G_NUM is broadcast from the processor of rank NPES-1 to the rest !* of the processors (0, 1, 2, ... , npes-2) !* !* 3. Each processor makes a copy of its own elements in the !* array G_NUM_PP(NOD,NELS_PP). The local elements are those numbered !* contiguously from IEL_START (the first element number on the !* processor) to IEL_START + NELS_PP, where NELS_PP is the total !* number of elements on the processor. !* !* 4. The global array G_NUM is deallocated to save space. !* ARGUMENTS !* INTENT(IN) !* !* fname : Character !* Filename. Any value accepted. !* Maximum character length not specified. !* !* iel_start : Integer !* The ID of the first finite element on the local !* processor (NUMPE) !* !* ndim : Integer !* Number of dimensions. !* !* nels : Integer !* Total number of elements in the mesh. !* !* nn : Integer !* Total number of nodes in the mesh. !* !* numpe : Integer !* The local processor ID or rank. !* !* INTENT(INOUT) !* !* g_num_pp(nod,nels) : Integer !* Global array with the nodal connectivity. !* !* RESULT !* Modifies the values of g_num_pp(nod,nels) in the calling program. !* AUTHOR !* Vendel Szeremi !* CREATION DATE !* 02.01.2007 !* COPYRIGHT !* (c) University of Manchester 2007-2010
16/03/2010
Page 10 of 11
!****** !* Place remarks that should not be included in the documentation here. !* !* There is currently no support for different element types in the !* same mesh. All finite elements in a mesh are assumed to be the same !* in this implementation. !* !* An improvement would be to read and broadcast without creating the !* global array. !*/ IMPLICIT NONE
INTEGER
:: bufsize ! size of buffer in broadcast :: iel ! loop counter :: nod ! nodes per element :: iel_start ! ID of first element on local processor :: ndim ! number of dimensions in problem :: nels ! total number of elements in mesh :: nn ! total number of nodes in mesh :: npes ! number of processors in COMM_WORLD :: numpe ! local processor ID or rank :: g_num_pp(:,:) ! node numbers for each local element :: g_num(:,:) ! node numbers for all elements in the mesh
INTEGER
INTEGER
INTEGER,INTENT(IN)
INTEGER,INTENT(IN)
INTEGER,INTENT(IN)
INTEGER,INTENT(IN)
INTEGER,INTENT(IN)
INTEGER,INTENT(IN)
INTEGER,INTENT(INOUT)
INTEGER,ALLOCATABLE
CHARACTER(*), INTENT(IN) :: fname ! filename !-----------------------------------------------------------------------------! 1. Allocate and populate global array G_NUM !-----------------------------------------------------------------------------nod = UBOUND(g_num_pp,1) ALLOCATE(g_num(nod,nels)) IF(numpe==npes)THEN OPEN(21,FILE=fname, STATUS='OLD', ACTION='READ', FORM='UNFORMATTED') READ(21) g_num CLOSE(21) END IF !-----------------------------------------------------------------------------! 2. Broadcast global array to all processors !-----------------------------------------------------------------------------bufsize = UBOUND(g_num,1)*UBOUND(g_num,2) CALL MPI_BCAST(g_num,bufsize,MPI_INTEGER,npes-1,MPI_COMM_WORLD,ier) !-----------------------------------------------------------------------------! 3. Copy own data to local array and deallocate global array
16/03/2010
Page 11 of 11
!-----------------------------------------------------------------------------g_num_pp = 0 ielpe = iel_start DO iel = 1,UBOUND(g_num_pp,2) g_num_pp(:,iel) = g_num(:,ielpe) ielpe = ielpe + 1 END DO DEALLOCATE(g_num) RETURN END SUBROUTINE READALL_ELS_BINARY_FNAME
16/03/2010