Coding Guidelines

  • Indent with two spaces per indentation level. Do not use tabs.

  • Keep lines strictly under 80 characters.

  • Use American English spelling.

  • Vertical alignment is important!

  • Separate subroutines/functions with exactly two blank lines. There should never be more than two consecutive blank lines in any Fortran file.

  • Format a subroutine as in the following examples:

    subroutine read_spectrum(spectrum, filename, freq_unit)
    
      type(pulse_t),              intent(inout) :: spectrum
      character(len=*),           intent(in)    :: filename
      character(len=*), optional, intent(in)    :: freq_unit
    
      character(len=unit_l) :: file_freq_unit
      real(idp), allocatable :: w(:), spectrum_real(:), spectrum_imag(:)
      integer :: i, nt
      real(idp) :: wmax
      real(idp) :: dt
    
      character (len=error_l) :: routine_name = 'read_spectrum'
      call add_backtrace(module_name, routine_name)
    
      [...]
    
      call del_backtrace()
    
    end subroutine read_spectrum
    
    
    subroutine getanrelax(an, n, alpha, nalpha)
    
      integer,   intent(in)    :: n
      real(idp), intent(inout) :: an(0:n)
      real(idp), intent(in)    :: alpha
      integer,   intent(inout) :: nalpha
    
      [...]
    
    end subroutine getanrelax
    
    • After the subroutine definition, there should be a blank line, followed by the declaration of the dummy variables.

    • The dummy variables must be declared in the same order as they appear in the subroutine definition, with one variable per line. The only exception are certain explicit shape declarations, c.f. the example for getanrelax above, where the Fortran standard demands that n is defined before an. Intent must be declared for all variables. The optional keyword, intents, and the double-colons should all be lined up vertically.

    • Any dummy variable of type character must be declared with len=*.

    • After the declaration of the dummy variables, there must be a blank line, followed by the declaration of the local variables.

    • After the declaration of the local variables, the routine_name should be set and the routine should be added to the backtrace. This can be omitted for routines that are performance-critical.

    • If a backtrace entry was added in the beginning of the routine, it should be deleted again at the end of the routine. Note that if you have other exit-points in the routine (return statements), you must make sure that del_backtrace is called.

    • The end subroutine line must include the subroutine name.

  • Logical sections within a routine should be separated by single blank lines within one routine. There must never be more than one consecutive blank line inside of a routine.

    If there are blocks within a loop, the loop do, and end do must also be surrounded by blank lines:

    ! [...]
    iter = para%oct%iter_start
    
    octloop: do
    
      ! Read dynamically chaning OCT params
      call read_oct_params(para, pulses01(:,S0))
      ![...]
    
      if (iter > para%oct%iter_stop) exit octloop
    
      ! calculate co-states
      call get_chis(chis, fw_states_T, targets)
      ! [...]
    
      ! Cleanup
      ! [...]
    
    end do octloop
    
    ! [...]
    

    The same holds for if or other block statements.

  • If there are many levels of nested loops, or if the loop body is longer than about 40 lines, name the loop (octloop in the example above).

  • For long or nested if blocks, consider adding comment to the else clauses

    if (targets(s)%in_liouville_space) then
      if (complex_flag) then
        ! [...]
      else
        ! [...]
      end if
    else ! Hilbert space
      if (complex_flag) then
        ! [...]
      else
        ! [...]
      end if
    end if
    
  • Generally, continued lines should have the continuation marker & at the beginning (not just at the end of the continued line.)

    if (complex_flag) then
      fw_temp_imag(s) = fw_temp(s)
      call dHdP_psi(fw_temp(s)%psi, fw_states(s)%psi, targets(s)%ham,  &
      &             pulses0, id, ti, add=.false.,                      &
      &             hpsi_imag = fw_temp_imag(s)%psi)
    else
      call dHdP_psi(fw_temp(s)%psi, fw_states(s)%psi, targets(s)%ham,  &
      &             pulses0, id, ti, add=.false.)
    end if
    

    The continuation symbols must line up to indicate the indentation level. In the above example, the & must line up under the c of call. After the continuation symbol, further spaces may be used to line up code vertically.

  • Generally, put the continuation mark at the end of a continued line in column 80 (so that all the continuation marks at the right edge of a file line up vertically). This is of course not possible if a line is continued within a string. You may also relax this rule if it improves readability.

  • Do not put spaces around equal signs when passing variables in a call:

    call diag_complex_matrix(hessenberg(1:m,1:m), ev, sort=.true.)
    

    Always put spaces around equal signs in assignments:

    m0 = m - 1
    
  • Generally, commas and operators (+, -, ==, etc.) should have spaces around them. Array indices should not contain spaces (hessenberg(1:m,1:m)).

  • When importing routines from other modules, import only the required routines:

    use inout_mod,   only: nr2str, lower_case
    use kin_mod,     only: kinop_psi
    

    Only the global_mod, def_mod, and error_mod should be used without only.

  • Modules should be formatted as in the following example:

    module qdyn_ham_mod
    
      !! @description: [...]
    
      use global_mod
      use def_mod
      use error_mod
      use inout_mod,   only: nr2str, lower_case
      ![...]
    
      implicit none
    
      public :: init_ham, H_psi, H_rho, L_rho, arnoldi, extend_arnoldi,    &
                get_ritz_eigenvals, init_ops, dHdP_psi, dLdP_rho ! [...]
      private
    
      character (len=error_l) :: module_name = 'qdyn_ham_mod'
    
    
      contains
    
    
      ! [... (subroutines) ...]
    
    
    end module
    
  • Make sure that every program or module contains the implicit none statement.

  • Only high level subroutines should be public. Low level routines, which a user should not use independently, should be private.

  • Do not use pointers unless absolutely necessary (usually, allocatable will do the job).

  • Never write something like if (a .eqv. .true.). This should be written as if (a).

  • Every routine and data structure must be documented.

  • Every module must be appropriately tested.

  • Always check the error code from allocations. Every call to allocate should be followed by a call to allocerror.

  • Always check the error code from I/O calls. All calls to read/write should be followed by the appropriate error handlers.

  • Do not use constants for file unit numbers. Always use the file management provided by the fileman module. This avoids problems in multi-threaded programs.

  • All calculations must be performed in an internal unit system. Conversion to/from physical units must only happen in input and output. See the notes on the unit system. You must follow the conventions for routines reading and writing data files described there.

