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¶
-
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.gitNow,
git remote -vwill show two remote repositories named:* `upstream`, which refers to the official `runner2` repository, * `origin`, which refers to your personal fork. -
Compile the project.
-
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-newalgorithmSince 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-orenh, 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.
-
-
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
typenames, e.g.AtomicNeuralNetworkorAtom. - 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 ifnotendif,end donotenddo. -
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
selfas 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 asreal*8. Thetypesmodule exports theDPparameter using theiso_standards. - Always write all float constants with the
_DPsuffix, 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
moduleor aprogram. -
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 ofuse types). implicit noneat the beginning of the file.- The module scope is set to
private, and only selected parts of the module are explicitly declared aspublic. - Four space indentation for the body of all procedures and the declaration of global variables.
- Explicit imports (
-
Do not use
go tostatements. 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:
- Directly behind the variable marked by
!<. Pay attention to the 80 character line length limit! - Below the variable, marked by
!<. - In the docstring above the function with the
@paramdecorator.
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
pureorelementalwhenever possible. - Declare loops as
concurrentif iterations are independent (non-counting). - Arrays should be initialized like this
array = 0.0_d0. This is much faster thanarray(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")