Skip to content

Development

Jean-Noël Grad edited this page Nov 2, 2023 · 8 revisions

Once you have installed the compiler toolchain and the python packages outlined in Tooling, you can start with your first contribution. This page will guide you through the different folders in the ESPResSo source code and explain where to add new features, how to run the tests and how to debug the code.

Table of Contents

Writing code

Source code structure

The source tree has the following structure:

  • src: the actual source code
    • utils: C++ utility functions
    • core: C++ source code of the simulation core
    • python/espressomd: espressomd Python module and its submodules
    • script_interface: C++ source code which links Python classes to functionality in the simulation core
  • doc: documentation
    • sphinx: the Sphinx-based documentation (user's guide)
    • doxygen: the Doxygen-based documentation (developer's guide)
    • tutorials: Jupyter notebooks and pdf files for the introductory tutorials
  • testsuite: integration tests
  • samples: Python samples
  • libs: external dependencies
  • maintainer: files used by the maintainers
    • configs: collection of myconfig.hpp files which activate different sets of features for testing
    • benchmarks: collection of benchmarking Python scripts
    • CI: support files for the continuous integration testing run on GitLab-CI

Build System

The build system of ESPResSo is based on CMake.

The central source files of the build system are the following:

  • CMakeLists.txt
  • contents of the cmake directory
  • the CMakeLists.txt files in the src/, doc/, and testsuite/ directories and their sub-directories
The most common reasons for editing these files are:
  • adding new source files
  • adding new external dependencies

Adding new source files

To add new files to ESPResSo (like C++ source files or header files) you need to look at the CMakeLists.txt in the directory where the file is located. Please note that .hpp header files usually do not have to be added to CMakeLists.txt. When contributing significant changes, please add integration tests as detailed in Testing.

Make sure to follow the instructions in Software design when developing new features.

Applying formatting locally

To apply the formatting enforced in the CI on your local tree, you can run

pre-commit

You will need the python packages autopep8, pylint, and pycodestyle with the versions given in requirements.txt. These versions are updated every two years to match the versions available in the Ubuntu LTS releases. If you don't have these versions installed, you can install them locally with:

pip3 install --user -c requirements.txt autopep8 pylint pycodestyle

Testing

Run the unit tests and the integration tests to make sure you don't accidentally introduce regressions:

make check_unit_tests
make check_python_skip_long

To run these tests in parallel, you can run once the following command to set the maximal number of cores:

cmake . -D ESPRESSO_CTEST_ARGS="-j$(nproc)"

For more details on how to run different types of tests, see the relevant "Running tests" subsections in Testing.

Debugging

The pypresso script has options to use common debugging tools with ESPResSo. Currently supported are gdb, lldb, valgrind, cuda-gdb, cuda-memcheck. They can be used like

pypresso --TOOL OTHER_ARGS

e.g. to use gdb to debug ESPResSo running my_script.py, you can issue

pypresso --gdb my_script.py

To pass additional arguments, you can do

pypresso --TOOL=ADDITIONAL_ARGUMENTS OTHER_ARGUMENTS

e.g. to use gdb and directly run the program until main, you can use

pypresso --gdb="--exec=start" my_script.py

Benchmarking

The folder maintainer/benchmarks/ contains Python scripts for common simulation techniques (P3M algorithm, LJ liquid) and bash scripts to run them for various configurations and commits.

Running individual benchmarks

All benchmarks rely on the argparse module to tweak their simulation parameters. Use pypresso --help lj.py to show the list of command line options. Several benchmarks provide a --visualizer option to visualize the simulation box.

Running a benchmark suite

The benchmark suite is based on the CTest framework. By default, each simulation will run with MPI using 1, 2, 4, 8 and 16 threads. The timing statistics will be stored as comma-separated values.

For a single configuration

Procedure:

  • customize the benchmark simulation parameters by editing the python_benchmark() calls in CMakeLists.txt
  • enable benchmarks with cmake . -D ESPRESSO_BUILD_BENCHMARKS=ON -D ESPRESSO_TEST_TIMEOUT=3600 -D CMAKE_BUILD_TYPE=Release
  • remove slow features from the myconfig.hpp files, such as ADDITIONAL_CHECKS
  • run make benchmark
The data is appended to a file benchmarks.csv.part.

To automatize that task for multiple myconfig files and/or multiple commits, see the next section.

For multiple configurations

The benchmarks can be executed in a loop over multiple commits and myconfig files. The loop is designed to halt and run a cleanup action at the first error using set -e.

Procedure:

  • provide custom CMake options and paths to myconfig files in runner.sh, in particular the test timeout must be large enough for your slowest benchmark
  • provide a whitespace-separated list of commits to compare in suite.sh
  • customize the benchmark simulation parameters by editing the python_benchmark() calls in CMakeLists.txt
  • run bash suite.sh
The data is appended to a file benchmarks.csv.part. After each myconfig has completed the benchmarks, the content of benchmarks.csv.part is appended to benchmarks.csv, which is itself appended to benchmarks_suite.csv after each commit has completed the benchmarks. These intermediate files are automatically cleaned up. The benchmarking is carried out in a custom folder build-benchmarks.

Data format

The timing statistics are written to a CSV file with the following structure:

commit config script arguments cores MPI mean ci nsteps duration label
42866e9e minimal.hpp lj.py --particles_per_core=1000 --volume_fraction=0.50 2 True 5.77e-4 1.04e-6 5000 57.7
42866e9e minimal.hpp p3m.py --particles_per_core=1000 --prefactor=4 2 True 1.23e-2 4.81e-5 500 122.7
42866e9e default.hpp lj.py --particles_per_core=1000 --volume_fraction=0.50 2 True 5.82e-4 3.11e-6 5000 58.2
42866e9e default.hpp p3m.py --particles_per_core=1000 --prefactor=4 2 True 1.21e-2 2.08e-5 500 120.6
d810014d minimal.hpp lj.py --particles_per_core=1000 --volume_fraction=0.50 2 True 3.67e-4 2.37e-7 5000 36.7
d810014d minimal.hpp p3m.py --particles_per_core=1000 --prefactor=4 2 True 1.02e-2 1.15e-5 500 102.1
d810014d default.hpp lj.py --particles_per_core=1000 --volume_fraction=0.50 2 True 6.30e-4 7.67e-6 5000 63.0
d810014d default.hpp p3m.py --particles_per_core=1000 --prefactor=4 2 True 1.19e-2 3.19e-5 500 119.2
0ec5b89c maxset.hpp mc.py --particles_per_core=500 1 False 2.43e-4 2.69e-6 1000 48.6 MC
0ec5b89c maxset.hpp mc.py --particles_per_core=500 1 False 7.53e-5 5.70e-6 100 15.1 MD

where mean is the average time for 30 integration loops of nsteps integration steps per loop, ci is the 95% confidence interval of the mean, duration is the total running time, label is an identifier for benchmarks that generate multiple rows. This data can be processed in a spreadsheet software to determine speed-up and slow-downs of commits compared to a reference commit.

When running a benchmark on ICP machines, the machine must be put on drain for the duration of the benchmarks to prevent interference from distributed jobs allocated by the HTCondor scheduler. This is best achieved by running the following bash script:

condor_drain ${HOSTNAME}
sleep 30 # delay: the drain request takes time
bash suite.sh
condor_drain -c ${HOSTNAME}

Coverage analysis

To run code coverage analysis, create a new build folder or update the current one with:

cmake . -D CMAKE_BUILD_TYPE=Coverage -D ESPRESSO_BUILD_WITH_COVERAGE=ON -D ESPRESSO_CTEST_ARGS="-j$(nproc)"
make -j$(nproc)

Run the C++ unit tests to get minimal code coverage, and then the Python tests for the feature of interest (or the full Python test suite):

rm -f $(find src _deps -type f -name "*.gcda") # remove old code coverage data
make -j$(nproc) check_unit_tests               # get minimal code coverage
mpiexec -n 2 ./pypresso ../testsuite/python/collision_detection_interface.py
mpiexec -n 4 ./pypresso ../testsuite/python/collision_detection.py
mpiexec -n 4 ./pypresso ../testsuite/python/save_checkpoint.py Test__lj
mpiexec -n 4 ./pypresso ../testsuite/python/test_checkpoint.py Test__lj
# alternative: run the full Python test suite with `make -j$(nproc) check_python_skip_long`

Now create the code coverage report, and remove superfluous information, such as the STL library, Boost libraries, external dependencies and generated waLBerla code. If multiple versions of the C++ compiler are installed on the computer, and the wrong version of gcov is picked up by lcov, warnings will appear in the terminal.

if [ -d "src/walberla_bridge" ]; then
rm -f $(find src/walberla_bridge/ -type f -name "*.gcda" | grep --color=never generated_kernels)
fi
lcov --gcov-tool "${GCOV:-gcov}" -q --directory . --ignore-errors graph --capture --output-file coverage.info # capture coverage info
lcov --gcov-tool "${GCOV:-gcov}" -q --remove "coverage.info" '/usr/*' --output-file "coverage.info" # filter out system
lcov --gcov-tool "${GCOV:-gcov}" -q --remove "coverage.info" '*/doc/*' --output-file "coverage.info" # filter out docs
lcov --gcov-tool "${GCOV:-gcov}" -q --remove "coverage.info" "$(realpath ..)/libs/*" --output-file "coverage.info" # filter out libs
lcov --gcov-tool "${GCOV:-gcov}" -q --remove "coverage.info" "$(realpath .)/src/python/espressomd/*" --output-file "coverage.info" # filter out cython
lcov --gcov-tool "${GCOV:-gcov}" -q --remove "coverage.info" "$(realpath .)/src/walberla_bridge/src/lattice_boltzmann/generated_kernels/*" --output-file "coverage.info"
lcov --gcov-tool "${GCOV:-gcov}" -q --remove "coverage.info" "$(realpath .)/src/walberla_bridge/src/electrokinetics/generated_kernels/*" --output-file "coverage.info"
lcov --gcov-tool "${GCOV:-gcov}" -q --remove "coverage.info" "$(realpath .)/src/walberla_bridge/src/electrokinetics/reactions/generated_kernels/*" --output-file "coverage.info"
if [ -d "_deps/" ]; then
lcov --gcov-tool "${GCOV:-gcov}" -q --remove "coverage.info" "$(realpath .)/_deps/*" --output-file "coverage.info" # filter out dependencies
fi

Finally, create the code coverage report in HTML format:

output="coverage_report"
cp "coverage.info" "${output}.info"
rm -rf "${output}_html"
genhtml "${output}.info" --demangle-cpp --quiet --output-directory "${output}_html"
echo 'center table tbody tr:hover td { box-shadow:inset 0px 3px 2px -2px #AAA, 0px 4px 2px -2px #AAA; }' >> "${output}_html/gcov.css"
xdg-open "${output}_html/index.html"