Development Tips#

Git collaborative workflow#

The recommended workflow is:

  1. Fork the repository.

  2. Create a topic branch for your changes.

  3. Rebase your branch on master when it is ready for review.

  4. Resolve any conflicts locally.

  5. Open a merge request.

  6. Address review comments.

  7. Merge with a fast-forward merge.

Fortran binding#

When updating the Fortran binding, make sure that the LibrpaOptions derived type in binding/fortran/librpa_f03.f90 matches the definition in include/librpa_options.h.

After modifying librpa_f03.f90, run the stub-conversion script so that the stub module stays synchronized with the main binding module.

cd binding/fortran
../../utilities/convert_fortran_module_to_stub.py librpa_f03.f90 librpa_f03_stubs.f90

Code style#

LibRPA does not enforce a strict project-wide code style. However, the repository does provide a .clang-format file for formatting C and C++ code with clang-format.

The goal is to avoid spending review time on personal formatting preferences, such as whether an opening brace should be placed on a new line. The .clang-format settings naturally encode some preferences, but the main purpose is to make formatting mechanical and consistent.

Use clang-format for new C and C++ code. Avoid reformatting old code unless you are making substantial changes in the same area, or unless the commit is explicitly dedicated to formatting cleanup. This keeps functional diffs focused and easier to review. Modern editors with language-server support should be able to pick up the repository formatting rules automatically.

For naming, follow these general conventions:

  • Use UpperCamelCase for classes and structs.

  • Use snake_case for variables, object instances, functions, and namespaces.

  • Use SCREAMING_SNAKE_CASE for constants.

Source code structure of LibRPA#

.
|-- binding
|   `-- fortran       : Fortran bindings and binding tests
|-- cmake             : CMake find modules and build helper scripts
|-- docs              : Sphinx/MyST user and developer documentation
|-- driver            : Command-line driver, input parsing, and data readers
|   `-- tasks         : Driver task implementations
|-- examples
|   `-- build         : Example build scripts for common platforms
|-- include           : Public C and C++ API headers
|-- regression_tests  : Regression suite definitions, test cases, and references
|-- src               : Source code for the library target
|   |-- api           : Public API implementation and handler/dataset glue
|   |-- core          : Physics objects and algorithms: basis, RPA, EXX, GW, symmetry
|   |-- elpa          : ELPA eigensolver integration
|   |-- gpu           : Optional CUDA/HIP device and linear-algebra adapters
|   |-- interface     : Thin interfaces to external codes, e.g. BLAS, LAPACK, and ScaLAPACK 
|   |-- io            : Filesystem, ELSI/GW I/O, and stream-printing helpers
|   |-- math          : Utilities of matrix, vector, interpolation, fitting, etc.
|   |-- mpi           : MPI, BLACS, and k-point/process-grid helpers
|   |-- utils         : Constants, errors, profiling, memory, and configured build information
|   `-- test          : C++ unit and MPI tests for library components
|-- thirdparty        : Bundled external libraries used when not supplied by the user
`-- utilities         : Standalone conversion, consistency-check, and maintenance tools

The public API path is include/librpa*.h(pp), implemented under src/api. The driver path is driver/main.cpp with tasks implemented under driver/tasks and input data coming from driver/read_data.cpp. Library code (src/) should stay independent of the command-line driver (driver/).

A few C++ guidelines#

General coding guidelines:

  • Avoid forward declarations for concrete classes and structs; include the header that provides the necessary definition.

  • Prefer RAII, standard containers, references, and const correctness; make ownership and mutation explicit.

  • Treat MPI layout, rank ownership, and collectives as part of the program behavior; check both serial and representative multi-rank cases when they may be affected.

  • Use clear, explicit control flow for indexing, basis mappings, matrix layouts, and symmetry logic. Add a short comment when the physical or parallel assumption is not obvious from the code.

Below are a few specific guidelines for LibRPA:

  • Library components (non-test code under src/) should remain host-agnostic: they must not assume that input data follows the convention of any particular host program. The host should communicate its conventions through the public API, for example via set_basis_convention; LibRPA internals should use only the parsed convention values when interpreting input data. The standalone driver follows the same rule by reading conventions from input files and passing them through the API. For user convenience, input files may still provide producer presets, such as a program name, as shorthand for a known set of conventions.

  • When adding code in src/core, consider whether reusable pieces belong in utility components such as src/math, src/mpi, src/io, or src/utils; implement those pieces in the appropriate place and assemble the core algorithm under src/core.

  • Source files outside src/core should not include headers from src/core; C APIs and dataset instances in src/api are the exceptions.

  • Implement public behavior in the C API first; the C++ API (src/api/librpa.cpp) and Fortran bindings (binding/fortran) should wrap that C layer.

  • Files in src/interface should not include internal headers outside src/interface.

  • In principle, do not read input files in library code (src/). Restart check-point files generated by the library itself are the only exception.

  • Use src/io/stl_io_helper.h when printing STL containers.

  • Driver code (driver/) should not directly modify internal objects owned by a LibrpaHandler. Input parsing and output retrieval should go through public APIs. The only exception is an early task prototype; in that case, helper functions that mutate internal objects should remain static in the prototype task source, driver/tasks/{proto}.cpp.

Adding new runtime options#

  • Prefer output_ or use_ prefixes for LibrpaSwitch options when they describe the behavior clearly; use another verb prefix when it is more precise, such as read_sigc_mat_rf.

  • Option with a few available values can either be an int type starting with option_, or a dedicated enum type defined in librpa_enums.h.

  • Try to keep the option name short, favorably no longer than 25 characters.

  • Doxygen docstrings in driver/driver.h and include/librpa_options.h are the source of truth for runtime-parameter documentation. Put the explanation, default value, status, version information, and deprecation note there.

  • Add the option to the appropriate block in docs/user_guide/runtime_parameters.yml; this controls where the generated user-guide table places the keyword.

  • Use utilities/check_librpa_options.py to cross-check the consistency between the C struct, Fortran interface and high-level wrapper of LibrpaOptions.