Syntax rules in QE

Dear QE developers,

After carefully thinking about a general syntax rules for the code, trying to make them as consistent with each other as possible, I propose the following rules:

Pre-processing

  • Preprocessing options should be capitalized and start with two underscores. Examples: __MPI, __LINUX, ...
  • Use preprocessing syntax #if defined (XXX), not #if defined XXX or #ifdef XXX

Common style

  • Fortran commands should be capitalized: CALL something(XXX)
  • Variable names should be lowercase: foo = bar/2
  • Use (KIND = DP) (defined in module kinds) to define the type of real and complex variables

Order of declaration

  • The recommanded order is as follow:
CHARACTER(LEN = 256) :: var
LOGICAL :: var
LOGICAL, ALLOCATED :: var(:)
INTEGER :: var
INTEGER, ALLOCATED :: var(:) 
REAL(KIND = DP) :: var
REAL(KIND = DP), ALLOCATED :: var(:)
COMPLEX(KIND = DP) :: var
COMPLEX(KIND = DP), ALLOCATED :: var(:)
  • First all INTENT variables are declared (in that order) and then all the local variables are declared (in that order).
  • Note: Do not use DIMENSION(:)

Typical header of subroutines

!------------------------------------------------------------------------
SUBROUTINE name(arg1, arg2)
!------------------------------------------------------------------------
!!
!! Description of subroutine
!!
!------------------------------------------------------------------------
USE kinds,         ONLY : DP
USE cell_base,     ONLY : at, bg, alat
!
IMPLICIT NONE
!
! Input variables
INTEGER, INTENT(in) :: arg1
!! Description
REAL(KIND = DP), INTENT(in) :: arg2(3, 5)
!! Description
!
! Local variables
INTEGER :: ik
!! Description

!------------------------------------------------------------------------
END SUBROUTINE name 
!------------------------------------------------------------------------

Indentation

  • Use two spaces for indentation
DO ik = 1, nkf
  DO imode = 1, nmodes
    code
  ENDDO
ENDDO 

Spaces

  • Leave one space after a comma "," and between "multiple conditions" in a IF statement
IF (cond) THEN
  CALL name(arg1, arg2, arg3)
ENDIF
ALLOCATE(var1(dim1, dim2), STAT = ierr)
IF (ierr /= 0) CALL io_error('Error allocating var1 in subroutine_name')

DO ik = 1, nkf
  ikk = 2 * ik - 1
  ikq = 2 * ik
  IF ((MINVAL(ABS(var1(:, ikk) - ef)) < fsthick) .AND. (MINVAL(ABS(var1(:, ikq) - ef)) < fsthick)) THEN
ENDDO

DEALLOCATE(var1, STAT = ierr)
IF (ierr /= 0) CALL io_error('Error deallocating var1 in subroutine_name')

Allocating and deallocating arrays

  • Check the status once an array is allocated or deallocated
ALLOCATE(var1(dim1, dim2), STAT = ierr)
IF (ierr /= 0) CALL errore('subroutine_name', 'Error allocating var1', 1)

DEALLOCATE(var1, STAT = ierr)
IF (ierr /= 0) CALL errore('subroutine_name', 'Error deallocating var1', 1)

Reading and writing files

  • Leave one space after a comma "," and after a statement
OPEN(UNIT = file_unit, FILE = 'file_name', STATUS = 'old', FORMAT = 'formatted', IOSTAT = ios)
IF (ios /= 0) CALL errore('subroutine', 'error opening file_name', iunit_name)
READ(file_unit) index
CLOSE(file_unit)

OPEN(UNIT = file_unit, FILE = 'file_name', STATUS = 'old', FORMAT = 'formatted', IOSTAT = ios)
IF (ios /= 0) CALL errore('subroutine', 'error opening file_name', iunit_name)
WRITE(file_unit, '(i7)') index
CLOSE(file_unit)

Intrinsic functions

  • Use capital letters when calling an intrinsic function or logical:
a = MATMUL(c, d)
c = TRANSPOSE(DBLE(e))
f = .TRUE.

Relational operator

  • Use modern relational operators:
> instead of .gt.
< instead of .lt.
== instead of .eq.
/= instead of .neq.

Mathematical operator

  • Use one space between mathematical operators
a = b + i
c = c / SQRT(s)

Spaces in the code

  • Avoid white space in the code. When a space is needed, add a comment (!) that follows the indentation:
!
a = b
! 
DO i = 1, n
  !
  y = a + c
ENDDO

Conditional allocation

  • Do NOT use: IF (.NOT. ALLOCATED(var)) ALLOCATE(var(dim1))
  • Do use:
ALLOCATE(var1(dim1, dim2), STAT = ierr)
IF (ierr /= 0) CALL errore('subroutine_name', 'Error allocating var1', 1)

Indeed conditional allocations create potential memory leaks and can always be avoided.

Good practice

  • Conversions should be explicitly indicated. For conversions to real, use DBLE, or else REAL(..., KIND = DP). For conversions to complex, use CMPLX(...,...,KIND = DP). For complex conjugate, use CONJG. For imaginary part, use AIMAG.
  • Do not use REAL or CMPLX without KIND = DP, or else you will lose precision (except when you take the real part of a double precision complex number).
  • Do not use automatic arrays (e.g. REAL(KIND = DP) :: A(N) with N defined at run time) unless you are sure that the array is small in all cases: large arrays may easily exceed the stack size, or the memory size.
  • Do not use pointers unless you have a good reason to: pointers may hinder optimization. Allocatable arrays should be used instead.
  • If you use pointers, nullify them before performing tests on their status.
  • Be careful with F90 array syntax and in particular with array sections. Passing an array section to a routine may look elegant but it may turn out to be inefficient: a copy will be silently done if the section is not contiguous in memory (or if the compiler decides it is the right thing to do), increasing the memory footprint.
  • Do not pass unallocated arrays as arguments, even in those cases where they are not actually used inside the subroutine: some compilers don't like it.
  • Always use IMPLICIT NONE and declare all local variables. All variables passed as arguments to a routine should be declared as INTENT(in), (out) , or (inout). All variables from modules should be explicitly specified via USE module, ONLY : variable. Variables used in an array declaration must be declared first, as in the following example:
INTEGER, INTENT(in) :: N
REAL(KIND = DP), INTENT(out) :: A(N)

in this order (some compilers complain if you put the second line before the first).




Explanation for some of the rules:

  • The reason for two and not three space indentation is because in some cases, one can end up with a lot of enclosed DO loops (in some case this is justified) and two indentation is the enough to clearly see the indentation.
  • The reason for space around = sign like in KIND = DP rather than KIND=DP is to be consistent. The idea is that space is put around all mathematical symbols like +, -, =, etc.
  • The reason for KIND = DP and not just DP is to make is explicit that it is a kind.
Edited by giannozz