Skip to content

RuNNer 2.0 Contributing Guide

Thank you for your interest in contributing to RuNNer 2.0! Any contribution you make is highly appreciated ✨.

This guide * summarizes the contribution workflow from opening an issue to creating a pull request, reviewing, and merging, * defines the coding guidelines and style specifications to which RuNNer 2.0 adheres, * gives an overview about the Linter and Unit Testing Framework that are used to keep the project clean.

Development Process

  1. As a first-time contributer:

    • The code of RuNNer 2.0 is managed on gitlab.com as part of the RuNNer suite.
    • Please read this document until the end before contributing.
    • Create your own copy of the code repository by forking the project.
    • Clone the project to your local computer and change directory:

      git clone https://gitlab.com/your-username/runner2.git
      cd runner2
      
    • Add the upstream repository:

      git remote add upstream https://gitlab.com/runner-suite/runner2.git
      

      Now, git remote -v will show two remote repositories named:

      * `upstream`, which refers to the official `runner2` repository,
      * `origin`, which refers to your personal fork.
      
    • Compile the project.

  2. Develop your contribution:

    • Pull the latest changes from upstream:

      git checkout main
      git pull upstream main
      
    • Create a branch for the feature you want to work on.

      git checkout -b feat-symmetryfunction-newalgorithm
      

      Since the branch name will appear in the merge message, use a sensible and descriptive name. Please prefix your branch name with a classifier for the contribution. Here is a selection:

      • bugfix- for bug fixes,
      • ci- for changes to the CI pipeline,
      • doc-, for work on the documentation,
      • feat- or enh, for new features or enhancements.
    • Commit locally as you progress (git add and git commit). Commit often and in small bites, this makes code review much easier. Use a proper commit message consisting of a short heading (less than 72 characters), and if necessary a descriptive and verbose commit message body, separated by a blank line.

    • For any contribution, write tests that fail before your change and pass afterward. Run all the tests locally to make sure that all existing tests will also pass.

    • Document any new procedures, modules, classes, ... with proper docstrings.

    • Check the formatting of your code with a Linter.

    • Before creating an MR, update the CHANGELOG if appropriate.

  3. Submit your contribution:

    • Push your changes back to your fork:

      git push origin [branchname]
      
    • On Gitlab, create a pull request with a descriptive title and clear message.

    • Wait for code review.

Stylistic Guidelines

RuNNer 2.0 tries to follow strict style guidelines. While some of them reflect nothing but the subjective taste of the initial programming team, many are considered best practice in the Modern Fortran community. Many of the rules here are a subset of the Fortran 90 Best Practice Guide. Check it out for more detailed information!

Naming Convention

Variables

  • All variable names should have meaning. Do not strive for the shortest code possible in exchange for readability.
  • Use snakecase for all variable and module names. Separate words with an underscore (_) to improve readability.
  • Use PascalCase for all type names, e.g. AtomicNeuralNetwork or Atom.
  • Use UPPERCASE for constant parameters.
  • No maxnum_* constants.

Associate statements

  • Recomended to improve readabilty of math expressions
  • Do not indent associate Block
  • There is no limitation for the selector's name (should be short and can be uppercase)
real(DP), :: cartesian_coordinates_of_atom_i(3)
real(DP), :: cartesian_coordinates_of_atom_j(3)
real(DP), :: cartesian_distance_between_atom_i_and_atom_j(3)
associate ( Ri  => cartesian_coordinates_of_atom_i, &
            Rj  => cartesian_coordinates_of_atom_j, &
            Rij => cartesian_distance_between_atom_i_and_atom_j)

Rij = Ri - Rj 

end associate

