MOM6

MOM6 code style guide

MOM6 makes use of Fortran2003 and later extensions but only when supported by all available compilers. We try to avoid the use of very modern Fortran constructs that can limit portability. To help keep the code readily understandable, this page makes recommendations about how to use or not use the various features of the modern Fortran language.

Code style is typically a personal choice, but when styles clash it can lead to discord. These standards have been adopted in an attempt to promote harmony and clarity.

White space

Line length

Some compilers handle very long lines of code gracefully, but MOM6 needs to adhere to the Fortran standard, which is 132 characters for code, after any macro expansion. MOM6 does use macros for some memory declarations, so we need to build in some added space in setting MOM6 guidelines:

Local variables

Block constructs

Loop indices

Soft case convention

Global / module data

Module use statements

Implicit variables

Array syntax

Data flow

Documentation in code

Optimization for divides

Divisions are prone to NaNs and relatively expensive. An optimizing compiler will often rearrange math which makes debugging divisions by zero harder to catch.

Arithmetic reproducibility

Floating point operations are sensitive to the order of operations (associativity), which can not generally be guaranteed due to compiler serialization and optimization.

Addition

Addition operations must be done in pairs. When more than one addition is required, the order should be specified using parentheses.

Ideally, the order of operation should be chosen to give the best accuracy. For example, if a = 1. b = -1. and c = 1.e-20, then the order should be chosen to preserve the residual value.

Not only does this impact reproducibility, but the second choice is more accurate and avoids a potential division by zero.

All operations should be ordered, but no particular ordering is enforced. Contributors are encouraged to consider the most accurate order of operations. In some cases the order of sums can be chosen to give expressions that yield identical answers if the underlying horizontal coordinate is rotated by 90 or 180 degrees, which would define the preferred order of operations.

sum() intrinsic

We avoid the Fortran sum() intrinsic since the result is dependent on the order of operations within the summation. Using explicit loops allows us to define the order of summation. So

a = sum(b(:))

should be

a = 0.
do k = 1, nz
  a = a + b(k)
enddo

The prod() and matmul() intrinsics should also not be used.

Global summation

Floating point operations across MPI ranks are volatile, since the order can change depending on the state of the network. Functions such as MPI_Reduce will not generally be reproducible when used for floating point arithmetic.

When performing summations over MPI ranks, use the reproducing_sum function.

use MOM_coms, only: reproducing_sum
...
sum = reproducing_sum(array(:,:))

Multiplication

Multiplication is also non-associative and thus not reproducible, but the impact is typically small. Results may depend on the order of operations, most often in the least significant bit of the fractional component.

In single precision, if a = b = 1 + 2**-23 and c = 1.5, then the following calculations differ:

Parentheses in multiplication operations are currently not enforced, but contributors should consider using them when applicable.

Transcendental functions

Use of transcendental functions, such as trigonometric functions, non-integer powers, and logarithms, are often implementation-dependent and should be avoided when possible.

Exponent operator

The exponent operator, a**b should be used sparingly, since compilers will often internally replace it with pow(a, b), which is often computed as a transcendental function, exp(b * log(a)). Even small integral power, such as a**3, have been known to be replaced with pow(a, 3). To maximize reproducibility, integral powers should be explicitly computed, e.g. a3 = a * a * a.

Square roots (a**0.5) should always use the sqrt() intrinsic. An IEEE-754 compliant sqrt function must be exactly rounded.

Cube roots (a**(1./3.)) should be avoided, the MOM6 intrinsic cuberoot should be used. This is not exactly rounded, but it is reproducible.

Module structure

Every module follows this pattern:

!> Brief module description
module MOM_module_name

! This file is part of MOM6. See LICENSE.md for the license.

use MOM_some_module, only : specific_symbol
use MOM_other,       only : other_thing

implicit none ; private

#include <MOM_memory.h>

public :: exported_routine_1, exported_routine_2

!> Control structure for this module
type, public :: module_CS ; private
  real :: param         !< Description [units]
  integer :: id_diag = -1 !< Diagnostic ID for some_field
end type module_CS

contains

!> Initialize the module, read parameters, register diagnostics
subroutine module_init(Time, G, GV, US, param_file, diag, CS)
  ! ... call log_version, get_param, register_diag_field ...
end subroutine module_init

!> Deallocate module memory
subroutine module_end(CS)
  ! ... cleanup ...
end subroutine module_end

!> \namespace MOM_module_name
!! Extended description, references, and equations
end module MOM_module_name

Key rules:

Naming conventions

Memory macros

Array dimensions use preprocessor macros from MOM_memory.h:

Unit documentation

MOM6 uses a dimensional annotation system for every real variable. Units are documented in square brackets at the end of comments, using a two-part notation:

[rescaled_dimensions ~> MKS_equivalent]

Dimensional symbols

Symbol Physical Dimension MKS Unit
Z Vertical depth/distance m
L Horizontal length m
T Time s
H Layer thickness m (Boussinesq) or kg m-2
R Density kg m-3
Q Enthalpy J kg-1
C Temperature degC
S Salinity ppt
A Arbitrary/generic units a

Examples

real :: velocity     !< Horizontal velocity [L T-1 ~> m s-1]
real :: pressure     !< Hydrostatic pressure [R L2 T-2 ~> Pa]
real :: thickness    !< Layer thickness [H ~> m or kg m-2]
real :: diffusivity  !< Vertical diffusivity [Z2 T-1 ~> m2 s-1]
real :: slope        !< Isopycnal slope [Z L-1 ~> nondim]
real :: efficiency   !< Mixing efficiency [nondim]
real :: field        !< A field in arbitrary units [A]
real :: Z_to_m       !< Scaling factor [m Z-1 ~> 1]

Unit annotation rules

  1. Every real variable must have units in [brackets] at the end of its comment
  2. Canonical symbol ordering: consistent order (e.g., H L2 not L2 H)
  3. Boussinesq variants first: [H ~> m or kg m-2] when units differ by approximation
  4. Simplified expressions only: write [T2 Z-1 ~> s2 m-1], not [H Z T-1 / H Z2 T-3 = T2 Z-1 ~> s2 m-1]
  5. Exponent notation: m-1, s-2, kg-3 (no slashes like 1/m)
  6. No extra spaces inside brackets
  7. Nondimensional: use [nondim]
  8. Arbitrary/generic: use [A] or [A ~> a], never [arbitrary]
  9. Scaling factors: [target source-1 ~> 1], e.g., [Z m-1 ~> 1]

Doxygen documentation

Comment syntax

Requirements