mirror of
https://github.com/cp2k/cp2k.git
synced 2026-07-26 05:05:16 -04:00
8.7 KiB
8.7 KiB
Coding Conventions
Conventions which are easily testible by reading the source files, are tested in
tools/precommit/check_file_properties.py as a part of ./make_pretty.sh and
tools/precommit/precommit.py. Checks needing to run the compiler are tested in
tools/conventions/test_conventions.sh.
Stick to the standard
- Code enabled by default should be standard Fortran 2008 [-std=f2008] and C2011 [-std=c11].
- OpenMP code should follow version 3.X of the standard.
- MPI should should follow version 3 of the standard.
- Extended functionality should match POSIX.
Write explicit code
- Every
USE-statement should have anONLY:-clause, which lists the imported symbols [-Wuse-without-only]. - Every
OMP PARALLEL-region should declaredefault(none). - Every static variable should be marked with the
SAVEattribute. - Every Fortran module should contain the line
IMPLICIT NONE[-fimplicit-none]. - Every conversion that might change value should be explicit [-Wconversion]. Rationale:
- INTEGER i=4.9999, i.e. i=INT(4.9999) implies i==4. Use NINT(4.9999) as appropriate.
natom*natom,nrow_global*ncol_globaloverflows INT(KIND=4) for large simulations.- always use REAL() with KIND parameter, i.e. r = REAL(i, KIND=dp).
- avoid FLOAT() in favour of REAL(, KIND=dp).
- the global number of grid points (pw_grid_type%ngpts,ngpts_cut) overflows INT(KIND=4) for large simulations.
- Every derived type needs a default initializer. This means that each of its non-allocatable
component needs a default value. This prevents errors from accessing uninitialized and thus
non-sensical data.
- For Fortran-provided types, set them to a suitable default.
- Non-default kinds (especially in the context of
ISO_C_BINDING) need to make the kind explicit by using<value>_<kind>. POINTERs are usually initialized to theNULL()-vector.- Derived type-components are initialized to the default type.
- If a derived type provided by an external library does not implement a default initializer, ask
the library developers to implement it (or suggest a fix directly) and temporarily do not
initialize the respective components on the CP2K-side. You will need to add an exception in
tools/conventions/conventions.supp. TYPE(C_PTR)andTYPE(C_FUN_PTR)of theISO_C_BINDINGare initialized toC_NULL_PTR.- Example:
TYPE :: my_derived_type
INTEGER :: my_integer = 0
REAL(KIND=dp) :: my_real = 0.0_dp
CHARACTER(LEN=42) :: my_char = ""
CHARACTER(LEN=21), DIMENSION(:), POINTER :: my_char_pointer => NULL()
! No initializer necessary
COMPLEX(KIND=dp), DIMENSION(:, :), ALLOCATABLE :: my_complex_allocatable
! ISO_C_BINDING
LOGICAL(C_BOOL) :: my_c_bool = .FALSE._C_BOOL
TYPE(C_PTR) :: my_c_ptr = C_NULL_PTR
! Call the default initializer of a previously defined tpe
TYPE(my_previously_defined_type) :: my_type = my_previously_defined_type()
! No default implemented
TYPE(library_type) :: lib_type
END TYPE my_derived_type
Especially GCC may raise false positives with allocatable components. These can be often ignored.
Don't use poorly designed language features
- Do not use the
GOTO-statement. See also this and this - Do not use left-hand-side (lhs) reallocations of allocatables [-Wrealloc-lhs]. See here why.
- Do not use
FORALLconstructs. See here why. - Do not use
OMP THREADPRIVATEvariables. - Do not query the
STATfrom aDEALLOCATE, the Fortran runtime takes care. - Do not use
RANDOM_NUMBER(), it's not consistent across different compilers.
Fight spaghetti code
There are two measures of defense against spaghetti code:
- Decoupling on the module and package level:
- Every module should depend on as few other modules as possible.
- Every package should depend on as few other packages as possible.
- Information hiding, also known as
encapsulation.
- External libraries should be wrapped within a single module or package.
- Every module should hide its content by containing the line
PRIVATEand only few public symbols. - Every package should hide its content by providing only a small public API through a single module.
- Each external library should be accesses using a wrapper module.
- The wrapper module should
Use existing infrastructure
Always prefer built-in (intrinsic) functions instead of hand-coded routines since they usually include extra numerical checks to avoid intermediate under- or overflow while still performing optimally. Examples:
NORM2(x)instead ofSQRT(x(1)**2 + x(2)**2 + x(3)**2)DOT_PRODUCT(x, x)instead ofx(1)**2 + x(2)**2 + x(3)**2DOT_PRODUCT(x, y)instead ofx(1)*y(1) + x(2)*y(2) + x(3)*y(3)
For many common operations there exist wrappers in CP2K to prevent usage errors and to allow for central redirections, i.e. avoid to use direct calls to external libraries in CP2K
- Use the routines from
cp_files.Finstead of callingOPENandCLOSEdirectly. - Use the routines from the full-matrix
fm-package instead of calling BLAS or Scalapack directly. Distributed matrix-matrix multiplications using full matrices from thefm-packe use theparallel_gemm_apimodule instead, large local matrix-matrix multiplications using local matrices use thelocal_gemm_apipackage instead. - Use the routines from
message_passing.Finstead of calling MPI directly. Most of the routines are bound to a communicator, for instance a call to MPI_Sendrecv from communicatorcommis performed by callingCALL comm%sendrecv(sendbuffer, send_process, recvbuffer, recv_process, tag). - Use the routines from
fft_lib.Finstead of calling FFT (any library) directly. - Use the routines from
machine.Fto access architecture depended things like e.g. the working directory. - Don't use
UNIT=*inWRITEorPRINTstatements. Instead request a unit from the logger:iw=cp_logger_get_default_unit_nr()and write only if you actually received a unit:IF(iw>0) WRITE (UNIT=iw, ,,,). - If you need to
WRITE,PRINTorREADto or from a special unit (standard input, standard output, standard error), usedefault_input_unit,default_output_unitfrom themachinemodule orerror_unitfromISO_FORTRAN_ENV. Beware that the respective output is not written to the standard output file opened by CP2K. - Use CP2K's error-handling, instead of the
STOPstatement.
Remove dead code
- Every line of code has to be compiled and maintained. Hence, unused variables and code poses an unnecessary burden and should be removed [-Wunused-variable -Wunused-but-set-variable -Wunused-dummy-argument].
- Sometimes it is beneficial to keep debugging code around nevertheless. Such code should be put
into a
IF(debug_this_module)-block, with a parameter set to.FALSE.. This way the code will stay up-to-date and easily callable. Do not hide dead code as a comment; the compiler has no chance to check the validity of this code.
Format and document code
- Each file should start with the official CP2K header. Some low-level packages make use of a different license.
- Each
.Ffile should contain either aPROGRAMor a singleMODULE, whose name matches the filename. - Each module and routine should be annotated with Doxygen documentation (see below).
- Each preprocessor flag should start with two underscores and be documented in the
INSTALL-file and added to the cp2k_flags function (cp2k_info.F). - The code should be formatted with the prettify tool by running
./make_pretty.sh.
Write tests
- Every feature should be tested, with the goal of complete code coverage.
- If combinations of features are relevant, prepare additional tests for them.
Doxygen documentation
- Every
FUNCTIONandSUBROUTINEshould be preceded by a valid doxygen block. - The following keywords are required:
\brief,\param(for each parameter),\retval(for functions). - The following keywords are optional:
\note,\par,\date,\author. - Run
./tools/precommit/precommit.py --allow-modifications <file>before committing modified Fortran sources. It runs Doxify before applying the Fortran formatter. To update Doxygen blocks only, run./tools/doxify/doxify.sh <file>. - See our doxygen pages for the result.