Code Review Guidelines

Differentiate between subjective and objective feedback and indicate it as such. Communicate which ideas you feel passionate about and those you don’t. Be kind and don’t forget to praise what you like.

In QDYN we aim for a compromise between code efficiency and readability. For high level routines the latter is more important, while low level routines may be more performance critical. Use your best judgment or consult with a team member. In any case, if you don’t understand a piece of code, say so. There’s a good chance that someone else would be confused as well.

As a guideline, you can use the following checklist for doing code reviews:

  • No potential problems with the code (bugs and maintainability)

  • Changes balance code efficiency and readability

  • Comments clarify complicated code blocks

  • All routines are properly documented according to the documentation checklist

  • New features or modules come with an how-to and/or example

  • How-tos, examples and topical-overviews are written according to their corresponding documentation checklist

  • New features are covered by a test

  • Code changes meet the coding guidelines

  • The commit history is clean

Setting Up Your Editor

Vim

If you are using vim or neovim, make sure your ~/.vimrc contains the line:

filetype plugin on

and put the following in either ~/.vim/ftplugin/fortran.vim or ~/.vim/after/ftplugin/fortran.vim:

setlocal textwidth=80
setlocal tabstop=2
setlocal shiftwidth=2
setlocal shiftround
setlocal expandtab
setlocal spellcapcheck=

" recognize any number of !'s as a single comment marker
" This means you can e.g. reflow QDYN docstrings blocks starting with '!! '
" with the gq shortcut.
setlocal comments=n:!

An example vim configuration can be found at https://github.com/goerz/vimrc

Emacs

If you are using emacs, add the following to your ~/.emacs file:

(setq f90-associate-indent 2)
(setq f90-auto-keyword-case (quote downcase-word))
(setq f90-comment-region "!!!")
(setq f90-indented-comment-re "!!?" )
(setq f90-do-indent 2)
(setq f90-if-indent 2)
(setq f90-program-indent 2)
(setq f90-type-indent 2)
(setq f90-continuation-indent 0)
(setq fortran-line-length 80)