Fortran Constructs

  • Use lowercase for all Fortran constructs (do, subroutine, module, ...).
  • For closing statements, separate words by a blank space (e.g. end if not endif, end do not enddo.
  • Give a name to all nested loops:

    loop_structures: do i=1, number_structures
        loop_atoms: do i=1, number_atoms
            ...
        end do loop_atoms
    end do loop_structures
    
  • When declaring type-bound procedures, use self as the name for the passed object.

Line Formatting

  • Use 4 spaces indentation.
  • Always indent with real spaces, never with tabs.
  • Keep lines as short as possible, and use line continuation & frequently. Aim for a target line length of not more then 80 characters. The hard limit enforced by our linter is 120 characters.
  • Always remove all dangling spaces.
  • No DOS line breaks (so only \n, never \n\r).
  • Every file should end with one single trailing newline, not more, not less.

  • Blank spaces before and after all operators (+, -, =, ==, !=, ::, ...)

  • Blank spaces after a comma.

Variables

  • Declare constants variables as parameters.
  • All arguments should have intent() statements.
  • Here is an example of an array of floats declaration:
real(DP), intent(in) :: a_matrix(:, :)

Floating Point Numbers

  • Always declare floats as real(DP), never as real*8. The types module exports the DP parameter using the iso_standards.
  • Always write all float constants with the _DP suffix, e.g. 1.0_DP.

Code Structure

  • Use modules to group different parts of code into one logical unit. All code should either be part of a module or a program.
  • Modules should always be set up in the following way:

    module example
    use types, only: DP
    implicit none
    private
    public mode1, mode2, ...
    
        ! Four spaces indentation for module variable declarations.
        ...
    
    contains
    
        subroutine mode1(...)
            ! Four space indentation for the subroutine body.
            ...
        end subroutine mode1
    
    ...
    
    end module example
    
    • Explicit imports (use types, only: ... instead of use types).
    • implicit none at the beginning of the file.
    • The module scope is set to private, and only selected parts of the module are explicitly declared as public.
    • Four space indentation for the body of all procedures and the declaration of global variables.
  • Do not use go to statements. Use loops or if-statements instead.

  • One function/subroutine should have one job. Therefore, every procedure should fit comfortably on a standard screen. No 1000 line subroutines which do a million things!

Comments

  • Comment frequently.
  • Comments should start with a single exclamation mark followed by a blank space: ! This is a comment..
  • Comments should always terminate with a full stop.
  • Comments should never exceed the 80 character line length limit.
  • Comments should start at the same indentation level as the code they comment.

Docstring Formatting

All modules, functions, classes, ... should have a docstring that, at the least, gives a short statement about the purpose of the routine and a short description of each input and output parameter. Moreover, examples and other important information may be added here.

We have decided to use Ford to automatically generate an API-documentation from these docstrings. Therefore, please adhere to the Ford docstring style. for Fortran. Here is an example:

!> Calculate the Cartesian distance between two atoms.
!!
!! The distance between two atoms is calculated as the vector distance
!! of their respective xyz coordinates.
function calculate_cart_distance(xyz_atom1, xyz_atom2) result(distance)
  implicit none
  real(DP), intent(in) :: xyz_atom1(:)
  !! The coordinates of the first atom.
  real(DP), intent(in) :: xyz_atom2(:)
  !! The xyz coordinates of the second atom in the pair.
  real(DP), intent(out) :: distance(:)
  !! The vector distance.

  distance = xyz_atom1 - xyz_atom2

end function calculate_cart_distance
  • The docstring of the function should be placed directly above the function.
  • The docstring is initiated by !> and all subsequent lines begin with !!.

In principle, a Doxygen docstring can contain plain text and Markdown directives. Apart from this, Doxygen comes with decorators (marked by @), which fulfill a specific purpose. In the example above, we make us of the @brief and @details decorators to separate a short description of the function (displayed as a summary at various places of the documentation) from a more detailed description (only shown on the page dedicated to this function).

Apart from this, every function parameters should have a separate explanation. It can be documented in three different ways:

  1. Directly behind the variable marked by !<. Pay attention to the 80 character line length limit!
  2. Below the variable, marked by !<.
  3. In the docstring above the function with the @param decorator.

Some more remarks: * Every comment should be separated from the comment marker (!>, !<, !!) by a blank space. * Every comment should be a meaningful sentence terminated by a full stop.

Commit Good Practice

  • When pushing your changes to the origin, do not leave commented code in a file "so I can use it later". It will end up cluttering the program source. Instead, keep it on a separate branch or save it in Gitlab Snippets.

Optimization

Apart from writing readable and maintainable code, a central goal is to write the fastest code possible. In this section, we summarize some guidelines that will help you achieve this.

  • Declare functions as pure or elemental whenever possible.
  • Declare loops as concurrent if iterations are independent (non-counting).
  • Arrays should be initialized like this array = 0.0_d0. This is much faster than array(2, :, :) = 0.0_d0.

Unit Testing

Unit tests make sure that an isolated part of the program works as intended. We use the framework pFUnit to run unit tests for all important parts of the code.

Pull requests (PRs) that modify code should either have new tests, or modify existing tests to fail before the PR and pass afterwards. You should run the tests locally before pushing a PR.

Linting

We use our own fork of the Python package fortran-linter to make sure that the code of RuNNer fulfills some (but not all) of the criteria mentioned above. The linter can be installed locally with pip install git+https://gitlab.com/runner-suite/runner-linter.git. All source files can then be checked with fortran-linter --syntax-only $(find . -name "*\.f90")