Coding conventions

Fortran Code Conventions

  • Naming Convention: We follow the Snake Case convention, meaning variable, module, procedure names, and code directives are written in lowercase and separated by underscores. For example:
    • this_is_a_variable
    • subroutine this_is_a_subroutine(var_a, var_b)
  • Capitalization:

    • Capital letters are reserved for global constants or special libraries. For example:
      • Working precision descriptor: 1.0_FP
      • Global constants: TWO_PI
      • Exdternal libraries: use MPI, call MPI_allreduce(...), MPI_COMM_WORLD
    • OpenMP directives should be written in lowercase: !$omp parallel do private(var_a, var_b).
  • Line Length:

    • Limit line length to 80 characters.
    • Break long lines using the continuation character & at appropriate places. For strings, place & at the end of the line and at the beginning of the next.
    • Strings may be an exception to this rule.
  • Indentation:

    • Use 4 spaces for indentation (not tabs).
    • Indent after (sub)module and program declarations, type, subroutine/function declarations, and each level of do loops, if clauses, and case statements.
  • Spaces:

    • Separate argument lists with spaces: call some_subroutine(var_a, var_b).
    • Add white space in variable declarations: real(FP), intent(in), dimension(dim) :: arr.
    • Add spaces around formulas, except for the power operator: var_a = 3.0_FP * x**2 + sin(var_b) / var_c.
    • Use spaces at do loops and if clauses: do i = 1, 3, if (i == 7) then.
    • Include spaces for logical operations: a .or. b .and. c.
    • Avoid spaces in array bounds definition: arr(1:3, 1:5).
    • Avoid spaces in pass-by-name arguments: call sub(x, y, z, mode=4, debug=1).
    • Avoid trailing white spaces.
  • Empty Lines:

    • Use a single empty line between type/subroutine/function definitions.
    • Insert a single empty line after declaring dummy variables.
    • Add empty lines to separate logically coherent code blocks.
    • Avoid double empty lines.
  • Access/Privacy:

    • Use only when importing modules, except for external libraries like MPI, NetCDF, etc.
    • Always specify intent for dummy variables.
    • Explicitly specify public/private access for module components.
    • Use default(none) for OpenMP parallel regions.
  • Documentation:

    • Comments should follow the principle: as much as necessary, as little as possible. Comment on complex code, but don’t over-comment simple or self-explanatory lines. Be concise, clear, and check for spelling mistakes.
    • Ideally, code should be self-descriptive; comments help clarify but should not replace clear code.
    • Provide a description for every variable, module/type/subroutine/function declaration using special FORD comments (!!), and normal comments (!) for everything else.
    • Include references (e.g., weblinks, books) when relying on external solutions or ideas.
    • For submodules, avoid redundant documentation. Document the interfaces in the module, but not the implementation in the submodule.
  • Naming Conventions:

    • Place each module, submodule, and program in a separate file at a logical location in the source folder.
    • Module names should end with _m (e.g., some_module_m), and the corresponding file should be named some_module_m.f90.
    • Submodule names should end with _s (e.g., some_submodule_s), and the corresponding file should be named some_submodule_s.f90.
    • Type names should end with _t (e.g., some_type_t).
    • Variable names should be at least 3 characters long (except for common loop variables like i, j, l). Use descriptive names like mass_electron instead of vague ones like m or me.
    • For long descriptive names, use a shorter alternative and provide a comment explaining its meaning.
    • Unit test names should reflect the tested module/procedure names. For example, test_some_module should be placed in tests/test_some_module_m.pf, corresponding to the file some_module_m.f90.
  • Error Handling:

    • Use PARALLAX error handling functionality (see error handling guide).
    • Provide meaningful error messages with relevant information.
  • Additional Guidelines:

    • Avoid obsolete Fortran 77 features like common blocks and goto (except for error handling).
    • Always use implicit none at the beginning of modules.
    • Use symbolic comparison operators, e.g., if (a /= b) then instead of if (a .ne. b) then.
    • For procedures with long or complex argument lists, use pass-by-name. For example, use call sub(x, y, z, mode=4, debug=1) instead of call sub(x, y, z, 4, 1). If necessary, separate arguments across multiple lines.

Example code

Here is an example source code for the file some_module_m.f90, illustrating the conventions mentioned above:

module some_module_m
    !! Description of module --> in-browser documentation
    ! Some further comments, not meant to enter in-browser doucumentation
    use MPI
    use precision_m, only : FP
    use error_handling_m, only : handle_error, error_info_t
    use status_codes_m, only : PARALLAX_ERR_<CODE>
    use some_other_module_m, only : some_variable
    implicit none

    type, public :: some_type_t
        !! Description for some_type_t
        integer, private :: foo
        !! Description for foo
        real(FP), public :: foo_bar
        !! Description for foo_bar
    contains
        procedure, public :: something_useful

    end type

contains

    subroutine something_useful(self, dim_arr, arr)
        !! Description for subroutine
        class(some_type_t), intent(inout) :: self
        !! Instance of the type
        integer, intent(in) :: dim_arr
        !! Dimension of arr
        real(FP), intent(out), dimension(arr) :: dim_arr
        !! Some array

        integer  :: i, foo, ierr
        real(FP) :: tmp

        ! This loop does something very useful ;)
        !$omp parallel default(none) private(i, tmp) &
        !$omp shared(self, some_variable, dim_arr)
        !$omp do
        do i = 1, dim_arr
            tmp = sqrt(2.0_FP) * i
            dim_arr(i) = some_variable * self%foo_bar + tmp
        enddo
        !$omp end do
        !$omp end parallel

        ! Sum foo over all MPI processes
         call MPI_allreduce(MPI_IN_PLACE, self%foo, count=1, &
             datatype=MPI_INTEGER, op=MP_SUM, comm=MPI_COMM_WORLD, &
             ierr=ierr)

        ! Some error handling statement 
        if (foo == 0) then
            call handle_error('foo must not be zero', &
                PARALLAX_ERR_<CODE>, __LINE__, __FILE__, &
                additional_info=error_info_t('foo=', [foo]))
        endif

    end subroutine

end module